异步FIFO的设计

Posted 朽月

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了异步FIFO的设计相关的知识,希望对你有一定的参考价值。

异步FIFO的设计

参考文献

[1]、小鱼FPGA(微信公众号)
[2]、aslmer(博客园)

项目简述

之前写过一篇异步FIFO的注意点,详情见博客。但是不免有许多同学要问为什么官方的IP都会出现这种问题,难道是官方的IP设计的不好?这种可能性肯定不大,因为官方的IP肯定设计的比较完美。那么肯定是这种现象在异步FIFO的设计中是不可避免的,那么为什么不可避免呢?这篇博客我们就着手设计异步FIFO,进行设计的过程中,大家可以很容易明白为什么异步FIFO的计数,空满信号是不准确的只能提供大概的参考。

本次实验所用到的软件环境如下:
1、VIVADO2019.1
2、Modelsim10.7

异步FIFO设计需要理解设计过程中的以下观点:
1.跨时钟域传递信号做时钟同步一般通过打两拍。
2.采用格雷码编码(解决汇聚问题),因为格雷码每次跳转只会有一位发生变化,所以如果出现不确定状态也只会有两种状况,即正确变化了和不变。因此在读写时钟不一样的情况下,纵使读写地址每bit同步过程中出现延时不一致,也不会使得FIFO在实际空或者满之后,FIFO却没有正确的产生出空满信号。只有可能是实际没有空或者满,但产生了空满信号,但这对于FIFO的功能不会有影响,只会使得FIFO的读或者写操作暂停。
3.读比写时钟更快,只会只出现实际没满,但误判为满;不会对功能(数据流)造成错误。
4.写比读时钟更快,只会出现实际没空,但误判为空;不会对功能(数据流)造成错误。
文章中对其进行详细说明。理解了上面的关键点,就可以明白博客中的问题是不可避免的。

异步FIFO设计难点

空满信号的产生1

首先同步FIFO很好设计,因为所有的信号都处于同一个时钟域内,不存在跨时钟问题。异步FIFO的难点就在于跨时钟处理,跨时钟处理的时候不可避免会引入一些延迟,快时钟到慢时钟的过程中也会造成一些数据的丢失。那么我们进行异步FIFO设计的时候需要充分考虑上面的问题进行设计,设计出来的IP需要克服跨时钟域与数据丢失的问题。

首先同步FIFO设计的原则:满不能写,空不能读。
关键在于:full与empty信号如何产生

方法1:用长度计数器factor。执行一次写操作,factor加1,执行一次读操作,factor减1。那么当facotor等于0的时候,empty信号使能,当factor等于FIFO的深度的时候,full信号使能。

方法2:读写地址扩展1位,当读写地址完全相同时,产生empty信号,当读写地址最高为相反,其余位相同时产生full信号。

上面是对于同步FIFO来说的,但是对于异步FIFO方法1肯定是不行的,因为factor的操作只能在一种时钟下进行操作。所以异步FIFO的empty与full信号只能使用方法二进行产生。

那么直接这样说或许还是不明白空满信号的产生,那么我们将进一步说明。
1、读空信号如何产生?写满信号如何产生?
读空信号:复位的时候,读指针和写指针相等,读空信号有效(这里所说的指针其实就是读地址、写地址), 当读指针赶上写指针的时候,写指针等于读指针意味着最后一个数据被读完,此时读空信号有效
写满信号:当写指针比读指针多一圈时,写指针等于读指针意味着写满了,此时写满信号有效,我们会发现 读空的条件是写指针等于读指针,写满的条件也是写指针等于读指针,到底如何区分呢?
解决方法:方法2
举个例子说明:假设要设计深度为 8 的异步FIFO,此时定义读写指针只需要 3 位(2^3=8)就够用了,
但是我们在设计时将指针的位宽设计成 4 位,最高位的作用就是区分是读空还是写满,具体理论 1 如下
当最高位相同,其余位相同认为是读空
当最高位不同,其余位相同认为是写满

读写地址跨时钟域问题

