Asynchronous FIFO design of FPGA: function of each module and detailed explanation of Verilog code

Keywords: FPGA FIFO

Implementation principle reference: Asynchronous fifo -- Verilog implementation_ Alan aixiao's blog - CSDN blog_ Asynchronous fifo

Code reference: IC Foundation (I): asynchronous fifo_ Maochuang an's blog - CSDN blog_ Asynchronous fifo

1. Key points of asynchronous FIFO design:

(1) Conversion from binary code to gray code:

The highest bit of the binary code remains unchanged, and the other bits are XOR with the left bit to get the gray code. That is, move the binary code one bit to the right and XOR with yourself to get the corresponding gray code.

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

(2) Synchronization of clock domain:

The gray code corresponding to the binary address is delayed by the synchronization module from the FIFO empty module to the FIFO full module to complete the clock synchronization. The same goes for the full module to the empty module.

(3) Logic of FIFO full and empty judgment through gray code:

Full judgment: the highest and second highest positions are different, and others are the same. Namely:

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

Full judgment: Gray code is exactly the same. Namely:

assign rempty_val = ( rgraynext == rq2_wptr ); 

2. Function of each module and detailed explanation of Verilog code:

(the picture is taken from the blog at the beginning, with changes)

(1) Dual port RAM module

Function: as an enclosure, the input read and write addresses are binary. The highest bit is used to store empty / full signals.

Port Description:

wclken:     Write enable
wclk:         Write clock
raddr:        Read address
waddr:       Write address
wdata:       Data written to RAM
rdata:       Data read from RAM

module DualRAM #(
    parameter           DATA_SIZE = 8,       // Data bit width
    parameter           ADDR_SIZE = 4        // FIFO address depth
    )(
    input                       wclken,      // Write enable
    input                       wclk,        // Write clock
    input  [ADDR_SIZE - 1: 0]   raddr,       // Read address
    input  [ADDR_SIZE - 1: 0]   waddr,       // Write address
    input  [DATA_SIZE - 1: 0]   wdata,       // Data written to RAM
    output [DATA_SIZE - 1: 0]   rdata        // Data read from RAM
    );
    
    localparam RAM_DEPTH = 1 << ADDR_SIZE;   // RAM depth, the highest bit stores the empty and full signal pointer
    
    reg [DATA_SIZE-1: 0] mem [0:RAM_DEPTH-1];// Open up memory
    
    // Write timing
    always @ ( posedge wclk ) begin          
        if ( wclken == 1'b1 ) begin 
            mem[ waddr ] <= wdata;           // Write enable signal is high, write data
        end
        
        else begin
            mem[ waddr ] <= mem[ waddr ];    // Write enable didn't come, keep it
        end
    end
    
    assign      rdata   =  mem [ raddr ];    // Read the address and give the data directly
    
endmodule

  (2) FIFO empty module

Function: input the read enable signal, generate the corresponding binary address and output it to the RAM module, convert the binary address into the corresponding gray code and output it to the synchronization module, which synchronizes the read address to the write clock domain.

Note that the signal transmitted from the module to the synchronization module in the figure is the gray code corresponding to the binary address.

Port Description:

rclk   :       Read clock signal
rinc   :       Read enable signal. For each read enable signal, add one to the read address
rrst_n:     Reset signal
rq2_wptr: write pointer code passed in by synchronization module
rempty :   Output FIFO null signal
raddr    :   Binary read address output to RAM
rptr :         Gray code output to write clock field

module rptr_empty # (
    parameter   ADDR_SIZE = 4
)
(
    input                           rclk,
    input                           rinc,       // Read enable signal: add one to the read address for each read enable signal
    input                           rrst_n,
    input       [ ADDR_SIZE    :0 ] rq2_wptr,   // Write pointer code passed in by synchronization module
    output reg                      rempty,
    output      [ ADDR_SIZE - 1:0 ] raddr,      // Read address output to RAM
    output reg  [ ADDR_SIZE    :0 ] rptr        // Gray code output to the write clock field: one more bit than the address, and the highest bit is used to judge the empty and full state   
    );
    
    reg     [ ADDR_SIZE: 0 ]    rbin;           // Binary address
    wire    [ ADDR_SIZE: 0 ]    rgraynext, rbinnext;    // Binary and gray code addresses
    wire                        rempty_val;
    
    //----------Address logic-------------//
    
    always @ ( posedge rclk or negedge rrst_n ) begin
        if ( !rrst_n ) begin
            rbin <= 0;
            rptr <= 0;
        end
        else begin
            rbin <= rbinnext;       // Clock to update the binary address
            rptr <= rgraynext;      // Update the gray code of the corresponding address
        end 
    end
    
    // Address generation logic
    assign rbinnext = !rempty ? ( rbin + rinc ): rbin;      // Binary address update logic: if FIFO is not empty, the address is current address + read enable (i.e. address plus one); If FIFO is empty, the address will not be updated
    assign rgraynext = ( rbinnext >> 1 ) ^ ( rbinnext );    // Gray code address generation: XOR after binary is shifted one bit to the right
    assign raddr = rbin[ ADDR_SIZE - 1: 0 ];                // Read address into RAM
    
    // FIFO empty judgment
    assign rempty_val = ( rgraynext == rq2_wptr );          // Null determination logic: if the gray code of the write pointer is exactly the same as the gray code of the read pointer, it is null
    
    
    // Judgment sequence of empty signal rempty
    always @ ( posedge rclk or negedge rrst_n ) begin
        if ( !rrst_n )
            rempty <= 1'b1;                 // When reset, FIFO is empty
        else
            rempty <= rempty_val;           // Need another signal rempty_val write timing logic
    end
    
