FIFOs

MIT Fall 2025

The questions below are due on Wednesday October 15, 2025; 11:59:00 PM.
 
You are not logged in.

Please Log In for full access to the web site.
Note that this link will take you to an external site (https://shimmer.mit.edu) to authenticate, and then you will be redirected back to this page.

Another very common circuit in digital design is the First-In-First-Out buffer aka "FIFO". FIFOs take in values on one side in order and hand them out on the other side. Upon first hearing this, one might think that a FIFO is a shift buffer like we used in earlier labs. For example something like this is a shift-buffer being used as an input/output buffer:

logic [7:0] shift_buffer;
always_ff@(posedge clk)begin
    shift_buffer <= {shift_buffer[6:0], din};
    dout <= shift_buffer[7];
end

Here din is inserted into the shift buffer and then its value will appear on the output dout nine clock cycles later.

Shifty Shift Buffer

This sort of seems like what a FIFO should do, but there are a few notable exceptions:

  • Shift buffers have a strict and synchronized one-in-one-out policy whereas FIFOs don't.
  • Shift buffers have a fixed latency from input to output whereas FIFOs don't.

The fact that the input and output of a FIFO are much more (though not completely) decoupled from one another necessitates more complicated logic. As discussed in lecture, FIFOs are built around a common shared memory with two pointers: the read pointer and the write pointer. The write pointer is used to insert new values into the memory, while the read pointer is used to read out values sitting in memory. The write pointer is incremented when the "write" command is asserted (and after data has been written to where it had been pointing). The read pointer is incremented when the "read" command is asserted.

The underlying memory of FIFOs is almost always clean powers of two in depth to take advantage of natural rollover in the read and write pointers (for example if a FIFO uses an 8-deep memory, having a pointer with a size of 3 bits will naturally "roll over" from index 7 to 0).

At reset, both pointers will be set at 0, and then as writes come in, information is written to address 0, and the write pointer moves upwards in address space. This continues further up into address space whenever future write commands are provided. When the write pointer eventually reaches the top, it will simply wrap back around to the beginning of the memory. The read pointer does the same thing. The two pointers move mostly independently in a game of cat-and-mouse.

There are some caveats in the read and write pointer locations and how they evolve, however! Both pointers will start at 0 at reset. From that point on, the write pointer will always be at or ahead of the read pointer, but it can never "lap" (meaning pass by) the read pointer. If it is about to lap the read pointer, the FIFO is full since there's no where "fresh" to write to...instead the system will have to wait for some read commands to arrive to free up some memory space.

In a complementary fashion, the read pointer will always be at or behind the write pointer.

The relation of the two pointers in memory will generally dictate two signals which can be used to determine the state of the FIFO:

  • The empty command is true when the read pointer equals the write pointer meaning
  • The full command is true when the write pointer is one less than read pointer (modulo the size of the array)

The Command FIFO

In lab this week, we'll run into a interesting, though frustrating, situation due to the variable latency inherent in SDRAM. We will submit commands (write or read requests) to the memory controller and it will take a indeterminant amount of time to accomplish them, the completion of which, it conveys using a simple acknowledgement ("ACK") signal. If we could simply send a request and then wait until the memory controller and SDRAM complete the task, our job would be easy since we'd know what request we sent in (read or write). However in order to use the SDRAM at closer to its maximum efficiency, the controller supports multiple "in-flight" requests at once (meaning we could submit a read request for address A1, then a write request to address A5, then a read request to address A2, then another read request to address A9 before we ever get the response back to the first read request related to A1). While the controller will always carry out the various requests and return/acknowledge in the order that they were sent, it is somewhat unpredictabl when they'll come back because the SDRAM might be refreshing or doing other maintenance tasks.

This delay in response isn't a huge problem for write commands (most of the time), but for read commands, where we're expecting to get information out of memory, not knowing when we get our result back is potentially disastrous. Having an on-demand history of what commands were sent into the memory controller (and marking them down as done/finished) so that we can react to the read responses to memory is therefore critical.

The in-order and variable latency nature of this problem means it is a great task to be solved by a FIFO.

So write (and testbench) a parameterized FIFO that will keep track of the in-flight commands.

  • Parameters:
    • DEPTH: The depth (in words) of the FIFO (should always be a clean power of two).
    • WIDTH: The width (in bits) of the FIFO entries.
  • Inputs:
    • clk: The clock of the system
    • rst: The active-high synchronous reset of the system.
    • command_in: The WIDTH-wide input to the FIFO
    • write: The write command for the FIFO
    • read: The read command for the FIFO
  • Outputs:
    • command_out: The WIDTH-wide output of the FIFO. Note this should be an asyncrhonous memory read from the internal FIFO memory structure! For purposes of latency, we do not want this to be a synchronous read. This will result in not using BRAM, but since the ultimate size of this will be rather small (using only several dozen distributed RAMs as discussed in lecture 8), this is fine.
    • empty: The combinationally-specified empty signal.
    • full: The combinationally-specified full signal.

The reason we want our FIFO's command_out signal to be generated asynchronously is because of how it will get used with the DRAM. Instead of waiting for a read command to trigger the outputting of the next available entry in the memory buffer, we instead want to automatically display the contents quickly. This will allow an entity like the traffic_generator module or the memory controller to be able to use the empty signal as a data qualifier about what is current in command_out.

`timescale 1ns / 1ps
`default_nettype none

module command_fifo #(parameter DEPTH=16, parameter WIDTH=16)(
        input wire clk,
        input wire rst,
        input wire write,
        input wire [WIDTH-1:0] command_in,
        output logic full,

        output logic [WIDTH-1:0] command_out,
        input wire read,
        output logic empty
    );

    logic [?:0]   write_pointer;
    logic [?:0]   read_pointer;
    logic [?:0] fifo [?:0]; //when read asynchronously/combinationally, will result in distributed RAM usage

    //your design here.

endmodule
`default_nettype wire

A few basic, checkers are provided below, however you should write a testbench for the command_fifo that demonstrates the following behaviors:

  • Successful writing in and reading out (of correct values) from the FIFO over multiple clock cycles.
  • Successful wrapping around of both the read and write pointers.
  • Demonstration of filling up of the FIFO and the subsequent assertion by the DUT of the full flag, followed by deassertion when read commands continue.
  • Demonstration of the empty flag going from 0 to 1 when the FIFO is read more than it was written to over a certain stretch.

Your testbench needs to demonstrate these behaviors clearly in order to get the checkoff for this page.

Code Skeleton
  No file selected

Testing with different parameters:

Code Skeleton
  No file selected

Checkoff 1:
Show a staff member your working command_fifo testbench. Demonstrate all of the behaviors listed earlier on the page. If you worked on this outside of lab, feel free to start working on the next page until you have the chance to come in and get your checkoff.

Upload your testbench for the command_fifo for staff review.
 No file selected

With your command_fifo written and tested, you're ready to move on and start harnessing bigger and better memory systems!