0
0
VerilogHow-ToIntermediate · 4 min read

Verilog Code for Asynchronous FIFO: Syntax and Example

An asynchronous FIFO in Verilog uses separate clocks for write and read sides, with dual-port memory and gray-coded pointers for safe crossing. The design includes write and read pointers, synchronization registers, and full/empty flags to manage data flow between clock domains.
📐

Syntax

An asynchronous FIFO module typically has separate clock inputs for writing and reading, data inputs and outputs, write and read enable signals, and status flags like full and empty. It uses dual-port RAM internally and gray-coded pointers to safely cross clock domains.

  • clk_wr: Write clock domain
  • clk_rd: Read clock domain
  • rst: Reset signal
  • wr_en: Write enable
  • rd_en: Read enable
  • data_in: Data input bus
  • data_out: Data output bus
  • full: FIFO full flag
  • empty: FIFO empty flag
verilog
module async_fifo #(parameter DATA_WIDTH=8, parameter ADDR_WIDTH=4) (
    input wire clk_wr,
    input wire clk_rd,
    input wire rst,
    input wire wr_en,
    input wire rd_en,
    input wire [DATA_WIDTH-1:0] data_in,
    output reg [DATA_WIDTH-1:0] data_out,
    output wire full,
    output wire empty
);

    // Internal signals and memory declaration here

endmodule
💻

Example

This example shows a complete asynchronous FIFO with 8-bit data width and 16-depth memory. It uses gray-coded pointers and synchronizers to safely transfer write and read pointers across clock domains. The FIFO sets full and empty flags accordingly.

verilog
module async_fifo #(parameter DATA_WIDTH=8, parameter ADDR_WIDTH=4) (
    input wire clk_wr,
    input wire clk_rd,
    input wire rst,
    input wire wr_en,
    input wire rd_en,
    input wire [DATA_WIDTH-1:0] data_in,
    output reg [DATA_WIDTH-1:0] data_out,
    output wire full,
    output wire empty
);

    localparam DEPTH = 1 << ADDR_WIDTH;

    reg [DATA_WIDTH-1:0] mem [0:DEPTH-1];

    // Write pointer and gray code
    reg [ADDR_WIDTH:0] wr_ptr_bin = 0;
    reg [ADDR_WIDTH:0] wr_ptr_gray = 0;

    // Read pointer and gray code
    reg [ADDR_WIDTH:0] rd_ptr_bin = 0;
    reg [ADDR_WIDTH:0] rd_ptr_gray = 0;

    // Synchronizers for pointers crossing clock domains
    reg [ADDR_WIDTH:0] wr_ptr_gray_sync1 = 0, wr_ptr_gray_sync2 = 0;
    reg [ADDR_WIDTH:0] rd_ptr_gray_sync1 = 0, rd_ptr_gray_sync2 = 0;

    // Convert gray to binary function
    function [ADDR_WIDTH:0] gray2bin;
        input [ADDR_WIDTH:0] gray;
        integer i;
        begin
            gray2bin[ADDR_WIDTH] = gray[ADDR_WIDTH];
            for (i=ADDR_WIDTH-1; i>=0; i=i-1) begin
                gray2bin[i] = gray2bin[i+1] ^ gray[i];
            end
        end
    endfunction

    // Write pointer logic
    always @(posedge clk_wr or posedge rst) begin
        if (rst) begin
            wr_ptr_bin <= 0;
            wr_ptr_gray <= 0;
        end else if (wr_en && !full) begin
            wr_ptr_bin <= wr_ptr_bin + 1;
            wr_ptr_gray <= (wr_ptr_bin + 1) ^ ((wr_ptr_bin + 1) >> 1);
            mem[wr_ptr_bin[ADDR_WIDTH-1:0]] <= data_in;
        end
    end

    // Read pointer logic
    always @(posedge clk_rd or posedge rst) begin
        if (rst) begin
            rd_ptr_bin <= 0;
            rd_ptr_gray <= 0;
            data_out <= 0;
        end else if (rd_en && !empty) begin
            data_out <= 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);
        end
    end

    // Synchronize read pointer into write clock domain
    always @(posedge clk_wr 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

    // Synchronize write pointer into read clock domain
    always @(posedge clk_rd 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

    // Convert synchronized pointers to binary
    wire [ADDR_WIDTH:0] rd_ptr_bin_sync = gray2bin(rd_ptr_gray_sync2);
    wire [ADDR_WIDTH:0] wr_ptr_bin_sync = gray2bin(wr_ptr_gray_sync2);

    // Full when next write pointer equals read pointer synchronized to write clock domain
    assign full = ((wr_ptr_bin + 1) & ((1 << (ADDR_WIDTH+1)) - 1)) == rd_ptr_bin_sync;

    // Empty when read pointer equals write pointer synchronized to read clock domain
    assign empty = rd_ptr_bin == wr_ptr_bin_sync;

endmodule
⚠️

Common Pitfalls

Common mistakes when designing asynchronous FIFOs include:

  • Not using gray code for pointers crossing clock domains, causing metastability and data corruption.
  • Failing to synchronize pointers properly between write and read clock domains.
  • Incorrect full and empty flag logic leading to data overwrite or underflow.
  • Not handling reset properly in both clock domains.

Always use double flip-flop synchronizers and gray-coded pointers to avoid these issues.

verilog
/* Wrong: Using binary pointers directly across clock domains */
// This can cause metastability and incorrect full/empty flags

/* Right: Use gray-coded pointers and synchronizers as shown in the example section */
📊

Quick Reference

  • Use separate clocks for write and read sides.
  • Use gray code for pointers crossing clock domains.
  • Synchronize pointers with double flip-flops.
  • Check full and empty flags carefully to avoid data loss.
  • Reset both clock domains properly.

Key Takeaways

Use gray-coded pointers to safely cross clock domains in asynchronous FIFOs.
Synchronize pointers with double flip-flops to prevent metastability.
Implement full and empty flags based on synchronized pointers to avoid data corruption.
Reset logic must be handled in both write and read clock domains.
Separate write and read clocks enable safe asynchronous data transfer.