Digital IC front-end study notes: Verilog implementation of FIFO (2)

related articles

Digital IC front-end study notes: LSFR (Linear Feedback Shift Register)

Digital IC front-end study notes: cross-clock domain signal synchronization

Digital IC front-end study notes: signal synchronization and edge detection

Digital IC front-end study notes: synthesis of latch Latch

Digital IC front-end study notes: Gray code (including binary Gray code converter implemented by Verilog)

Digital IC front-end study notes: Verilog implementation of FIFO (1)

Digital IC front-end study notes: arbitration polling (1)

Digital IC front-end study notes: arbitration polling (2)

Digital IC front-end study notes: arbitration polling (3)

Digital IC front-end study notes: arbitration polling (4)

Digital IC front-end study notes: arbitration polling (5)

Digital IC front-end study notes: arbitration polling (6)

Digital IC front-end study notes: least recently used (LRU) algorithm


Table of contents

4. Asynchronous FIFO principle

5. Verilog implementation of asynchronous FIFO


4. Asynchronous FIFO principle

        Earlier, we discussed synchronous FIFOs, which have limited applications because they have a single clock. In actual reference, we often encounter the situation of multiple clock domains. At this time, data needs to be transmitted between the two clock domains, and glitches and metastable states cannot occur. Let's take an Ethernet adapter board on a PCIe slot as an example. The board receives data packets from a local area network (LAN) or Ethernet and transfers the data to system memory. In turn, it accepts data packets from system memory and passes them on to the network. One side of the board communicates with the network, and uses Ethernet to handle related operations locally. The other side of the board interacts with the PCEe interface and works with the board's own clock. These two clocks are not only different in frequency, but also asynchronous (the frequency is not a multiple relationship, if the stone is a multiple relationship, it can be considered as a synchronous clock because the phase difference is constant). At this time, it is necessary to use an asynchronous FIFO to transfer data from one clock domain to another clock domain.

        Although the operation principle of the asynchronous FIFO is similar to that of the synchronous FIFO, since the former is related to two clocks, the complexity of the circuit will also increase. The way of writing and reading data to asynchronous FIFO is very similar to that of synchronous FIFO. The writing and reading operation also has its own signal set. The complexity is mainly reflected in the generation of FIFO_full, FIFO_empty, room_avaliable, data_avaliable and other signs. The method of generating these flags in an asynchronous FIFO is much more complicated than that in a synchronous FIFO.

        Here we cannot use the counter to save the amount of data in the FIFO according to the read or store signal, because they are signals of two clock domains, and two clocks cannot be used to assign values ​​to the same reg. We know that the write pointer and read pointer are equal when the FIFO is full or empty. But this is not enough, we need another condition to distinguish "empty" from "full". As mentioned earlier, when the FIFO is working, the write pointer is in front, and the read pointer follows the write pointer. When the FIFO is full, the state of the write pointer is to reach the top and then return to the bottom. Finally, it is the same as the read pointer, that is, one round beyond the read pointer (FIFO depth). If we add an auxiliary bit to the highest bit of the pointer for writing and Reading the pointer can be used to indicate when the two pointers are the same (excluding auxiliary bits), whether they are the same without overriding, or the same over one round.

        Let's take a FIFO with a depth of 4 as an example. At this time, the counter needs two bits to represent, and the counting sequence is 00→01

10→11→00, after adding an auxiliary digit to the highest digit, the counting sequence is 000→001→010→011→100→101→110

→111. As we can see, except the highest bit (auxiliary bit), the other bits will cycle twice, by comparing the auxiliary bits, we can determine whether the FIFO status is full or empty.

        Although the identification of the empty and full state is solved, there is still a problem, that is, the read pointer and the write pointer are generated in their respective clock domains and cannot be compared with each other, otherwise a metastable state will occur due to timing problems. The solution to this problem is to pass pointers from one clock domain to another and then do the comparison. And we should pay attention to that multi-bit signals are transmitted instead of one-bit signals. At this time, it is necessary to use Gray code encoding and decoding circuits to safely transmit pointers across clock domains. In the Gray code encoding scheme, only one bit in adjacent encodings changes, and this feature is used for cross-clock domain vector transfer (there are other methods, see Digital IC front-end study notes: Cross-clock domain signal synchronization article). We put all these scattered knowledge in a diagram for easy browsing and understanding of specific operation methods, as shown in the figure below.

        The write pointer is converted into Gray code code and saved by a flip-flop, then synchronized to the read clock domain through two-level registers and decoded into binary code, and finally compared with the read pointer to generate the fifo_empty signal. The read pointer behaves similarly, generating the fifo_full signal in the write clock domain.

        It should be noted that when the read pointer is transferred to the write clock domain, there may be a delay of 3 to 4 cycles relative to the read pointer of the read clock domain, that is, it may appear that the read pointer has increased , but it does not appear to increase in the write clock domain, which will cause fifo to give a fifo_full signal when there is still space. This is the conservative side of asynchronous FIFO, so that data overflow can be ensured.

