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
- The art of hardware architecture
- https://www.cnblogs.com/digital-wei/p/6103271.html
- https://blog.csdn.net/huangzhicong3/article/details/108317910