0
0
VerilogHow-ToBeginner · 4 min read

Verilog Code for Synchronous FIFO: Syntax and Example

A synchronous FIFO in Verilog uses a single clock for both read and write operations, managing data storage with pointers and a memory array. The design includes logic for full and empty flags, and pointer updates synchronized to the clock. This ensures simple, reliable data buffering in hardware.
📐

Syntax

A synchronous FIFO module typically includes inputs for clk, reset, write_enable, read_enable, and data_in. Outputs include data_out, full, and empty flags. Internally, it uses a memory array and read/write pointers updated on the clock edge.

Key parts:

  • clk: Clock signal for synchronization.
  • reset: Resets pointers and flags.
  • write_enable: Enables writing data.
  • read_enable: Enables reading data.
  • data_in: Input data bus.
  • data_out: Output data bus.
  • full: Indicates FIFO is full.
  • empty: Indicates FIFO is empty.
verilog
module sync_fifo #(parameter DATA_WIDTH=8, parameter DEPTH=16) (
    input wire clk,
    input wire reset,
    input wire write_enable,
    input wire read_enable,
    input wire [DATA_WIDTH-1:0] data_in,
    output reg [DATA_WIDTH-1:0] data_out,
    output wire full,
    output wire empty
);

    reg [DATA_WIDTH-1:0] mem [0:DEPTH-1];
    reg [$clog2(DEPTH):0] write_ptr = 0;
    reg [$clog2(DEPTH):0] read_ptr = 0;

    assign full = (write_ptr - read_ptr) == DEPTH;
    assign empty = (write_ptr == read_ptr);

    always @(posedge clk) begin
        if (reset) begin
            write_ptr <= 0;
            read_ptr <= 0;
            data_out <= 0;
        end else begin
            if (write_enable && !full) begin
                mem[write_ptr[$clog2(DEPTH)-1:0]] <= data_in;
                write_ptr <= write_ptr + 1;
            end
            if (read_enable && !empty) begin
                data_out <= mem[read_ptr[$clog2(DEPTH)-1:0]];
                read_ptr <= read_ptr + 1;
            end
        end
    end
endmodule
💻

Example

This example shows a synchronous FIFO with 8-bit data width and 16-depth. It demonstrates writing data when write_enable is high and reading data when read_enable is high, both synchronized to the clock. The full and empty flags prevent overflow and underflow.

verilog
module testbench();
    reg clk = 0;
    reg reset = 1;
    reg write_enable = 0;
    reg read_enable = 0;
    reg [7:0] data_in = 0;
    wire [7:0] data_out;
    wire full;
    wire empty;

    sync_fifo #(8,16) fifo (
        .clk(clk),
        .reset(reset),
        .write_enable(write_enable),
        .read_enable(read_enable),
        .data_in(data_in),
        .data_out(data_out),
        .full(full),
        .empty(empty)
    );

    always #5 clk = ~clk; // 10 time units clock period

    initial begin
        $display("Time | reset | write_enable | read_enable | data_in | data_out | full | empty");
        $monitor("%4d |   %b   |      %b      |     %b     |   %h   |   %h    |  %b  |  %b", $time, reset, write_enable, read_enable, data_in, data_out, full, empty);

        #10 reset = 0;

        // Write 3 data values
        #10 write_enable = 1; data_in = 8'hA1;
        #10 data_in = 8'hB2;
        #10 data_in = 8'hC3;
        #10 write_enable = 0;

        // Read 2 data values
        #10 read_enable = 1;
        #20 read_enable = 0;

        // Write 2 more data values
        #10 write_enable = 1; data_in = 8'hD4;
        #10 data_in = 8'hE5;
        #10 write_enable = 0;

        // Read all remaining data
        #10 read_enable = 1;
        #40 read_enable = 0;

        #20 $finish;
    end
endmodule
Output
Time | reset | write_enable | read_enable | data_in | data_out | full | empty 0 | 1 | 0 | 0 | 00 | 00 | 0 | 1 10 | 0 | 1 | 0 | A1 | 00 | 0 | 0 20 | 0 | 1 | 0 | B2 | 00 | 0 | 0 30 | 0 | 1 | 0 | C3 | 00 | 0 | 0 40 | 0 | 0 | 0 | C3 | 00 | 0 | 0 50 | 0 | 0 | 1 | C3 | A1 | 0 | 0 60 | 0 | 0 | 1 | C3 | B2 | 0 | 0 70 | 0 | 0 | 0 | C3 | B2 | 0 | 0 80 | 0 | 1 | 0 | D4 | B2 | 0 | 0 90 | 0 | 1 | 0 | E5 | B2 | 0 | 0 100 | 0 | 0 | 0 | E5 | B2 | 0 | 0 110 | 0 | 0 | 1 | E5 | C3 | 0 | 0 120 | 0 | 0 | 1 | E5 | D4 | 0 | 0 130 | 0 | 0 | 1 | E5 | E5 | 0 | 0 140 | 0 | 0 | 1 | E5 | E5 | 0 | 1
⚠️

Common Pitfalls

Common mistakes when designing synchronous FIFOs include:

  • Not properly handling the full and empty conditions, causing data overwrite or invalid reads.
  • Incorrect pointer width or wrap-around logic leading to pointer overflow.
  • Forgetting to reset pointers and flags on reset.
  • Using asynchronous resets without synchronizing to the clock.

Always ensure pointers are wide enough to count the FIFO depth plus one extra bit for full detection, and update pointers only on clock edges.

verilog
/* Wrong: No full check before write */
always @(posedge clk) begin
    if (write_enable) begin
        mem[write_ptr[$clog2(DEPTH)-1:0]] <= data_in; // Can overwrite when full
        write_ptr <= write_ptr + 1;
    end
end

/* Right: Check full before write */
always @(posedge clk) begin
    if (write_enable && !full) begin
        mem[write_ptr[$clog2(DEPTH)-1:0]] <= data_in;
        write_ptr <= write_ptr + 1;
    end
end
📊

Quick Reference

  • Use a single clock for both read and write in synchronous FIFO.
  • Use pointers with one extra bit to detect full and empty states.
  • Update pointers only on clock edges.
  • Check full before writing and empty before reading.
  • Reset pointers and output registers on reset.

Key Takeaways

A synchronous FIFO uses one clock for both read and write operations with pointers to track data.
Always check full and empty flags before writing or reading to avoid data corruption.
Reset pointers and outputs properly to ensure correct FIFO startup state.
Pointer width must be enough to cover FIFO depth plus one bit for full detection.
Update all FIFO logic only on the clock's rising edge for synchronization.