The art of hardware architecture: synchronous FIFO design

Posted by only one on Tue, 22 Feb 2022 19:25:39 +0100

1. General

   FIFO(First In First Out) is a first in first out data interaction mode. Almost all digital chips will use FIFO for data buffering between modules, cross asynchronous transmission of data, etc. According to the working clock, it can be divided into synchronous FIFO and asynchronous FIFO. All circuits in synchronous FIFO work in the same clock domain, which is commonly used for data buffering between modules; It is used to read and write data asynchronously across two logical clock domains, which are different from each other.

   this paper will give the typical design of synchronous FIFO, and the typical design of asynchronous FIFO will be analyzed in subsequent articles.

2. Synchronous FIFO design

2.1 synchronous FIFO structure

   typical FIFO consists of three parts: FIFO write control, FIFO read control and FIFO memory (DPRAM or reg). The functions of each part are as follows:

  • FIFO write control: write pointer and FIFO full signal generation;
  • FIFO read control: generation of read pointer and FIFO null signal;
  • FIFO memory: read / write memory logic;


   FIFO generates read and write pointers by counting write requests and read requests. The read and write pointers are the read and write addresses of memory. The write pointer points to the next address to be written, the read pointer points to the next address to be read, the write request increments the write pointer, and the read request increments the read pointer.

   FIFO module outputs empty and full signals to indicate its status, fifo_full indicates that the space in FIFO is full and no more data can be written_ Empty indicates that there is no next valid data to read in FIFO.

2.2 synchronous FIFO empty full signal generation

   FIFO read and write pointers are reset. At this point fifo_empty is raised, which can only be written but not read. Once data is written, FIFO will be deleted_ Empty is pulled down to allow data to be read. When FIFO's write pointer points to FIFO_ When depth-1, a write operation will reset the write pointer to zero (there is no read operation at this time) and pull up fifo_full.

   in short, when the read and write pointers are equal, the FIFO is either empty or full, so we need to distinguish between the two cases.

2.2.1 sequential logic generation empty full

2.2.1.1 fifo full signal generation

   FIFO full status is triggered by write operation:

"When the write operation keeps the read and write pointers equal in the next clock, the FIFO is full"

   a more popular explanation is that "the write operation makes the write pointer catch up with the read pointer, that is, the write pointer covers the read pointer for one circle (running angle)", at this time FIFO_ The condition of full can be written as:

condition = (wr_en == 1'b1 && (wr_ptr+1'b1 == rd_ptr))

   the code of FIFO full signal generated by timing logic is as follows:

// ---- fifo full
always @(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0) begin
        fifo_full <= 1'b0;
    end
    else if(rd_en == 1'b1) begin
        fifo_full <= 1'b0;
    end
    else if((wr_en == 1'b1) && (wr_ptr + 1'b1 == rd_ptr)) begin
        fifo_full <= 1'b1;
    end
end

2.2.1.2 fifo null signal generation

   FIFO empty status is triggered by read operation:

"When the read operation keeps the read and write pointers equal in the next clock, FIFO is empty"

   a more popular explanation is "the read operation makes the read pointer catch up with the write pointer". At this time, FIFO_ The condition of empty can be written as:

condition = (rd_en == 1'b1 && (rd_ptr+1'b1 == wr_ptr))

