Generic Depth Synchronous FIFO

Synchronous FIFO (first-in-first-out) buffers are one of the most commonly used RTL designs in almost all digital systems. They are heavily used in communicating data/signals between two different logic blocks, interconnection interfaces, and storing data packets. These are just a few examples of their usage. Synchronous means the producer and the consumer work on the same clock, i.e. we use the same rate at which we write and read to/from FIFO.

In this part, I present the RTL design of Synchronous FIFO where the depth of the memory can be of any size, not necessarily power of 2.

GitHub link for full implementation and testbench: [generic-sync-fifo]

EDA-playground link: [eda-generic-fifo]

RTL Design and Implementation

FIFO is a sequential block that consists of a write (producer) interface and a read (consumer) interface. As our design is synchronous, so we will have one single clock and a reset port. Further, the device will also have two flags representing the fill state fo the FIFO they are called Empty and Full. Let's summarize the interface below.

System Inputs:

  • clk: System clock

  • rst_n: Asynchronous active low reset

Write Interface:

  • wr_data_i: [Input] Data to be written in the FIFO memory

  • wr_en_i: [Input] Indicating when we want to perform the write

  • full_o: [Output] Status if FIFO is full

Read Interface:

  • rd_data_o: [Output] Data read out of the FIFO

  • rd_en_i: [Input] Indicating when we want to perform the read

  • empty_o: [Output] Status if FIFO is empty

// Using above interface we can easily define our module

// Module Definition

module sync_fifo_sv #(WIDTH = 8,

DEPTH = 7 )

(

input logic clk,

input logic rst_n,

input logic wr_en_i,

input logic [WIDTH-1:0] wr_data_i,

output logic full_o,

input logic rd_en_i,

output logic [WIDTH-1:0] rd_data_o,

output logic empty_o

);

To capture the location to perform read and write operations we also define two pointers along with the memory block. The memory would be an SRAM but for now we define an array block to be used as memory.

Notice below that we are using an extra-bit in our read/write pointers. For example, if the depth is selected as 4 then we will use 2-bits to address the memory as usual, along with an extra third bit to help us generate full and empty flags.

logic [MSB:0] wr_ptr, rd_ptr;

logic [WIDTH-1:0] mem [DEPTH-1:0];

assign empty_o = (wr_ptr == rd_ptr);

assign full_o = (wr_ptr[MSB]^rd_ptr[MSB])

&& (wr_ptr[MSB-1:0] == rd_ptr[MSB-1:0]);

Now let us look at how to correctly set our pointers when we perform write to FIFO. First, we have to follow two golden rules in FIFO.

  1. Never write to a FIFO when it is full.

  2. Never read from a FIFO when it is empty.

That is even if write or read enable is asserted we should check in our logic that we don't perform write/read based on above two rules. Next, when we write or read to FIFO then we point to next location to be written or to be read. That is done by incrementing the our pointers. Now the tricky part is how to wrap our pointers if depth is not a power of 2?

For this, we enhance our logic by checking if we have reached the last location to be written/read, if we have then we wrap our pointers back to the starting address. Care must be taken that we still maintain the correct value for our extra-bit that we used in our pointers else we will produce incorrect full and empty flag. This is done by inverting the last bit in the pointers when we reach the last location. This is shown below for the write logic.

// Wrap the pointer when reached the required depth

// toggle the MSB bit of pointer for correct full/empty flags

else if (wr_en_i && !full_o) begin

if (wr_ptr[MSB-1:0] == DEPTH-1) begin

wr_ptr[MSB-1:0] <= '0;

wr_ptr[MSB] <= ~wr_ptr[MSB];

end

else begin

wr_ptr <= wr_ptr + 1;

end

// Write the data to memory

mem[wr_ptr[MSB-1:0]] <= wr_data_i;

end

Similarly, we can design the read logic for our FIFO and you can see the Github link for the full logic implementation.

GitHub link for full implementation and testbench: [generic-sync-fifo]

EDA-playground link: [eda-generic-fifo]

Full verification is a work-in-progress and will update it in this block soon :-)