由于是异步FIFO的设计,读写时钟不一样,在产生读空信号和写满信号时,会涉及到跨时钟域的问题。因为读指针是属于读时钟域的,写指针是属于写时钟域的,而异步FIFO的读写时钟域不同,是异步的。如果将读时钟域的读指针与写时钟域的写指针不做任何处理直接比较肯定是错误的,因此需要进行跨时钟域处理。

这里多说一些跨时钟域的问题,这方面我们后面的博客也会进行相应的讲解。
1、单比特信号,一般是打两拍
2、多比特信号,一、经过异步FIFO,二、先将多比特信号转变成格雷码,再打两拍。
当然相信的跨时钟域处理我们会在后面的文章中进行讲解,这里只是稍微提一下。

对于异步FIFO的设计,地址的比较肯定是多比特信号,肯定也不能用异步FIFO来跨时钟域,所以只能使用解决方法:两级寄存器同步 + 格雷码
同步的过程有两个:
(1)将写时钟域的写指针同步到读时钟域,将同步后的写指针与读时钟域的读指针进行比较产生读空信号
(2)将读时钟域的读指针同步到写时钟域,将同步后的读指针与写时钟域的写指针进行比较产生写满信号

这里再稍微解释一下为什么跨时钟域时多比特信号直接打两拍会存在问题,将多比特信号转换成格雷码之后打两拍就不存在问题。
因为二进制编码的指针在跳变的时候有可能是多位数据一起变化,如二进制的7–>8 即 0111 --> 1000 ,在跳变的过程中 4 位全部发生了改变,这样很容易产生毛刺,例如:异步FIFO的写指针和读指针分属不同时钟域,这样指针在进行同步过程中很容易出错,比如写指针在从0111到1000跳变时4位同时改变,这样读时钟在进行写指针同步后得到的写指针可能是0000-1111的某个值,一共有2^4个可能的情况,而这些都是不可控制的,你并不能确定会出现哪个值,那出错的概率非常大,怎么办呢?到了格雷码发挥作用的时候了,而格雷码的编码特点是相邻位每次只有 1 位发生变化, 这样在进行指针同步的时候,只有两种可能出现的情况:
1.指针同步正确,正是我们所要的;
2.指针同步出错;
举例假设格雷码写指针从000->001,将写指针同步到读时钟域同步出错,出错的结果只可能是000->000,因为相邻位的格雷码每次只有一位变化,这个出错结果实际上也就是写指针没有跳变保持不变,我们所关心的就是这个错误会不会导致读空判断出错?答案是不会,最多是让空标志在FIFO不是真正空的时候产生,而不会出现空读的情形。所以格雷码保证的是同步后的读写指针即使在出错的情形下依然能够保证FIFO功能的正确性。在同步过程中的亚稳态不可能消除,但是我们只要保证它不会影响我们的正常工作即可。

由于设计的时候读写指针用了至少两级寄存器同步,同步会消耗至少两个时钟周期,势必会使得判断空或满有所延迟,这会不会导致设计出错呢?
采用格雷码编码(解决汇聚问题),因为格雷码每次跳转只会有一位发生变化,所以如果出现不确定状态也只会有两种状况,即正确变化了和不变。因此在读写时钟不一样的情况下,纵使读写地址每bit同步过程中出现延时不一致,也不会使得FIFO在实际空或者满之后,FIFO却没有正确的产生出空满信号。只有可能是实际没有空或者满,但产生了空满信号,但这对于FIFO的功能不会有影响,只会使得FIFO的读或者写操作暂停。 那么为什么会产生这个现象呢,下面将详细说明:

异步FIFO通过比较读写指针进行满空判断,但是读写指针属于不同的时钟域,所以在比较之前需要先将读写指针进行同步处理,
将写指针同步到读时钟域再和读指针比较进行FIFO空状态判断,因为在同步写指针时需要时间,而在这个同步的时间内有可能还会写入新的数据,因此同步后的写指针一定是小于或者等于当前实际的写指针,所以此时判断FIFO为空不一定是真空,这样更加保守,一定不会出现空读的情况,虽然会影响FIFO的性能,但是并不会出错,同理将读指针同步到写时钟域再和写指针比较进行FIFO满状态判断,同步后的读指针一定是小于或者等于当前的读指针,所以此时判断FIFO为满不一定是真满,这样更保守,这样可以保证FIFO的特性:FIFO空之后不能继续读取,FIFO满之后不能继续写入。总结来说异步逻辑转到同步逻辑不可避免需要额外的时钟开销,这会导致满空趋于保守,但是保守并不等于错误,这么写会稍微有性能损失,但是不会出错。

