Digital IC Design Series----Synchronous FIFO

Learning route:

  • Synchronous FIFO design (with source code RTL/TB)
  • Asynchronous FIFO design (with source code RTL/TB)
  • uart communication protocol (with source code RTL/TB)
  • SPI protocol interface design (with source code RTL/TB)
  • AMBA 3 APB interface design (with source code RTL/TB)
  • AMBA AHB interface design (with source code RTL/TB)
  • AMBA AXI interface design (with source code RTL/TB)
  • UART 2 APB bridge design (with source code RTL/TB)
  • APB 2 UART bridge design (with source code RTL/TB)

    1. Basic concepts  

  1. What is FIFO and what is it used for?
  2. What does "synchronous" mean for synchronous FIFO?
  3. What are the interfaces:

    Insert image description here

        FIFO is afirst-in-first-out data buffer. FIFO and RAM can be said to be twin brothers, first in, first out. As the name suggests, it cannot read and write according to the address like RAM, but can only write and read data sequentially. a>This is the biggest difference between it and RAM, and therefore determines their different usage scenarios. Logically speaking, FIFO is composed of RAM and control logic. In terms of interface timing, there are no read address and write address signals, but more empty signals and full signals. Other configurations, such as size configuration, operating frequency and other characteristics are consistent with RAM. The data address is automatically added by 1 to the internal read-write pointer.

        Synchronous FIFO, the same clock is used for reading and writing. Its role is generally to serve as a buffer for interactive data, that is It is said that its main function is a buffer. The buffer is used to achieve rate matching.  Asynchronous FIFO, different clocks are used for reading and writing. It has two main functions, one isto realize data transfer in different clock domains, and the other function isto realize different data widths Data interface.
        

 Since it is a data buffer, the buffer size, storage depth, read and write address and memory full status all need to be determined.
  Generally, FIFO uses a circular pointer (the count automatically returns to zero when it overflows). Generally, the write pointer can be called the head, and the read pointer can be called the tail. During initialization, the read and write pointers point to the same data address.

  As shown in the above figure, when the FIFO is initialized, the WP and RP pointers point to the same data unit. WP points to the next data unit to be written, and RP points to the data unit to be read. The two are a catching-up process. You can set a counter, write only, come one data, write one, write address, +1, counter +1, until full; read only, come one, read one data, read address +1, counter -1; at the same time Reading and writing, the counter value remains unchanged, and the reading and writing addresses are both +1.
 

3. Spec

(1) Function description

  Synchronous FIFO implements write/read control, and its interface solves the problem of data rate mismatch at both ends of the interface.

(2) Feature list

  • Supports configurable storage width and depth
  • The clock operating frequency is 1MHz

(3) Block diagram

Insert image description here  The module is mainly divided into four parts: read/write interface, read/write pointer, comparison logic of read and write pointer, and array storage array.

  1. Read/write interface: Provides the module with read and write data and read and write enable signals;
  2. Read-write pointer: main flag read-write pointer current array address
  3. Comparison logic:
  •  Use element counter (elem_cnt) to record the number of data in FIFO RAM:
    ▷ When equal to 0, the empty signal is given; when equal to BUF_LENGTH, the full signal is given
  • elem_cnt:
    ▷ Increase by 1 when writing but not full
    ▷ Decrease 1 when reading but not empty
    ▷ When read and write operations occur at the same time, elem_cnt remains unchanged

(4) Interface description

(5) Timing

Insert image description here  Divided into three parts, writing operations, reading operations, and read and write operations.

四、RTL design

  • DUT module
module sync_fifo 
#(
  parameter DATA_WIDTH = 32,
  parameter DATA_DEPTH = 8 ,
  parameter PTR_WIDTH  = 3 
//parameter PTR_WIDTH  = $clog2(DATA_DEPTH)
)
(
  input  wire                    clk_i   ,
  input  wire                    rst_n_i ,
  
  //write interface
  input  wire                    wr_en_i  ,
  input  wire  [DATA_WIDTH-1:0]  wr_data_i,
  
  //read interface
  input  wire                    rd_en_i  ,
  output reg   [DATA_WIDTH-1:0]  rd_data_o,
  
  //Flags_o
  output reg                     full_o   ,
  output reg                     empty_o  
);
  reg  [DATA_WIDTH-1:0]  regs_array  [DATA_DEPTH-1:0];
  reg  [PTR_WIDTH-1 :0]  wr_ptr                      ;
  reg  [PTR_WIDTH-1 :0]  rd_ptr                      ;
  reg  [PTR_WIDTH   :0]  elem_cnt                    ;
  reg  [PTR_WIDTH   :0]  elem_cnt_nxt                ;
 //Flags
  wire                   full_comb                   ;
  wire                   empty_comb                  ;

/*---------------------------------------------------\
  --------------- write poiter addr ----------------
\---------------------------------------------------*/
always @ (posedge clk_i or negedge rst_n_i) begin
  if (!rst_n_i) begin
    wr_ptr <= 3'b0;
  end
  else if (wr_en_i && !full_o) begin
    wr_ptr <= wr_ptr + 3'b1;
  end
end

/*---------------------------------------------------\
  -------------- read poiter addr ------------------
\---------------------------------------------------*/
always @ (posedge clk_i or negedge rst_n_i) begin
  if (!rst_n_i) begin
    rd_ptr <= 3'b0;
  end
  else if (rd_en_i && !empty_o) begin
    rd_ptr <= rd_ptr + 3'b1;
  end
end

/*---------------------------------------------------\
  --------------- element counter ------------------
\---------------------------------------------------*/

