异步FIFO设计:各个模块的作用及Verilog代码详解

实现原理参考:异步FIFO---Verilog实现_alangaixiaoxiao的博客-CSDN博客_异步fifo

代码参考:IC基础(一):异步FIFO_MaoChuangAn的博客-CSDN博客_异步fifo

1.异步FIFO设计的关键点:

(1)二进制码到格雷码的转化:

二进制码的最高位不变,其余位分别与自己的左边一位进行异或,得到的就是格雷码。也就是将二进制码右移一位后再与自己异或,就得到了对应的格雷码。

即:  assign  gray  =  ( bin  >>  1 )  ^  bin  ;

(2)时钟域的同步:

二进制地址对应的格雷码从FIFO判空模块到FIFO判满模块,经过同步模块进行了打一拍的延迟完成时钟的同步。判满模块到判空模块同理。

(3)通过格雷码,FIFO判满判空的逻辑:

判满:最高位和次高位不同,其它都相同。即:

assign wfull_val = ( wgraynext == { ~wq2_rptr [ ADDR_SIZE: ADDR_SIZE - 1 ], wq2_rptr [ ADDR_SIZE-2: 0 ] } )

判满:格雷码完全相同。即:

assign rempty_val = ( rgraynext == rq2_wptr ); 

2.各模块作用及Verilog代码详解:

(图摘自篇头博客,有改动)

(1)双口RAM模块

作用:用作存储模块,输入的读写地址都是二进制的。最高位用于存放空/满信号。

端口说明:

wclken:   写使能
wclk:       写时钟
raddr:      读地址
waddr:     写地址
wdata:     写入RAM的数据
rdata:      从RAM读出的数据

module DualRAM #(
    parameter           DATA_SIZE = 8,       // 数据位宽
    parameter           ADDR_SIZE = 4        // FIFO地址深度
    )(
    input                       wclken,      // 写使能
    input                       wclk,        // 写时钟
    input  [ADDR_SIZE - 1: 0]   raddr,       // 读地址
    input  [ADDR_SIZE - 1: 0]   waddr,       // 写地址
    input  [DATA_SIZE - 1: 0]   wdata,       // 写入RAM的数据
    output [DATA_SIZE - 1: 0]   rdata        // 从RAM读出的数据
    );
    
    localparam RAM_DEPTH = 1 << ADDR_SIZE;   // RAM深度,最高位存放判定空满信号指针
    
    reg [DATA_SIZE-1: 0] mem [0:RAM_DEPTH-1];// 开辟内存
    
    // 写时序
    always @ ( posedge wclk ) begin          
        if ( wclken == 1'b1 ) begin 
            mem[ waddr ] <= wdata;           // 写使能信号为高,将数据写入
        end
        
        else begin
            mem[ waddr ] <= mem[ waddr ];    // 写使能没来,保持
        end
    end
    
    assign      rdata   =  mem [ raddr ];    // 读地址来,直接给出数据
    
endmodule

 (2)FIFO判空模块

作用:输入读使能信号,产生对应二进制地址输出到RAM模块,并将二进制地址转化为相应的格雷码输出到同步模块,同步模块将该读地址同步到写时钟域中。

注意图中该模块传至同步模块的信号为二进制地址对应的格雷码。

端口说明:

rclk  :     读时钟信号
rinc  :     读使能信号。每给一个读使能信号,读地址加一
rrst_n:    复位信号
rq2_wptr:同步模块传入的写指针格雷码
rempty :  输出的FIFO空信号
raddr   :  输出到RAM的二进制读地址
rptr :       输出到写时钟域的格雷码

module rptr_empty # (
    parameter   ADDR_SIZE = 4
)
(
    input                           rclk,
    input                           rinc,       // 读使能信号,每给一个读使能信号,读地址加一
    input                           rrst_n,
    input       [ ADDR_SIZE    :0 ] rq2_wptr,   // 同步模块传入的写指针格雷码
    output reg                      rempty,
    output      [ ADDR_SIZE - 1:0 ] raddr,      // 输出到RAM的读地址
    output reg  [ ADDR_SIZE    :0 ] rptr        // 输出到写时钟域的格雷码: 比地址多一位,最高位用来判断空满状态   
    );
    
    reg     [ ADDR_SIZE: 0 ]    rbin;           // 二进制地址
    wire    [ ADDR_SIZE: 0 ]    rgraynext, rbinnext;    // 二进制和格雷码地址
    wire                        rempty_val;
    
    //---------- 地址逻辑 -------------//
    
    always @ ( posedge rclk or negedge rrst_n ) begin
        if ( !rrst_n ) begin
            rbin <= 0;
            rptr <= 0;
        end
        else begin
            rbin <= rbinnext;       // 时钟来,更新二进制地址
            rptr <= rgraynext;      // 更新对应地址的格雷码
        end 
    end
    
    // 地址产生逻辑
    assign rbinnext = !rempty ? ( rbin + rinc ): rbin;      // 二进制地址更新逻辑: 若FIFO非空, 地址为当前地址 + 读使能(即地址加一); FIFO空则地址不更新
    assign rgraynext = ( rbinnext >> 1 ) ^ ( rbinnext );    // 格雷码地址产生:二进制右移一位后异或
    assign raddr = rbin[ ADDR_SIZE - 1: 0 ];                // 读地址传入 RAM
    
    // FIFO 判空
    assign rempty_val = ( rgraynext == rq2_wptr );          // 判空逻辑:写指针的格雷码和读指针的格雷码完全一样,则空
    
    
    // 判空信号rempty判断时序
    always @ ( posedge rclk or negedge rrst_n ) begin
        if ( !rrst_n )
            rempty <= 1'b1;                 // 复位时,FIFO为空
        else
            rempty <= rempty_val;           // 需要再用一个信号 rempty_val写入时序逻辑
    end
    