举个例子:大多数情形下,异步FIFO两端的时钟不是同频的,或者读快写慢,或者读慢写快,慢的时钟域同步到快的时钟域不会出现漏掉指针的情况,但是将指针从快的时钟域同步到慢的时钟域时可能会有指针遗漏,举个例子以读慢写快为例,进行满标志判断的时候需要将读指针同步到写时钟域,因为读慢写快,所以不会有读指针遗漏,同步消耗时钟周期,所以同步后的读指针滞后(小于等于)当前读地址,所以可能满标志会提前产生,满并非真满。进行空标志判断的时候需要将写指针同步到读指针 ,因为读慢写快,所以当读时钟同步写指针 的时候,必然会漏掉一部分写指针,我们不用关心那到底会漏掉哪些写指针,我们在乎的是漏掉的指针会对FIFO的空标志产生影响吗? 比如写指针从0写到10,期间读时钟域只同步捕捉到了3、5、8这三个写指针而漏掉了其他指针。当同步到8这个写指针时,真实的写指针可能已经写到10 ,相当于在读时钟域还没来得及觉察的情况下,写时钟域可能偷偷写了数据到FIFO去,这样在判断它是不是空的时候会出现不是真正空的情况,漏掉的指针也没有对FIFO的逻辑操作产生影响。

所以这里就解释了异步FIFO的空满信号必须是:
(1)将写时钟域的写指针同步到读时钟域,将同步后的写指针与读时钟域的读指针进行比较产生读空信号
(2)将读时钟域的读指针同步到写时钟域,将同步后的读指针与写时钟域的写指针进行比较产生写满信号
如果将上面的状态换成
(1)将写时钟域的读指针同步到写时钟域,将同步后的写指针与读时钟域的读指针进行比较产生读空信号
(2)将读时钟域的写指针同步到读时钟域,将同步后的读指针与写时钟域的写指针进行比较产生写满信号
那么将是错误的,因为打两拍的原因将使得空了判断成没空继续读,满了判断为没满继续写。

空满信号的产生2

经过上面的分析,可以知道我们判断空满信号的标准是:
读空信号:复位的时候,读指针和写指针相等,读空信号有效(这里所说的指针其实就是读地址、写地址), 当读指针赶上写指针的时候,写指针等于读指针意味着最后一个数据被读完,此时读空信号有效
写满信号:当写指针比读指针多一圈时,写指针等于读指针意味着写满了,此时写满信号有效,我们会发现 读空的条件是写指针等于读指针,写满的条件也是写指针等于读指针
使用二进制判断的具体体现如下:
方法2:读写地址扩展1位,当读写地址完全相同时,产生empty信号,当读写地址最高为相反,其余位相同时产生full信号。

但是经过跨时钟域的分析,可以得知,实际进行读写地址比较不是二进制的比较,而是将同步后的写指针与读指针进行的比较。换句话说就是比较的格雷码,那么对于格雷码方法2是显然不对的(因为写满信号会出现错误),举例来说,7的格雷码与8的格雷码的最高位不同,其余位相同,所以判断出为写满。这就出现误判了,同样套用在 6 和 9,5 和 10等也会出现误判,因为格雷码是镜像对称的,若只根据最高位是否相同来区分是读空还是写满是有问题的。那么方法2对应到格雷码中的体现形式是什么呢。

因此用格雷码判断是否为读空或写满时应看最高位和次高位是否相等,具体如下:
当最高位和次高位相同,其余位相同认为是读空
当最高位和次高位不同,其余位相同认为是写满

多位二进制码如何转化为格雷码

二进制码转换成二进制格雷码,其法则是保留二进制码的最高位作为格雷码的最高位,而次高位格雷码为二进制码的高位与次高位相异或,而格雷码其余各位与次高位的求法相类似。

再换种更简单的描述

