异步FIFO常用知识总结

在做集创赛项目时使用到了异步fifo,查找了很久才发现了漏数据的问题,因此再次补习了一下异步FIFO知识。

1.同步FIFO和异步FIFO

        FIFO作为数字电路中的常客经常用于数据的缓冲存储(当然也是面试笔试中的常考问题)。对于同步FIFIO,主要是实现速率匹配,起到数据缓冲的作用。对于异步FIFO,主要是实现不同时钟域之间的数据交互,涉及到指针跨时钟域的问题,处理起来更复杂一些,因此本篇主要讨论异步fifo的问题。

2.为什么需要异步FIFO

        如前面所说,在实际的数字电路项目中,往往存在很多个时钟源,不同时钟源的数据需要进行传输互通。如果是单bit数据,可以通过打两拍寄存,展宽等方式进行同步,异步fifo主要解决的是多bit数据的跨时钟传输问题。

3.异步fifo中的读写指针,为什么需要格雷码?

        我们都知道fifo中的关键变量是读写指针,读写指针决定了将往fifo中写入或者读出的位置。异步fifo在处理跨时钟信号时,采用的是将读指针和写指针转化为格雷码的形式,这时候一定会有初学者产生疑问:为什么采用格雷码的形式可以有效改善亚稳态的问题?

3.1fifo空满和读写指针的逻辑

        要理解采用格雷码的原因,首先要明白fifo的“写满”和“读空”逻辑是怎样生成的。

        注意:指针的定义位宽比fifo实际深度会多出一位,例如深度为32的fifo读指针和写指针的位宽都为6位,这样当读写指针的低五位都相同时,可以根据最高位判断写指针究竟是超了读指针一圈还是两者完全相同。

        读指针rd_ptr指向fifo读出的地址,写指针wr_ptr则指向下一个讲写入的地址,那就不难理解用指针来判断写满和读空的逻辑:

        当读指针等于(追上)写指针时,证明此时fifo已经读空,需要停止继续向外读数,避免一直读0。

        当写指针超过读指针一圈赶上读指针时,此时fifo已经写满,需要停止继续写入数据,造出数据丢失。

        显然,满和空的关键在于指针比较的判断,在同步fifo中这很容易处理,因为是相同的时钟源只需要直接比较即可。但在异步fifo中,我们需要将读写指针同步到一个时钟源中进行比较。

        具体来说,对“读空”的判断要将写指针同步到读时钟域中进行比较,对“写满”的判断要将读指针同步到写时钟域进行比较这其实也很好理解,同步到另一个时钟域需要打拍消耗时间,以读空举例,在写指针同步的这段时间,实际的写指针可能已经继续向前,所以当同步完成后,同步的写指针一定小于等于实际写时钟域的写指针。这会造成什么情况?由于读指针始终处于读时钟域不需要同步,所以读指针一定采样的是正确的数据;而进行比较的写指针有可能小于真正的写指针,因此存在可能:实际上读指针并没有追上写指针,但在读时钟域,读指针已经追上了同步后的写指针,fifo做出已经读空的判断,称为假空

        但是,这种“假空”判断是我们完全可以接受的,在fifo没有空时我们说它已空停止读出数据,只会牺牲它的一小部分性能和深度。但是反过来如果在写时钟域做读空的判断,就有可能出现实际上已经空了却判断还未空的现象。相对来说,当然是假空可以被接受,毕竟不会影响fifo正确的功能,甚至可以说留有了一部分裕量。

3.2读写指针的格雷码

        明白了读写指针的逻辑之后,就更容易理解为什么采用格雷码了,具体原因大概有两个:

        1.我们知道格雷码临近的两位只有1bit数据发生变化,在本就容易出现亚稳态的跨时钟域,我们当然希望每一拍数据的变化尽可能小,因此使用格雷码可以有效降低亚稳态出现的可能。

        2.读写指针采用格雷码,即使出现传输错误,也不会影响fifo的功能。

        这也许是更关键的一个问题,在fifo中我们不希望看见的是fifo已空或者已满却没有被指示出来的情况,而在使用bcd码时,例如从0111翻转到1000时,有可能翻转出没有出现过的错误数据1111,这肯定会对空满的逻辑判断带来错误影响。

        但是,如果使用的是格雷码,即使0101没有成功翻转成0111,代价也只是指针多停留了一拍。与指针同步相同的逻辑,此时真正的指针信号大于我们未成功翻转的信号,带来的影响是更易判断出假空或者假满,但绝不会有已空已满却没被判断出来这种情况的出现。

4.异步fifo代码

