异步FIFO设计及仿真Verilog

异步FIFO设计Verilog

介绍

Clifford E. Cummings的《Simulation and Synthesis Techniques for Asynchronous FIFO Design》这篇异步FIFO仿真分析写的真的厉害,使用了非常巧妙的方法解决的空满标志判断的问题还有跨时钟阈信号亚稳态的问题,我就写一下自己读了这个之后对异步FIFO的感悟吧。
两个设计要点
1:读写地址各增加一位得到地址指针使用格雷码判断数据空满状态
2:读写指针信号跨时钟阈处理

空满标志判断原理

空标志:
空标志相对好判断,只要读写指针在同一地址时即可,但是如何区分空满标志呢,所以采用增加一位地址得到地址指针,使其作为标志位,当完成一次计数时最高位变为一,其余为继续计数。为了降低多比特亚稳态所以采用格雷码计数。
满标志
虽然扩展一位最高标志位解决了空满信号判断的问题(写指针最高位与读指针相反其余位相同为满信号),虽然格雷码解决了多比特跨时钟阈问题,但是格雷码相邻两个数只有1bit数据变化所以不能满足写指针最高位与读指针相反其余位相同为满信号的要求。具体如下图所示
在这里插入图片描述
空满信号代码实现如下段所示

assign   full   =   (wr_gray == {~(rd_gray_d2[data_addr- : 2]),rd_gray_d2[2:0]} )          ; 
assign   empty  =   (rd_gray ==  wr_gray_d2)                                               ;

通过上图可以观察出,在使用格雷码编码规则时,判断满信号需要使读写指针最高位和次高位相反其余为相同。这种方法即解决的多比特信号亚稳态的问题,也解决了无法区分空满信号的问题。

读写指针信号同步化

因为异步FIFO读写时钟不同步所以,在判断空满信号时必然会采用打拍处理跨时钟阈问题。大部分资料都说的是第一级寄存器产生亚稳态后,第二级寄存器稳定输出概率为90%,第三极寄存器稳定输出的概率为99%,但是亚稳态也有可能跟随电路一直传递下去,如果电路容错能力强的话还好,要不然就需要后续进行容错处理。本设计在在转换为格雷码后进行打俩拍处理。代码如下段所示。

always  @(posedge rd_clk )begin                   //打俩拍降低亚稳态
        wr_gray_d1   <= wr_gray         ;
		wr_gray_d2   <= wr_gray_d1		;
end

always  @(posedge wr_clk )begin                   //打俩拍降低亚稳态
        rd_gray_d1   <= rd_gray         ;
		rd_gray_d2   <= rd_gray_d1	    ;	
end

格雷码编码
编码解码规则:二进制最高位保留,其余位与这位的高一位进行异或运算。原理如下图所示。在这里插入图片描述
具体实现可以通过将原码右移一位后与原码进行异或运算。具体代码如下段所示。

