| |
fifo是跨时钟域的典型应用,其设计涉不少需要注意的事项,趁着找工作之际,好好研究一下。
首先赶紧记录几个容易遗忘的地方:
1.
读写指针用什么形式编码?二进制还是格雷码?
答:要用二进制,因为读写memory地址还是要二进制编码。
2.
读写指针同步到对方的clock domain为什么要用格雷码?答:因为格雷码特点是每次只有一bit变化,这样可以采对,否则二进制若有两位变化引起毛刺可能会被同步到,引起地址错误。
3. 怎样判断满full和空empty标志? 方法一:Full=读指针等于写指针并且上一次写指针加1即等于读指针;方法二:多扩展一位地址位,如果读写指针最高位不等并且低位相等,那么就是满标志,如果地址完全相等,那么就是空标志;
下面是我写的一种比较通用的异步FIFO,参数化数据位宽和地址位宽(这里没有列出TB和格雷码二进制相互转换电路代码)。VCS仿真过,见附件波形。
`timescale
1ns/1ps
module
async_fifo(
clk_wr,rst_wr,wr_en,wr_data,
clk_rd,rst_rd,rd_en,
wr_ptr,rd_ptr,
rd_data,full,empty);
parameter DATA_WIDTH=8;
parameter FIFO_DEPTH=8;
parameter ADDR_WIDTH=3;
input clk_wr;
input rst_wr;
input wr_en;
input [DATA_WIDTH-1:0] wr_data;
input clk_rd;
input rst_rd;
input rd_en;
output logic [ADDR_WIDTH-1:0] wr_ptr;
output logic [ADDR_WIDTH-1:0] rd_ptr;
output logic [DATA_WIDTH-1:0] rd_data;
output logic full;
output logic empty;
//memory
logic
[DATA_WIDTH-1:0] fifo_mem [FIFO_DEPTH-1:0];
logic [ADDR_WIDTH-1:0] rd_ptr_gray_r,wr_ptr_gray_r;
//read/write pointer gray code register out
//write
clock domain begin
logic [ADDR_WIDTH-1:0] wr_ptr_next;
logic
[ADDR_WIDTH-1:0] wr_ptr_next_gray;
always_ff
@(posedge clk_wr or negedge rst_wr)
if(~rst_wr)
begin
wr_ptr<={ADDR_WIDTH{1'b0}};
wr_ptr_gray_r<={ADDR_WIDTH{1'b0}};
end
else begin
wr_ptr<=wr_ptr_next;
wr_ptr_gray_r<=wr_ptr_next_gray;
end
always_comb
wr_ptr_next=wr_en
? wr_ptr+1'b1 : wr_ptr;
logic
[ADDR_WIDTH-1:0] wr_ptr_next_gray;
/*bin2gray
AUTO_TEMPLATE (
.gray
(wr_ptr_next_gray[ADDR_WIDTH-1:0]),
.bin (wr_ptr_next[ADDR_WIDTH-1:0]),
);
*/
bin2gray #(
.WDITH(ADDR_WIDTH)
)
bin2gray_wr_ptr(/*AUTOINST*/
// Outputs
.gray
(wr_ptr_next_gray[ADDR_WIDTH-1:0]), // Templated
// Inputs
.bin (wr_ptr_next[ADDR_WIDTH-1:0])); //
Templated
always_ff
@(posedge clk_wr)
if(wr_en&!full)
fifo_mem[wr_ptr]<=wr_data;
logic
[ADDR_WIDTH-1:0] rd_ptr_sync_d1,rd_ptr_sync_d2;
always_ff
@(posedge clk_wr or negedge rst_wr)
if(~rst_wr)
begin
rd_ptr_sync_d1<={ADDR_WIDTH{1'b0}};
rd_ptr_sync_d2<={ADDR_WIDTH{1'b0}};
end
else begin
rd_ptr_sync_d1<=rd_ptr_gray_r;
rd_ptr_sync_d2<=rd_ptr_sync_d1;
end
logic
[ADDR_WIDTH-1:0] rd_ptr_wr_side;
/*gray2bin
AUTO_TEMPLATE(
.bin (rd_ptr_wr_side[ADDR_WIDTH-1:0]),
.gray
(rd_ptr_sync_d2[ADDR_WIDTH-1:0]),
);
*/
gray2bin
#(ADDR_WIDTH) gray2bin_wr_ptr(/*AUTOINST*/
//
Outputs
.bin
(rd_ptr_wr_side[ADDR_WIDTH-1:0]), // Templated
//
Inputs
.gray
(rd_ptr_sync_d2[ADDR_WIDTH-1:0])); // Templated
logic
fifo_nearly_full;
always_ff
@(posedge clk_wr or negedge rst_wr)
if(~rst_wr)
fifo_nearly_full<=1'b0;
else
if((wr_ptr_next==rd_ptr_wr_side)&wr_en&!full)
fifo_nearly_full<=1'b1;
else
if(wr_ptr!=rd_ptr_sync_d2)
fifo_nearly_full<=1'b0;
assign
full=(wr_ptr==rd_ptr_wr_side)&fifo_nearly_full;
//write
clock domain end
//read clock
domain begin
logic
[ADDR_WIDTH-1:0] rd_ptr_next_gray;
logic
[ADDR_WIDTH-1:0] rd_ptr_next;
always_ff
@(posedge clk_rd or negedge rst_rd)
if(~rst_rd)
begin
rd_ptr<={ADDR_WIDTH{1'b0}};
rd_ptr_gray_r<={ADDR_WIDTH{1'b0}};
end
else begin
rd_ptr<=rd_ptr_next;
rd_ptr_gray_r<=rd_ptr_next_gray;
end
always_comb
rd_ptr_next=rd_en
? rd_ptr+1'b1 : rd_ptr;
/*bin2gray
AUTO_TEMPLATE (
.gray
(rd_ptr_next_gray[ADDR_WIDTH-1:0]),
.bin
(rd_ptr_next[ADDR_WIDTH-1:0]),
);
*/
bin2gray
#(ADDR_WIDTH) bin2gray_rd_ptr(/*AUTOINST*/
// Outputs
.gray
(rd_ptr_next_gray[ADDR_WIDTH-1:0]), // Templated
//
Inputs
.bin
(rd_ptr_next[ADDR_WIDTH-1:0])); // Templated
always_ff
@(posedge clk_rd)
if(rd_en&!empty)
rd_data<=fifo_mem[rd_ptr];
logic
[ADDR_WIDTH-1:0] wr_ptr_sync_d1,wr_ptr_sync_d2;
always_ff
@(posedge clk_rd or negedge rst_rd)
if(~rst_rd)
begin
wr_ptr_sync_d1<={ADDR_WIDTH{1'b0}};
wr_ptr_sync_d2<={ADDR_WIDTH{1'b0}};
end
else begin
wr_ptr_sync_d1<=wr_ptr_gray_r;
wr_ptr_sync_d2<=wr_ptr_sync_d1;
end
logic
[ADDR_WIDTH-1:0] wr_ptr_rd_side;
/*gray2bin
AUTO_TEMPLATE(
.bin (wr_ptr_rd_side[ADDR_WIDTH-1:0]),
.gray
(wr_ptr_sync_d2[ADDR_WIDTH-1:0]),
);
*/
gray2bin
#(ADDR_WIDTH) gray2bin_rd_ptr(/*AUTOINST*/
//
Outputs
.bin (wr_ptr_rd_side[ADDR_WIDTH-1:0]),
// Templated
//
Inputs
.gray
(wr_ptr_sync_d2[ADDR_WIDTH-1:0])); // Templated
logic
fifo_nearly_empty;
always_ff
@(posedge clk_rd or negedge rst_rd)
if(~rst_rd)
fifo_nearly_empty<=1'b0;
else
if((rd_ptr_next==wr_ptr_rd_side)&rd_en&!empty)
fifo_nearly_empty<=1'b1;
else
if(rd_ptr!=wr_ptr_rd_side)
fifo_nearly_empty<=1'b0;
assign
empty=(rd_ptr==wr_ptr_rd_side)&fifo_nearly_empty;
//read clock
domain end
endmodule
首先,一定要理解清楚FIFO的应用场景,这个会直接关系到FIFO深度的计算,如果是面试官抛出的问题,那么有不清楚的地方,就应该进行询问。如果是笔试或者工程中需要计算FIFO深度的话,那么就需要自己考虑清楚。
其次,异步FIFO,读写时钟不同频,那么FIFO主要用于数据缓存,我们选择的FIFO深度应该能够保证在最极端的情况下,仍然不会溢出。因此考虑的前提一般都是写时钟频率大于读时钟频率,但是若写操作是连续的数据流,那么再大的FIFO都无法保证数据不溢出。因此可以认为这种情况下写数据的传输是“突发Burst”的,即写操作并不连续,设计者需要根据满标志控制或者自己来控制写操作的起止。
宏观地,从整个时间域上看,"写数据=读数据",这个条件必须要满足,如果这个大条件不满足的话,用FIFO是没有效果的。但是在发送方"突发"发送数据的时间T内,是很有可能写数据>读数据的,因此FIFO的深度要能够保证,在这段时间T内,如果接收方未能将发送方发送的数据接收完毕的话,剩下的数据都是可以存储在FIFO内部而且不会溢出的,那么在发送方停止发送数据的"空闲时隙"内,接收方可以从容地接收剩下来的数据。
红字部分就是个人认为在FIFO深度计算中,最重要的部分了。接着来看一个例子,这是我看一个网友写时,是他当时遇到的一道笔试题。
一个8bit宽的AFIFO,输入时钟为100MHz,输出时钟为95MHz,设一个package为4Kbit,且两个package之间的发送间距足够大。问AFIFO的深度。
因为这位网友可能只是简述,因此信息并不完整,我的个人理解是这样的场景,一个异步FIFO,读写频率不同,读写位宽相同。发送发一次Burst突发的数据量为4Kbit,即500Word,在两次Burst突发之间有足够的时间,因此我们只用考虑在发送方Burst发送数据的时间T内,如果接受方没法将数据全部接受,其余数据均可存在FIFO内且不溢出,那么在发送方停止Burst发送数据的时间段内,接收方就可以从容的从FIFO内读取数据。首先发送方Burst发送数据的时间段为 T =
500/100MHz,发送的数据量为 B_send = 500word,而在T这段时间内,接收方能够接受的数据量为B_rec = T*95MHz = 500 * 95 / 100 word
= 475word,因此 B_remain = B_send - B_rec = 500 - 475 = 25
。那么FIFO的深度至少要大于等于25才行。
再看另外一个例子,还是从网上找到的,
写时钟频率w_clk,
读时钟频率r_clk,
写时钟周期里,每B个时钟周期会有A个数据写入FIFO
读时钟周期里,每Y个时钟周期会有X个数据读出FIFO
则,FIFO的最小深度是?
首先,我们可以认为写操作是Burst突发的。
其次,写操作的效率并不是100%的,而是A/B的,因此我们可以认为实际的F_wr = (A/B)*w_clk,同理,实际中F_rd
= (X/Y)*r_clk。
另外,和第一个例子不同的是,这个题目里面并没有约束Burst突发的场景,在正常情况下,应该是这样的
空闲---Burst突发---空闲---Burst突发---空闲---Burst突发。但是我们在计算中,需要考虑最极端的情况,即
空闲---Burst突发---Burst突发---空闲---Burst突发---空闲。即传输过程中,可能会出现"背靠背"的情况,那么我们设计的FIFO深度必须能够保正,在"背靠背"的时间段内,如果接收方没法接受所有数据,那么剩余的数据可以被存储在FIFO内部且不会溢出。那么就可以开始计算了。假设"背靠背"时发送的数据 = BL,那么"背靠背"的时间 = BL / w_clk ,注意,这段时间内 F_wr
= w_clk 而不是之前提到的 (A/B)*w_clk。在这段时间内,接收方可以接受的数据
= (BL / w_clk) * (X/Y)*r_clk ,
剩下的数据量 = BL - ( BL /
w_clk ) * (X/Y)*r_clk,那么FIFO的深度至少就要为
" depth = BL - ( BL / w_clk ) *
(X/Y)*r_clk "这样的深度了。
将上述公式变换下,得到 depth = BL - BL *
(X/Y) * (r_clk/w_clk) 。这个公式就是网上流传的计算FIFO深度的公式,我想应该就是这个推理过程吧。
上述的讨论的一个前提就是FIFO的读写位宽一致,如果这个条件不满足的话,那么FIFO的深度的计算就更加复杂一些,但是我们还是可以把FIFO的读写位宽也折合成一定的因子,带入
实际的F_wr = (A/B)*w_clk 和 F_rd = (X/Y)*r_clk 中去,应该是是可以解决的。
一、异步FIFO最小深度计算原理
如果数据流连续不断则FIFO深度无论多少,只要读写时钟不同源同频则都会丢数;
FIFO用于缓冲块数据流,一般用在写快读慢时,遵循的规则如下:
{FIFO深度 /(写入速率 - 读出速率)} = {FIFO被填满时间} > {数据包传送时间}= {写入数据量 / 写入速率}
即:确保对FIFO写数据时不存在overflow,从FIFO读出数据时不存在underflow.
例:A/D采样率50MHz,dsp读A/D读的速率40MHz,要不丢失地将10万个采样数据送入DSP,在A/D在和DSP之间至少加多大容量(深度)的FIFO才行?
100,000 / 50MHz = 1/500 s = 2ms
(50MHz - 40MHz) *
1/500 = 20k既是FIFO深度。
一种错误的算法(我也犯了同样的错误):
100,000 / 40MHZ= 1/400s = 2.50ms
(50M - 400M)*1/400 =25K.那么这样进去的数据就不是100K了,而是100K+50M*(0.0025-0.002)=125,000bit,错误在时间的计算。
二、异步FIFO最小深度常用计算公式(假如读写FIFO是同时进行的)
写时钟频率w_clk,
读时钟频率 r_clk,
写时钟周期里,每B个时钟周期会有A个数据写入FIFO
读时钟周期里,每Y个时钟周期会有X个数据读出FIFO
则,FIFO的最小深度是?
计算公式如下:
fifo_depth = burst_length - burst_length
* X/Y * r_clk/w_clk
此公式可有原理推导而来。
例举说明:
如果100个写时钟周期可以写入80个数据,10个读时钟可以读出8个数据。令wclk=rclk