当前位置:   article > 正文

异步FIFO理解及设计_异步fifo结构

异步fifo结构

一、异步FIFO理解

1.1异步FIFO结构

由图可见,异步FIFO的核心部件就是一个 Simple Dual Port RAM ;左右两边的长条矩形是地址控制器,负责控制地址自增、将二进制地址转为格雷码以及解格雷码;下面的两对D触发器 sync_r2w 和 sync_w2r 是同步器,负责将写地址同步至读时钟域、将读地址同步至写时钟域。 

1.2异步FIFO空满的判断 

        在同步FIFO设计中,fifo_num是由写地址减去读地址所得到,利用fifo_num 来作为FIFO空或满的判断依据。而对于异步FIFO而言,数据是由某一个时钟域的控制信号写入FIFO,而由另一个时钟域的控制信号将数据读出FIFO。也就是说,读写指针的变化动作是由不同的时钟产生的,处在不同的时钟域上,所以不能直接相减。因此,对FIFO空或满的判断也是跨时钟域的,我们首先需要解决的就是如何进行异步FIFO空满的判断。

        对于异步FIFO 而言,我们用wr_num和rd_num(对应同步FIFO中的fifo_num)作为读写时钟域各自维护的一套数据计数器。wr_num作为FIFO写满的判断依据,rd_num作为FIFO读空的判断依据。为了得到wr_num,写地址wr_ptr_exp与其处于同一时钟域(写时钟域),然后,读地址rd_ptr_exp则需要从读时钟域同步到写时钟域才能使用,若没有同步,wr_num则会出现亚稳态,从而影响full和almost_full的指示信号,造成数据的丢失。同理,对于rd_num来讲,我们需要将写地址同步到读时钟域来进行rd_num的计算。

  • “写满”的判断:需要将读指针同步到写时钟域,再与写指针判断
  • “读空”的判断:需要将写指针同步到读时钟域,再与读指针判断

1.2.1跨时钟域:读慢写快  、 读快写慢  

  •  判断写满:(虚满:因为实际读地址>=同步后的地址)

(1)读慢写快:读地址同步到写时钟域后(快时钟采样慢时钟信号),经过两级触发器的时间消耗,实际的读地址一定是大于等于同步到写时钟域之后的读地址(用于wr_num计算,判断FIFO是否读满),此时若wr_num判断FIFO写满(实际写指针未超过读指针一圈),即满非真满。可以想象一下,假设一个深度为100的FIFO,在写到第98个数据的时候就报了“写满”,会引起什么后果?答案是不会造成功能错误,只会造成性能损失(2%),大不了FIFO的深度我少用一点点就是的。只会造成性能损失。 

  •  判断读空:(虚空:实际写地址>=同步后的地址)

(1)读快写慢:同理,写地址同步到读时钟域后(快时钟采样慢时钟信号),实际的写地址一定是大于等于同步到读时钟域之后的写地址(用于rd_num计算,判断FIFO是否读空),此时若rd_num判断FIFO读空(实际读指针未赶上写指针),即空非真空。可以想象一下,假设某个FIFO,在读到还剩2个数据的时候就报了“读空”,会引起什么后果?答案是不会造成功能错误,只会造成性能损失(2%),大不了我先不读了,等数据多了再读就是的。 

上面两种情况都是慢时钟域信号同步到快时钟域,我们采取打两拍可以正常进行FIFO功能,但是对于快同步到慢是否会有问题,即快时钟域的信号同步到慢时钟域造成的漏采  ?

  •  判断写满: (虚满:漏掉读指针,对于写满来说,实际读指针跑到了前面)

(2)读快写慢: 进行写满判断的时候需要将读指针同步到写时钟域,因为读快写慢,所以当写时钟同步读指针的时候,必然会漏掉一部分读指针,我们不用关心那到底会漏掉哪些读指针,我们在乎的是漏掉的指针会对FIFO的写满产生影响吗?比如读指针从0读到10,期间写时钟域只同步捕捉到了3、5、8这三个读指针而漏掉了其他指针。当同步到8这个读指针时,真实的读指针可能已经读到10 ,相当于在写时钟域还没来得及觉察的情况下,读时钟域可能从FIFO读了数据出来,这样在判断它是不是满的时候会出现不是真正满的情况,漏掉的指针也没有对FIFO的逻辑操作产生影响。

  •  判断读空:(虚空:漏掉写指针,对于读空来说。实际的写指针跑到了前面)

