FIFO in VLSI

FIFO(First-In-First-Out) is a basic memory structure that finds widespread application in VLSI design to perform data buffering and communication between system blocks. FIFO permits data to be read and written in the same order in which it was inputted, which makes it well-suited to those applications where preserving the order of data is of paramount importance.

There are two primary types of FIFOs in VLSI design: synchronous FIFO and asynchronous FIFO.

Synchronous FIFO

A synchronous FIFO has one clock domain in which read and write operations are both driven by the same clock. This keeps the design simple since there is no requirement to cross the clock domain. Some of the components of a synchronous FIFO include:

  1. Data Storage Array: Contains the data.
  2. Write Pointer: Refers to the next point to which data is to be written.
  3. Read Pointer: Indicates the next place data will be read.
  4. Control Logic: Handles the full and empty status flags.

Operations

  • Write Operation: Data is written into the location addressed by the write pointer if the FIFO is not full; otherwise, it overflows.
  • Read Operation: Data is read from the position marked by the read pointer if the FIFO is not empty. The read pointer is incremented thereafter.

A simple basic Synchronous FIFO will look like this.

Synchronous FIFO Verilog Code:

module fifo (clk,rst,wr_en,rd_en,addr,data_in,full,empty,data_out);
  input clk,rst,wr_en,rd_en;
  input [7:0] data_in;
  input [3:0] addr;
  output reg full,empty;
  output reg[7:0] data_out;
  reg [7:0]mem[15:0];
  reg [3:0] wr_pt,rd_pt;
  
  assign full = ((wr_pt==4'b1111) & (rd_pt==4'b0000)?1:0);
  assign empty = ((wr_pt == rd_pt)?1:0);
  
  always @ (posedge clk)
    begin
      if(rst) begin
        for (int i=0;i<16;i++) begin
          mem[i] <= 0;
        end
        data_out<=0; 
        wr_pt = 0;rd_pt = 0;
       
      end
      else
        begin
          if (wr_en && !full) begin
            mem[addr] <= data_in;
            wr_pt <= wr_pt + 1;
        end
          else if (rd_en && !empty) begin
            data_out <= mem[addr];
          rd_pt <= rd_pt + 1;
          end
        end
    end
  
endmodule

Testbench:

module tb;
  reg clk,rst,wr_en,rd_en;
  reg [7:0] data_in;
  reg [3:0] addr;
  wire full,empty;
  wire [7:0] data_out;
  
  fifo f1(clk,rst,wr_en,rd_en,addr,data_in,full,empty,data_out);
  
  initial begin
    clk=0;rst=1;
  end
  always #5 clk = ~clk;
  
  initial begin
    
    @ (posedge clk);
 
    //writing the data
     rst<=0;wr_en<=1;rd_en<=0;data_in<=8'd123;addr<=5;
    
    @ (posedge clk);
     rst<=0;wr_en<=1;rd_en<=0;data_in<=8'd132;addr<=7;
    
    @ (posedge clk);
    
    
    //reading the data
    @ (posedge clk);
     wr_en=0;rd_en=1;addr=5;
    @ (posedge clk);
     if (data_out==8'd123)
       $display("pass %p, addr %0d",data_out,addr);
    else $display("fail %p, addr %0d",data_out,addr);
     
      wr_en=0;rd_en=1;addr=7;
    
   @ (posedge clk);
    if (data_out==8'd132)
       $display("pass %p, addr %0d",data_out,addr);
    else $display("fail %p, addr %0d",data_out,addr);
    
    #20 $finish;

  end
  
  initial begin
    $dumpfile("dump.vcd");
    $dumpvars;
  end
  
endmodule

Output:

pass 123, addr 5
pass 132, addr 7
$finish called from file "testbench.sv", line 45.
$finish at simulation time

Asynchronous FIFO

An asynchronous FIFO is utilized to support operations of writing and reading in separate clock domains. This adds complexity because data transfer between clock domains has to be done in a safe manner. An asynchronous FIFO has the following components:

  1. Dual-Ported Memory: Permits read and write operations in different clock domains simultaneously.
  2. Write Pointer (in write clock domain): Tracks where to write the data.
  3. Read Pointer (read clock domain): Keeps track of the location to read the data.
  4. Synchronization Logic: Synthesizes safely the pointers between the two clock domains.
  5. Control Logic: Handles full and empty flags.

Operations

  • Write Operation: When in the write clock domain and the FIFO is not full, data is written in and the write pointer is incremented.
  • Read Operation: When in read clock domain and FIFO is not empty, data is read and read pointer is advanced.

When write and read clocks are asynchronous, there’s a risk of metastability if you directly transfer multi-bit signals (like wr_ptr or rd_ptr) from one domain to another.

So we use:

  • Gray code to reduce metastability risk (only 1 bit changes at a time).
  • Double flip-flop synchronizers to stabilize the signals across clock domains.

Asynchronous FIFO Verilog Code:

module asynchronous_fifo #(
    parameter DATA_WIDTH = 8,
    parameter DEPTH = 16,
    parameter ADDR_WIDTH = $clog2(DEPTH)
)(
    input  logic                  wr_clk,
    input  logic                  rd_clk,
    input  logic                  rst,
    input  logic                  wr_en,
    input  logic                  rd_en,
    input  logic [DATA_WIDTH-1:0] wr_data,
    output logic [DATA_WIDTH-1:0] rd_data,
    output logic                  full,
    output logic                  empty
);

    // FIFO memory
    logic [DATA_WIDTH-1:0] mem [0:DEPTH-1];

    // Write and read pointers (binary and gray)
    logic [ADDR_WIDTH:0] wr_ptr_bin, wr_ptr_gray, wr_ptr_gray_sync1, wr_ptr_gray_sync2;
    logic [ADDR_WIDTH:0] rd_ptr_bin, rd_ptr_gray, rd_ptr_gray_sync1, rd_ptr_gray_sync2;

    // Write logic
    always_ff @(posedge wr_clk or posedge rst) begin
        if (rst) begin
            wr_ptr_bin <= 0;
            wr_ptr_gray <= 0;
        end else if (wr_en && !full) begin
            mem[wr_ptr_bin[ADDR_WIDTH-1:0]] <= wr_data;
            wr_ptr_bin <= wr_ptr_bin + 1;
            wr_ptr_gray <= (wr_ptr_bin + 1) ^ ((wr_ptr_bin + 1) >> 1);  // Binary to Gray
        end
    end

    // Read logic
    always_ff @(posedge rd_clk or posedge rst) begin
        if (rst) begin
            rd_ptr_bin <= 0;
            rd_ptr_gray <= 0;
            rd_data <= 0;
        end else if (rd_en && !empty) begin
            rd_data <= mem[rd_ptr_bin[ADDR_WIDTH-1:0]];
            rd_ptr_bin <= rd_ptr_bin + 1;
            rd_ptr_gray <= (rd_ptr_bin + 1) ^ ((rd_ptr_bin + 1) >> 1);  // Binary to Gray
        end
    end

    // Synchronize pointers across clock domains
    // Sync read pointer to write clock domain
    always_ff @(posedge wr_clk or posedge rst) begin
        if (rst) begin
            rd_ptr_gray_sync1 <= 0;
            rd_ptr_gray_sync2 <= 0;
        end else begin
            rd_ptr_gray_sync1 <= rd_ptr_gray;
            rd_ptr_gray_sync2 <= rd_ptr_gray_sync1;
        end
    end

    // Sync write pointer to read clock domain
    always_ff @(posedge rd_clk or posedge rst) begin
        if (rst) begin
            wr_ptr_gray_sync1 <= 0;
            wr_ptr_gray_sync2 <= 0;
        end else begin
            wr_ptr_gray_sync1 <= wr_ptr_gray;
            wr_ptr_gray_sync2 <= wr_ptr_gray_sync1;
        end
    end

    // Gray to Binary conversion function
    function [ADDR_WIDTH:0] gray_to_bin(input [ADDR_WIDTH:0] gray);
        integer i;
        begin
            gray_to_bin[ADDR_WIDTH] = gray[ADDR_WIDTH];
            for (i = ADDR_WIDTH - 1; i >= 0; i = i - 1)
                gray_to_bin[i] = gray_to_bin[i + 1] ^ gray[i];
        end
    endfunction

    // Full and Empty logic
    assign full  = (wr_ptr_gray == {~rd_ptr_gray_sync2[ADDR_WIDTH:ADDR_WIDTH-1], rd_ptr_gray_sync2[ADDR_WIDTH-2:0]});
    assign empty = (rd_ptr_gray == wr_ptr_gray_sync2);

endmodule
How Asynchronous FIFO Works
ComponentDescription
wr_clk, rd_clkSeparate write and read clocks to allow asynchronous operation.
wr_ptr, rd_ptrTrack positions for write and read. Converted to Gray code for safe synchronization.
*_sync1, *_sync2Synchronization registers to avoid metastability across clock domains.
full, emptyDetermined using Gray-coded pointers to ensure valid comparisons.
Purpose of Each Pointer
NameDomainPurpose
wr_ptr_binWriteBinary write pointer used to address FIFO memory.
wr_ptr_grayWriteGray-coded version of wr_ptr_bin, sent to read domain.
wr_ptr_gray_sync1, wr_ptr_gray_sync2ReadSynchronized version of wr_ptr_gray in read domain for safe comparison.
rd_ptr_binReadBinary read pointer used to access data from FIFO.
rd_ptr_grayReadGray-coded version of rd_ptr_bin, sent to write domain.
rd_ptr_gray_sync1, rd_ptr_gray_sync2WriteSynchronized version of rd_ptr_gray in write domain for safe comparison.
Testbench:
module tb_asynchronous_fifo;

    parameter DATA_WIDTH = 8;
    parameter DEPTH = 8;

    logic wr_clk, rd_clk, rst;
    logic wr_en, rd_en;
    logic [DATA_WIDTH-1:0] wr_data, rd_data;
    logic full, empty;

    asynchronous_fifo #(
        .DATA_WIDTH(DATA_WIDTH),
        .DEPTH(DEPTH)
    ) dut (
        .wr_clk(wr_clk),
        .rd_clk(rd_clk),
        .rst(rst),
        .wr_en(wr_en),
        .rd_en(rd_en),
        .wr_data(wr_data),
        .rd_data(rd_data),
        .full(full),
        .empty(empty)
    );

    // Clock generation
    always #4 wr_clk = ~wr_clk;  // 125 MHz
    always #7 rd_clk = ~rd_clk;  // ~71 MHz

    initial begin
        $display("Starting Asynchronous FIFO Test...");
        wr_clk = 0; rd_clk = 0; rst = 1;
        wr_en = 0; rd_en = 0; wr_data = 0;

        #20 rst = 0;

        // Write some data
        repeat (10) begin
            @(posedge wr_clk);
            if (!full) begin
                wr_en = 1;
                wr_data = $random;
            end else begin
                wr_en = 0;
            end
        end
        wr_en = 0;

        // Read data
        repeat (10) begin
            @(posedge rd_clk);
            if (!empty) rd_en = 1;
            else rd_en = 0;
        end
        rd_en = 0;

        #50 $finish;
    end

    // Monitor
    always @(posedge wr_clk) begin
        if (wr_en && !full)
            $display("WR CLK @ %0t: Writing data %0d | full: %b", $time, wr_data, full);
    end

    always @(posedge rd_clk) begin
        if (rd_en && !empty)
            $display("RD CLK @ %0t: Reading data %0d | empty: %b", $time, rd_data, empty);
    end

endmodule

Output:

Starting Asynchronous FIFO Test...
WR CLK @ 20: Writing data 36 | full: 0
WR CLK @ 28: Writing data 129 | full: 0
WR CLK @ 36: Writing data 9 | full: 0
WR CLK @ 44: Writing data 99 | full: 0
WR CLK @ 52: Writing data 13 | full: 0
WR CLK @ 60: Writing data 141 | full: 0
WR CLK @ 68: Writing data 101 | full: 0
WR CLK @ 76: Writing data 18 | full: 0
RD CLK @ 105: Reading data 0 | empty: 0
RD CLK @ 119: Reading data 36 | empty: 0
RD CLK @ 133: Reading data 129 | empty: 0
RD CLK @ 147: Reading data 9 | empty: 0
RD CLK @ 161: Reading data 99 | empty: 0
RD CLK @ 175: Reading data 13 | empty: 0
RD CLK @ 189: Reading data 141 | empty: 0
RD CLK @ 203: Reading data 101 | empty: 0
$finish called from file "testbench.sv", line 59.
$finish at simulation time                  281
           V C S   S i m u l a t i o n   R e p o r t