assign  wr_gray  =  (wr_ptr >> 1'd1) ^ wr_ptr  ;  //格雷码转换
assign  rd_gray  =  (rd_ptr >> 1'd1) ^ rd_ptr  ;  //格雷码转换

异步FIFO Verilog设计

异步FIFO代码实现如下

module  FIFO_async#(
  
  parameter   data_wid    =  4'd8   ,      //FIFO中数据位宽
  parameter   data_deep   =  4'd16  ,      //FIFO中存储深度
  parameter   data_addr   =  4'd4          //地址位宽
 
)
(

    input                         rest                            ,
	input                         wr_clk                          ,
	input                         wr_en                           ,
	input      [data_wid : 1]     data_in                         ,
	                                                
	input                         rd_clk                          ,
	input                         rd_en                           ,
    output reg [data_wid : 1]     data_out                        ,
	output                        full                            ,
	output                        empty                           
	
);
//-----------------------------------------------------------------------------------------------

	wire   [data_addr - 1'd1 : 0]     wr_addr 				 ;    //写数据地址
	reg    [data_addr        : 0]     wr_ptr 				 ;    //写指针
    wire    [data_addr 		 : 0]     wr_gray 				 ; 	  
	reg    [data_addr 		 : 0]     wr_gray_d1 		     ; 
	reg    [data_addr 		 : 0]     wr_gray_d2 			 ; 

    wire   [data_addr - 1'd1 : 0]     rd_addr 				 ;   //读数据地址
	reg    [data_addr        : 0]     rd_ptr 				 ;   //读指针
	wire   [data_addr 		 : 0]     rd_gray 				 ; 
	reg    [data_addr 		 : 0]     rd_gray_d1 		     ; 
	reg    [data_addr 		 : 0]     rd_gray_d2 			 ; 
	 
    reg    [data_wid - 1'd1  : 0]     fifo_ram  [data_deep - 1'd1  : 0]    ;

//--------------------------------------------------------------------------- fifo地址产生
   
	assign   wr_addr =  wr_ptr[data_addr - 1'd1 : 0]   ;
	assign   rd_addr =  rd_ptr[data_addr - 1'd1 : 0]   ;
	
	always @(posedge wr_clk or negedge rest )begin
	    if (!rest)
		   wr_ptr  <=  5'd0 ;
	    else if ( (wr_en) && (!full) )
		   wr_ptr  <=  5'd1 + wr_ptr;
	    else
		   wr_ptr  <=  wr_ptr ; 
		
	end
	
	always @(posedge rd_clk or negedge rest )begin
	    if (!rest)
		   rd_ptr  <=  5'd0 ;
	    else if ( (rd_en) && (!empty) )
		   rd_ptr  <=  5'd1 + rd_ptr;
	    else
		   rd_ptr  <=  rd_ptr ;	
	end
	
//----------------------------------------------------------------------- 格雷码转换 打两拍

	assign  wr_gray  =  (wr_ptr >> 1'd1) ^ wr_ptr  ;
	assign  rd_gray  =  (rd_ptr >> 1'd1) ^ rd_ptr  ;

	always  @(posedge rd_clk )begin
            wr_gray_d1   <= wr_gray         ;
			wr_gray_d2   <= wr_gray_d1		;
	end
	
	always  @(posedge wr_clk )begin
            rd_gray_d1   <= rd_gray         ;
			rd_gray_d2   <= rd_gray_d1	        ;	
	end
	
//-------------------------------------------------------------产生fifo读写数据 产生空满信号 	
	
	assign   full   =   (wr_gray == {~(rd_gray_d2[data_addr- : 2]),rd_gray_d2[2:0]} )          ;  
	assign   empty  =   (rd_gray ==  wr_gray_d2)                                               ;

	integer i ;
		always  @(posedge wr_clk or negedge rest)begin
			if (!rest)begin
			     for ( i=0 ; i<16 ; i=i+1 )		   //当前地址下FIFO总线上数据为零,并非fifo_ram数据清零  
			     fifo_ram [i]   <=  8'd0			     ;
			   end
			else if ( (wr_en) && (!full) )            
			     fifo_ram[wr_addr]  <=  data_in          ;    
			else
                 fifo_ram[wr_addr]  <= fifo_ram[wr_addr] ;		
		end
	
	
	always  @(posedge rd_clk or negedge rest)begin
			if (!rest)
			   data_out   <=  8'd0		       ;
			else if ( (rd_en) && (!empty) )
			   data_out  <=  fifo_ram[rd_addr]         ;
			else
               data_out  <=  data_out                  ;			
		end	
			
		
endmodule

仿真代码如下(写时钟50M读时钟100M)

`timescale 1ns/1ns
 module tb_FIFO;
 
 parameter DATA_W            =  8                 ;   //数据位宽
 parameter ADDR_W            =  4                 ;   //地址位宽
 parameter data_depth        = 16                 ;

 reg                         wr_clk              ;
 reg                         rd_clk              ;
 reg                         rst_n               ;
 reg                         wr_en               ;
 reg      [DATA_W-1:0]       wr_data             ;
 reg                         rd_en               ;
 
 wire      [DATA_W-1:0]      rd_data             ;
 wire                        empty               ;
 wire                        full                ;
 
 
 FIFO_async
 #(
     .data_wid            (DATA_W             ),
     .data_deep           (data_depth         ),
	 .data_addr           (ADDR_W             )
 )
 u_FIFO_async
 (
     .rest                (rst_n              ),
     .wr_clk              (wr_clk             ),
     .wr_en               (wr_en              ),
     .data_in             (wr_data            ),
     .rd_clk              (rd_clk             ),
     .rd_en               (rd_en              ),
     .data_out            (rd_data            ),
     .empty               (empty              ),
	 .full                (full              )
	 
 );
 
 always #10 wr_clk=~wr_clk;
 always #5  rd_clk=~rd_clk;

 initial begin
 
     wr_clk  = 1;
     rd_clk  = 0;
     rst_n   = 0;
     wr_en   = 0;
     wr_data = 0;
     rd_en   = 0;
     
     #101;
     rst_n = 1;
     
     #20;
     gen_data;
     
     repeat(16)@(posedge rd_clk);
     rd_en=0;
 end

initial begin
     #221 rd_en = 1;
end
 
 task gen_data;
     integer i;
     begin
         for(i=0; i<16; i=i+1) begin
             wr_en   = 1;
             wr_data = i;
             #20;
         end
         wr_en   = 0;
         wr_data = 0;
     end
 endtask
 
 endmodule

仿真波形如下所示
在这里插入图片描述
可以看出在空闲信号产生时 rd_gray == wr_gray_d2

注:还有很多问题没有仔细讨论如,在打拍后实际地址和打拍后的地址会有一些差别,所以一般会把FIFO深度设的大一些,会出现虚满虚空的现象,而且这个问题影响并不大。本博客是通过阅读网上各位大佬的资料自行编写,欢迎各位与我进行讨论。

  • 4
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值