二进制数                                       	1 0 1 1 0
二进制数右移1位,空位补0              		    0 1 0 1 1
异或运算                                         1 1 1 0 1  

这样就可以实现二进制到格雷码的转换了,总结就是移位并且异或,verilog代码实现就一句:

assign wgraynext = (wbinnext>>1) ^ wbinnext

异步FIFO系统框图

上面介绍了异步FIFO设计需要注意的地方,那么接下来给出相应的系统框图,理解了上面的问题相信图形很容易理解。

接下来我们代码的书写将严格按照上面的框图进行书写。

异步FIFO代码

FIFO模块:

`timescale 1ns / 1ps
// *********************************************************************************
// Author       : zhangningning
// Email        : nnzhang1996@foxmail.com
// Website      : https://blog.csdn.net/zhangningning1996
// Module Name  : FIFO.v
// Create Time  : 2020-07-13 16:53:32
// Revise       : 2020-07-13 16:53:32
// Editor       : sublime text3, tab size (4)
// CopyRight(c) : All Rights Reserved
//
// *********************************************************************************
// Modification History:
// Date             By              Version                 Change Description
// -----------------------------------------------------------------------
// XXXX       zhangningning          1.0                        Original
//  
// *********************************************************************************


module FIFO #(
    parameter   DSIZE   =   8               ,        
    parameter   ASIZE   =   4
)
(
    input                   rst_n           ,

    input                   wr_clk          ,
    input                   wr_en           ,
    input       [DSIZE-1:0] wr_data         ,
    output  wire            wr_full         ,

    input                   rd_clk          ,
    input                   rd_en           , 
    output  wire[DSIZE-1:0] rd_data         ,
    output  wire            rd_empty                     
);
 
//========================================================================================\\
//**************Define Parameter and  Internal Signals**********************************
//========================================================================================/
wire            [ASIZE:0]   rr_gray_addr    ;
wire            [ASIZE:0]   rw_gray_addr    ; 
wire            [ASIZE:0]   ww_gray_addr    ;
wire            [ASIZE:0]   wr_gray_addr    ;
wire            [ASIZE:0]   wr_bin_addr     ;
wire            [ASIZE:0]   rd_bin_addr     ;

 
//========================================================================================\\
//**************     Main      Code        **********************************
//========================================================================================/

write_sync  
#(
    .DSIZE                  (DSIZE                  ),        
    .ASIZE                  (ASIZE                  )
) write_sync_inst(
    .wr_clk                 (wr_clk                 ),
    .rr_gray_addr           (rr_gray_addr           ),
    .rw_gray_addr           (rw_gray_addr           )    
);

bin2gray 
#(
    .DSIZE                  (DSIZE                  ),        
    .ASIZE                  (ASIZE                  )
) bin2gray_inst_w(
    .sclk                   (wr_clk                 ),
    .bin_addr               (wr_bin_addr            ),
    .gray_addr              (ww_gray_addr           )
);

wr_ctrl 
#(
    .DSIZE                  (DSIZE                  ),        
    .ASIZE                  (ASIZE                  )
) wr_ctrl_inst(
    .wr_clk                 (wr_clk                 ),
    .rst_n                  (rst_n                  ),
    .wr_en                  (wr_en                  ),
    .rw_gray_addr           (rw_gray_addr           ),

    .wr_addr                (wr_bin_addr            ),
    .wr_full                (wr_full                )
);
 

ram 
#( 
    .DSIZE                  (DSIZE                  ),        
    .ASIZE                  (ASIZE                  )
) ram_inst(
    .wr_clk                 (wr_clk                 ),
    .wr_bin_addr            (wr_bin_addr            ),
    .wr_req                 (wr_en                  ),
    .wr_full                (wr_full                ),
    .wr_data                (wr_data                ),

    .rd_clk                 (rd_clk                 ),
    .rd_bin_addr            (rd_bin_addr            ),
    .rd_req                 (rd_en                  ),
    .rd_empty               (rd_empty               ),
    .rd_data                (rd_data                )         
);

read_sync 
#(
    .DSIZE                  (DSIZE                  ),        
    .ASIZE                  (ASIZE                  )
) read_sync_inst(
    .rd_clk                 (rd_clk                 ),
    .ww_gray_addr           (ww_gray_addr           ),
    .wr_gray_addr           (wr_gray_addr           )
);

bin2gray 
#(
    .DSIZE                  (DSIZE                  ),        
    .ASIZE                  (ASIZE                  )
) bin2gray_inst_r(
    .sclk                   (rd_clk                 ),
    .bin_addr               (rd_bin_addr            ),
    .gray_addr              (rr_gray_addr           )
);

rd_ctrl 
#(
    .DSIZE                  (DSIZE                  ),        
    .ASIZE                  (ASIZE                  )
) rd_ctrl_inst(
    .rd_clk                 (rd_clk                 ),
    .rst_n                  (rst_n                  ),
    .rd_en                  (rd_en                  ),
    .wr_gray_addr           (wr_gray_addr           ),

    .rd_addr                (rd_bin_addr            ),
    .rd_empty               (rd_empty               )   
);
    

endmodule

write_sync模块:

`timescale 1ns / 1ps
// *********************************************************************************
// Author       : zhangningning
// Email        : nnzhang1996@foxmail.com
// Website      : https://blog.csdn.net/zhangningning1996
// Module Name  : write_sync.v
// Create Time  : 2020-07-13 14:46:31
// Revise       : 2020-07-13 14:46:31
// Editor       : sublime text3, tab size (4)
// CopyRight(c) : All Rights Reserved
//
// *********************************************************************************
// Modification History:
// Date             By              Version                 Change Description
// -----------------------------------------------------------------------
// XXXX       zhangningning          1.0                        Original
//  
// *********************************************************************************