(2)读慢写快: 进行读空判断的时候需要将写指针同步到读指针 ,因为读慢写快,所以当读时钟同步写指针的时候,必然会漏掉一部分写指针,我们不用关心那到底会漏掉哪些写指针,我们在乎的是漏掉的指针会对FIFO的读空产生影响吗?比如写指针从0写到10,期间读时钟域只同步捕捉到了3、5、8这三个写指针而漏掉了其他指针。当同步到8这个写指针时,真实的写指针可能已经写到10 ,相当于在读时钟域还没来得及觉察的情况下,写时钟域可能写了数据到FIFO去,这样在判断它是不是空的时候会出现不是真正空的情况,漏掉的指针也没有对FIFO的逻辑操作产生影响。

现在我们会发现,所谓的空满信号实际上是不准确的,在还没有空、满的时钟就已经输出了空满信号,这样的空满信号一般称为假空、假满。假空、假满信号本质上是一种保守设计, 对FIFO 的正常功能没什么影响。我们就仅采用结构图中所用的两级触发器进行打拍同步。


另外,我们还需要理解并解决另一个问题,除了采取打拍对读写指针(地址)进行跨时钟域处理,还需要什么操作?因为跨时钟域传输一旦没处理好就会引起亚稳态问题,造成指针的值异常,从而引发FIFO的功能错误。

        在同步FIFO的设计中,我们采用改为扩展法,采用二进制编码,扩展出来的最高位来判断究竟是读指针追上写指针(读空了)还是写指针超过读指针一圈(写满了)。而在异步FIFO中,倘若我们仍然采取二进制编码的话,会很容易造成跨时钟域的亚稳态。

        因为采用二进制计数器时所有位都可能同时变化,在同一个时钟沿同步多个信号的变化会产生亚稳态问题。而使用格雷码只有一位变化,因此在两个时钟域间同步多个位不会产生问题。举个例子,二进制的7(0111)跳转到8(1000),4位都会发生变化,所以发生亚稳态的概率就比较大。而格雷码的跳转就只有一位(从0100--1100,仅第四位发生变化)会发生变化,有效地减小亚稳态发生的可能性。所以需要一个二进制到gray码的转换电路,将地址值转换为相应的gray码,然后将该gray码同步到另一个时钟域进行对比,作为空满状态的检测。

1.2.2格雷码 

  • 二进制码与格雷码之间的相互转换

1.二进制转格雷码

gray[3] = 0         ^ bin[3];----gray[3] = bin[3] 异或0等于自身
gray[2] = bin[3]  ^ bin[2];
gray[1] = bin[2]  ^ bin[1];   
gray[0] = bin[1]  ^ bin[0]; 

2.格雷码转回二进制 

 最高位不需要转换,从次高位开始使用二进制的高位和次高位格雷码相异或 

仿真图: 

  •  格雷码如何进行空满判断 

          与二进制一样,首先我们需要将指针向高位拓展一位,这是为了判断写指针是否超过读指针一圈。然后通过对比除了最高位的其余位来判断读写指针是否重合。这种方法判断二进制的指针是没有问题的,但是这不适合格雷码形式的指针,因为格雷码是镜像对称的,若只根据最高位是否相同来区分是读空还是写满是有问题的,如下图:

例如,假设FIFO深度为8,读指针在0_100 (7),写指针位于1_100 (8) ,若按照二进制的指针判断法则去判断,写指针超过读指针一圈,则说明FIFO写满。但此时,实际的写指针指向的地址为0_000(0),读写指针并未重合(写满)。因此,我们采取格雷码进行判断FIFO空满我们应采用另外的方法,我们可以看出,0-8,1-9,2-10,3-11,4-12,5-13,6-14,7-15,(最高和次高为不同,剩余位形同).

  • 当最高位和次高位相同,其余位相同认为是读空
  • 当最高位和次高位不同,其余位相同认为是写满

