一 串行数据添加帧头
添加帧头的思路参考以下两个链接,将帧头数据保存在ROM中,将串行数据保存在FIFO中,当检测到FIFO中有数据后,先将帧头输出,等帧头输出完毕再将FIFO中串行数据输出,实现添加帧头的功能。
科学网—利用Verilog将CEO文件中的帧头识别出来,并发送一帧完整的数据 - 符加乐的博文 (sciencenet.cn)
加帧头帧尾怎么用verilog实现 - 数字IC设计讨论(IC前端|FPGA|ASIC) - EETOP 创芯网论坛 (原名:电子顶级开发网) -
1.先配置ROM IP核
将Block Memory Generator IP核配置为单端口ROM,位宽为1,深度为13,输出端不使用寄存器,ROM内存储的数据由coe文件初始化,coe文件的配置可以参考
23_实战篇:RAM IP 核实验(第二讲:程序设计)_哔哩哔哩_bilibili
vivado ROM IP核简单使用_小李干净又卫生的博客-CSDN博客_vivadorom
帧头使用13位巴克码组,1 1 1 1 1 0 0 1 1 0 1 0 1,则coe文件的编写如下
memory_initialization_radix=2; //数据的格式,可以是2进制,10进制,16进制数据
memory_initialization_vector=
1,
1,
1,
1,
1,
0,
0,
1,
1,
0,
1,
0,
1; //每个数据后面用逗号或者空格或者换行符隔开,最后一个数据后面加分号
//使用13位巴克码组作为帧头1 1 1 1 1 0 0 1 1 0 1 0 1
2.编写顶层模块
模块的编写思路如下:将FIFO的empty信号作为开始添加帧头的使能信号,当FIFO非空时表示FIFO中已经存在串行数据,此时开始输出帧头,并通过判断ROM的地址给出帧头输出结束的信号,帧头输出结束信号再给到FIFO的读使能,让模块输出FIFO中的数据。代码如下:
`timescale 1ns / 1ps
module add_frame_header(
input clk, //时钟
input rst_n, //低使能复位
input fifo_data, //FIFO的输出的1位数据
input fifo_empty, //将FIFO的非空信号作为开始插入帧头的使能信号
output reg [3:0]bram_addr, //bram的地址
output reg [3:0]bram_addr_temp, //bram_addr_temp延迟一个时钟后得到bram_addr,为了让ram使能后数据输出和地址增加能分开
output reg bram_douta_temp, //ram输出数据缓存,bram_douta延迟一个时钟后得到bram_douta_temp。如果直接读取bram数据发现数据输出不能与时钟对齐,存在100ps延时,所以增加一级寄存器进行缓冲
output wire header_end, //帧头结束信号,也是fifo的读使能信号
output wire out_data //添加帧头后的输出数据
);
//reg [3:0]bram_addr;
//reg [3:0]bram_addr_temp;
//reg bram_douta_temp;
reg bram_en; //bram的使能信号
wire bram_douta; //bram输出数据
reg header_end_temp;
//控制bram的使能信号
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
bram_en <= 1'b0;
else if(bram_addr_temp == 4'd15) //输出完关闭bram使能
bram_en <= 1'b0;
else if(!header_end && !fifo_empty) //接收到开始插入帧头的使能信号,开启bram使能
bram_en <= 1'b1;
else
bram_en <= bram_en;
end
//控制bram的地址信号
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
bram_addr_temp <= 4'd0;
else if(bram_addr_temp == 4'd15) //输出完关闭bram地址保持在15
bram_addr_temp <= 4'd15;
else if(!header_end && !fifo_empty)
bram_addr_temp <= bram_addr_temp + 4'd1; //接收到开始插入帧头的使能信号,bram地址开始递增
else
bram_addr_temp <= bram_addr_temp;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
bram_addr <= 4'd0;
else if(bram_addr == 4'd12)
bram_addr <= 4'd12;
else
bram_addr <= bram_addr_temp; //bram_addr_temp延迟一个时钟后得到bram_addr,为了让ram使能后数据输出和地址增加能分开
end
//控制帧头结束信号
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
header_end_temp <= 1'b0;
else if(bram_addr_temp == 4'd15) //为什么输出完的标志是15,bram本身有一个延迟,bram地址有一个缓冲,dram输出又增加了一个缓冲
header_end_temp <= 1'b1;
else
header_end_temp <= header_end_temp;
end
assign header_end = ~fifo_empty && header_end_temp; //防止读空
//控制输出帧头还是输出fifo数据
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
bram_douta_temp <= 1'b0;
else
bram_douta_temp <= bram_douta; //ram输出数据缓存,bram_douta延迟一个时钟后得到bram_douta_temp。如果直接读取bram数据发现数据输出不能与时钟对齐,存在100ps延时,所以增加一级寄存器进行缓冲
end
assign out_data = (header_end)?fifo_data:bram_douta_temp;
//例化bram模块
blk_mem_gen_0 bram_inst (
.clka(clk), // input wire clka
.ena(bram_en), // input wire ena
.addra(bram_addr), // input wire [3 : 0] addra
.douta(bram_douta) // output wire [0 : 0] douta
);
endmodule
注意,因为帧头结束信号header_end要作为FIFO的读使能信号,为了防止读空的情况,需要和和~empty信号进行与操作,且必须是组合逻辑,参考如下链接
Xilinx IP解析之FIFO Generator v13.2_徐晓康的博客的博客-CSDN博客_fifo generator
3.仿真结果
首先编写testbanch,设置复位信号、FIFO的empty信号和FIFO中的数据。为了简单起见,设置了一个16位数0x1234作为FIFO中的数据,并在FIFO读使能的情况下,将数据从高位到低位依次输出,代码如下:
`timescale 1ns / 1ps
module tb_add_frame_header();
//输入
reg clk; //时钟
reg rst_n; //低使能复位
wire fifo_data; //FIFO的输出的1位数据
reg fifo_empty; //将FIFO的非空信号作为开始插入帧头的使能信号
//输出
wire [3:0]bram_addr;
wire [3:0]bram_addr_temp;
wire bram_douta_temp;
wire header_end; //帧头结束信号,也是fifo的读使能信号
wire out_data; //添加帧头后的输出数据
reg [15:0] num;
reg [15:0] fifo_data_store;
//信号初始化
initial begin
clk = 1'b0;
rst_n = 1'b0;
fifo_empty = 1'b1;
num=16'd15;
fifo_data_store = 16'h1234;
#10
rst_n = 1'b1;
#40
fifo_empty = 1'b0;
#700
rst_n = 1'b0;
fifo_empty = 1'b1;
num=16'd16;
#20
rst_n = 1'b1;
#40
fifo_empty = 1'b0;
end
//生成时钟
always #10 clk = ~clk;
//fifo的输出数据
always@(posedge clk) begin
if(header_end) begin
num <= num - 16'd1;
end
end
assign fifo_data = (!fifo_empty)?fifo_data_store[num]:1'b0;
always@(posedge clk) begin
if(num == 16'd0) begin
fifo_empty <= 1'b1;
end
end
//例化
add_frame_header add_frame_header_inst(
.clk(clk), //时钟
.rst_n(rst_n), //低使能复位
.fifo_data(fifo_data), //FIFO的输出的1位数据
.fifo_empty(fifo_empty), //将FIFO的非空信号作为开始插入帧头的使能信号
.bram_addr(bram_addr),
.bram_addr_temp(bram_addr_temp),
.bram_douta_temp(bram_douta_temp),
.header_end(header_end), //帧头结束信号,也是fifo的读使能信号
.out_data(out_data) //添加帧头后的输出数据
);
endmodule
仿真结果如下,在FIFO的empty信号为零时,隔两个时钟周期开始输出帧头数据,当帧头数据输出结束后header_end信号拉高,然后开始输出FIFO中的数据。
一开始在仿真的时候还遇到一个问题,如果直接输出BRAM中的数据,发现输出数据与时钟没有对齐,数据延迟了100ps,可以看以下两个链接的描述:
FPGA设计block ram中,读数据存在100ps的延时,如何解释-CSDN社区
100 ps delay for Block Memory Generator 6.3 (xilinx.com)
解决方法就是将BRAM的输出数据缓冲一级后再输出。
二 帧头检测
帧头检测的思路主要参考下面这篇论文,首先将串行输入数据存放到寄存器中,然后将输入数据于目标帧头数据做做相关运算,判断运算结果是否符合条件,若符合则输出帧头后面的有效数据。
基于AD9361的无线收发系统设计与实现 - 中国知网 (cnki.net)
1. 模块代码
需要检测的帧头为13位巴克码组,1 1 1 1 1 0 0 1 1 0 1 0 1,首先将串行输入数据移位存放到一个13位寄存器input_buffer中。然后计算每一位加权的结果,例如input_buffer寄存器的第0位,对应的是巴克码组的第0位—1,那么如果输入的数据为1,则加权结果为1,如果为0,则加权结果为0。再例如input_buffer寄存器的第1位,对应的是巴克码组的第1位—0,那么如果输入的数据为1,则加权结果为0,如果为0,则加权结果为1。总之,如果对应输入数据与巴克码组相同,则加权结果为1,不同则为0。因此如果找到对应帧头数据,则加权求和sum的结果为13。最后根据加权求和的结果判断是否找到帧头,如果找到帧头,则将有效数据使能信号置为1,该信号会作为后级FIFO的写使能信号,并将帧头后面的有效数据写入到FIFO中。
`timescale 1ns / 1ps
module detect_frame_header(
input clk, //时钟
input rst_n, //低使能复位
input data_in, //输入串行数据
input axis_last, //将counter_tlast输入的last信号作为fifo的写不使能信号
output reg [12:0]input_buffer, //输入缓存寄存器
output wire [3:0]sum, //加权求和结果
output reg valid_en, //有效数据使能
output reg data_out //输出串行数据
);
reg [3:0]temp[12:0]; //加权结果
//reg signed[4:0]temp0; //加权结果,对应1
//reg signed[4:0]temp1; //加权结果,对应0
//reg signed[4:0]temp2; //加权结果,对应1
//reg signed[4:0]temp3; //加权结果,对应0
//reg signed[4:0]temp4; //加权结果,对应1
//reg signed[4:0]temp[5]; //加权结果,对应1
//reg signed[4:0]temp[6]; //加权结果,对应0
//reg signed[4:0]temp[7]; //加权结果,对应0
//reg signed[4:0]temp[8]; //加权结果,对应1
//reg signed[4:0]temp[9]; //加权结果,对应1
//reg signed[4:0]temp[10]; //加权结果,对应1
//reg signed[4:0]temp[11]; //加权结果,对应1
//reg signed[4:0]temp[12]; //加权结果,对应1
//reg [12:0]input_buffer; //输入缓存寄存器
//wire [3:0]sum; //加权求和结果
reg data_temp; //串行数据延迟
//控制串行输入数据进入缓存寄存器
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
input_buffer <= 13'd0;
else
input_buffer <= {input_buffer[11:0],data_in};
end
//加权结果,对应1
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
temp[0] <= 4'd0;
else if(input_buffer[0])
temp[0] <= 4'd1;
else
temp[0] <= -4'd0;
end
//加权结果,对应0
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
temp[1] <= 4'd0;
else if(input_buffer[1])
temp[1] <= -4'd0;
else
temp[1] <= 4'd1;
end
//加权结果,对应1
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
temp[2] <= 4'd0;
else if(input_buffer[2])
temp[2] <= 4'd1;
else
temp[2] <= -4'd0;
end
//加权结果,对应0
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
temp[3] <= 4'd0;
else if(input_buffer[3])
temp[3] <= -4'd0;
else
temp[3] <= 4'd1;
end
//加权结果,对应1
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
temp[4] <= 4'd0;
else if(input_buffer[4])
temp[4] <= 4'd1;
else
temp[4] <= -4'd0;
end
//加权结果,对应1
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
temp[5] <= 4'd0;
else if(input_buffer[5])
temp[5] <= 4'd1;
else
temp[5] <= -4'd1;
end
//加权结果,对应0
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
temp[6] <= 4'd0;
else if(input_buffer[6])
temp[6] <= -4'd0;
else
temp[6] <= 4'd1;
end
//加权结果,对应0
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
temp[7] <= 4'd0;
else if(input_buffer[7])
temp[7] <= -4'd0;
else
temp[7] <= 4'd1;
end
//加权结果,对应1
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
temp[8] <= 4'd0;
else if(input_buffer[8])
temp[8] <= 4'd1;
else
temp[8] <= -4'd0;
end
//加权结果,对应1
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
temp[9] <= 4'd0;
else if(input_buffer[9])
temp[9] <= 4'd1;
else
temp[9] <= -4'd0;
end
//加权结果,对应1
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
temp[10] <= 4'd0;
else if(input_buffer[10])
temp[10] <= 4'd1;
else
temp[10] <= -4'd0;
end
//加权结果,对应1
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
temp[11] <= 4'd0;
else if(input_buffer[11])
temp[11] <= 4'd1;
else
temp[11] <= -4'd0;
end
//加权结果,对应1
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
temp[12] <= 4'd0;
else if(input_buffer[12])
temp[12] <= 4'd1;
else
temp[12] <= -4'd0;
end
//计算加权求和结果
assign sum=temp[0]+temp[1]+temp[2]+temp[3]+temp[4]+temp[5]+temp[6]+temp[7]+temp[8]+temp[9]+temp[10]+temp[11]+temp[12];
//控制输出串行数据,需要将数据延迟一拍,目的是与有效数据使能信号valid_en对齐
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
data_temp <= 1'b0;
else if(axis_last)
data_temp <= 1'b0;
else
data_temp <= data_in;
end
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
data_out <= 1'b0;
else if(axis_last)
data_out <= 1'b0;
else
data_out <= data_temp;
end
//控制有效数据使能valid_en
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
valid_en <= 1'b0;
else if(axis_last)
valid_en <= 1'b0;
else if(sum == 4'd13)
valid_en <= 1'b1;
else
valid_en <= valid_en;
end
endmodule
需要注意的是,当帧头输入到帧头检测模块中,需要延迟两拍后才会产生有效数据使能信号,因此有效数据输出的时候才能与使能信号对齐。其次是将axis_last信号作为使能信号结束的判断标志,产生last信号表示一帧传输完成,则可以拉低有效数据使能信号,将FIFO的写使能失效。
2. 模块仿真
测试代码如下,与实验一的写法相同,产生一个包含帧头的串行数据
`timescale 1ns / 1ps
module tb_detect_frame_header();
//输入
reg clk; //时钟
reg rst_n; //低使能复位
wire data_in; //输入串行数据
reg axis_last; //将counter_tlast输入的last信号作为fifo的写不使能信号
//输出
wire [12:0]input_buffer; //输入缓存寄存器
wire [3:0]sum; //加权求和结果 五位有符号数表示范围-15~+15
wire valid_en; //有效数据使能
wire data_out; //输出串行数据
reg [31:0]temp_data; //模拟输入数据
reg [15:0] num; //计数
//信号初始化
initial begin
clk = 1'b0;
rst_n = 1'b0;
axis_last = 1'b0;
temp_data=32'b0001_1111_0011_0101_1001_0010_0011_0100;
#30
rst_n = 1'b1;
#660
axis_last = 1'b1;
end
//生成时钟
always #10 clk = ~clk;
//输出数据
always@(posedge clk) begin
if(!rst_n)
num <= 16'd31;
else
num <= num - 16'd1;
end
assign data_in = temp_data[num];
detect_frame_header detect_frame_header_inst(
.clk(clk),
.rst_n(rst_n),
.data_in(data_in),
.axis_last(axis_last),
.input_buffer(input_buffer),
.sum(sum),
.valid_en(valid_en),
.data_out(data_out)
);
endmodule
仿真结果如下,从图中可以看出,当帧头输入到检测模块后,延迟两拍使能信号置为1,并且开始输出有效数据,当axis_last信号被置为1后,使能信号置为0,功能与预期相符。
三 帧头环路测试
该实验在基于FIFO的串并转换环路实验_yoga1020的博客-CSDN博客
的基础上进行改进,1.FIFO设置为读写时钟独立的模式;2.添加Clocking Wizard模块产生慢时钟;3.添加ind_clk_fifo_rst模块产生FIFO的复位信号;4.添加add_frame_header和detect_frame_header模块,给串行数据添加帧头并检测帧头,将有用的数据转换为并行数据,通过DMA保存到内存DDR3中。
1. 硬件设计
Block Design如下图所示,数据环路用橘黄色线标记出来,
关于时钟IP核的使用可以参考:
Chapter004-FPGA学习之IP核相关功能【时钟、RAM、FIFO】_ASWaterbenben的博客-CSDN博客
Vivado 时钟IP核的使用_海盐nnn的博客-CSDN博客_vivado 时钟ip
关于FIFO的异步复位问题,可以参考官方文档pg057以及下面的链接
Xilinx FIFO Generator 需要注意RST复位_ShareWow丶的博客-CSDN博客_fifo复位需要几个时钟周期
如上图所示,使用安全电路的FIFO在异步复位的时候,复位信号需要维持至少8个慢时钟周期,并且复位开始后的60个慢时钟周期内都不能进行读写操作。因此设计产生FIFO复位信号的模块ind_clk_fifo_rst,该模块将DMA产生的复位信号作为输入,和FIFO同用慢时钟,产生可以维持8个周期的复位信号,并且在60个慢时钟周期后产生复位结束信号rst_down。
rst_done和header_end信号(包含not_empty)相与后作为FIFO_8to1的读使能信号;
rst_done和axis_converter的tvalid以及not_full信号相与后作为FIFO_8to1的写使能信号;
rst_done和axis_converter的tready以及not_empty信号相与后作为FIFO_1to8的读使能信号;
rst_done和有效数据使能valid_en以及not_full信号相与后作为FIFO_1to8的写使能信号;
这样可以保证在复位结束后FIFO才开始读写操作,并且也不会读空以及写满。
ind_clk_fifo_rst的代码如下:
module ind_clk_fifo_rst(
input clk, //FIFO的慢时钟
input sys_rst_n, //系统复位
input dma_rst_n, //DMA产生的复位信号
// output reg [7:0]cnt, //计数器
// output reg rst_flag, //DMA产生复位信号标志
output reg fifo_rst, //给FIFO的复位信号
output reg rst_done //复位完成信号
);
reg [7:0]cnt; //计数器
reg rst_flag; //DMA产生复位信号标志
//检测DMA是否产生复位信号
always@(posedge clk or negedge sys_rst_n or negedge dma_rst_n) begin
if(!sys_rst_n)
rst_flag <= 1'b1;
else if(!dma_rst_n)
rst_flag <= 1'b1;
else if(cnt == 8'd63)
rst_flag <= 1'b0;
else
rst_flag <= rst_flag;
end
//控制计数器
always@(posedge clk or negedge sys_rst_n or negedge dma_rst_n) begin
if(!sys_rst_n || !dma_rst_n || cnt == 8'd63)
cnt <= 8'd0;
else if(rst_flag)
cnt <= cnt + 8'd1;
end
//控制FIFO的复位信号
always@(posedge clk or negedge sys_rst_n) begin
if(!sys_rst_n)
fifo_rst <= 1'b0;
else if(rst_flag && cnt <= 8'd9 && cnt >8'd0)
fifo_rst <= 1'b1;
else
fifo_rst <= 1'b0;
end
//控制复位完成信号
always@(posedge clk or negedge sys_rst_n or negedge dma_rst_n) begin
if(!sys_rst_n || !dma_rst_n)
rst_done <= 1'b0;
else if(cnt == 8'd63)
rst_done <= 1'b1;
else
rst_done <= rst_done;
end
endmodule
2.软件设计
代码部分做了部分修改,因为还是遇到了单步调试没有问题,但是直接运行就会数据不符。
首先如果禁用cache,则在接收完成后打印一遍内存数据后,数据恢复正常。但是打印的数据显示前几个是错误的,也就是打印前后内存数据不一样,而且也不用全打印,只打印前8个数据就可以,但小于8还是会出问题。
如果使用cache,则接收完成后需要使用
Xil_DCacheFlush();
Xil_DCacheInvalidateRange((UINTPTR)RxBufferPtr, MAX_PKT_LEN);
再打印前8个数据,否则会出问题。
使用ILA查看DMA的M_AXI_S2MM接口,数据传输都是正常的,说明还是读取内存的问题。目前不清楚怎么更好的解决这个问题。
3.运行结果
软件运行结果如下
通过ILA捕获到的数据如下图所示,从图中可以看出添加帧头数据结束后header_end置为1,并开始输出FIFO中的数据。检测到帧头后,valid_en置为1,也就是fifo_1_to_8的读使能置为1,帧头后面的数据保存到FIFO中,转换为8位并行数据后通过AXIS传输。
这里需要注意的一个问题是,JTAG的时钟频率要小于ILA的时钟频率,JTAG默认时钟频率为15MHz,由于这里ILA使用的是5MHz的慢时钟,因此在硬件调试的时候会报错,所以需要将JTAG的时钟频率改为1MHz。关于这一点可以参考以下链接:
vivado-ila调试之时钟问题 - 灰信网(软件开发博客聚合) (freesion.com)
【vivado】ILA调试报错 The debug hub core was not detected 以及 Data read from hw_ila [hw_ila_1] is corrupted..._weixin_33909059的博客-CSDN博客
文章来源地址https://www.toymoban.com/news/detail-425245.html
四 设计改进
在前面的设计中涉及到跨时钟域控制的问题,可能会出现时序无法通过的问题。例如在并转串中FIFO的wr_en应该是在快时钟下,但是其控制信号为m_axis_tvalid & rst_done_p & (~fifo_full),其中rst_done是在慢时钟下产生的,这就导致跨时钟域控制,从而时序不收敛。因此需要对某些模块进行改进。
1.修改ind_clk_fifo_rst模块
首先需要修改的模块就是产生FIFO复位信号的模块。该模块根据AXIS_DMA输出的复位信号产生一个给FIFO使用的复位信号,并控制FIFO复位完成的时间。由于DMA输出的复位信号是在快时钟下,而产生的FIFO复位信号是在慢时钟下,因此导致跨时钟域控制的问题,为了解决该问题,参考了以下解决跨时钟域时序不收敛的处理方法,
FPGA中跨时钟域数据处理办法_好运公爵的博客-CSDN博客
FPGA跨时钟域处理方法_yang)的博客-CSDN博客_fpga跨时钟域处理
但经过尝试后并没有用。因此改使用Processor System Reset模块将DMA的复位信号同步到慢时钟下,并对之前的模块进行了修改:
Xilinx IP解析之Processor System Reset v5.0_徐晓康的博客的博客-CSDN博客_proc_sys_reset
主要的改动就是将慢时钟复位和快时钟复位分开处理。根据dma输出的快时钟下复位信号,产生rst_flag,并进行计数,最终产生快时钟下的rst_done;根据同步到慢时钟下的dma复位信号,产生慢时钟下的rst_done和fifo_rst。具体代码如下:
module ind_clk_fifo_rst(
input s_aclk, //慢时钟(串行信号)
input s_sys_rstn, //慢时钟的复位信号
input p_aclk, //快时钟(并行信号)
input p_sys_rstn, //快时钟的复位信号
input dma_rstn_p, //dma输出的复位信号(快时钟)
input dma_rstn_s, //dma输出的复位信号(同步到慢时钟下)
// output reg [8:0]cnt_p; //快时钟下的计数器
// output reg [8:0]cnt_s; //慢时钟下的计数器
output reg dma_rstn_p_ext, //展宽后的dma输出的复位信号(快时钟)
output reg fifo_rst, //给FIFO的复位信号
output reg rst_done_s, //慢时钟复位完成标志
output reg rst_done_p //快时钟复位完成标志
);
reg [9:0]cnt_p; //快时钟下的计数器
reg [8:0]cnt_s; //慢时钟下的计数器
reg rst_flag_p; //快时钟DMA产生复位信号标志
reg rst_flag_s; //慢时钟DMA产生复位信号标志
reg cnt_100_flag_p; //快时钟下cnt100信号
reg cnt_100_flag_s; //慢时钟下cnt100信号
reg cnt_10_flag_s; //慢时钟下cnt10信号
reg cnt_1_flag_p; //快时钟下cnt1信号
//检测DMA是否产生复位信号
always@(posedge p_aclk or negedge p_sys_rstn or negedge dma_rstn_p) begin
if(!p_sys_rstn || !dma_rstn_p)
rst_flag_p <= 1'b1;
else if(cnt_100_flag_p)
rst_flag_p <= 1'b0;
// else
// rst_flag_p <= rst_flag_p;
end
always@(posedge s_aclk or negedge s_sys_rstn or negedge dma_rstn_s) begin
if(!s_sys_rstn || !dma_rstn_s)
rst_flag_s <= 1'b1;
else if(cnt_100_flag_s)//(cnt == 8'd100)
rst_flag_s <= 1'b0;
// else
// rst_flag_s <= rst_flag_s;
end
//控制计数器
always@(posedge p_aclk or negedge p_sys_rstn or negedge dma_rstn_p) begin
if(!p_sys_rstn || !dma_rstn_p)
cnt_p <= 10'd0;
else if(rst_flag_p)
cnt_p <= cnt_p + 10'd1;
end
always@(posedge s_aclk or negedge s_sys_rstn or negedge dma_rstn_s) begin
if(!s_sys_rstn || !dma_rstn_s)
cnt_s <= 9'd0;
else if(rst_flag_s)
cnt_s <= cnt_s + 9'd1;
end
//控制cnt_flag100
always@(posedge p_aclk or negedge p_sys_rstn or negedge dma_rstn_p) begin
if(!p_sys_rstn || !dma_rstn_p)
cnt_100_flag_p <= 1'b0;
else if(cnt_p >= 10'd800)
cnt_100_flag_p <= 1'b1;
else
cnt_100_flag_p <= 1'b0;
end
always@(posedge s_aclk or negedge s_sys_rstn or negedge dma_rstn_s) begin
if(!s_sys_rstn || !dma_rstn_s)
cnt_100_flag_s <= 1'b0;
else if(cnt_s >= 9'd80)
cnt_100_flag_s <= 1'b1;
else
cnt_100_flag_s <= 1'b0;
end
//控制cnt_flag10
always@(posedge s_aclk or negedge s_sys_rstn or negedge dma_rstn_s) begin
if(!s_sys_rstn || !dma_rstn_s)
cnt_10_flag_s <= 1'b0;
else if(cnt_s <= 9'd10 && cnt_s >9'd0)
cnt_10_flag_s <= 1'b1;
else
cnt_10_flag_s <= 1'b0;
end
//控制cnt_flag1
always@(posedge p_aclk or negedge p_sys_rstn or negedge dma_rstn_p) begin
if(!p_sys_rstn || !dma_rstn_p)
cnt_1_flag_p <= 1'b0;
else if(cnt_p >= 10'd20)
cnt_1_flag_p <= 1'b1;
else
cnt_1_flag_p <= 1'b0;
end
//控制FIFO的复位信号
always@(posedge s_aclk or negedge s_sys_rstn or negedge dma_rstn_s) begin
if(!s_sys_rstn || !dma_rstn_s)
fifo_rst <= 1'b0;
else
fifo_rst <= cnt_10_flag_s;
end
//控制慢信号下的复位完成信号
always@(posedge s_aclk or negedge s_sys_rstn or negedge dma_rstn_s) begin
if(!s_sys_rstn || !dma_rstn_s)
rst_done_s <=1'b0;
else
rst_done_s <= cnt_100_flag_s;
end
//控制快信号下的复位完成信号
always@(posedge p_aclk or negedge p_sys_rstn or negedge dma_rstn_p) begin
if(!p_sys_rstn || !dma_rstn_p)
rst_done_p <=1'b0;
else
rst_done_p <= cnt_100_flag_p;
end
//在快时钟下,生成展宽后的dma输出的复位信号
always @ (posedge p_aclk or negedge p_sys_rstn)
begin
if (p_sys_rstn == 1'b0)
dma_rstn_p_ext <= 1'b1;
else if (~dma_rstn_p) //检测到dma输出的复位信号
dma_rstn_p_ext <= 1'b0;
else if (cnt_1_flag_p) //检测到复位完成信号,拉高
dma_rstn_p_ext <= 1'b1;
else;
end
endmodule
需要注意的是,因为快时钟的频率更快,所以快时钟的计数目标值要更高。需要先产生慢时钟的复位完成信号,再产生快时钟的复位完成信号,这样对FIFO的读写没有影响。在使用的时候还需要连接Processor System Reset模块,产生慢时钟下的DMA复位信号。
2.并转串的控制信号
(1)将fifo_not_full & rst_done_p(快时钟)作为m_axis_tready信号,表示FIFO不满并且快时钟复位完成时才开启axis传输。
(2)将m_axis_tvalid & fifo_not_full & rst_done_p(快时钟)作为fifo_wr_en信号,表示axis准备好、FIFO不满且快时钟复位完成时才开始往FIFO里些数据。
(3)将header_end(包含not empty) & rst_done_s(慢时钟)作为fifo_rd_en信号,表示FIFO不空、帧头输出完毕且慢时钟复位完成后才从FIFO中读出数据。
3.串转并的控制信号
(1)将not_tlast & fifo_not_empty & rst_done_p(快时钟)作为s_axis_tvalid信号,表示FIFO非空、快时钟复位完成且不是最后一个字节传输时,才开启axis传输。否则会出现两次传输之间有FF信号保存到converter的接收端,导致第二次接收数据出问题。
(2)将s_axis_tready & fifo_not_empty & rst_done_p(快时钟)作为fifo_rd_en信号,表示FIFO非空、快时钟复位完成且axis准备好才开始从FIFO中读出数据。
(3)将fifo_not_full & valid_en & rst_done_s(慢时钟)作为fifo_wr_en信号,表示FIFO不满、慢时钟复位完成且检测到帧头后才向FIFO中写入数据。
4.修改detect_frame_header模块
该模块取消掉axis_last控制信号。改让tlast信号通过控制s_axis_tvalid信号来控制什么时候结束串到并的转换。
5.修改counter_tlast模块
之前的设计中直接监测m_axis_tvalid的上升沿产生last_sig信号,没有同步到时钟下。因此参考一下链接实现上升沿检测:
FPGA基础学习——Verilog实现的边沿检测(上升沿下降沿检测)及Modelsim仿真_Fighting_XH的博客-CSDN博客_verilog边沿检测
module counter_tlast
#(
parameter CNT_TARGET = 16'd20 //parameter定义参数,目标字节数
)
(
input wire sys_clk,
input wire sys_rst_n,
// input wire cnt_sig, //计数信号
// output reg[15:0] cnt_num,
input edge_sig,
// output last_sig_r1,
// output reg last_sig_r2,
// input m_axis_tvalid,
output last_sig //tlast信号
);
reg [15:0] cnt_num; //16位宽的计数器
wire data;
reg [1:0]data_r;
wire pose_edge_flag;
wire last_sig_r1;
reg last_sig_r2;
assign data = edge_sig;
//设置两个寄存器,实现前后电平状态的寄存
//相当于对dat_i 打两拍
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
data_r <= 2'b00;
else
data_r <= {data_r[0], data}; //{前一状态,后一状态}
end
//组合逻辑进行边沿检测
//data_r[1]表示前一状态,data_r[0]表示后一状态
assign pose_edge_flag = ~data_r[1] & data_r[0];
/* 计数器cnt_num */
always@ (posedge sys_clk or negedge sys_rst_n)
begin
if(sys_rst_n == 1'b0) //复位信号有效时(复位时),cnt_num清零
cnt_num <= 16'd0;
else if (cnt_num == (CNT_TARGET>>2)-1) //计数到预设值,cnt_num清零
cnt_num <= cnt_num;
else if(pose_edge_flag)
cnt_num <= cnt_num + 16'd1; //每个cnt_sig上升沿cnt_num自增1
end
/* 由计数器控制的last_sig */
assign last_sig_r1 = (cnt_num == (CNT_TARGET>>2)-1)? 1'b1:1'b0;
always@ (posedge sys_clk or negedge sys_rst_n)
begin
if(sys_rst_n == 1'b0) //初始状态,需要使用dma的复位来给计数器进行复位
last_sig_r2 <= 1'b0;
else if (last_sig_r1 & edge_sig) //计数器cnt_num记满一次,last_sig拉高
last_sig_r2 <= 1'b1;
// else //cnt未记满,last_sig保持低电平
// last_sig = 1'b0;
else
last_sig_r2 <= last_sig_r2;
end
assign last_sig = last_sig_r2 | (last_sig_r1 & edge_sig);
//assign last_sig = last_sig_r2;
endmodule
需要注意的是,为了使tlast信号能和tvalid信号对齐,需要使用如下语句
last_sig = last_sig_r2 | (last_sig_r1 & edge_sig);
6.修改后的整体框图
7.软件运行结果
软件的运行结果如下,可以正确发射和接收。
软件部分发现之前需要打印的发送数据的问题解决了,现在不需要打印也可以正确接收,但还是需要使用Xil_DCacheFlush()更新内存。
文章来源:https://www.toymoban.com/news/detail-425245.html
到了这里,关于串行数据添加帧头以及帧头检测的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!