当前位置:   article > 正文

异步FIFO常用知识总结_异步fifi

异步fifi

在做集创赛项目时使用到了异步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代码

  1. `timescale 1ns/1ns
  2. module fifo_in #(
  3.     parameter   DSIZE = 256,//fifo宽度
  4.     parameter   ASIZE = 32  //fifo深度
  5. )
  6. (
  7.     input  wire             rstn,//低电平复位
  8.     input  wire             wclk,//写时钟
  9.     input  wire [DSIZE-1:0] wdata,//写数据
  10.     input  wire             w_en,//写使能,1有效
  11.     output wire             w_full,//写满信号,1有效,组合逻辑输出
  12.     output reg  [ASIZE-1:0] wuse,//写域fifo已使用空间  
  13.     input  wire             rclk,//读时钟
  14.     output reg  [DSIZE-1:0] rdata,//读数据
  15.     output wire             r_empty,//读空信号,1有效,组合逻辑输出
  16.     input  wire             r_en,//读使能,1有效
  17.     output reg                   r_valid,//读数据有效,1有效
  18.     output reg  [ASIZE-1:0] ruse//读域fifo已使用空间
  19. );
  20. reg    [5:0]    wr_addr_ptr;//地址指针,比地址多一位,MSB用于检测在同一圈
  21. reg    [5:0]    rd_addr_ptr;
  22. wire   [4:0]    wr_addr;//RAM 地址
  23. wire   [4:0]    rd_addr;
  24. wire   [5:0]    wr_addr_gray;//地址指针对应的格雷码
  25. reg    [5:0]    wr_addr_gray_d1;
  26. reg    [5:0]    wr_addr_gray_d2;
  27. wire   [5:0]    rd_addr_gray;
  28. reg    [5:0]    rd_addr_gray_d1;
  29. reg    [5:0]    rd_addr_gray_d2;
  30. reg [DSIZE-1:0] fifo_ram [ASIZE-1:0];
  31. always@(posedge wclk or negedge rstn)
  32.     begin
  33.        if(!rstn)
  34.        begin
  35.           wr_addr_ptr <= 0;
  36.           fifo_ram[0] <= 0;
  37.         end
  38.        else if(w_en && (~w_full))
  39.        begin
  40.           @(posedge wclk);
  41.           fifo_ram[wr_addr] <= wdata;
  42.           wr_addr_ptr <= wr_addr_ptr+1;
  43.         end
  44.        else
  45.        begin
  46.           fifo_ram[wr_addr] <= fifo_ram[wr_addr];
  47.           wr_addr_ptr <= wr_addr_ptr;
  48.         end
  49.     end      
  50. //========================================================read_fifo
  51. always@(posedge rclk or negedge rstn)
  52.    begin
  53.       if(!rstn)
  54.          begin
  55.             rdata <= 'h0;
  56.             r_valid <= 1'b0;
  57.             rd_addr_ptr <= 0;
  58.          end
  59.       else if(r_en && (~r_empty))
  60.          begin
  61.             rdata <= fifo_ram[rd_addr];
  62.             r_valid <= 1'b1;
  63.             rd_addr_ptr <= rd_addr_ptr +1;
  64.          end
  65.       else
  66.          begin
  67.             rdata <=   'h0;//fifo复位后输出总线上是0,并非ram中真的复位,只是让总线为0
  68.             r_valid <= 1'b0;
  69.             rd_addr_ptr<=rd_addr_ptr;
  70.          end
  71.    end
  72. assign wr_addr = wr_addr_ptr[4:0];
  73. assign rd_addr = rd_addr_ptr[4:0];
  74. //=============================================================graycode syn
  75. always@(posedge wclk )
  76.    begin
  77.       rd_addr_gray_d1 <= rd_addr_gray;
  78.       rd_addr_gray_d2 <= rd_addr_gray_d1;
  79.    end
  80. //=========================================================rd_clk
  81. always@(posedge rclk )
  82.       begin
  83.          wr_addr_gray_d1 <= wr_addr_gray;
  84.          wr_addr_gray_d2 <= wr_addr_gray_d1;
  85.       end
  86. //========================================================== translation gary code
  87. assign wr_addr_gray = (wr_addr_ptr >> 1) ^ wr_addr_ptr;
  88. assign rd_addr_gray = (rd_addr_ptr >> 1) ^ rd_addr_ptr;
  89. assign w_full = (wr_addr_gray == {~(rd_addr_gray_d2[5:4]),rd_addr_gray_d2[3:0]}) ? 1:0;
  90. assign r_empty = ( rd_addr_gray == wr_addr_gray_d2 ) ? 1:0;
  91. 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.

5.3读写时钟频率

        以上考虑的均为读写时钟频率差异不大的情况,一般在实际使用时,读写时钟也不会有太大差异。但是考虑极端情况,如果写时钟频率几倍或数十倍于读时钟频率,或者反过来呢?采用这样的异步fifo还能实现正确功能吗?今天华为面试题:异步FIFO读时钟是写时钟的100倍,或者写是读的100倍会出现什么问题? - 第3页 - 数字IC设计讨论(IC前端|FPGA|ASIC) - EETOP 创芯网论坛 (原名:电子顶级开发网) -

        可以。记住使用格雷码的核心仍然是确保满空的判断不会出错,即留出了一定余量。在此情形下,fifo仍然会输出假空假满信号,需要注意的也就是fifo的深度设置,比如当写时钟太高,fifo就会极易被写满...

        比如:写时域为100mhz,读时域为1mhz,开始写入数据之前,FIFO置空指示信号,写入数据后,由于写地址同步到读时钟域有延迟,因此空指示仍然有效,如果FIFO深度不够,当数据写满之后,写地址还是没有成功同步到读时钟域,这个时候就会同时置空满指示信号。例如FIFO深度为256时,写满FIFO只需要256个写时钟周期,即2560ns,而在这个时间内,变化的写地址都还没有能够同步到读时钟域。因此,当读写时钟速率差异太大时,需要注意FIFO的深度是否合适。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/黑客灵魂/article/detail/1009819
推荐阅读
相关标签
  

闽ICP备14008679号