// ---- fifo empty
always @(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0) begin
        fifo_empty <= 1'b0;
    end
    else if(wr_en == 1'b1) begin
        fifo_empty <= 1'b0;
    end
    else if((rd_en == 1'b1) && (rd_ptr + 1'b1 == wr_ptr)) begin
        fifo_empty <= 1'b1;
    end
end

2.2.2 the counter generates empty full

   when combinational logic generates empty full, it continuously indicates the number of FIFO empty or full positions by using counters. The bit width of the counter shall be able to represent the FIFO depth, and the bit width relationship is as follows:

localparam FIFO_COUNT_WIDTH = FIFO_DEPTH_WIDTH + 1;

   when the counter is reset, the write operation will increase the counter by one, the read operation will decrease the counter by one, and the read-write operation occurs at the same time, and the counter remains unchanged. The empty full signal is generated as follows:

  • Null signal: the counter is zero
  • Full signal: counter equals FIFO depth

   the disadvantage of this method is that when the FIFO depth is large, the scale of the comparator is large. At this time, the combined logic delay becomes large, which will eventually reduce the maximum working frequency of FIFO. The code for the counter to generate empty and full is as follows:

// ==========================================================================
// localpara
// ==========================================================================
localparam FIFO_DEPTH_WIDTH = $clog2(FIFO_DEPTH);
localparam FIFO_COUNT_WIDTH = FIFO_DEPTH_WIDTH + 1;
// ==========================================================================
// fifo count
always @(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0) begin
        fifo_count <= {FIFO_COUNT_WIDTH{1'b0}};
    end
    else if((wr_en == 1'b1) && (rd_en == 1'b0) && (fifo_full != 1'b1)) begin
        fifo_count <= fifo_count + 1'b1;
    end
    else if((rd_en == 1'b1) && (wr_en == 1'b0) && (fifo_empty != 1'b1)) begin
        fifo_count <= fifo_count - 1'b1;
    end
end
// ==========================================================================
// fifo status
assign fifo_full  = (fifo_count[FIFO_COUNT_WIDTH] == 1'b1);
assign fifo_empty = (fifo_count == {FIFO_COUNT_WIDTH{1'b0}});

2.3 verilog code implementation

2.3.1 FIFO memory selection experience

   the memory selection of FIFO uses register or DPRAM, which depends on the depth and data bit width of FIFO_ DEPTH * FIFO_ DATA_ When width > 1024, select DPRAM for memory, and select register on the contrary.

   when the memory scale is 1024bit, the area of DPRAM may be larger than the register. It is not the best choice to choose DPRAM as memory at any time, not only in the design of FIFO, but also in the design of cache buffer.

2.3.2 FIFO memory usage register

   the code of synchronous FIFO using register is as follows:

module sync_fifo_reg (
module sync_fifo_reg(
    clk,
    rst_n,
    wr_en,
    wr_data,
    rd_en,
    rd_data,
    fifo_full,
    fifo_empty
);

// ==========================================================================
// parameter
// ==========================================================================
parameter FIFO_DATA_WIDTH = 8;
parameter FIFO_DEPTH      = 16;

// ==========================================================================
// localpara
// ==========================================================================
localparam FIFO_DEPTH_WIDTH = $clog2(FIFO_DEPTH);
localparam FIFO_COUNT_WIDTH = FIFO_DEPTH_WIDTH + 1;

// ==========================================================================
// I/O
// ==========================================================================

// -------- clock && reset
input                               clk;
input                               rst_n;

// -------- fifo wr 
input                               wr_en;
input   [FIFO_DATA_WIDTH-1:0]       wr_data;

// -------- fifo rd
input                               rd_en;
output  [FIFO_DATA_WIDTH-1:0]       rd_data;

// -------- fifo status
output                              fifo_full;
output                              fifo_empty;

// ==========================================================================
// signal define
// ==========================================================================

// -------- fifo wr ctrl
reg     [FIFO_DEPTH_WIDTH-1:0]      wr_ptr;

// -------- fifo rd ctrl
reg     [FIFO_DEPTH_WIDTH-1:0]      rd_ptr;

// -------- fifo count
reg     [FIFO_COUNT_WIDTH-1:0]      fifo_count ;

// -------- fifo memory
reg     [FIFO_DATA_WIDTH-1:0]       fifo_mem[FIFO_DEPTH-1:0];

// ==========================================================================
// main body
// ==========================================================================

// ==========================================================================
// fifo wr ctrl
always @(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0) begin
        wr_ptr <= {FIFO_DATA_WIDTH{1'b0}};
    end
    else if((wr_en == 1'b1) && (fifo_full != 1'b1)) begin
        wr_ptr <= wr_ptr + 1'b1;
    end
end
// ==========================================================================
// fifo rd ctrl
always @(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0) begin
        rd_ptr <= {FIFO_DATA_WIDTH{1'b0}};
    end
    else if((rd_en == 1'b1) && (fifo_empty != 1'b1)) begin
        rd_ptr <= rd_ptr + 1'b1;
    end
end 
// ==========================================================================
// fifo count
always @(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0) begin
        fifo_count <= {FIFO_COUNT_WIDTH{1'b0}};
    end
    else if((wr_en == 1'b1) && (rd_en == 1'b0) && (fifo_full != 1'b1)) begin
        fifo_count <= fifo_count + 1'b1;
    end
    else if((rd_en == 1'b1) && (wr_en == 1'b0) && (fifo_empty != 1'b1)) begin
        fifo_count <= fifo_count - 1'b1;
    end
end

// ==========================================================================
// fifo wr/rd
integer i;
always @(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0) begin
        for(i=0;i<FIFO_DEPTH-1;i++) begin
            fifo_mem[i] <= {FIFO_DATA_WIDTH{1'b0}};
        end
    end
    else if((wr_en == 1'b1) && (fifo_full != 1'b1)) begin
        fifo_mem[wr_ptr] <= wr_data;
    end
end

assign rd_data = fifo_mem[rd_ptr];

// ==========================================================================
// fifo status
assign fifo_full  = (fifo_count[FIFO_COUNT_WIDTH] == 1'b1);
assign fifo_empty = (fifo_count == {FIFO_COUNT_WIDTH{1'b0}});


endmodule

2.3.3 FIFO memory uses DPRAM

2.3.3.1 DPRAM implementation

  generally speaking, DPRAM is provided by the manufacturer. The larger the capacity of DPRAM, the smaller the area of DPRAM provided by the manufacturer compared with the DPRAM built by the register. The DPRAM built by register is given here, and the code is as follows:

module dpram (
    clk_rd,
    ram_rd,
    ram_rd_addr,
    ram_rd_data,
    clk_wr,
    ram_wr,
    ram_wr_addr,
    ram_wr_data
);

// ==========================================================================
// parameter
// ==========================================================================
parameter DPRAM_DATA_WIDTH = 8;
parameter DPRAM_DEPTH      = 16;

// ==========================================================================
// localpara
// ==========================================================================
localparam DPRAM_ADDR_WIDTH = $clog2(DPRAM_DEPTH);

// ==========================================================================
// I/O
// ==========================================================================
input                               clk_rd;
input                               ram_rd;
input   [DPRAM_ADDR_WIDTH-1:0]      ram_rd_addr;
output  [DPRAM_DATA_WIDTH-1:0]      ram_rd_data;

input                               clk_wr;
input                               ram_wr;
input   [DPRAM_ADDR_WIDTH-1:0]      ram_wr_addr;
input   [DPRAM_DATA_WIDTH-1:0]      ram_wr_data;

// ==========================================================================
// signal define
// ==========================================================================
reg     [DPRAM_DATA_WIDTH-1:0]      dpram_mem[DPRAM_DEPTH-1:0];

// ==========================================================================
// main body
// ==========================================================================

// ==========================================================================
// write dpram
always@(posedge clk_rd) begin
    if(ram_rd == 1'b1) begin
        ram_rd_data <= dpram_mem[ram_rd_addr] ;
    end
end

// ==========================================================================
// read dpram
always@(posedge clk_wr) begin
    if(ram_wr == 1'b1) begin
        dpram_mem[ram_wr_addr] <= ram_wr_data;
    end
end

endmodule

2.3.3.2 realization of DPRAM FIFO

   the implementation code of synchronous FIFO using DPRAM as FIFO memory is as follows:

module sync_fifo_reg(
    clk,
    rst_n,
    wr_en,
    wr_data,
    rd_en,
    rd_data,
    fifo_full,
    fifo_empty
);

// ==========================================================================
// parameter
// ==========================================================================
parameter FIFO_DATA_WIDTH = 8;
parameter FIFO_DEPTH      = 16;

// ==========================================================================
// localpara
// ==========================================================================
localparam FIFO_DEPTH_WIDTH = $clog2(FIFO_DEPTH);
localparam FIFO_COUNT_WIDTH = FIFO_DEPTH_WIDTH + 1;

// ==========================================================================
// I/O
// ==========================================================================

// -------- clock && reset
input                               clk;
input                               rst_n;

// -------- fifo wr 
input                               wr_en;
input   [FIFO_DATA_WIDTH-1:0]       wr_data;

// -------- fifo rd
input                               rd_en;
output  [FIFO_DATA_WIDTH-1:0]       rd_data;

// -------- fifo status
output                              fifo_full;
output                              fifo_empty;

// ==========================================================================
// signal define
// ==========================================================================

// -------- fifo wr ctrl
reg     [FIFO_DEPTH_WIDTH-1:0]      wr_ptr;

// -------- fifo rd ctrl
reg     [FIFO_DEPTH_WIDTH-1:0]      rd_ptr;

// -------- fifo count
reg     [FIFO_COUNT_WIDTH-1:0]      fifo_count ;

// ==========================================================================
// main body
// ==========================================================================

// ==========================================================================
// fifo wr ctrl
always @(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0) begin
        wr_ptr <= {FIFO_DATA_WIDTH{1'b0}};
    end
    else if((wr_en == 1'b1) && (fifo_full != 1'b1)) begin
        wr_ptr <= wr_ptr + 1'b1;
    end
end
// ==========================================================================
// fifo rd ctrl
always @(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0) begin
        rd_ptr <= {FIFO_DATA_WIDTH{1'b0}};
    end
    else if((rd_en == 1'b1) && (fifo_empty != 1'b1)) begin
        rd_ptr <= rd_ptr + 1'b1;
    end
end 
// ==========================================================================
// fifo count
always @(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0) begin
        fifo_count <= {FIFO_COUNT_WIDTH{1'b0}};
    end
    else if((wr_en == 1'b1) && (rd_en == 1'b0) && (fifo_full != 1'b1)) begin
        fifo_count <= fifo_count + 1'b1;
    end
    else if((rd_en == 1'b1) && (wr_en == 1'b0) && (fifo_empty != 1'b1)) begin
        fifo_count <= fifo_count - 1'b1;
    end
end

// ==========================================================================
// fifo wr/rd

// -------- instance dpram
dpram #(
    .DPRAM_DATA_WIDTH   (FIFO_DATA_WIDTH),
    .DPRAM_DEPTH        (FIFO_DEPTH     )
)
fifo_mem(
    .clk_rd             (clk    ),
    .ram_rd             (rd_en  ),
    .ram_rd_addr        (rd_ptr ),
    .ram_rd_data        (rd_data),
    .clk_wr             (clk    ),
    .ram_wr             (wr_en  ),
    .ram_wr_addr        (wr_ptr ),
    .ram_wr_data        (wr_data)
);

// ==========================================================================
// fifo status
assign fifo_full  = (fifo_count[FIFO_COUNT_WIDTH] == 1'b1);
assign fifo_empty = (fifo_count == {FIFO_COUNT_WIDTH{1'b0}});

endmodule

reference material

  1. The art of hardware architecture
  2. https://www.cnblogs.com/digital-wei/p/6103271.html
  3. https://blog.csdn.net/huangzhicong3/article/details/108317910

Topics: microchip