A synchronous FIFO (First-In-First-Out) is a digital circuit that is used to transfer data between the same clock domain and the main function is to buffer data when the rate of data transfer is faster than the rate of data processing.

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.

Synchronous FIFOs typically consist of two basic parts: a write port and a read port. The write port is used to write data into the FIFO, while the read port is used to read data from the FIFO. The write and read pointers are used to keep track of the data that has been written and read, respectively, and to ensure that the FIFO operates in a correct and efficient manner.


module sync_fifo #(parameter DEPTH=8, DWIDTH=16) 
( 
        input               	rstn,               // Active low reset                       
                            	Wr_clk, 			// Write clock
                            	rd_clk, 			// Read 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 wr_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 rd_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 	 		wr_clk;
  //reg 	 		rd_clk;
  wire 			rd_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),
                         .wr_clk(wr_clk),
                         .rd_clk(rd_clk),
                         .din(din),
                         .dout(dout),
                         .empty(empty),
                         .full(full)
                        );
  
  always #10 wr_clk <= ~wr_clk;
  //always #20 rd_clk <= ~rd_clk;
  
  assign rd_clk = wr_clk;
  
  initial begin
    wr_clk 	<= 0;
    //rd_clk 	<= 0;
    rstn 	<= 0;
    wr_en 	<= 0;
    rd_en 	<= 0;
    stop  	<= 0;
    
    #50 rstn <= 1;
  end
  
  initial begin
    @(posedge wr_clk);
    
    for (int i = 0; i < 50; i = i+1) begin
      
      // Wait until there is space in fifo
      while (full) begin
      	@(posedge wr_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] wr_clk i=%0d wr_en=%0d din=0x%0h ", $time, i, wr_en, din);
      
      // Wait for next clock edge
      @(posedge wr_clk);
    end
    
    stop = 1;
  end
  
  initial begin
    @(posedge rd_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 rd_clk);
      end;
      
      // Sample new values from FIFO at random pace
      rd_en <= $random;
      @(posedge rd_clk);
      rdata <= dout;
      $display("[%0t] rd_clk rd_en=%0d rdata=0x%0h ", $time, rd_en, rdata);      
    end
    
    #500 $finish;
  end
endmodule