endmodule

 (3)读时钟域到写时钟域同步模块

作用:将FIFO判空模块生成的格雷码地址打一拍的延迟,然后将该地址输出到写时钟域。

端口说明:

rptr  :       判空模块传入的格雷码地址指针
wclk :      外部传入的写时钟信号
wrst_n:   外部传入的写复位信号
wq2_rptr:输出到判满模块的格雷码地址指针

module sync_r2w # (
    parameter       ADDR_SIZE = 4
)
(
    input       [ ADDR_SIZE: 0 ]    rptr,       // 判空模块传入的格雷码地址指针
    input                           wclk,       // 外部传入的写时钟信号
    input                           wrst_n,     // 外部传入的写复位信号
    output  reg [ ADDR_SIZE: 0 ]    wq2_rptr    // 输出到判满模块的格雷码地址指针
    );
    
    reg [ ADDR_SIZE: 0 ]    wq1_rptr;           // 该寄存器用于生成打一拍的延迟
    
    // D触发器,两级同步
    always @ ( posedge wclk or negedge wrst_n ) begin
        if ( !wrst_n )
            { wq2_rptr, wq1_rptr } <= 0;
        else                                                    // 相当于将输入的地址延迟一个时钟周期:
            { wq2_rptr, wq1_rptr } <= { wq1_rptr, rptr };       // 第一个周期把判空模块的地址指针给到打拍寄存器,第二个周期再取出给输出信号
    end
    
endmodule

(4)判满模块和另一个同步模块同理,直接贴代码:

判满模块:

module wptr_full# (
    parameter   ADDR_SIZE = 4
)
(
    input                           wclk,
    input                           winc,
    input                           wrst_n,
    input       [ ADDR_SIZE    :0 ] wq2_rptr,
    output reg                      wfull,
    output      [ ADDR_SIZE - 1:0 ] waddr,      // 输出到RAM的读地址
    output reg  [ ADDR_SIZE    :0 ] wptr        // 输出到写时钟域的格雷码    
    );
    
    reg     [ ADDR_SIZE: 0 ]    wbin;           // 二进制地址
    wire    [ ADDR_SIZE: 0 ]    wgraynext, wbinnext;    // 二进制和格雷码地址
    wire                        wfull_val;
    
    //---------- 地址逻辑 -------------//
    
    always @ ( posedge wclk or negedge wrst_n ) begin
        if ( !wrst_n ) begin
            wbin <= 0;
            wptr <= 0;
        end
        else begin
            wbin <= wbinnext;
            wptr <= wgraynext;
        end 
    end
    
    // 地址产生逻辑
    assign wbinnext = !wfull ? ( wbin + winc ): wbin;
    assign wgraynext = ( wbinnext >> 1 ) ^ ( wbinnext );
    assign waddr = wbin[ ADDR_SIZE - 1: 0 ];
    
    // FIFO 判满
    assign wfull_val = ( wgraynext == { ~wq2_rptr [ ADDR_SIZE: ADDR_SIZE - 1 ], wq2_rptr [ ADDR_SIZE-2: 0 ] } );//最高两位取反,然后再判断
    
    always @ ( posedge wclk or negedge wrst_n ) begin
        if ( !wrst_n )
            wfull <= 1'b0;
        else
            wfull <= wfull_val;
    end
endmodule

 同步模块:

// 写指针同步到读时钟模块
module sync_w2r # (
    parameter       ADDR_SIZE = 4
)
(
    input       [ ADDR_SIZE: 0 ]    wptr,       // 判满模块产生的写地址格雷码
    input                           rclk,
    input                           rrst_n,
    output  reg [ ADDR_SIZE: 0 ]    rq2_wptr    // 输出到读时钟域的写地址格雷码
    );
    
    reg [ ADDR_SIZE: 0 ]    rq1_wptr;           // 打一拍的延迟
    
    // D触发器,两级同步
    always @ ( posedge rclk or negedge rrst_n ) begin
        if ( !rrst_n )
            { rq2_wptr, rq1_wptr } <= 0;
        else
            { rq2_wptr, rq1_wptr } <= { rq1_wptr, wptr };       // 相当于将输入的地址延迟一个时钟周期
    end
    
endmodule

(5)顶层模块进行连线:

module AsyncFIFO # (
    parameter   ADDR_SIZE = 4,
    parameter   DATA_SIZE = 8
    )
(
    input       [ DATA_SIZE - 1: 0 ]    wdata,
    input                               winc,
    input                               wclk,
    input                               wrst_n,
    input                               rinc,
    input                               rclk,
    input                               rrst_n,
    output      [ DATA_SIZE - 1: 0 ]    rdata,
    output                              wfull,
    output                              rempty
    );
    
    wire        [ ADDR_SIZE - 1: 0 ]     waddr, raddr;
    wire        [ ADDR_SIZE    : 0 ]     wptr, rptr, wq2_rptr, rq2_wptr;
    
    
    sync_r2w # (
    .ADDR_SIZE ( ADDR_SIZE )
)
    I1_sync_r2w (
        .rptr       ( rptr      ),
        .wclk       ( wclk      ),
        .wrst_n     ( wrst_n    ),
        .wq2_rptr   ( wq2_rptr  )
    );
    
    
    sync_w2r # (
    .ADDR_SIZE ( ADDR_SIZE )
)
    I2_sync_w2r (
        .wptr       ( wptr      ),
        .rclk       ( rclk      ),
        .rrst_n     ( rrst_n    ),
        .rq2_wptr   ( rq2_wptr  )
    );
    
    
    DualRAM # (
    .DATA_SIZE ( DATA_SIZE ),           // 数据位宽
    .ADDR_SIZE ( ADDR_SIZE )            // FIFO地址深度
    )
        I3_DualRAM (
        .wclken     ( winc      ),      // 写使能
        .wclk       ( wclk      ),      // 写时钟
        .raddr      ( raddr     ),      // 读时钟
        .waddr      ( waddr     ),      // 写地址
        .wdata      ( wdata     ),      // 写入RAM的数据
        .rdata      ( rdata     )       // 从RAM读出的数据
    );
    
    
    rptr_empty # (
    .ADDR_SIZE ( ADDR_SIZE )
    )
        I4_rptr_empty (
        .rclk       ( rclk      ),
        .rinc       ( rinc      ),
        .rrst_n     ( rrst_n    ),    
        .rq2_wptr   ( rq2_wptr  ),
        .rempty     ( rempty    ),
        .raddr      ( raddr     ),      // 输出到RAM的读地址
        .rptr       ( rptr      )        // 输出到写时钟域的格雷码    
    );
    
    
    wptr_full# (
   .ADDR_SIZE ( ADDR_SIZE )
    )
       I5_wptr_full  (
        .wclk       ( wclk      ),
        .winc       ( winc      ),
        .wrst_n     ( wrst_n    ),    
        .wq2_rptr   ( wq2_rptr  ),
        .wfull      ( wfull     ),
        .waddr      ( waddr     ),      // 输出到RAM的读地址
        .wptr       ( wptr      )        // 输出到写时钟域的格雷码     
    );
    