二、异步FIFO 设计(Verilog代码及仿真) 

2.1.TOP文件

  1. `timescale 1ns / 1ps
  2. module asnch_fifo_top#(
  3. parameter DATASIZE_T = 32, // Memory data word width
  4. parameter ADDRSIZE_T = 6 // 地址指针位宽设定为6,地址宽度则为5(定义为6是为进位),fifo深度则为2^5
  5. )(
  6. input w_clk ,
  7. input w_rst_n ,
  8. input winc ,
  9. input [DATASIZE_T - 1:0] w_data ,
  10. output w_full ,
  11. input r_clk ,
  12. input r_rst_n ,
  13. input rinc ,
  14. output[DATASIZE_T - 1:0] r_data ,
  15. output r_empty
  16. );
  17. wire[ADDRSIZE_T:0] wptr , rptr ;
  18. wire[ADDRSIZE_T:0] r_gray_rptr,r_gray_rptr_d2;
  19. wire[ADDRSIZE_T:0] r_gray_wptr,r_gray_wptr_d2;
  20. wire[ADDRSIZE_T - 1:0] w_addr , r_addr;
  21. //*******************写时钟域:产生ram写地址,写指针以及其格雷码,写满判断**************\\
  22. wptr_full#(
  23. .ADDRSIZE (ADDRSIZE_T)
  24. )
  25. wptr_full_u0(
  26. .w_clk ( w_clk ),
  27. .w_rst_n ( w_rst_n ),
  28. .winc ( winc ),
  29. .rq2_rptr ( r_gray_rptr_d2),
  30. .wptr ( wptr ), //写指针
  31. .r_gray_wptr ( r_gray_wptr ), //写指针格雷码
  32. .w_addr ( w_addr ), //ram写地址
  33. .w_full ( w_full )
  34. );
  35. //*******************格雷码读指针在写时钟域同步**************\\
  36. fifo_r2w#(
  37. .ADDRSIZE (ADDRSIZE_T)
  38. )
  39. fifo_r2w_u0(
  40. .w_clk (w_clk ),
  41. .w_rst_n (w_rst_n ),
  42. .rptr (r_gray_rptr),
  43. .rq2_rptr (r_gray_rptr_d2) //同步用于full判断
  44. );
  45. //*******************读时钟域:产生ram读地址,读指针以及其格雷码,读空判断**************\\
  46. fifo_empty#(
  47. .ADDRSIZE (ADDRSIZE_T)
  48. )
  49. fifo_empty_u0(
  50. .r_clk (r_clk ),
  51. .r_rst_n (r_rst_n),
  52. .rinc (rinc ),
  53. .rq2_wptr (r_gray_wptr_d2),
  54. .rptr (rptr ), //读指针(二进制
  55. .r_gray_rptr (r_gray_rptr), //读指针(格雷码
  56. .r_addr (r_addr ), //ram读地址
  57. .r_empty (r_empty)
  58. );
  59. //*******************格雷码写指针在读时钟域同步**************\\
  60. fifo_w2r#(
  61. .ADDRSIZE (ADDRSIZE_T)
  62. )fifo_w2r_u0(
  63. .r_clk (r_clk ),
  64. .r_rst_n (r_rst_n),
  65. .wptr (r_gray_wptr ),
  66. .rq2_wptr (r_gray_wptr_d2) //同步用于empty判断
  67. );
  68. //*******************ram寄存器**************\\
  69. fifo_mem#(
  70. .DATASIZE (DATASIZE_T) ,
  71. .ADDRSIZE (ADDRSIZE_T)
  72. )
  73. fifo_mem_u0(
  74. .w_clk (w_clk ) ,
  75. .winc (winc ) ,
  76. .w_full (w_full ) ,
  77. .w_addr (w_addr ) ,
  78. .w_data (w_data ) ,
  79. .r_addr (r_addr ) ,
  80. .r_data (r_data )
  81. );
  82. endmodule

 2.2 写文件

  1. `timescale 1ns / 1ps
  2. module wptr_full#(
  3. parameter ADDRSIZE = 6
  4. )(
  5. input w_clk ,
  6. input w_rst_n ,
  7. input winc ,
  8. input [ADDRSIZE : 0] rq2_rptr, //格雷码
  9. output[ADDRSIZE : 0] wptr , //二进制
  10. output[ADDRSIZE : 0] r_gray_wptr,//格雷码
  11. output[ADDRSIZE - 1 : 0] w_addr , //二进制
  12. output w_full
  13. );
  14. reg [ADDRSIZE : 0] r_wptr ;
  15. wire [ADDRSIZE : 0] w_gray_wptr;
  16. assign wptr = r_wptr;
  17. assign w_addr = wptr[ADDRSIZE - 1 : 0]; //产生二进制实际ram地址
  18. assign r_gray_wptr = w_gray_wptr;
  19. always @(posedge w_clk or negedge w_rst_n) begin
  20. if(!w_rst_n)
  21. r_wptr <= 'd0;
  22. else if(winc && !w_full)
  23. r_wptr <= r_wptr + 1;
  24. else
  25. r_wptr <= r_wptr;
  26. end
  27. bin2gray#(
  28. .P_DATA_WIDTH (ADDRSIZE)
  29. )
  30. bin2gray_u0(
  31. .i_bin (r_wptr ),
  32. .o_gray (w_gray_wptr)
  33. );
  34. assign w_full = (w_gray_wptr == {~rq2_rptr[ADDRSIZE:ADDRSIZE-1],rq2_rptr[ADDRSIZE-2:0]});
  35. endmodule

