What is a synchronous FIFO ?

A synchronous FIFO (First-In-First-Out) is a type of data buffer used in digital systems that operates under a single clock domain, meaning both read and write operations occur using the same clock signal. This design ensures that data is processed in the order it was received, which is critical for maintaining data integrity in various applications.

A synchronous FIFO is called "synchronous" because it uses synchronized clocks to control the read and write operations. The read and write pointers of the FIFO are updated synchronously with the clocks, and data is transferred between the FIFO and the external circuit synchronously with the clocks.

sync fifo

Synchronous FIFOs are primarily used to buffer data when the rate of data transfer exceeds the rate of data processing. This is particularly important in high-speed systems where timing discrepancies can lead to data loss or corruption.

What does depth and width indicate ?

The depth of a FIFO refers to the total number of data entries it can hold at any given time. It determines how much data can be buffered between the writing and reading processes.

   Depth = (Writing Rate - Reading Rate)/Clock Frequency

The width of a FIFO refers to the number of bits that can be stored in each entry or slot within the FIFO. It essentially defines how much data can be written or read in one operation.

What are the main IO ports ?

  • Data ports: It contains two ports, write and read, where the write port is used to write data into the FIFO, and the read port is used to read data from the FIFO.
  • Pointers: It contains two pointers, write and read, where the write pointer tracks the position where new data will be written and the read pointer tracks the position from where data will be read. Both pointers are updated synchronously with the clock.
  • Status Flags: When full , it indicates that no more data can be written until some is read and ]empty indicates that there is no data available to read.

How does it work ?

Data is written into the FIFO at each clock cycle when the write enable signal is active, and the FIFO is not full. The write pointer increments after each successful write.

Data can be read from the FIFO at each clock cycle when the read enable signal is active, and the FIFO is not empty. The read pointer increments after each successful read.

Synchronous FIFO Verilog Code


module sync_fifo #(parameter DEPTH=8, DWIDTH=16) 
( 
        input               	rstn,               // Active low reset                       
                            	clk,                // Clock
                            	wr_en, 				// Write enable
                            	rd_en, 				// Read enable
        input      [DWIDTH-1:0] din, 				// Data written into FIFO
        output reg [DWIDTH-1:0] dout, 				// Data read from FIFO
        output              	empty, 				// FIFO is empty when high
                            	full 				// FIFO is full when high
);
  
  
  reg [$clog2(DEPTH)-1:0]   wptr;
  reg [$clog2(DEPTH)-1:0]   rptr;
  
  reg [DWIDTH-1 : 0]    fifo[DEPTH];
  
  always @ (posedge clk) begin
    if (!rstn) begin
      wptr <= 0;      
    end else begin
      if (wr_en & !full) begin
        fifo[wptr] <= din;
        wptr <= wptr + 1;
      end
    end
  end
  
  initial begin
    $monitor("[%0t] [FIFO] wr_en=%0b din=0x%0h rd_en=%0b dout=0x%0h empty=%0b full=%0b",
             $time, wr_en, din, rd_en, dout, empty, full);
  end
  
  always @ (posedge clk) begin
    if (!rstn) begin
      rptr <= 0;
    end else begin
      if (rd_en & !empty) begin
        dout <= fifo[rptr];
        rptr <= rptr + 1;
      end
    end
  end
  
  assign full  = (wptr + 1) == rptr;
  assign empty = wptr == rptr;
endmodule

The FIFO design is implemented using an internal memory array fifo and two pointers, rptr and wptr , that point to the read and write locations in the buffer. When data is written to the FIFO, it is stored in the memory location pointed to by wptr . The write pointer is then incremented to point to the next available memory location.

Similarly, when data is read from the FIFO, it is retrieved from the memory location pointed to by rptr . The read pointer is then incremented to point to the next available memory location. The number of queued items is represented by the difference between the write and read pointers. The empty and full flags are set based on the relative positions of wptr and rptr .

This is a simple example of a FIFO design with fixed capacity. There are other designs with additional features like programmable depth, programmable threshold for empty/full flags, and dual-clock operation, which are more suitable for use in complex systems.

Testbench


module tb;
  
  reg 	 		clk;
  reg [15:0]    din;
  wire [15:0] 	dout;
  reg [15:0] 	rdata;
  reg 			empty;
  reg 			rd_en;
  reg 			wr_en;
  wire 			full;
  reg 			rstn;
  reg 			stop;
  
  sync_fifo u_sync_fifo ( .rstn(rstn),
                         .wr_en(wr_en),
                         .rd_en(rd_en),
                         .clk(clk),
                         .din(din),
                         .dout(dout),
                         .empty(empty),
                         .full(full)
                        );
  
  always #10 clk <= ~clk;
  
  initial begin
    clk 	<= 0;
    rstn 	<= 0;
    wr_en 	<= 0;
    rd_en 	<= 0;
    stop  	<= 0;
    
    #50 rstn <= 1;
  end
  
  initial begin
    @(posedge clk);
    
    for (int i = 0; i < 20; i = i+1) begin
      
      // Wait until there is space in fifo
      while (full) begin
      	@(posedge clk);
        $display("[%0t] FIFO is full, wait for reads to happen", $time);
      end;
      
      // Drive new values into FIFO      
      wr_en <= $random;
      din 	<= $random;
      $display("[%0t] clk i=%0d wr_en=%0d din=0x%0h ", $time, i, wr_en, din);
      
      // Wait for next clock edge
      @(posedge clk);
    end
    
    stop = 1;
  end
  
  initial begin
    @(posedge clk);
    
    while (!stop) begin
      // Wait until there is data in fifo
      while (empty) begin
        rd_en <= 0;
        $display("[%0t] FIFO is empty, wait for writes to happen", $time);
        @(posedge clk);
      end;
      
      // Sample new values from FIFO at random pace
      rd_en <= $random;
      @(posedge clk);
      rdata <= dout;
      $display("[%0t] clk rd_en=%0d rdata=0x%0h ", $time, rd_en, rdata);      
    end
    
    #500 $finish;
  end