The write pointer also has a similar problem, and there will be a situation where the FIFO still has data and the FIFO gives the fifo_empty signal. These will not affect the correct operation of the FIFO, and the correct fifo_emty and fifo_full signals can be given a few cycles after stopping writing and reading.

5. Verilog implementation of asynchronous FIFO

module asynch_fifo #(parameter FIFO_PTR=4,
                               FIFO_WIDTH=32)
                    (wrclk, rstb_wrclk,
                    write_en, write_data,
                    snapshot_wrptr, rollback_wrptr,
                    reset_wrptr,
                    rdclk, rstb_rdclk,
                    read_en, read_data,
                    snapshot_rdptr, rollback_rdptr,
                    reset_rdptr,
                    fifo_full, fifo_empty,
                    room_avail, data_avail);

    input wrclk;
    input rstb_wrclk;
    input write_en;
    input [FIFO_WIDTH-1:0] write_data;
    input snapshot_wrptr; //记录写指针快照
    input rollback_wrptr; //恢复写指针快照
    input reset_wrptr; //写指针重置为0
    input rdclk;
    input rstb_rdclk;
    input read_en;
    output [FIFO_WIDTH-1:0] read_data;
    input snapshot_rdptr; //记录读指针快照
    input rollback_rdptr; //恢复读指针快照
    input reset_rdptr; //读指针重置为0
    output reg fifo_full, fifo_empty;
    output reg [FIFO_PTR:0] room_avail, data_avail;

    localparam FIFO_DEPTH = 1 << FIFO_PTR //2^FIFO_PTR
    localparam FIFO_TWICEDEPTH_MINUS1 = 2*FIFO_DEPTH - 1
    reg [FIFO_PTR:0] wr_ptr_wab, wr_ptr_wab_nxt;//有辅助位的写指针
    wire [FIFO_PTR:0] room_avail_nxt, data_avail_nxt; 
    reg [FIFO_PTR:0] wr_ptr_snapshot_value;
    wire [FIFO_PTR:0] wr_ptr_snapshot_value_nxt;
    wire fifo_full_nxt, fifo_empty_nxt;
    reg [FIFO_PTR:0] rd_ptr_snapshot_value;
    wire [FIFO_PTR:0] rd_ptr_snapshot_value_nxt;       
    wire [FIFO_PTR-1:0] wr_ptr,rd_ptr;
    reg [FIFO_PTR:0] rd_ptr_wab, rd_ptr_wab_nxt;//有辅助位的读指针      
    
    //写指针控制逻辑  
    //*********************************************
    always@(*) begin
        wr_ptr_wab_nxt=wr_ptr_wab;
        
        if(reset_wrptr)
            wr_ptr_wab_nxt = 0;
        else if(rollback_wrptr)
            wr_ptr_wab_nxt = wr_ptr_snapshot_value;
        else if(write_en && (wr_ptr_wab == FIFO_TWICEDEPTH_MINUS1))
            wr_ptr_wab_nxt = 0;
        else if(write_en)
            wr_ptr_wab_nxt = wr_ptr_wab + 1;
    end

    //写指针快照
    //*********************************************
    assign wr_ptr_snapshot_value_nxt = 
        snapshot_wrptr ? wr_ptr_wab : wr_ptr_snapshot_value;
    
    //寄存器操作
    //*********************************************
    always@(posedge wrclk or negedge rstb_wrclk) begin
        if(!rstb_wrclk)begin
            wr_ptr_wab <= 0;
            wr_ptr_snapshot_value <= 0;
        end    
        else begin
            wr_ptr_wab <= wr_ptr_wab_nxt;
            wr_ptr_snapshot_value <= wr_ptr_snapshot_value_nxt;
        end
    end
    
    //写指针二进制转格雷码
    //*********************************************
    reg [FIFO_PTR:0] wr_ptr_wab_gray;
    wire [FIFO_PTR:0] wr_ptr_wab_gray_nxt;


    //实例化转码模块(在之前的文章中已设计)
    binary_to_gray #(.PTR(FIFO_PTR)) binary_to_gray_wr
                    (.binary (wr_ptr_wab_nxt),
                     .gray_value (wr_ptr_wab_gray_nxt));

    always@(posedge wrclk or negedge rstb_wrclk)begin
        if(!rst_wrclk)
            wr_ptr_wab_gray <= 0;
        else 
            wr_ptr_wab_gray <= wr_ptr_wab_gray_nxt;
    end
    
    //写指针同步到读时钟域
    //*********************************************
    reg [FIFO_PTR:0] wr_ptr_wab_gray_sync1;
    reg [FIFO_PTR:0] wr_ptr_wab_gray_sync2;
    
    always@(posedge rdclk or negedge rstb_rdclk) begin
        if(!rstb_rdclk) begin
            wr_ptr_wab_gray_sync1 <= 0;
            wr_ptr_wab_gray_sync2 <= 0;
        else
            wr_ptr_wab_gray_sync1 <= wr_ptr_wab_gray;
            wr_ptr_wab_gray_sync2 <= wr_ptr_wab_gray_sync1;
        end
    end

    //格雷码写指针转二进制
    //*********************************************
    reg [FIFO_PTR:0] wr_ptr_wab_rdclk;
    wire [FIFO_PTR:0] wr_ptr_wab_rdclk_nxt;
    gray_to_binary #(.PTR(FIFO_PTR)) gray_to_binary_wr
                    (.gray_value(wr_ptr_wab_gray_sync2),
                     .binary(wr_ptr_wab_rdclk_nxt));

    always@(posedge rdclk or negedge rstb_rdclk) begin
        if(!rstb_rdclk) begin
            wr_ptr_wab_rdclk <= 0;
        else
            wr_ptr_wab_rdclk <= wr_ptr_wab_rdclk_nxt;
        end
    end

    //读指针控制逻辑  
    //*********************************************
    always@(*) begin
        rd_ptr_wab_nxt=rd_ptr_wab;
        
        if(reset_rdptr)
            rd_ptr_wab_nxt = 0;
        else if(rollback_rdptr)
            rd_ptr_wab_nxt = rd_ptr_snapshot_value;
        else if(read_en && (rd_ptr_wab == FIFO_TWICEDEPTH_MINUS1))
            rd_ptr_wab_nxt = 0;
        else if(read_en)
            rd_ptr_wab_nxt = rd_ptr_wab + 1;
    end

    //读指针快照
    //*********************************************
    assign rd_ptr_snapshot_value_nxt = 
        snapshot_rdptr ? rd_ptr_wab : rd_ptr_snapshot_value;

    //寄存器操作
    //*********************************************
    always@(posedge rdclk or negedge rstb_rdclk) begin
        if(!rstb_rdclk)begin
            rd_ptr_wab <= 0;
            rd_ptr_snapshot_value <= 0;
        end    
        else begin
            rd_ptr_wab <= rd_ptr_wab_nxt;
            rd_ptr_snapshot_value <= rd_ptr_snapshot_value_nxt;
        end
    end

    /读指针二进制转格雷码
    //*********************************************
    reg [FIFO_PTR:0] rd_ptr_wab_gray;
    wire [FIFO_PTR:0] rd_ptr_wab_gray_nxt;
    
    //实例化转码模块(在之前的文章中已设计)
    binary_to_gray #(.PTR(FIFO_PTR)) binary_to_gray_rd
                    (.binary (rd_ptr_wab_nxt),
                     .gray_value (rd_ptr_wab_gray_nxt));

    always@(posedge rdclk or negedge rstb_rdclk)begin
        if(!rst_rdclk)
            rd_ptr_wab_gray <= 0;
        else 
            rd_ptr_wab_gray <= rd_ptr_wab_gray_nxt;
    end

    //读指针同步到写时钟域
    //*********************************************
    reg [FIFO_PTR:0] rd_ptr_wab_gray_sync1;
    reg [FIFO_PTR:0] rd_ptr_wab_gray_sync2;
    
    always@(posedge wrclk or negedge rstb_wrclk) begin
        if(!rstb_wrclk) begin
            rd_ptr_wab_gray_sync1 <= 0;
            rd_ptr_wab_gray_sync2 <= 0;
        else
            rd_ptr_wab_gray_sync1 <= rd_ptr_wab_gray;
            rd_ptr_wab_gray_sync2 <= rd_ptr_wab_gray_sync1;
        end
    end

    //格雷码读指针转二进制
    //*********************************************
    reg [FIFO_PTR:0] rd_ptr_wab_rdclk;
    wire [FIFO_PTR:0] rd_ptr_wab_rdclk_nxt;
    gray_to_binary #(.PTR(FIFO_PTR)) gray_to_binary_rd
                    (.gray_value(rd_ptr_wab_gray_sync2),
                     .binary(rd_ptr_wab_rdclk_nxt));

    always@(posedge wrclk or negedge rstb_wrclk) begin
        if(!rstb_wrclk) 
            rd_ptr_wab_rdclk <= 0;
        else
            rd_ptr_wab_rdclk <= rd_ptr_wab_wrclk_nxt;
    end

    assign wr_ptr = wr_ptr_wab[FIFO_PTR-1:0];
    assign rd_ptr = rd_ptr_wab[FIFO_PTR-1:0];

    //SRAM 存储器实例化
    //*********************************************
    sram #(.FIFO_PTR(FIFO_PTR),
           .FIFO_WIDTH(FIFO_WIDTH)) sram_0
          (.wrclk(wrclk),
           .wren(write_en),
           .wrptr(wr_ptr),
           .wrdata(write_data),
           .rdclk(rdclk),
           .rden(read_en),
           .rdptr(rd_ptr),
           .rddata(read_data));

    //fifo_full和room_avail信号产生
    //*********************************************
    assign fifo_full_nxt = 
            (wr_ptr_wab_nxt[FIFO_PTR] != rd_ptr_wab_wrclk_nxt[FIFO_PTR])&&
            (wr_ptr_wab_nxt[FIFO_PTR-1:0] == rd_ptr_wab_wrclk_nxt[FIFO_PTR-1:0]);
    
    assign room_avail_nxt = 
            (wr_ptr_wab_nxt[FIFO_PTR] == rd_ptr_wab_wrclk_nxt[FIFO_PTR])?
            (FIFO_DEPTH-(wr_ptr_wab_nxt[FIFO_PTR-1:0] - 
            rd_ptr_wab_wrclk_nxt[FIFO_PTR-1:0])):
            (rd_ptr_wab_wrclk_nxt[FIFO_PTR-1:0] - 
            wr_ptr_wab_nxt[FIFO_PTR-1:0]);  

    always@(posedge wrclk or negedge rstb_wrclk) begin
        if(!rstb_wrclk) begin
            fifo_full <= 0;
            room_avail <= 0;
        end        
        else begin
            fifo_full <= fifo_full_nxt;
            room_avail <= room_avail_nxt;
        end
    end 
         
    //如果两者没有差一轮,则指针相减代表着FIFO内数据量,用深度减去数据量则为剩余空间,否则直接相减(不包括辅助位)就可得到剩余空间
    
    //fifo_empty和room_empty信号产生
    //*********************************************
    assign fifo_empty_nxt = 
            (rd_ptr_wab_nxt[FIFO_PTR] != wr_ptr_wab_rdclk_nxt[FIFO_PTR])&&
            (rd_ptr_wab_nxt[FIFO_PTR-1:0] == wr_ptr_wab_dclk_nxt[FIFO_PTR-1:0]);
    
    assign data_avail_nxt = 
            (rd_ptr_wab_nxt[FIFO_PTR] == wr_ptr_wab_rdclk_nxt[FIFO_PTR])?
            (FIFO_DEPTH-(rd_ptr_wab_nxt[FIFO_PTR-1:0] - 
            wr_ptr_wab_rdclk_nxt[FIFO_PTR-1:0])):
            (wr_ptr_wab_rdclk_nxt[FIFO_PTR-1:0] - 
            rd_ptr_wab_nxt[FIFO_PTR-1:0]);   
 
    always@(posedge rdclk or negedge rstb_rdclk) begin
        if(!rstb_rdclk) begin
            fifo_empty <= 0;
            data_avail <= 0;
        end        
        else begin
            fifo_empty <= fifo_empty_nxt;
            data_avail <= data_avail_nxt;
        end
    end 
endmodule

The above content comes from "Verilog Advanced Digital System Design Technology and Example Analysis"

Guess you like

Origin blog.csdn.net/weixin_45791458/article/details/131147771