数字IC秋招手撕代码(六)异步FIFO
-
- 常用情况
- FIFO的空满标志
- FIFO指针格雷码
- 格雷码下的空满信号
- 重点
- 最小深度FIFO的计算
- 代码
最近两天晚上在看异步FIFO,之前也使用过异步FIFO,但是一直没有仔细搞懂原理。这次准备写篇博客记录一下。
常用情况
以下两种情况,经常会使用到异步FIFO
1.跨时钟域问题
2.位宽不一致(例如 输入8bit 输出16bit)这时候fifo中的内存结构应该和常见的不同,具体还不清楚
FIFO的空满标志
一个深度为(2^n)的FIFO,其地址位宽需要n+1位
- MSB作为指针折回标志,当指针完成一轮读或写,MSB就翻转一次。所以当wptr和rptr相遇时,如果MSB=1,为满;MSB=0,为空。
FIFO指针格雷码
由于二进制在跨时钟域上可能有所有位同时翻转的可能,而格雷码每次只有一位变化,可以大大降低亚稳态出现的可能。(同时如果多bit原码直接同步器传输,传输线等差异,会在bit间很容易造成亚稳态)
格雷码下的空满信号
因为地址是格雷码,所以不能单纯的比较n位是否相同来判断空满,而要考虑格雷码的对称性。
- 空:仍为wptr和rptr完全相等。
- 满:wptr和rptr的最高位和次高位都不同,而其余位都相同。
重点
因为在地址的同步逻辑中常使用打两拍来消除亚稳态,所以同步时常有延迟的现象,那么这会导致什么问题吗?
写快读慢的情况下
- fifo_empty的产生:这时候从读端看进去,要将wptr同步到rptr,所以wptr可能慢于实际wptr,不满足读空的条件时没问题;当条件满足时,因为延迟的关系,实际可能wptr>=rptr,fifo实际有写入数据,但这写不影响使用,这种情况叫虚空。
- fifo_full的产生:这时候从写端看进去,要将rptr同步到wptr,所以rptr可能慢于实际wptr,满足写满的条件时,没有问题;当条件不满足时,由于延迟的关系,实际可能rptr>=wptr-fifo_depth,这时候信号可能显示为full,实际已经多读了,这种情况叫虚满
写慢读快的情况分析类似
最小深度FIFO的计算
- 如果是同步FIFO问题:
例如
考虑背靠背情况,最坏在160cycle中写入了160个数据,而这16个cycle只能读出16*8=128个数据,FIFO中还有32个数据,所以FIFO的最小深度为32.
- 如果是异步FIFO,将上述情况改为
那么背靠背情况,80个cycle里写入了80个数据,而读侧在这些时间内只能读出
8*(80cylce/10cycle)*(100MHz/200MHz)=32
所以FIFO里至少还有48个数据
深度需要2^6 = 64
代码
module afifo(
input clka,
input rstn,
input wr_en,
input [len-1:0] din,
input clkb,
input rd_en,
output reg [len-1:0] dout,
output full, //for write domain
output empty //for read domain
);
parameter len = 4;
parameter depth = 16;
reg [len-1:0] memory [depth-2:0];
reg [3:0] wr_ptr,rd_ptr,status_cnt;
wire full,empty;
wire [3:0] gray_wr_ptr,gray_rd_ptr;
reg [3:0] gray_wr_clkb_dly0,gray_wr_clkb_dly1;
reg [3:0] gray_rd_clka_dly0,gray_rd_clka_dly1;
assign full = (gray_wr_ptr[3:0] == {~gray_rd_clka_dly1[3:2],gray_rd_clka_dly1[1:0]});
assign empty = (gray_rd_ptr[3:0] == gray_wr_clkb_dly1[3:0]) ;
always @(posedge clka or negedge rstn)begin
if(!rstn)
wr_ptr <= 0;
else if(wr_ptr==depth-1)
wr_ptr <= 0;
else if(wr_en & ~full)
wr_ptr <= wr_ptr + 1;
end
always @(posedge clkb or negedge rstn)begin
if(!rstn)
rd_ptr <= 0;
else if(rd_ptr==depth-1)
rd_ptr <= 0;
else if(rd_en & ~empty)
rd_ptr <= rd_ptr + 1;
end
integer i;
always @(posedge clka or negedge rstn)begin
if(!rstn)
for(i=0;i<depth;i=i+1)
memory[i] <= 0;
else if(wr_en & ~full)
memory[wr_ptr] <= din;
end
always @(posedge clkb or negedge rstn)begin
if(!rstn)
dout <= 0;
else if(rd_en & ~empty)
dout <= memory[rd_ptr];
end
assign gray_wr_ptr = wr_ptr ^ (wr_ptr>>1);
assign gray_rd_ptr = rd_ptr ^ (rd_ptr>>1);
always @(posedge clkb or negedge rstn)begin
if(!rstn)begin
gray_wr_clkb_dly0 <= 0;
gray_wr_clkb_dly1 <= 0;
end
else begin
gray_wr_clkb_dly0 <= gray_wr_ptr;
gray_wr_clkb_dly1 <= gray_wr_clkb_dly0;
end
end
always @(posedge clka or negedge rstn)begin
if(!rstn)begin
gray_rd_clka_dly0 <= 0;
gray_rd_clka_dly1 <= 0;
end
else begin
gray_rd_clka_dly0 <= gray_rd_ptr;
gray_rd_clka_dly1 <= gray_rd_clka_dly0;
end
end
endmodule