Digital IC tearing code - synchronous FIFO

 Foreword: 

        This column aims to record high-frequency pen interview hand-torn code questions for digital front-end autumn recruits. All articles in this column provide principle analysis, codes and waveforms, and all codes have been verified by myself.

The directory is as follows:

1. Digital IC hand-tear code-frequency divider (any even number frequency division)

2. Digital IC hand-tear code-frequency divider (any odd frequency division)

3. Digital IC hand-tear code-frequency divider (any decimal frequency division)

4. Digital IC hand tearing code - asynchronous reset and synchronous release

5. Digital IC hand tear code - edge detection (rising edge, falling edge, double edge)

6. Digital IC hand tearing code-sequence detection (state machine writing method)

7. Digital IC hand tearing code-sequence detection (shift register writing method)

8. Digital IC tearing code - half adder, full adder

9. Digital IC hand tearing code - serial to parallel, parallel to serial

10. Digital IC hand tearing code-data bit width converter (width-narrow, narrow-width conversion)

11. Digital IC hand tearing code - finite state machine FSM - beverage machine

12. Digital IC hand tear code - handshake signal (READY-VALID)

13. Digital IC hand tearing code - water handshake (use handshake to solve the problem of pipeline interruption and back pressure)

14. Digital IC hand tearing code - Telink micro written test questions

15. Digital IC hand tearing code - Pingtouge technology final face hand tearing real question

16. Digital IC manual tearing code-Zhaoyi innovation written test real questions

17. Digital IC hand tearing code - Espressif Technology written test real questions (4 times frequency)

18. Digital IC tearing code - dual-port RAM (dual-port-RAM)

        ...Continually updated

For more hand-tearing code questions, you can go to  the digital IC hand-tearing code--question bank .


Table of contents

Principle introduction

How synchronous FIFOs work

FIFO full generation

The counter is judged to be empty

the code

Dual Port RAM

sync_FIFO

         testbench

Waveform


Principle introduction

        When you design a system, you include elements such as processors and peripherals that operate at different clock frequencies. FIFO first in first out arrays play an important role when data is transferred between these elements. A FIFO is a simple storage structure used to arrange data for transmission on a communication bus.

        Therefore, FIFOs are often used to transfer data across different clock domains.

        This section introduces a simple synchronous FIFO architecture, which uses the same clock for reading and writing, paving the way for our subsequent writing of asynchronous FIFOs (the reading and writing clocks are not of the same source).

The general structure of a synchronous FIFO         is given below . DPRAM (Dual Port RAM) is used as a memory to store information. After adding a component to judge whether the DPRAM is full or empty, the entire module is a synchronous FIFO. Read and write Use different enable and address signals (read and write enable, separate read and write addresses), so that the entire module can be read and written at the same time.

         Generate respective read and write addresses through the read and write pointers, and send them to the read and write ports. The write pointer points to the next address to be written, and the read pointer points to the next address to be read. An active write enable increments the write pointer, and an active read enable increments the read pointer.

        The "status module" in the figure generates a FIFO empty-full signal. If "fifo_full" is valid, it means that the space inside the FIFO is full and no more data can be written. If "fifo_empty" is valid, it means that there is next valid data available for reading in FIFO. By judging the position of the read-write pointer, the module can also indicate the number of empty or full areas of the FIFO at any time.

How synchronous FIFOs work

        After reset, both read and write pointers return to 0. At this time, the "fifo_empty" signal is asserted and "fifo_full" remains low. Because the FIFO is empty, the read operation to the FIFO is blocked and only write operations can be performed. Subsequent write operations increment the write pointer and deassert the "fifo_empty" signal. When the last data is written, the write pointer is equal to RAM_SIZE-1. Performing a write pointer at this time will roll the write pointer back to 0 and set the "fifo_full" signal high.

       In short, when the read and write pointers are equal, the FIFO is either empty or full, so it is necessary to distinguish between the two cases.