2.3 读文件

  1. `timescale 1ns / 1ps
  2. module fifo_empty#(
  3. parameter ADDRSIZE = 6
  4. )(
  5. input r_clk ,
  6. input r_rst_n ,
  7. input rinc ,
  8. input [ADDRSIZE : 0] rq2_wptr ,
  9. output[ADDRSIZE : 0] rptr ,
  10. output[ADDRSIZE : 0] r_gray_rptr ,
  11. output[ADDRSIZE - 1 : 0] r_addr ,
  12. output r_empty
  13. );
  14. reg [ADDRSIZE : 0] r_rptr ;
  15. reg r_r_empty ;
  16. wire[ADDRSIZE : 0] w_gray_rptr ;
  17. wire empty_value ;
  18. assign r_empty = r_r_empty;
  19. assign rptr = r_rptr;
  20. assign r_addr = rptr[ADDRSIZE - 1 : 0];
  21. assign r_gray_rptr = w_gray_rptr;
  22. always @(posedge r_clk or negedge r_rst_n) begin
  23. if(!r_rst_n)
  24. r_rptr <= 'd0;
  25. else if(rinc && !r_empty)
  26. r_rptr <= r_rptr + 1;
  27. else
  28. r_rptr <= r_rptr;
  29. end
  30. bin2gray#(
  31. .P_DATA_WIDTH (ADDRSIZE)
  32. )
  33. bin2gray_u2(
  34. .i_bin (r_rptr ),
  35. .o_gray (w_gray_rptr)
  36. );
  37. assign empty_value = (w_gray_rptr == rq2_wptr);
  38. always @(posedge r_clk or negedge r_rst_n) begin
  39. if(!r_rst_n)
  40. r_r_empty <= 'd0;
  41. else
  42. r_r_empty <= empty_value;
  43. end
  44. endmodule

2.4 读写指针同步文件 

  1. `timescale 1ns / 1ps
  2. module fifo_r2w#(
  3. parameter ADDRSIZE = 6
  4. )(
  5. input w_clk ,
  6. input w_rst_n ,
  7. input [ADDRSIZE : 0] rptr ,
  8. output[ADDRSIZE : 0] rq2_rptr
  9. );
  10. reg[ADDRSIZE:0] r_rptr_d1;
  11. reg[ADDRSIZE:0] r_rptr_d2;
  12. always @(posedge w_clk or negedge w_rst_n) begin
  13. if(!w_rst_n)begin
  14. r_rptr_d1 <= 'd0;
  15. r_rptr_d2 <= 'd0;
  16. end
  17. else begin
  18. r_rptr_d1 <= rptr;
  19. r_rptr_d2 <= r_rptr_d1;
  20. end
  21. end
  22. assign rq2_rptr = r_rptr_d2;
  23. endmodule
  24. `timescale 1ns / 1ps
  25. module fifo_w2r#(
  26. parameter ADDRSIZE = 6
  27. )(
  28. input r_clk ,
  29. input r_rst_n ,
  30. input [ADDRSIZE : 0] wptr ,
  31. output[ADDRSIZE : 0] rq2_wptr
  32. );
  33. reg[ADDRSIZE:0] r_wptr_d1;
  34. reg[ADDRSIZE:0] r_wptr_d2;
  35. always @(posedge r_clk or negedge r_rst_n) begin
  36. if(!r_rst_n)begin
  37. r_wptr_d1 <= 'd0;
  38. r_wptr_d2 <= 'd0;
  39. end
  40. else begin
  41. r_wptr_d1 <= wptr;
  42. r_wptr_d2 <= r_wptr_d1;
  43. end
  44. end
  45. assign rq2_wptr = r_wptr_d2;
  46. endmodule

2.5 TB文件

  1. `timescale 1ns / 1ps
  2. module asnch_fifo_tb();
  3. reg w_clk ;
  4. reg w_rst_n ;
  5. reg winc ;
  6. reg [8 - 1:0] w_data ;
  7. wire w_full ;
  8. reg r_clk ;
  9. reg r_rst_n ;
  10. reg rinc ;
  11. wire[8- 1:0] r_data ;
  12. wire r_empty ;
  13. asnch_fifo_top#(
  14. .DATASIZE_T (8), // Memory data word width
  15. .ADDRSIZE_T (3) // 地址指针位宽设定为4,地址宽度则为3(定义为6是为进位),fifo深度则为2^3
  16. )
  17. asnch_fifo_top_u0(
  18. .w_clk (w_clk ),
  19. .w_rst_n (w_rst_n ),
  20. .winc (winc ),
  21. .w_data (w_data ),
  22. .w_full (w_full ),
  23. .r_clk (r_clk ),
  24. .r_rst_n (r_rst_n ),
  25. .rinc (rinc ),
  26. .r_data (r_data ),
  27. .r_empty (r_empty )
  28. );
  29. initial begin //设置写时钟,写周期是20ns,50Mhz
  30. w_clk=0;
  31. forever #10 w_clk=~w_clk;
  32. end
  33. initial begin //设置读时钟,读周期是10ns,100Mhz
  34. r_clk=0;
  35. forever #5 r_clk=~r_clk;
  36. end
  37. initial begin
  38. w_rst_n=1'b0; //写复位
  39. r_rst_n=1'b0; //读复位
  40. winc =1'b0; //写无效
  41. rinc =1'b0; //读无效
  42. w_data=0; //初始写数据为0
  43. #28 w_rst_n=1'b1; //松开写复位
  44. r_rst_n=1'b1; //松开读复位
  45. winc =1'b1; //写有效
  46. w_data=1; //输入数据为1
  47. @(posedge w_clk);//写入数据
  48. repeat(7) //接着写入2,3,4,5,6,7,8这些数据
  49. begin
  50. #18;
  51. w_data=w_data+1'b1;
  52. @(posedge w_clk);
  53. end
  54. #18 w_data = w_data+1'b1; //此时异步FIFO已经写满了,在往同步FIFO中写数据8
  55. //8这个数据不会被写进
  56. @(posedge r_clk);
  57. #8 rinc=1'b1; //读使能,写无效
  58. winc=1'b0;
  59. @(posedge r_clk); //第一个读出的数为1
  60. repeat(7) //读取剩余的数
  61. begin
  62. @(posedge r_clk);
  63. end
  64. #2;
  65. rinc=1'b0; //结束读操作
  66. end
  67. endmodule

仿真

 能够成功实现仿真,具体的细致分析与应用需要进一步学习!

PS:小白学习,借鉴颇多,若有雷同,纯属抄袭。

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

闽ICP备14008679号