endmodule

Simulation Results

Note from the log below that there are times when the FIFO becomes empty and full depending on how fast the write the and read pointers operate.

 Simulation Log
xcelium> run
[0] [FIFO] wr_en=0 din=0xx rd_en=0 dout=0xx empty=x full=x
[10] clk i=0 wr_en=0 din=0xx 
[10] [FIFO] wr_en=0 din=0x5e81 rd_en=1 dout=0xx empty=1 full=0
[30] clk i=1 wr_en=0 din=0x5e81 
[30] clk rd_en=1 rdata=0xx 
[30] FIFO is empty, wait for writes to happen
[30] [FIFO] wr_en=1 din=0x7b0d rd_en=0 dout=0xx empty=1 full=0
[50] clk i=2 wr_en=1 din=0x7b0d 
[50] FIFO is empty, wait for writes to happen
[50] [FIFO] wr_en=1 din=0x8465 rd_en=0 dout=0xx empty=0 full=0
[70] clk i=3 wr_en=1 din=0x8465 
[70] [FIFO] wr_en=0 din=0xe301 rd_en=1 dout=0xx empty=0 full=0
[90] clk i=4 wr_en=0 din=0xe301 
[90] clk rd_en=1 rdata=0xx 
[90] [FIFO] wr_en=0 din=0xcd3d rd_en=1 dout=0x7b0d empty=0 full=0
[110] clk i=5 wr_en=0 din=0xcd3d 
[110] clk rd_en=1 rdata=0xx 
[110] [FIFO] wr_en=0 din=0xe9f9 rd_en=0 dout=0x8465 empty=1 full=0
[130] clk i=6 wr_en=0 din=0xe9f9 
[130] clk rd_en=0 rdata=0x7b0d 
[130] FIFO is empty, wait for writes to happen
[130] [FIFO] wr_en=1 din=0xd2aa rd_en=0 dout=0x8465 empty=1 full=0
[150] clk i=7 wr_en=1 din=0xd2aa 
[150] FIFO is empty, wait for writes to happen
[150] [FIFO] wr_en=1 din=0x7277 rd_en=0 dout=0x8465 empty=0 full=0
[170] clk i=8 wr_en=1 din=0x7277 
[170] [FIFO] wr_en=0 din=0xdb8f rd_en=0 dout=0x8465 empty=0 full=0
[190] clk i=9 wr_en=0 din=0xdb8f 
[190] clk rd_en=0 rdata=0x8465 
[190] [FIFO] wr_en=0 din=0x7ae8 rd_en=1 dout=0x8465 empty=0 full=0
[210] clk i=10 wr_en=0 din=0x7ae8 
[210] clk rd_en=1 rdata=0x8465 
[210] [FIFO] wr_en=0 din=0x28bd rd_en=1 dout=0xd2aa empty=0 full=0
[230] clk i=11 wr_en=0 din=0x28bd 
[230] clk rd_en=1 rdata=0x8465 
[230] [FIFO] wr_en=1 din=0x6263 rd_en=0 dout=0x7277 empty=1 full=0
[250] clk i=12 wr_en=1 din=0x6263 
[250] clk rd_en=0 rdata=0xd2aa 
[250] FIFO is empty, wait for writes to happen
[250] [FIFO] wr_en=0 din=0x2120 rd_en=0 dout=0x7277 empty=0 full=0
[270] clk i=13 wr_en=0 din=0x2120 
[270] [FIFO] wr_en=0 din=0xcc9d rd_en=0 dout=0x7277 empty=0 full=0
[290] clk i=14 wr_en=0 din=0xcc9d 
[290] clk rd_en=0 rdata=0x7277 
[290] [FIFO] wr_en=1 din=0x380d rd_en=1 dout=0x7277 empty=0 full=0
[310] clk i=15 wr_en=1 din=0x380d 
[310] clk rd_en=1 rdata=0x7277 
[310] [FIFO] wr_en=1 din=0x2ad5 rd_en=0 dout=0x6263 empty=0 full=0
[330] clk i=16 wr_en=1 din=0x2ad5 
[330] clk rd_en=0 rdata=0x7277 
[330] [FIFO] wr_en=0 din=0xe91d rd_en=1 dout=0x6263 empty=0 full=0
[350] clk i=17 wr_en=0 din=0xe91d 
[350] clk rd_en=1 rdata=0x6263 
[350] [FIFO] wr_en=1 din=0x650a rd_en=0 dout=0x380d empty=0 full=0
[370] clk i=18 wr_en=1 din=0x650a 
[370] clk rd_en=0 rdata=0x6263 
[370] [FIFO] wr_en=0 din=0xbdf2 rd_en=0 dout=0x380d empty=0 full=0
[390] clk i=19 wr_en=0 din=0xbdf2 
[390] clk rd_en=0 rdata=0x380d 
[390] [FIFO] wr_en=1 din=0x34d8 rd_en=0 dout=0x380d empty=0 full=0
[410] clk i=20 wr_en=1 din=0x34d8 
[410] clk rd_en=0 rdata=0x380d 
[410] [FIFO] wr_en=1 din=0xdeb rd_en=0 dout=0x380d empty=0 full=0
[430] clk rd_en=0 rdata=0x380d 
[490] [FIFO] wr_en=1 din=0xdeb rd_en=0 dout=0x380d empty=0 full=1
Simulation complete via $finish(1) at time 930 NS + 0