`timescale 1ns/1ns
module fifo_in #(
    parameter   DSIZE = 256,//fifo宽度
    parameter   ASIZE = 32  //fifo深度
)
(
    input  wire             rstn,//低电平复位

    input  wire             wclk,//写时钟
    input  wire [DSIZE-1:0] wdata,//写数据
    input  wire             w_en,//写使能,1有效
    output wire             w_full,//写满信号,1有效,组合逻辑输出
    output reg  [ASIZE-1:0] wuse,//写域fifo已使用空间  

    input  wire             rclk,//读时钟
    output reg  [DSIZE-1:0] rdata,//读数据
    output wire             r_empty,//读空信号,1有效,组合逻辑输出
    input  wire             r_en,//读使能,1有效
    output reg                   r_valid,//读数据有效,1有效
    output reg  [ASIZE-1:0] ruse//读域fifo已使用空间
);

reg    [5:0]    wr_addr_ptr;//地址指针,比地址多一位,MSB用于检测在同一圈
reg    [5:0]    rd_addr_ptr;
wire   [4:0]    wr_addr;//RAM 地址
wire   [4:0]    rd_addr;

wire   [5:0]    wr_addr_gray;//地址指针对应的格雷码
reg    [5:0]    wr_addr_gray_d1;
reg    [5:0]    wr_addr_gray_d2;
wire   [5:0]    rd_addr_gray;
reg    [5:0]    rd_addr_gray_d1;
reg    [5:0]    rd_addr_gray_d2;

reg [DSIZE-1:0] fifo_ram [ASIZE-1:0];
always@(posedge wclk or negedge rstn)
    begin
       if(!rstn)
       begin
          wr_addr_ptr <= 0;
          fifo_ram[0] <= 0;
        end
       else if(w_en && (~w_full))
       begin
          @(posedge wclk);
          fifo_ram[wr_addr] <= wdata;
          wr_addr_ptr <= wr_addr_ptr+1;
        end
       else
       begin
          fifo_ram[wr_addr] <= fifo_ram[wr_addr];
          wr_addr_ptr <= wr_addr_ptr;
        end
    end      

//========================================================read_fifo
always@(posedge rclk or negedge rstn)
   begin
      if(!rstn)
         begin
            rdata <= 'h0;
            r_valid <= 1'b0;
            rd_addr_ptr <= 0;
         end
      else if(r_en && (~r_empty))
         begin
            rdata <= fifo_ram[rd_addr];
            r_valid <= 1'b1;
            rd_addr_ptr <= rd_addr_ptr +1;
         end
      else
         begin
            rdata <=   'h0;//fifo复位后输出总线上是0,并非ram中真的复位,只是让总线为0;
            r_valid <= 1'b0;
            rd_addr_ptr<=rd_addr_ptr;
         end
   end
assign wr_addr = wr_addr_ptr[4:0];
assign rd_addr = rd_addr_ptr[4:0];
//=============================================================graycode syn
always@(posedge wclk )
   begin
      rd_addr_gray_d1 <= rd_addr_gray;
      rd_addr_gray_d2 <= rd_addr_gray_d1;
   end
//=========================================================rd_clk
always@(posedge rclk )
      begin
         wr_addr_gray_d1 <= wr_addr_gray;
         wr_addr_gray_d2 <= wr_addr_gray_d1;
      end
//========================================================== translation gary code
assign wr_addr_gray = (wr_addr_ptr >> 1) ^ wr_addr_ptr;
assign rd_addr_gray = (rd_addr_ptr >> 1) ^ rd_addr_ptr;

assign w_full = (wr_addr_gray == {~(rd_addr_gray_d2[5:4]),rd_addr_gray_d2[3:0]}) ? 1:0;
assign r_empty = ( rd_addr_gray == wr_addr_gray_d2 ) ? 1:0;

endmodule

特别需要注意的是在wdata写入fiforam时需要@一个上升沿或者等一定延时,否则就可能出现像我一样的采样到上一拍数据的情况。

5.其他一些思考问题

        明白了异步fifo的逻辑之后,还可以考虑一些其他有意思(面试中常问的问题)

5.1异步fifo的深度为什么是2的幂次?

        因为格雷码始终相邻只改变一个单位的前提是总体数据是2的幂次;并且fifo实际上使用的ram资源也是2的幂次,不用也是浪费。

5.2fifo的最小深度如何计算

        对fifo深度影响最大的条件无外乎两个:写频率和读频率。根据总结,可以直接给出以下公式:

        其中,wclk和rclk代表写时域时钟和读时域时钟,写数据时每B个时钟周期内会有A个数据写入FIFO、读时每Y个时钟周期会有X个数据读出FIFO。burst_length表示这段时间写入的数据量,burst_length/wclk则表示这个burst的持续时间。

        一眼看起来好像比较复杂不便于理解,实际上也并不用记住这个公式,只要能确定当fifo内部数据存储最多的时刻就能计算出它的最小深度。

        看一道乐鑫的笔试题:假设FIFO的写时钟为100MHz,读时钟为80MHz。在FIFO输入侧,每100个写时钟,写入80个数据;读数据侧,假定每个时钟读走一个数据,问FIFO深度设置多少可以保证FIFO不会上溢出和下溢出?

        这个fifo的写入速度大于读出速度,在读数据侧,每个时钟稳定读走一个数据,而写数据侧,并不是每个时钟都会写入数据。因此问题就转变成了在哪一次写数据暂时停止后fifo内部存储最多的数据。

        不难想到,当某100个写时钟最后连写的80个数据连上了后100个写时钟连写的80个数据时,fifo的深度达到最大。

        因此可以计算:一共写入160个数据,占用160个wclk,也就是1600ns。1600ns等于128个rclk,在这期间读出了128个数,随后写入暂时停止,开始只读。最小需要的fifo深度即为:160-128=32.

相关推荐

  1. python知识总结

    2024-06-16 13:34:02       11 阅读
  2. ES6 知识点英文单词总结

    2024-06-16 13:34:02       18 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-06-16 13:34:02       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-06-16 13:34:02       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-06-16 13:34:02       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-06-16 13:34:02       18 阅读

热门阅读

  1. Web前端经验汇总:深入探索与实战心得

    2024-06-16 13:34:02       6 阅读
  2. 我理解的中台架构

    2024-06-16 13:34:02       7 阅读
  3. gitlab问题记录

    2024-06-16 13:34:02       5 阅读
  4. C# —— 条件分支语句

    2024-06-16 13:34:02       8 阅读
  5. 洛谷题解 - P1036 [NOIP2002 普及组] 选数

    2024-06-16 13:34:02       7 阅读
  6. 深度神经网络

    2024-06-16 13:34:02       5 阅读