Maker.io main logo

FIFO design in SystemVerilog

2025-11-26 | By Mustahsin Zarif

A few weeks ago, I was building a low-latency stock market data feed handler using the ITCH communication protocol (Intra-day Trade Capture Handler). As you can imagine, trading firms want to trade quickly, so the data feed handler needs to parse ITCH messages efficiently and output them for use by downstream logic, such as an order book. However, if the order book cannot read the parsed messages as soon as they’re ready, we can lose data or slow down the trading process, which results in a loss of money. In these cases, it is a good idea to use a buffer like a FIFO (First In, First Out), which can store messages in memory. Let’s take a look at how a FIFO implemented with a queue looks, and then design a msg_fifo module in SystemVerilog.

Before we begin designing the FIFO, it is important to understand what the overall system looks like for the data feed handler. It includes a parser to decode a message, an order book that performs actions such as bid, sell, and update stocks, and the FIFO that sits in the middle:

Image of FIFO design in SystemVerilog

A typical FIFO implemented with a queue data structure looks like the diagram below. We keep track of the next empty slot to store a message in (write pointer), and the slot which holds the oldest message not yet consumed by the order book (read pointer). Once the write_ptr reaches the end of the queue, we circle it back to the beginning (index 0) if the buffer is not full:

Image of FIFO design in SystemVerilog

Combining the two I/O relational diagram and internal structure of the FIFO, I know that I have to implement the following logic:

  1. Store a message when it has been completely parsed, indicated by a done flag.
  2. Read a message from the FIFO when the order book asserts a read signal only if it is not empty (order book has read as an input for test purposes. In reality, it decides internally when to read a message from the FIFO and is not driven by an external signal).
  3. Sample on clock edge (i.e., synchronous).
  4. Reset clears all messages in memory and sets read_ptr and write_ptr to point to the first index of the FIFO.
  5. Circular queue: after write_ptr reaches index 6, loop back to index 0. Same with read_ptr.

Now I can write up the module header, parameter list, and port list. Notice that I use parameter FIFO_DEPTH = 16 because I want my FIFO to hold 16 messages. Each message is 16 bytes long and of custom type parsed_msg_t:

Copy Code
module msg_fifo #(

    parameter FIFO_DEPTH = 16

)

(

    input logic clk,

    input logic reset,

    input logic write_en, 			// signal to write a message

    input logic read_en, 			// signal to read a message

    input parsed_msg_t msg_in, 		// message to write into FIFO

    output logic full, 			// indicates if FIFO is full

    output logic empty, 			// indicates if FIFO is empty

    output parsed_msg_t msg_out 	// message read from FIFO

);

________________________________________

Followed by internal logic and memory allocation:

Copy Code
parsed_msg_t fifo_mem [0:FIFO_DEPTH-1]; 	// memory to hold the messages in FIFO, using packed struct

  //use 0:FIFO_DEPTH-1 instead of 0:FIFO_DEPTH-1 to avoid issues with indexing in SystemVerilog

logic [3:0] write_ptr; 

logic [3:0] read_ptr;

logic [3:0] count; 				// count of messages in the FIFO.

________________________________________  

Next, we must assign our outputs:

Copy Code
assign full = (count == FIFO_DEPTH); 

assign empty = (count == 0); 

assign msg_out = fifo_mem[read_ptr]; 		// assign message output from FIFO

________________________________________  

We can use a flip-flop to update our pointers, count, and messages to finish up our module:

Copy Code
always_ff @(posedge clk or posedge reset) begin

    if (reset) begin

      write_ptr <= 0;

      read_ptr <= 0;

      count <= 0;

    end 

    

    else begin

      if (write_en && !full) begin

        fifo_mem[write_ptr] <= msg_in; 					// write message to FIFO

        write_ptr <= (write_ptr == FIFO_DEPTH-1) ? 0 : write_ptr + 1; 	// increment write pointer circularly

        count <= count + 1;

      end




      if (read_en && !empty) begin

        read_ptr <= (read_ptr == FIFO_DEPTH-1) ? 0 : read_ptr + 1; 	// increment read pointer circularly

        count <= count - 1; 

      end




    end

  end

________________________________________  

And that’s it! We have designed the complete module ready to be tested. I wrote a simple testbench (with a FIFO_DEPTH of 4 for simplicity) to simulate the FIFO working in Vivado, available on my GitHub repo for the project if you want to take a look. Here is what the simulated waveform looks like:

Image of FIFO design in SystemVerilog

This verifies our functionality because:

  1. Before write_en goes high, no data (X) is reflected in msg_out, and the empty flag is high
  2. Write_en is held high for 3 cycles → first message is immediately ready to be consumed from FIFO
  3. As read_en is held high for three cycles, all three messages in FIFO are read then the empty flag is set high
  4. We try to write 5 messages to an empty FIFO, but the full flag is raised after the 4th message is written, so the 5th data is lost
 ________________________________________  

Memory buffers are powerful tools in Digital Design projects because of their ability to pipeline systems. This reduces bottlenecks, allowing for low latency that is needed in trading and other high-speed applications. Queues are by far the most popular way to implement First In, First Out (FIFO) structures, since the first message to enter is the first message that gets to leave. For my project, I used a circular queue for its simplicity and effectiveness. After designing the module, I wrote a simple yet robust testbench to verify functionality. Using the simulator in Vivado, we saw how the waveform shows expected behavior.

Have questions or comments? Continue the conversation on TechForum, DigiKey's online community and technical resource.