module write_sync
#(
    parameter   DSIZE   =   8               ,        
    parameter   ASIZE   =   4
)
(
    input                   wr_clk          ,
    input       [ASIZE:0]   rr_gray_addr    ,
    output  wire[ASIZE:0]   rw_gray_addr    
);
 
//========================================================================================\\
//**************Define Parameter and  Internal Signals**********************************
//========================================================================================/
reg             [ASIZE:0]   rw_gray_addr_r1 ;
reg             [ASIZE:0]   rw_gray_addr_r2 ;

 
//========================================================================================\\
//**************     Main      Code        **********************************
//========================================================================================/
assign  rw_gray_addr            =           rw_gray_addr_r2;

always @(posedge wr_clk)
    begin
        rw_gray_addr_r1         <=          rr_gray_addr;
        rw_gray_addr_r2         <=          rw_gray_addr_r1;
    end
    




endmodule

bin2gray模块:

`timescale 1ns / 1ps
// *********************************************************************************
// Author       : zhangningning
// Email        : nnzhang1996@foxmail.com
// Website      : https://blog.csdn.net/zhangningning1996
// Module Name  : bin2gray.v
// Create Time  : 2020-07-13 14:54:10
// Revise       : 2020-07-13 14:54:10
// Editor       : sublime text3, tab size (4)
// CopyRight(c) : All Rights Reserved
//
// *********************************************************************************
// Modification History:
// Date             By              Version                 Change Description
// -----------------------------------------------------------------------
// XXXX       zhangningning          1.0                        Original
//  
// *********************************************************************************

module bin2gray
#(
    parameter   DSIZE   =   8               ,        
    parameter   ASIZE   =   4
)
(
    input                   sclk            ,
    input       [ASIZE:0]   bin_addr        ,
    output reg  [ASIZE:0]   gray_addr 
);
 
//========================================================================================\\
//**************Define Parameter and  Internal Signals**********************************
//========================================================================================/

 
//========================================================================================\\
//**************     Main      Code        **********************************
//========================================================================================/

always @(posedge sclk)
    gray_addr       =       bin_addr ^ (bin_addr >> 1);
    


endmodule

wr_ctrl模块:

`timescale 1ns / 1ps
// *********************************************************************************
// Author       : zhangningning
// Email        : nnzhang1996@foxmail.com
// Website      : https://blog.csdn.net/zhangningning1996
// Module Name  : wr_ctrl.v
// Create Time  : 2020-07-13 15:43:51
// Revise       : 2020-07-13 15:43:51
// Editor       : sublime text3, tab size (4)
// CopyRight(c) : All Rights Reserved
//
// *********************************************************************************
// Modification History:
// Date             By              Version                 Change Description
// -----------------------------------------------------------------------
// XXXX       zhangningning          1.0                        Original
//  
// *********************************************************************************

