天天看点

数字IC秋招手撕代码(六)异步FIFO

数字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问题:

例如

数字IC秋招手撕代码(六)异步FIFO

考虑背靠背情况,最坏在160cycle中写入了160个数据,而这16个cycle只能读出16*8=128个数据,FIFO中还有32个数据,所以FIFO的最小深度为32.

  • 如果是异步FIFO,将上述情况改为
    数字IC秋招手撕代码(六)异步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
           

继续阅读