当前位置:   article > 正文

一文搞定同步与异步FIFO基本知识_fifo 格雷码

fifo 格雷码

1、同步FIFO设计

较简单,省略,请参照其余作者。

2、异步FIFO设计

异步fifo组成部分

1.DRAM或BRAM 储存单元

2.格雷码部分 跨时钟

3.读写RAM部分 进行先入先出的读写操作

4.判断空满部分

注意事项:

1、对于读写地址的位宽一定要是[$clog2(DEPTH):0],其实真正使用的是[$clog2(DEPTH)-1:0],最高位的使用是为了判断写指针是否追上读指针而产生full信号,若写指针追上读指针,那么写指针的高位就会变为1,其余位和读指针相同。

2、跨时钟域处理是使用格雷码,在异步fifo中,格雷码的产生就是读写地址与它自己右移一位的值异或。

 格雷码的使用是将读指针和写指针打两拍之后的格雷码分别转换至自己的时钟域进行空满信号的判断。

 3、RAM读写指针加减需要在自己的时钟域进行,若使用DRAM,则需要在读写指针加减的同时按照真实的读写指针[$clog2(DEPTH)-1:0]完成读写操作。

4、判断空满部分

full信号是说写追上读,因此在没有转化位格雷码之前应该是这样:

比如写指针为1010,读指针为0010,说明写指针多走了一圈,转换为格雷码之后为:

写指针格雷码:1111 ,读指针格雷码:0011,因此在格雷码情况下判断full的条件为高位相反,低位相同。

assign wfull = ((wr_addr_g[$clog2(DEPTH)]!=rd_addr_grr[$clog2(DEPTH)])&&(wr_addr_g[$clog2(DEPTH)-1]!=rd_addr_grr[$clog2(DEPTH)-1])&&(wr_addr_g[$clog2(DEPTH)-2:0]==rd_addr_grr[$clog2(DEPTH)-2:0]))?1'b1:1'b0;

empty信号为读追上写,读追上写不需要多走一圈,第一次读指针追上写指针即为读空,因此两者格雷码相同即为empty信号的判断条件。

assign rempty = (rd_addr_g[$clog2(DEPTH):0]==wr_addr_grr[$clog2(DEPTH):0])?1'b1:1'b0;

5、判断空满信号时使用进行打拍处理的格雷码信号与当前的信号做比较,因此此时并不是真正的空满信号,更准确的是虚空或者虚满。真正的满信号应该是不进行跨时钟处理的读写指针信号做比较。

