Synchronous FIFO Design in Verilog - Complete Guide

A FIFO (First-In-First-Out) buffer is essential in digital design for clock domain crossing, rate matching, and data buffering. This guide covers synchronous FIFO design with parameterized depth and width.

FIFO Architecture

Key Components

  • Memory array: Stores the data
  • Write pointer: Points to next write location
  • Read pointer: Points to next read location
  • Full/Empty flags: Prevent overflow/underflow

Full/Empty Detection (Extra-Bit Method)

The classic challenge: both full and empty occur when rd_ptr == wr_ptr. Solution: add one extra MSB to pointers.

ConditionDetection
Emptyrd_ptr == wr_ptr (entire pointer matches)
Fullrd_ptr[ADDR-1:0] == wr_ptr[ADDR-1:0] AND rd_ptr[ADDR] != wr_ptr[ADDR]

The MSB acts as a "wrap counter" - when pointers have different MSBs but same address bits, the write pointer has wrapped around and caught up to the read pointer.

RTL Implementation

//////////////////////////////////////////////////////////////
// Synchronous FIFO with Parameterized Depth and Width
// Uses extra-bit method for full/empty detection
//////////////////////////////////////////////////////////////

module sync_fifo #(
  parameter DATA_WIDTH = 8,
  parameter DEPTH      = 16,
  parameter ADDR_WIDTH = $clog2(DEPTH)  // Auto-calculate
)(
  input  wire                  clk,
  input  wire                  rst_n,
  
  // Write interface
  input  wire                  wr_en,
  input  wire [DATA_WIDTH-1:0] wr_data,
  output wire                  full,
  
  // Read interface  
  input  wire                  rd_en,
  output reg  [DATA_WIDTH-1:0] rd_data,
  output wire                  empty
);

  // Pointers with extra MSB for full/empty detection
  reg [ADDR_WIDTH:0] wr_ptr, rd_ptr;
  
  // Memory array
  reg [DATA_WIDTH-1:0] mem [0:DEPTH-1];
  
  // Full and Empty flags
  assign empty = (wr_ptr == rd_ptr);
  assign full  = (wr_ptr[ADDR_WIDTH-1:0] == rd_ptr[ADDR_WIDTH-1:0]) &&
                 (wr_ptr[ADDR_WIDTH] != rd_ptr[ADDR_WIDTH]);
  
  // Write logic
  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      wr_ptr <= 0;
    end else if (wr_en && !full) begin
      mem[wr_ptr[ADDR_WIDTH-1:0]] <= wr_data;
      wr_ptr <= wr_ptr + 1;
    end
  end
  
  // Read logic
  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      rd_ptr  <= 0;
      rd_data <= 0;
    end else if (rd_en && !empty) begin
      rd_data <= mem[rd_ptr[ADDR_WIDTH-1:0]];
      rd_ptr  <= rd_ptr + 1;
    end
  end

endmodule

Testbench

module tb_sync_fifo;

  parameter DATA_WIDTH = 8;
  parameter DEPTH = 4;
  
  reg                   clk, rst_n;
  reg                   wr_en, rd_en;
  reg  [DATA_WIDTH-1:0] wr_data;
  wire [DATA_WIDTH-1:0] rd_data;
  wire                  full, empty;
  
  // Instantiate FIFO
  sync_fifo #(
    .DATA_WIDTH(DATA_WIDTH),
    .DEPTH(DEPTH)
  ) dut (
    .clk     (clk),
    .rst_n   (rst_n),
    .wr_en   (wr_en),
    .wr_data (wr_data),
    .full    (full),
    .rd_en   (rd_en),
    .rd_data (rd_data),
    .empty   (empty)
  );
  
  // Clock generation
  initial clk = 0;
  always #5 clk = ~clk;
  
  // Test sequence
  initial begin
    $dumpfile("fifo.vcd");
    $dumpvars(0, tb_sync_fifo);
    
    // Reset
    rst_n = 0; wr_en = 0; rd_en = 0; wr_data = 0;
    #15 rst_n = 1;
    
    // Test 1: Check empty flag
    #10;
    assert (empty == 1) else $error("Should be empty");
    assert (full == 0)  else $error("Should not be full");
    
    // Test 2: Write until full
    @(posedge clk);
    for (int i = 0; i < DEPTH; i++) begin
      wr_en = 1; wr_data = i + 8'hA0;
      @(posedge clk);
    end
    wr_en = 0;
    
    #10;
    assert (full == 1)  else $error("Should be full");
    assert (empty == 0) else $error("Should not be empty");
    
    // Test 3: Read all data and verify
    for (int i = 0; i < DEPTH; i++) begin
      rd_en = 1;
      @(posedge clk);
      #1;
      assert (rd_data == i + 8'hA0) 
        else $error("Data mismatch: exp=%h got=%h", i + 8'hA0, rd_data);
    end
    rd_en = 0;
    
    #10;
    assert (empty == 1) else $error("Should be empty after reads");
    
    // Test 4: Simultaneous read/write
    @(posedge clk);
    wr_en = 1; wr_data = 8'hFF;
    @(posedge clk);
    wr_en = 1; rd_en = 1; wr_data = 8'hEE;
    @(posedge clk);
    wr_en = 0; rd_en = 0;
    
    #20;
    $display("All tests passed!");
    $finish;
  end

endmodule

Design Variations

First-Word Fall-Through (FWFT)

Data appears on rd_data as soon as it's written (no read cycle needed for first word):

// FWFT read - combinational output
assign rd_data = mem[rd_ptr[ADDR_WIDTH-1:0]];

Almost Full/Empty Flags

parameter ALMOST_FULL_THRESH  = DEPTH - 2;
parameter ALMOST_EMPTY_THRESH = 2;

wire [ADDR_WIDTH:0] fifo_count = wr_ptr - rd_ptr;

assign almost_full  = (fifo_count >= ALMOST_FULL_THRESH);
assign almost_empty = (fifo_count <= ALMOST_EMPTY_THRESH);

With Fill Level Output

output wire [ADDR_WIDTH:0] fill_level;
assign fill_level = wr_ptr - rd_ptr;

Common Gotchas

Pointer Width: Don't forget the extra MSB! A DEPTH=16 FIFO needs 5-bit pointers (4 for address + 1 for wrap detection).
Power-of-2 Depth: This design assumes DEPTH is a power of 2. For non-power-of-2 depths, you need modulo arithmetic or different full/empty logic.
Read During Empty: Always gate reads with !empty. Reading from an empty FIFO returns stale data.

Timing Diagram

clk     _|^|_|^|_|^|_|^|_|^|_|^|_|^|_|^|_

wr_en   ____|^^^^^^^^^^^|________________
wr_data ____| A | B | C | D |____________
full    ________________________|^^^^^^^^

rd_en   __________________________|^^^^^^
rd_data ___________________________| A | B
empty   ^^^^|__________________________|^^

         [write 4]     [full]  [read 2]

Synthesis Notes

  • Memory inference: Most tools infer block RAM for deeper FIFOs (>64 entries)
  • Timing: Full/empty are combinational - register them if they're on critical paths
  • Reset: Only pointers need reset, not memory contents (saves reset routing)

Related Topics

Author
Mayur Kubavat
VLSI Design and Verification Engineer sharing knowledge about SystemVerilog, UVM, and hardware verification methodologies.

Comments (0)

Leave a Comment