前言
米联客给出的数据输入仅仅是在相同时钟域下的实现,fifo的输入输出时钟都为100MHz,然而在我的项目中,需要的是对音频信号以31.25KHz进行采样,然后将采样得到的数据以40MHz的速率送到fifo中,并以120MHz的速率进行读取。
在读取到DDR中之后,在PS端进行滤波处理,滤波结束后DMA以120Mhz传输到fifo_out,并以8Mhz进行读取输出。
这篇将记录异步时钟域fifo的实现。
BD工程结构
版本1
版本2
测试数据产生
已知实际数据为31.25kHz频率采样,且每个数据会伴随一个valid信号,故采用下列代码产生31.25kHz的递增数列数据。
module test(
input clk,
input rst,
input en,
output reg valid,
output reg [63:0] data_out
);
reg [10:0] cnt;
// reg [9:0] cnt1;
always @(posedge clk or negedge rst) begin
if (!rst || !en) begin
cnt = 1'b0;
end
else begin
if (cnt >= 11'd1279) begin
cnt = 1'b0;
end
else begin
cnt = cnt + 1'b1;
end
end
end
always @(posedge clk or negedge rst) begin
if (!rst) begin
valid = 1'b0;
end
else begin
if (cnt == 11'd1) begin
valid = 1'b1;
end
else begin
valid = 1'b0;
end
end
end
always @(posedge clk or negedge rst) begin
if (!rst || !en) begin
data_out = 64'b0;
end
else begin
// if (data_out == 64'd1023 && cnt == 5'd9) begin
// data_out = 64'b0;
// end
// else
if (cnt == 5'd9) begin
data_out = data_out + 64'd1;
end
else begin
data_out = data_out;
end
end
end
endmodule
LAST信号产生
为了实现axis stream fifo的正确读写和时序同步,每帧数据末尾产生一个last信号。
module datainput(
input wire clk40M,
input wire s_axis_aresetn,
input wire valid,
input wire [63:0] data_in,
input wire S_AXIS_tready,
input en,
input wire s_axis_aclk,
output reg S_AXIS_tvalid,
output reg S_AXIS_tlast,
output wire [7:0]S_AXIS_tkeep,
output wire [63:0] S_AXIS_tdata,
output reg [10:0] cnt_r
);
reg [10:0] cnt ;
wire flag;
reg first = 1'b1;
assign S_AXIS_tkeep = 8'b11111111;
assign S_AXIS_tdata = data_in;
always @(posedge clk40M or negedge s_axis_aresetn) begin //计数器
if(!s_axis_aresetn || !en) begin
cnt <= 11'd0;
cnt_r <= 11'd0;
end
else if(valid && S_AXIS_tready) begin
// if (first) begin
// if(cnt < 11'd515) begin
// cnt <= cnt + 1'b1;
// end
// else begin
// cnt <= 11'd0;
// end
// cnt_r <= cnt;
// end
// else begin
if(cnt < 11'd511) begin
cnt <= cnt + 1'b1;
end
else begin
cnt <= 11'd0;
end
cnt_r <= cnt;
// end
end
else begin
cnt <= cnt;
cnt_r <= cnt;
end
end
// always @(posedge clk40M ) begin
// cnt_r <= cnt;
// end
// always @(posedge clk40M or negedge s_axis_aresetn) begin
// if(!s_axis_aresetn)begin
// S_AXIS_tvalid <= 1'b0;
// S_AXIS_tlast <= 1'b0;
// flag <= 1'b0;
// end
// else if (cnt == 11'd0&& flag) begin
// flag <= 1'b0;
// S_AXIS_tlast <= 1'b1;
// S_AXIS_tvalid <= valid;
// end
// else if (cnt == 11'd1023) begin
// flag <= 1'b1;
// S_AXIS_tvalid <= valid;
// end
// else
// S_AXIS_tlast <= 1'b0;
// S_AXIS_tvalid <= valid;
// end
// always @(posedge clk40M or negedge s_axis_aresetn) begin
// if(!s_axis_aresetn)begin
// S_AXIS_tvalid <= 1'b0;
// S_AXIS_tlast <= 1'b0;
// // flag <= 1'b0;
// end
// else if (valid && S_AXIS_tready) begin
// S_AXIS_tvalid <= 1'b1;
// if (cnt_r == 11'd1023) begin
// S_AXIS_tlast <= 1'b1;
// end
// end
// else if(!S_AXIS_tready) begin
// S_AXIS_tvalid <= S_AXIS_tvalid;
// S_AXIS_tlast <= S_AXIS_tlast;
// end
// else begin
// S_AXIS_tvalid <= 1'b0;
// S_AXIS_tlast <= 1'b0;
// end
// end
reg [1:0] state;
assign flag = (cnt != cnt_r )? 1'b1:1'b0;
always@(posedge clk40M)
begin
if(!s_axis_aresetn) begin
S_AXIS_tvalid <= 1'b0;
S_AXIS_tlast <= 1'b0;
state <=0;
end
else begin
case(state)
0: begin
if(en && S_AXIS_tready) begin
if (flag) begin
S_AXIS_tvalid <= 1'b1;
state <= 1;
end
else begin
S_AXIS_tvalid <= 1'b0;
state <= 0;
end
end
else begin
S_AXIS_tvalid <= 1'b0;
state <= 0;
end
end
1:begin
if(en && S_AXIS_tready) begin
if (flag) begin
S_AXIS_tvalid <= 1'b1;
// if (first) begin
// if (cnt_r == 11'd515) begin
// S_AXIS_tlast <= 1'b1;
// first <= 1'b0;
// state <= 2;
// end
// else begin
// S_AXIS_tlast <= 1'b0;
// state <= 1;
// end
// end
// else begin
if(cnt_r == 11'd511) begin
S_AXIS_tlast <= 1'b1;
state <= 2;
end
else begin
S_AXIS_tlast <= 1'b0;
state <= 1;
end
// end
end
else begin
S_AXIS_tvalid <= 1'b0;
S_AXIS_tlast <= 1'b0;
state <= 1;
end
end
else begin
state <= 1;
end
end
2:begin
if(!S_AXIS_tready) begin
S_AXIS_tvalid <= S_AXIS_tvalid;
S_AXIS_tlast <= S_AXIS_tlast;
state <= 2;
end
else begin
S_AXIS_tvalid <= 1'b0;
S_AXIS_tlast <= 1'b0;
state <= 0;
end
end
default: state <=0;
endcase
end
end
endmodule
异步读写实现
以axis data fifo in为例
写入速度小于读入速度
使能读写数据计数
添加一个判断模块fifoin_full_0,每当fifo中写入512个数据时,就输出一个中断信号full到ps端,ps端进入中断处理函数,使能Rxgo信号,当Rxgo为1且Rxready为1接收就绪时,执行一次DMA传输
由于DMA读写速度为120MHz,而数据的采样速率为31.25Khz,且fifo写入速度为40Mhz,所以当DMA读取512个数据时,fifo里面数据甚至还没写入下一个数据,且fifo深度设为1024,一定不会产生溢出。
以axis data fifo out为例
写入速度大于读入速度
添加一个判断模块fifoout_empty_0,每当fifo为空,就输出一个中断信号empty,ps端进入中断处理函Txgo信号,当Txgo=1且Txready=1发送就绪时,执行一次DMA传输,传输512个32位数据到FIFO out。
由于DMA读写速度为120MHz,而数据的输出速率位31.25kHz,且fifo输出速度为8Mhz,所以一定要等到fifo慢慢读空之后再往里面写入512个数据,由于DMA写入速度很快,所以不会出现输出缺失数据的现象。
数据输出
输出数据照样以31.25kHz输出,因为FIFOout为8MHz,所以设计一个计数器,每256拉高一次M axis ready信号,读取一个数据输出。
module audio_Left_out
(
// Inputs
input clk_i //8M hZ
,input rst_i
,input audio_valid_i
,input [ 31:0] inport_tdata_i
// Outputs
,output i2s_sck_o
,output i2s_sdata_o
,output i2s_ws_o
//ready
,output M_AXIS_TREADY
//debug
//,output[4:0] bit_count_q
//,output[4:0] cnt
);
//-----------------------------------------------------------------
// Registers
//-----------------------------------------------------------------
//reg sck_q;
reg sdata_q;
reg ws_q;
//reg [4:0] bit_count_q;
reg [5:0] cnt;
//reg bit_clock_w;
//-----------------------------------------------------------------
// Buffer
//-----------------------------------------------------------------
reg [31:0] buffer_q;
reg [8:0] cnt1;
//计数器
always @ (posedge rst_i or posedge clk_i)
if (rst_i)
begin
cnt1 <= 9'd0;
end
else
begin
if (cnt1 >= 255) begin
cnt1 <= 0;
end
else begin
cnt1 <= cnt1 + 1;
end
end
//M_AXIS_TREADY 31.25kHz
assign M_AXIS_TREADY = (cnt1 >= 255) ? 1'b1 : 1'b0;
always @ (posedge rst_i or posedge clk_i or posedge audio_valid_i)
if (rst_i || audio_valid_i)
begin
cnt <= 6'd63;
buffer_q <= inport_tdata_i;
end
else
begin
cnt <= cnt - 6'd1;
buffer_q <= buffer_q;
end
always @ (posedge rst_i or negedge clk_i )
if(rst_i)
begin
sdata_q <= 1'b0;
ws_q <= 1'b0;
end
else if( cnt > 31)
begin
sdata_q <= buffer_q[cnt-32];
ws_q <= 1'b1;
end
else
begin
sdata_q <= buffer_q[cnt];
ws_q <= 1'b0;
end
assign i2s_sck_o = clk_i;
assign i2s_sdata_o = sdata_q;
assign i2s_ws_o = ws_q;
endmodule
坑:
1.status = 15
2.fifo last问题
3.fifo 内部有四个缓存
现象分析:
FIFO在写入数据时,前几个数据虽然已经写入fifo,但是wr_data_count并不会增加,如图中所示,在输入的数据已经到511时,wr_data_count只有507。同样对于rd_data_count,在第二张图可以看到,前几个写入的数据直接被放在了输出的总线上,所以rd_data_count会短暂的变成1后又重新回到0,一直输入四个数据后,rd_data_count才开始正常增加。
原因猜测:
好像没什么影响,就是个现象
可能1
FIFO First-word-Fall-Through模式的仿真 - 知乎 (zhihu.com)
FWFT与Standard模式的区别,在axis stream fifo默认是fwft模式,会先将得到的数据放在输出总线上,以减少延迟。
可能2
【FPGA基础篇】Xilinx FIFO详细解析_mrVillain的博客-优快云博客
Read Data Count 和 Write Data Count 都是采用的保守估计的方式。什么是保守估计,意思就是假设FIFO中至少有8个数据可读,但FIFO只跟你说我这里只有8个数据可读,那么你就会认为只能读8个数据,实际上可能能读9个或者10个,这么做的原因时为了保证FIFO读操作的安全性,后面我们分析异步FIFO的时候就能深刻理解这么做的精妙之处,这就是保守估计。类似的,当FIFO中还可以写至少8个数据时,FIFO会说只能写8个数据了,这样子你也只会写8个数据,实际上FIFO可以写9-10个左右。
4.中断冲突问题,不能在一个中断没有结束的时候产生另一个中断
现象分析:
在一开始,我在vitis中初始化中断后,将DMA传输函数放在了fifo的空/满中断处理函数中,
Status = XAxiDma_SimpleTransfer(&AxiDma,(u32)(TxBufferPtr),
(u32)(MAX_PKT_LEN_TX), XAXIDMA_DMA_TO_DEVICE);
但是DMA传输函数会产生中断信号来告诉ps端传输结果,这样就导致了在其中一个中断运行过程中触发了另一个中断。(理论上是不是可以设置中断优先级来处理?没试验过
解决方案:
在fifo的空/满中断处理函数就改变一个全局参数,之后在主函数中根据该参数判断是否运行传输函数,这样子fifo的空/满中断结束很快,不会影响到DMA传输函数中断。
7. DMA输入输出之间的最大延迟和最小延迟
现象分析:
DMA的两次传输函数应用的间隔有最大延迟时间和最小延迟时间的要求,必须在某个时间区间内。
产生延迟的主要原因是ps端的处理函数,比如滤波过程产生的延迟。
若延迟过大,则会导致FIFO in两次读取的间隔过大,FIFO in溢出,FIFO out两次写入的间隔过大,FIFO out长时间等待数据。
若延迟过小,则会导致FIFO in两次读取的间隔过小,DMA不能读取到完整的一帧数据,导致数据处理紊乱,FIFO out两次写入间隔过短,数据来不及传输出去,导致溢出。
解决方案:
采用了根据wr/rd data count计数的中断来触发DMA传输函数,可以有效避免延迟过小的问题。比如FIFO in 写入512个数据就DMA读取一次,FIFO out为空就传输一次。
延迟过大的问题无法直接解决,主要根据计算FIFO写入和读取一帧数据的时间差来计算最大延迟,并保证PS的处理函数所需时间小于最大延迟。或者采用乒乓缓存的方式。
8. 810 1800 输出中断没响应
现象分析:
由于输出中断是在FIFO out的rd data count为0时触发,所以在一开始产生了输出中断,但是此时DDR中仍未传入有效数据,所以输出的也是随机乱码。
解决方案:
采用了方案2
1、可以让FIFO out的rd data count为1时触发
2、添加了一个GPIO模块作为FIFO out的使能模块,并在第一次传输完成后将gpio拉高。
9.输出dma的fifo读取很快 810 1900-1952
10.DcacheFlushRange和DcacheInvalidateRange
用DMA进行数据传输时,需要解决DCache(Data Cache)和DDR Memory之间的数据一致性问题。
PS通过DMA向PL写数据
1)调用:Xil_DCacheFlushRange(INTPTR adr, u32 len);
2)DMA写PL:XAXIDMA_DMA_TO_DEVICE
PS通过DMA从PL读数据
1)DMA读PL:XAXIDMA_DEVICE_TO_DMA
2)调用:Xil_DCacheInvalidateRange(INTPTR adr, u32 len)
解析:
Xil_DCacheFlushRange(INTPTR adr, u32 len);的作用时将Cache中的数据刷新到DDR中去,也就是说当我们对得到的数据进行处理,并想将这些数据输出到PL端时,我们处理后的数据都是先放在Cache当中,需要先刷新到DDR中,再由DMA将数据从DDR中传输到PL端。
Xil_DCacheInvalidateRange(INTPTR adr, u32 len);的作用时将Cache中数据无效化,并将DDR中的数据刷新到Cache中,以得到最新的数据。当我们从PL端读取数据后,新的数据若要显示在Cache中就要调用Xil_DCacheInvalidateRange(INTPTR adr, u32 len)。
11. 811 jtag速度过快
现象分析:
ERROR: [Labtools 27-3412] Mismatch between the design programmed into the device 'xc7a100t' (JTAG device index = '0'
and the probes file(s) 'E:/0_WORK/FQPB_Prj/par/FQPB_Prj_0126.runs/impl_1/IDCU_FPGA_TOP.ltx'.
The hw_probe 'u0_sys_clk_gen/async_rst' in the probes file has port index '1'. This port location for the ILA core at location (uuid_44686827098D55E5A4639609BA1F5BBF), does not support a data probe.
.
Resolution:
1) Ensure that the clock signal connected to the debug core and/or debug hub is clean and free-running.
2) Ensure that the clock connected to the debug core and/or debug hub meets all timing constraints.
3) Ensure that the JTAG clock frequency is 2.5x times slower than the frequency of the clock connected to your debug hub.
jtag的速度必须小于等于ILA最小时钟频率的1/2.5,因为本次项目中最小ILA的频率为8M,所以jtag的传输速度必须小于3.2M。
解决方案:
修改Jtag速度:xilinx usb下载器 速度高速极限设置 JTAG-SMT2 JTAG-HS2 JTAG-HS3和Platform Cable USB DLC9 DLC10速度测试-优快云博客
12.wr_data_count和rd_data_count不一样
【FPGA基础篇】Xilinx FIFO详细解析_mrVillain的博客-优快云博客
由于wr_data_cnt同步于wr_clk,wr_en也同步于wr_clk,所以wr_en使能后,每写入一个data,都可以在下个时钟沿反馈到wr_data_cnt上,写入一个数据,wr_data_cnt就加一;相应的,读出一个数据,wr_data_cnt也需要减一,才能正确反应FIFO中的数据量。但对于rd_en,由于rd_en是同步于rd_clk的,所以rd_en的使能需要通过一系列的时钟同步到wr_clk中才能反映到wr_data_cnt上,通常采用的方式是格雷码和打拍子的方式,如下图所示,可以看到需要将Read Counter通过格雷码转换(格雷码转换可以消除很多中间不定态,大大增强了稳定性),再进行跨时钟域传输,这里跨时钟域传输一般可以选择打拍子的方式,由于已经转化成了格雷码,此时多bit传输就可以变成单bit传输,因为相邻state只有其中一个bit发生了变化,单bit跨时钟域传输就可以采用打拍子的方式进行时钟域转换,这样子就造成了的结果就是传送到wr_clk时钟域时的Read Counter的数值是过去的Read Counter,比最新的Read Counter要小,因此Write Counter-Read Counter偏大,从而认为FIFO中有更多的数据存储(实际上并没有那么多数据),因此wr_data_cnt输出的信息就是FIFO中最多有多少个数据,至少还能写入多少个数据,而我们能知道的就是我们只要写入的数据不超过它的最小值,就能保证FIFO永远不会写满,这样子安全性就提高了。