always @ (posedge clk_i or negedge rst_n_i) begin
  if (!rst_n_i) begin
    elem_cnt <= 4'b0;
  end
  else if (wr_en_i && rd_en_i && !full_o && !empty_o) begin
    elem_cnt <= elem_cnt;
  end
  else if(wr_en_i && !full_o) begin
    elem_cnt <= elem_cnt + 1'b1;
  end
  else if(rd_en_i && !empty_o) begin
    elem_cnt <= elem_cnt - 1'b1;
  end
end

/*---------------------------------------------------\
  ------------- generate the flags -----------------
\---------------------------------------------------*/
always @(*) begin
  if(!rst_n_i) begin
    elem_cnt_nxt = 1'b0;
  end
  else if(elem_cnt != 4'd0 && rd_en_i && !empty_o) begin
    elem_cnt_nxt = elem_cnt - 1'b1; 
  end
  else if(elem_cnt != 4'd8 && wr_en_i && !full_o) begin
    elem_cnt_nxt = elem_cnt + 1'b1; 
  end
  else begin
    elem_cnt_nxt = elem_cnt;
  end
end

assign full_comb  = (elem_cnt_nxt == 4'd8);
assign empty_comb = (elem_cnt_nxt == 4'd0);

always @ (posedge clk_i or negedge rst_n_i) begin
  if (!rst_n_i) begin
    full_o <= 1'b0;
  end
  else begin
    full_o <= full_comb;
  end
end

always @ (posedge clk_i or negedge rst_n_i) begin
  if (!rst_n_i) begin
    empty_o <= 1'b1;
  end
  else begin
    empty_o <= empty_comb;
  end
end

/*---------------------------------------------------\
  -------------------- read data -------------------
\---------------------------------------------------*/
always @ (posedge clk_i or negedge rst_n_i) begin
  if (!rst_n_i) begin
    rd_data_o <= 32'b0;
  end
  else if(rd_en_i && !empty_o) begin
    rd_data_o <= regs_array[rd_ptr];
  end
end

/*---------------------------------------------------\
  ------------------- write data -------------------
\---------------------------------------------------*/
reg [PTR_WIDTH:0] i;

always @ (posedge clk_i or negedge rst_n_i) begin
  if (!rst_n_i) begin
    for(i=0;i<DATA_DEPTH;i=i+1) begin
      regs_array[i] <= 32'b0;
    end
  end
  else if(wr_en_i && !full_o) begin
    regs_array[wr_ptr] <= wr_data_i;
  end
end

endmodule
  • tb
module tb_sync_fifo;
  reg          clk_i    ;
  reg          rst_n_i  ;

  reg          wr_en_i  ;
  reg  [31:0]  wr_data_i;
  
  reg          rd_en_i  ;
  reg  [31:0]  rd_data_o;

  wire         full_o   ;
  reg          empty_o  ;

initial begin
  rst_n_i   = 1  ;
  clk_i     = 0  ;
  
  rd_en_i   = 0  ;

  wr_en_i   = 0  ;
  wr_data_i = 32'b0;

  #2 rst_n_i = 0 ;
  #5 rst_n_i = 1 ;
end

initial begin
  #10 wr_en_i = 1;
      rd_en_i = 0;

  #10 wr_en_i = 0;
      rd_en_i = 1;

  #10 wr_en_i = 1;
      rd_en_i = 0;

  #3  rd_en_i = 1;
  #10
  repeat(100) begin
    #5 wr_en_i = {$random}%2;
       rd_en_i = {$random}%2;
  end
end

initial #2000 $finish;

always #0.5 clk_i     = ~clk_i       ;
always #1   wr_data_i = {$random}%10;
sync_fifo u_sync_fifo
(
  .clk_i    (clk_i    ),
  .rst_n_i  (rst_n_i  ),
  .wr_en_i  (wr_en_i  ),
  .wr_data_i(wr_data_i),
  .rd_en_i  (rd_en_i  ),
  .rd_data_o(rd_data_o),
  .full_o   (full_o   ),
  .empty_o  (empty_o  )
);

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

5. Analysis and Summary

(1) Analysis

  • writing stage
    Insert image description here

  After reset, write operation is performed until full. After the full flag is generated, no new data will be written.


  • reading stage
    Insert image description here

  Perform reading operations until the read is empty. After the empty flag is generated, no new data will be read.


  • Simultaneous reading and writing stage
    Insert image description here

  Perform writing operations first, and after writing three new data, read and write operations are performed at the same time. During this period, new data is written and data is read, but the elem_cnt counter does not change and is dynamically balanced.


(2) Summary
  Design idea: first analyze the requirements, define the interface, and draw a specific implementation block diagram; draw the corresponding sequence diagram according to the protocol and understanding; write the program based on the diagram , verify whether the simulation waveform corresponds to the timing diagram.
  The key point of synchronous FIFO design is when to generate the empty and full flag, that is, how to measure whether the array is full or empty. Here, I use the 4-bit elem_cnt representation, and use the value of elem_cnt to represent the resource usage of the current array storage array. 0 means there is no data, that is, an empty state; 8 means it is full, because the storage depth of the array is 8. It is mentioned in the spec that the FIFO can be configured. Here, only a synchronous fifo design with a width of 32 bits and a depth of 8 is implemented. The preliminary verification of the simulation waveform corresponds to the timing diagram.


————————————————
Copyright statement: This article is an original article by CSDN blogger "xlinxdu" and follows CC 4.0 BY- SA Copyright Agreement, please attach the original source link and this statement when reprinting.
Original link: https://blog.csdn.net/qq_43244515/article/details/124163224

Guess you like

Origin blog.csdn.net/Arvin_ing/article/details/132761271