ok 理解了这几点直接写出异步fifo代码。下面是使用BRAM实现异步fifo代码。

  1. `timescale 1ns/1ns
  2. /***************************************BRAM*****************************************/
  3. module dual_port_RAM #(parameter DEPTH = 16,
  4.                        parameter WIDTH = 8)(
  5.      input wclk
  6.     ,input wenc
  7.     ,input [$clog2(DEPTH)-1:0] waddr  //深度对2取对数,得到地址的位宽。
  8.     ,input [WIDTH-1:0] wdata          //数据写入
  9.     ,input rclk
  10.     ,input renc
  11.     ,input [$clog2(DEPTH)-1:0] raddr  //深度对2取对数,得到地址的位宽。
  12.     ,output reg [WIDTH-1:0] rdata         //数据输出
  13. );
  14. reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1];
  15. always @(posedge wclk) begin
  16.     if(wenc)
  17.         RAM_MEM[waddr] <= wdata;
  18. end 
  19. always @(posedge rclk) begin
  20.     if(renc)
  21.         rdata <= RAM_MEM[raddr];
  22. end 
  23. endmodule  
  24. /***************************************ASYN_FIFO*****************************************/
  25. module asyn_fifo#(
  26.     parameter    WIDTH = 8,
  27.     parameter     DEPTH = 16
  28. )(
  29.     input                     wclk    , 
  30.     input                     rclk    ,   
  31.     input                     wrstn    ,
  32.     input                    rrstn    ,
  33.     input                     winc    ,
  34.     input                      rinc    ,
  35.     input         [WIDTH-1:0]    wdata    ,
  36.     output wire                wfull    ,
  37.     output wire                rempty    ,
  38.     output wire [WIDTH-1:0]    rdata
  39. );
  40. reg [$clog2(DEPTH):0] wr_addr;
  41. reg [$clog2(DEPTH):0] rd_addr;
  42. dual_port_RAM #(
  43.     .DEPTH(16),
  44.     .WIDTH(8)
  45.     )dual_port_RAM_u(
  46.     
  47.     .wclk    (wclk),
  48.     .wenc    (!wfull&winc),
  49.     .waddr  (wr_addr),
  50.     .wdata  (wdata),
  51.     .rclk    (rclk),
  52.     .renc    (!rempty&rinc),
  53.     .raddr  (rd_addr),
  54.     .rdata     (rdata)
  55. );
  56. wire [$clog2(DEPTH):0] wr_addr_g;
  57. wire [$clog2(DEPTH):0] rd_addr_g;
  58. assign  wr_addr_g= wr_addr^(wr_addr>>1);
  59. assign  rd_addr_g= rd_addr^(rd_addr>>1);
  60.  
  61. reg [$clog2(DEPTH):0] wr_addr_gr,wr_addr_grr;
  62. reg [$clog2(DEPTH):0] rd_addr_gr,rd_addr_grr;
  63. always @(posedge wclk or negedge wrstn)begin
  64.     if(!wrstn)begin
  65.         rd_addr_gr<='d0;
  66.         rd_addr_grr<='d0;
  67.     end else begin
  68.         rd_addr_gr<=rd_addr_g;
  69.         rd_addr_grr<=rd_addr_gr;
  70.     end
  71. end
  72. always @(posedge rclk or negedge rrstn)begin
  73.     if(!rrstn)begin
  74.         wr_addr_gr<='d0;
  75.         wr_addr_grr<='d0;
  76.     end else begin
  77.         wr_addr_gr<=wr_addr_g;
  78.         wr_addr_grr<=wr_addr_gr;
  79.     end
  80. end
  81. assign wfull = ((wr_addr_g[$clog2(DEPTH)]!=rd_addr_grr[$clog2(DEPTH)])&&(wr_addr_g[$clog2(DEPTH)-1]!=rd_addr_grr[$clog2(DEPTH)-1])&&(wr_addr_g[$clog2(DEPTH)-2:0]==rd_addr_grr[$clog2(DEPTH)-2:0]))?1'b1:1'b0;
  82. assign rempty = (rd_addr_g[$clog2(DEPTH):0]==wr_addr_grr[$clog2(DEPTH):0])?1'b1:1'b0;
  83. always @(posedge wclk or negedge wrstn)begin
  84.     if(!wrstn)begin
  85.         wr_addr<='d0;
  86.     end else if(!wfull&winc)begin
  87.         wr_addr<=wr_addr+1;
  88.     end
  89. end
  90. always @(posedge rclk or negedge rrstn)begin
  91.     if(!rrstn)begin
  92.         rd_addr<='d0;
  93.     end else if(!rempty&rinc)begin
  94.         rd_addr<=rd_addr+1;
  95.     end
  96. end
  97. endmodule

3、异步FIFO作用

        经常用作数据跨时钟的处理。

4、异步FIFO中为什么要用格雷码?假如格雷码采到了错误的值,会有什么影响?

(1)异步FIFO使用格雷码的原因是由于需要用其进行跨时钟传输

        在进行多bit信号的跨时钟处理时,每个比特之间总有一些小的skew,导致它们无法被在同一个时钟沿被采样。即便在布局布线的时候完美地控制它们的路径长度,每个比特信号的上升下降时间、die上的加工工艺差异分布也会引起skew。

        当在同一个时钟时,无法对多位bit进行同时采样,就会导致数据出错。例如四位二进制码从0111变为1000的过程中,这两个数虽然在数值上相邻,但它们的每个比特都将发生改变,这时如果它们之间有skew,采样的值就可能是任意的四位二进制数。

        当任意的二进制数作为读写地址时,就会出现问题,如果非空报空或者非满报满可以接受,毕竟不影响fifo正常输出,如果已空未报空或者已满未报满,就会导致数据被覆盖掉或者重复读出。

(2)格雷码采错之后不会影响FIFO的正常工作

        使用格雷码就不会出现这个问题,因为格雷码的变化是只变一位的,当单bit信号未正确通过跨时钟传递,比如作为读写地址的格雷码信号010需要递增变为110,因为地址信号010没有正确通过跨时钟传递,采集到的地址仍然是010,那么对空满信号的判断也不会出错,原因是:虽然采集到的地址没有正确变为110,它也不会造成已空未报空或者已满未报满的情况。

        由于格雷码在递增的时候是单bit变化,所以在递增的时候且进行跨时钟后,格雷码信号只会有两种情况,一种是原地踏步,一种是递增。前者对于传递来说是错误的,会导致非空报空或者非满报满,但是不影响FIFO的正常输出,后者是正确的,我们所期待的。因此在格雷码在做地址的时候,进行跨时钟之后的信号只会是比真实的信号滞后或者相同,并不会乱跳。如:

        由于读时钟为快时钟,因此可以采集到每次写指针的变化情况进行比较,在进行比较时读指针得到的写指针有两种情况,一种为原地踏步值(发生亚稳态),一种为增加之后变化的值(正常)。这两种情况都不会造成FIFO嗝屁。

        还有一个有意思的事情:此时其实读指针追的是写指针的残影,什么意思呢,读指针追的时写指针两拍之前的值,并不是此时真正写指针真正的位置。这个也是为了防止已空未报空或者满未报满。

5、异步FIFO的深度计算

参照下面这篇大神的文章即可应对大部分的计算问题,同时对于项目中的具体问题需要具体分析。

FIFO深度的计算_fifo深度计算-CSDN博客

6、格雷码记忆小妙招

以三位的格雷码为例,前四个格雷码和后四个格雷码的低两位是中心对称的,高位变化。

感谢大神:城外南风起跨时钟域传输的黄金搭档:异步FIFO与格雷码_格雷码跨时钟域处理 csdn 变两位-CSDN博客的精彩分析才让我对第四部分有更加深刻的理解。

以上纯属个人理解,如果有误欢迎指正。

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

闽ICP备14008679号