endmodule

  (3) Read clock domain to write clock domain synchronization module

Function: delay the gray code address generated by FIFO blank judgment module by one beat, and then output the address to the write clock field.

Port Description:

rptr   :         Gray code address pointer passed in by null determination module
wclk :       External incoming write clock signal
wrst_n:     External incoming write reset signal
wq2_rptr: Gray code address pointer output to the full judgment module

module sync_r2w # (
    parameter       ADDR_SIZE = 4
)
(
    input       [ ADDR_SIZE: 0 ]    rptr,       // Gray code address pointer passed in by null determination module
    input                           wclk,       // External incoming write clock signal
    input                           wrst_n,     // External incoming write reset signal
    output  reg [ ADDR_SIZE: 0 ]    wq2_rptr    // Gray code address pointer output to full judgment module
    );
    
    reg [ ADDR_SIZE: 0 ]    wq1_rptr;           // This register is used to generate a delay of one beat
    
    // D trigger, two-stage synchronization
    always @ ( posedge wclk or negedge wrst_n ) begin
        if ( !wrst_n )
            { wq2_rptr, wq1_rptr } <= 0;
        else                                                    // It is equivalent to delaying the input address by one clock cycle:
            { wq2_rptr, wq1_rptr } <= { wq1_rptr, rptr };       // In the first cycle, the address pointer of the empty judgment module is given to the beat register, and in the second cycle, it is taken out to the output signal
    end
    
endmodule

(4) The full rating module is the same as another synchronization module, and the code is pasted directly:

Full rating module:

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,      // Read address output to RAM
    output reg  [ ADDR_SIZE    :0 ] wptr        // Gray code output to write clock field    
    );
    
    reg     [ ADDR_SIZE: 0 ]    wbin;           // Binary address
    wire    [ ADDR_SIZE: 0 ]    wgraynext, wbinnext;    // Binary and gray code addresses
    wire                        wfull_val;
    
    //----------Address logic-------------//
    
    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
    
    // Address generation logic
    assign wbinnext = !wfull ? ( wbin + winc ): wbin;
    assign wgraynext = ( wbinnext >> 1 ) ^ ( wbinnext );
    assign waddr = wbin[ ADDR_SIZE - 1: 0 ];
    
    // FIFO full
    assign wfull_val = ( wgraynext == { ~wq2_rptr [ ADDR_SIZE: ADDR_SIZE - 1 ], wq2_rptr [ ADDR_SIZE-2: 0 ] } );//The highest two digits are reversed and then judged
    
    always @ ( posedge wclk or negedge wrst_n ) begin
        if ( !wrst_n )
            wfull <= 1'b0;
        else
            wfull <= wfull_val;
    end
endmodule

  Synchronization module:

// The write pointer is synchronized to the read clock module
module sync_w2r # (
    parameter       ADDR_SIZE = 4
)
(
    input       [ ADDR_SIZE: 0 ]    wptr,       // Write address gray code generated by full judgment module
    input                           rclk,
    input                           rrst_n,
    output  reg [ ADDR_SIZE: 0 ]    rq2_wptr    // Write address code output to read clock field
    );
    
    reg [ ADDR_SIZE: 0 ]    rq1_wptr;           // One shot delay
    
    // D trigger, two-stage synchronization
    always @ ( posedge rclk or negedge rrst_n ) begin
        if ( !rrst_n )
            { rq2_wptr, rq1_wptr } <= 0;
        else
            { rq2_wptr, rq1_wptr } <= { rq1_wptr, wptr };       // It is equivalent to delaying the input address by one clock cycle
    end
    
endmodule

(5) Connect the top module:

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 ),           // Data bit width
    .ADDR_SIZE ( ADDR_SIZE )            // FIFO address depth
    )
        I3_DualRAM (
        .wclken     ( winc      ),      // Write enable
        .wclk       ( wclk      ),      // Write clock
        .raddr      ( raddr     ),      // Read clock
        .waddr      ( waddr     ),      // Write address
        .wdata      ( wdata     ),      // Data written to RAM
        .rdata      ( rdata     )       // Data read from 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     ),      // Read address output to RAM
        .rptr       ( rptr      )        // Gray code output to write clock field    
    );
    
    
    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     ),      // Read address output to RAM
        .wptr       ( wptr      )        // Gray code output to write clock field     
    );
    
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 diagram:

Simulation diagram:

Posted by torvald_helmer on Tue, 21 Sep 2021 12:04:51 -0700