FIFO full generation

        Take a FIFO with a depth of 4 as an example. At the beginning, the read and write pointers point to the same position, and the FIFO is empty. After writing three data, the write pointer points to the position of RAM_SIZE-1=3. At this time, another data is written, and the write pointer (wr_ptr) rolls back to 0, which points to the same position as the read pointer. At this time, the FIFO is full.

        According to this logic, it is easy to deduce such a conclusion: no matter where the read-write pointer points at this time, when wr_ptr+1==rd_ptr, the FIFO will be full after writing one more data, so there are: 

        fifo_full = (rd_ptr == (wr_ptr + 1'b1))&& wr_fifo

Thus, there is an RTL code for judging that the FIFO is full:

always@(posedge clk or negedge rstn)begin
    if(!rstn)
        fifo_full <= 1'b0;
    else if(wr_fifo && rd_fifo)
        ;//do nothing
    else if(rd_fifo)
        fifo_full <= 1'b0;
    else if((rd_ptr = wr_ptr + 1'b1) && wr_fifo)
        fifo_full <= 1'b1;
end

        Similarly, when a read operation makes the two pointers equal on the next clock, the FIFO becomes empty and the "fifo_empty" signal is generated. There is the following relationship: No matter what position the read/write pointer points to at this time, when rd_ptr+1==wr_ptr, the FIFO will be empty after reading another data.

        fifo_empty = (wr_ptr == (rd_ptr + 1'b1))&& rd_fifo

Therefore, there is an RTL code for judging that the FIFO is empty: 

always @(posedge clk or negedge rstn)begin
    if(!rstn)
        fifo_empty <= 1'b1;
    else if(wr_fifo && rd_fifo)
        ;//do nothing
    else if(wr_fifo)
        fifo_empty <= 1'b0;
    else if((wr_ptr = rd_ptr + 1'b1) && rd_fifo)
        fifo_empty <= 1'b1;
end

The counter is judged to be empty

        FIFOs have another way of using counters to indicate when a FIFO is full.

        The width of the counter should be equal to the depth of the FIFO, so that the counter can record the maximum number of FIFO data. The counter is initialized to 0 at reset, any subsequent write operation will increment it by 1, and any read operation will decrement it by 1.

        When the counter is 0, it is easy to judge that the FIFO is in an empty state, and when the value of the counter is equal to the size of the FIFO, it can be judged that the FIFO is in a full state.

        It is relatively simple to implement this method of using a counter to judge whether it is empty or full, but compared with the previous method of comparing the position of the read and write pointers, the resource usage will be higher. Because this method requires additional hardware (counter) to count.

the code

        To put it simply, FIFO is a dual-port RAM with logic for judging whether it is empty or full. Let’s write a synchronous FIFO that judges whether it is full or empty by a pointer cycle. But before we write a dual-port RAM to store data .

Dual Port RAM

module dual_port_ram#(
    parameter DEPTH = 16,
    parameter WIDTH = 8
)(
    input                       wr_clk      ,
    input                       wr_en       ,
    input   [$clog(DEPTH)-1:0]  wr_addr     ,
    input   [WIDTH-1:0]         wr_data     , 

    input                       rd_clk      ,
    input                       rd_en       ,
    input   [$clog(DEPTH)-1:0]  rd_addr     ,
    output  [WIDTH-1:0]         rd_data
);
reg [WIDTH-1:0] RAM_MEM [DEPTH-1:0];

always @(posedge wr_clk)begin
    if(wr_en)
        RAM_MEM[wr_addr] <= wr_data;
end

always @(posedge rd_clk)begin
    if(rd_en)
        RAM_MEM[rd_addr] <= rd_data;     
end

endmodule

        The entire dual-port RAM is actually a simple write address that writes data into the input when the write is enabled. When the read is enabled, the function of reading data from the address is followed by instantiating the Dual Port RAM module in the FIFO.

sync_FIFO

`include "clog.v"

module sync_fifo#(
    parameter WIDTH = 8     ,
    parameter DEPTH = 16 
)(
    input                       clk         ,       
    input                       rstn        ,   // reset while rstn is negative
    
    //write interface
    input       [WIDTH-1:0]     data_in     ,   // input data 
    input                       wr_en       ,   // write enable 
    
    //read interface
    input                       rd_en       ,   // read enable
    output      [WIDTH-1:0]     data_out    ,

    output  reg                 fifo_empty  ,
    output  reg                 fifo_full     
);

//signal define 
reg [clog(DEPTH)-1:0]    wr_ptr;
reg [clog(DEPTH)-1:0]    rd_ptr;

wire wr_fifo;
wire rd_fifo;

//write data opration
always @(posedge clk or negedge rstn)begin
    if(!rstn)
        wr_ptr <= 1'b0;
    else if(wr_fifo)
        wr_ptr <= wr_ptr + 1'b1;  
end

assign wr_fifo = wr_en && !fifo_full;

//read data opration
always @(posedge clk or negedge rstn)begin
    if(!rstn)
        rd_ptr <= 1'b0;
    else if(rd_fifo)
        rd_ptr <= rd_ptr + 1'b1;
end

assign rd_fifo = rd_en && !fifo_empty;

//full signal judgment
always@(posedge clk or negedge rstn)begin
    if(!rstn)
        fifo_full <= 1'b0;
    else if(wr_fifo && rd_fifo)
        ;//do nothing
    else if(rd_fifo)
        fifo_full <= 1'b0;
    else if((rd_ptr == wr_ptr + 1'b1) && wr_fifo)
        fifo_full <= 1'b1;
end

//empty signal judgment
always @(posedge clk or negedge rstn)begin
    if(!rstn)
        fifo_empty <= 1'b1;
    else if(wr_fifo && rd_fifo)
        ;//do nothing
    else if(wr_fifo)
        fifo_empty <= 1'b0;
    else if((wr_ptr == rd_ptr + 1'b1) && rd_fifo)
        fifo_empty <= 1'b1;
end


dual_port_ram #(
    .DEPTH      (DEPTH)     ,
    .WIDTH      (WIDTH)
)u_dual_port_ram
(
    .wr_clk      (clk)       ,       //sync FIFO ,wr_clk = rd_clk
    .wr_en       (wr_fifo)   ,
    .wr_addr     (wr_ptr)    ,
    .wr_data     (data_in)   ,

    .rd_clk      (clk)       ,
    .rd_en       (rd_fifo)   ,
    .rd_addr     (rd_ptr)    ,
    .rd_data     (data_out)
);

endmodule

        In addition, I also wrote a function clog.v to judge the bit width

`ifndef MY_CLOG
`define MY_CLOG

function integer clog (input integer depth);
    begin
        for (clog=0; depth-1>0; clog=clog+1) 
            depth = depth >>1;                          
    end
endfunction

`endif

testbench

`timescale 1ns/1ns

module sync_fifo_tb();

parameter WIDTH = 8;
parameter DEPTH = 16;

reg                     clk         ;
reg                     rstn        ;

reg     [WIDTH-1:0]     data_in     ;
reg                     rd_en       ;
reg                     wr_en       ;

wire    [WIDTH-1:0]     data_out    ;
wire                    empty       ;
wire                    full        ;

always #5 clk = ~clk;

initial begin
    clk     <= 1'b0;
    rstn    <= 1'b0;
    data_in <= 'd0;
    rd_en   <= 1'b0;
    wr_en   <= 1'b0;
    
    //write 16 times to make fifo full
    #10
    rstn    <= 1'b1;
    repeat(16)begin
        @(negedge clk)begin
            wr_en   <= 1'b1;
            data_in <= $random; // generate 8bit random number data_in
        end
    end
    
    //read 16 times to make fifo empty
    repeat(16)begin
        @(negedge clk)begin
            wr_en   <= 1'b0;
            rd_en   <= 1'b1;
        end
    end

    //read and write 8 times
    repeat(8)begin
        @(negedge clk)begin
            wr_en   <= 1'b1;
            data_in <= $random; 
            rd_en   <= 1'b0;
        end
    end

    //Continuous read and write
    forever begin
        @(negedge clk)begin
            wr_en   <= 1'b1;
            data_in <= $random;
            rd_en   <= 1'b1;
        end
    end
end

initial begin
    #800
    $finish();
end

initial begin
    $fsdbDumpfile("sync_fifo.fsdb");
    $fsdbDumpvars(0);
end

sync_fifo #(
    .WIDTH      (WIDTH)     ,
    .DEPTH      (DEPTH)
)u_sync_fifo
(
    .clk        (clk)       ,
    .rstn       (rstn)      ,
    .data_in    (data_in)   ,
    .rd_en      (rd_en)     ,
    .wr_en      (wr_en)     ,

    .data_out   (data_out)  ,
    .fifo_empty (empty)     ,
    .fifo_full  (full)
);

endmodule

Waveform

        The simulation results are consistent with the analysis results. Write 16 data to fill the FIFO, and the full signal is pulled high at this time; read 16 data to empty the FIFO, and the empty signal is pulled high at this time; after writing 8 data, simultaneously Read and write, write data and read data are consistent and function correctly.

        After understanding the design method of synchronous FIFO, it is relatively simple to design asynchronous FIFO. The next blog records how to write an asynchronous FIFO to solve the problem of crossing clock domains in FIFO.


        For more hand-tearing code questions, you can go to  the digital IC hand-tearing code--question bank .

Guess you like

Origin blog.csdn.net/qq_57502075/article/details/128180751