module wr_ctrl
#(
    parameter   DSIZE   =   8               ,        
    parameter   ASIZE   =   4
)
(
    input                       wr_clk      ,
    input                       rst_n       ,
    input                       wr_en       ,
    input           [ASIZE:0]   rw_gray_addr,

    output  reg     [ASIZE:0]   wr_addr     ,
    output  reg                 wr_full 
);
 
//========================================================================================\\
//**************Define Parameter and  Internal Signals**********************************
//========================================================================================/
wire                [ASIZE:0]   ww_gray_addr;


 
//========================================================================================\\
//**************     Main      Code        **********************************
//========================================================================================/
assign      ww_gray_addr =          wr_addr ^ (wr_addr >> 1);

always @(posedge wr_clk or negedge rst_n)
    if(rst_n == 1'b0)
        wr_addr         <=          'd0;
    else if(wr_full == 1'b0 && wr_en == 1'b1)
        wr_addr         <=          wr_addr + 1'b1;
    else
        wr_addr         <=          wr_addr;

always @(*)
    if(ww_gray_addr == ~rw_gray_addr[ASIZE],~rw_gray_addr[ASIZE-1],rw_gray_addr[ASIZE-2:0])
        wr_full         <=          1'b1;
    else
        wr_full         <=          1'b0;
        
    
endmodule

ram模块:

`timescale 1ns / 1ps
// *********************************************************************************
// Author       : zhangningning
// Email        : nnzhang1996@foxmail.com
// Website      : https://blog.csdn.net/zhangningning1996
// Module Name  : ram.v
// Create Time  : 2020-07-13 15:00:55
// Revise       : 2020-07-13 15:00:55
// Editor       : sublime text3, tab size (4)
// CopyRight(c) : All Rights Reserved
//
// *********************************************************************************
// Modification History:
// Date             By              Version                 Change Description
// -----------------------------------------------------------------------
// XXXX       zhangningning          1.0                        Original
//  
// *********************************************************************************


module ram
#( 
    parameter   DSIZE   =   8               ,        
    parameter   ASIZE   =   4
)
(
    input                   wr_clk          ,
    input       [ASIZE:0]   wr_bin_addr     ,
    input                   wr_req          ,
    input                   wr_full         ,
    input       [DSIZE-1:0] wr_data         ,

    input                   rd_clk          ,
    input       [ASIZE:0]   rd_bin_addr     ,
    input                   rd_req          ,
    input                   rd_empty        ,
    output reg  [DSIZE-1:0] rd_data         
);
 
//========================================================================================\\
//**************Define Parameter and  Internal Signals**********************************
//========================================================================================/
localparam      DEPTH       =   1 << ASIZE  ;  

reg             [DSIZE:0]   mem_data[DEPTH-1:0]     ;

 
//========================================================================================\\
//**************     Main      Code        **********************************
//========================================================================================/
always @(posedge wr_clk)
    if(wr_req == 1'b1 && wr_full == 1'b0)
        mem_data[wr_bin_addr[ASIZE-1:0]]       <=          wr_data;        
    else
        mem_data[wr_bin_addr[ASIZE-1:0]]       <=          mem_data[wr_bin_addr[ASIZE-1:0]];

always @(posedge rd_clk)
    if(rd_req == 1'b1 && rd_empty == 1'b0)
        rd_data                     <=          mem_data[rd_bin_addr[ASIZE-1:0]];             
    else
        rd_data                     <=          rd_data;


    


endmodule

read_sync模块:

以上是关于异步FIFO的设计的主要内容,如果未能解决你的问题,请参考以下文章

v3学院 FPGA专家 带你学习FPGA实现格雷码跨时钟域异步fifo

设计开发 典型同步电路设计- 同步FIFO

Verilog设计之异步fifo设计

Verilog设计之异步fifo设计

Verilog设计之异步fifo设计

异步FIFO设计的一些注意事项