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:
- Data Storage Array: Contains the data.
- Write Pointer: Refers to the next point to which data is to be written.
- Read Pointer: Indicates the next place data will be read.
- 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:
- Dual-Ported Memory: Permits read and write operations in different clock domains simultaneously.
- Write Pointer (in write clock domain): Tracks where to write the data.
- Read Pointer (read clock domain): Keeps track of the location to read the data.
- Synchronization Logic: Synthesizes safely the pointers between the two clock domains.
- 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
| Component | Description |
|---|---|
wr_clk, rd_clk | Separate write and read clocks to allow asynchronous operation. |
wr_ptr, rd_ptr | Track positions for write and read. Converted to Gray code for safe synchronization. |
*_sync1, *_sync2 | Synchronization registers to avoid metastability across clock domains. |
full, empty | Determined using Gray-coded pointers to ensure valid comparisons. |
Purpose of Each Pointer
| Name | Domain | Purpose |
|---|---|---|
wr_ptr_bin | Write | Binary write pointer used to address FIFO memory. |
wr_ptr_gray | Write | Gray-coded version of wr_ptr_bin, sent to read domain. |
wr_ptr_gray_sync1, wr_ptr_gray_sync2 | Read | Synchronized version of wr_ptr_gray in read domain for safe comparison. |
rd_ptr_bin | Read | Binary read pointer used to access data from FIFO. |
rd_ptr_gray | Read | Gray-coded version of rd_ptr_bin, sent to write domain. |
rd_ptr_gray_sync1, rd_ptr_gray_sync2 | Write | Synchronized 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