endmodule

(6)testbench

module tb();

parameter		DATA_SIZE = 16;
parameter		ADDR_SIZE = 16;


reg  [ DATA_SIZE - 1: 0]    wdata;
reg                         winc, wclk, wrst_n; 
reg                         rinc, rclk, rrst_n;
wire [ DATA_SIZE - 1: 0 ]   rdata;  
wire                        wfull;  
wire                        rempty;  
integer		                i=0;

AsyncFIFO # (
    .ADDR_SIZE ( ADDR_SIZE ),
    .DATA_SIZE ( DATA_SIZE )
)
u_fifo (
               .rdata   ( rdata     ),  
               .wfull   ( wfull     ),  
               .rempty  ( rempty    ),  
               .wdata   ( wdata     ),  
               .winc    ( winc      ), 
               .wclk    ( wclk      ), 
               .wrst_n  ( wrst_n    ), 
               .rinc    ( rinc      ), 
               .rclk    ( rclk      ), 
               .rrst_n  ( rrst_n    )
 );
localparam CYCLE = 20;
localparam CYCLE1 = 40;

            initial begin
                wclk = 0;
                forever
                # ( CYCLE / 2 )
                wclk =~ wclk;
            end
            
            initial begin
                rclk = 0;
                forever
                # ( CYCLE1 / 2)
                rclk =~ rclk;
            end

            initial begin
                wrst_n = 1;
                #2;
                wrst_n = 0;
                # ( CYCLE * 3 );
                wrst_n = 1;
            end
            
             initial begin
                rrst_n = 1;
                #2;
                rrst_n = 0;
                # ( CYCLE * 3 );
                rrst_n = 1;
            end

            always  @ ( posedge wclk or negedge wrst_n ) begin
                if ( wrst_n == 1'b0 ) begin
                    i <= 0;
                end
                else if ( !wfull ) begin
                    i = i+1;
                end
                else begin
                	i <= i;
                end
            end

            always  @ ( rempty or rrst_n ) begin
                if ( rrst_n == 1'b0 ) begin                  
                    rinc = 1'b0;
                end
                else if ( !rempty ) begin                
                    rinc = 1'b1;
                end
                else
                	rinc = 1'b0;
            end
            
            always @ ( wfull or wrst_n ) begin
	           if ( wrst_n )
		          winc = 1'b0;
	           if ( !wfull )
		          winc = 1'b1;
	           else
		          winc = 1'b0;
            end

            always @ ( * ) begin
                if ( !wfull )
                    wdata = i;
                else
                    wdata = 0;
            end  

endmodule

RTL图:

仿真图:

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值