A FIFO is not a memory trick. It is a rate-matching and backpressure block: the producer and consumer no longer have to handshake on every beat in lockstep. Start here before Gray code or dual clocks — those only make sense after you know what problem the FIFO is solving.
FIFO = First-In, First-Out queue. Writes push at the tail. Reads pop from the head. Order is preserved. Overflow is writing when full. Underflow is reading when empty.
Two blocks want to exchange data. If they share a wire and a ready/valid handshake on the same clock, they still stall each other every time the consumer cannot accept a beat. If they run at different average rates, or in bursts, a single-register handshake is not enough.
A camera dumps 1920 pixels in a line, then goes idle during blanking. The downstream DMA wants a steady stream. A FIFO absorbs the burst.
full tells the producer to stop. empty tells the consumer there is nothing to pop. Those flags are the entire contract.
UART RX at 115200 baud vs a 200 MHz core. You cannot share a pointer register between those clocks. That is the async FIFO — later in this course.
A warehouse between a truck (bursty writer) and a retail counter (steady reader). If the warehouse is full, the truck waits. If it is empty, the counter waits. The warehouse depth is sized from the burst, not from the average rate alone.
A skid buffer (1–2 deep registered handshake) covers one cycle of downstream stall. Use a FIFO when occupancy can grow beyond one or two beats, or when clocks differ.
| Block | Depth | Clocks | Use when |
|---|---|---|---|
| Registered ready/valid | 0–1 | Same | Combinational ready cut; no burst |
| Skid buffer | 1–2 | Same | One-cycle stall isolation |
| Sync FIFO | N (power of 2 typical) | Same | Bursts, rate matching, elastic buffering |
| Async FIFO | N, power of 2 required for Gray pointers | Different | CDC of a data stream |
If the writer can produce faster than the reader for a finite burst:
depth ≥ burst_beats + ceiling((W − R) × T_burst / beat_time) + margin
Where W and R are write and read rates during the burst window. If long-term W > R, no finite FIFO works — you must throttle the writer or speed up the reader. A FIFO never creates bandwidth; it only stores it.
Sizing from “it felt full in sim” is not a method. Interviews want: identify the worst-case burst, compute occupancy, add CDC pointer-sync latency (async case), then round up to the next power of two if you use Gray pointers.
Writes raise occupancy. When full is 1, further wr_en must not commit (wr_fire = wr_en && !full). Reads lower occupancy until empty.
Open the free simulator and type a 4-deep register file plus two pointers — that is already a FIFO. We write the real one in lesson 2.2. Launch Verilog simulator →
On AXI-Stream style interfaces, the producer’s tready is usually !full (sometimes registered). The consumer’s tvalid is !empty for a standard FIFO, or a peek of the head for FWFT. Never drive tready combinationally from a path that also depends on tvalid in a loop — that is how you get combinational deadlock. A registered full flag plus a skid in front of the FIFO is a common fix.
If the producer ignores tready and keeps pulsing tvalid, that is overflow at the protocol layer even if your FIFO RTL gated the write. The contract is end-to-end: the writer FSM must stall.
Suppose a writer emits 200 MB/s for 40 µs (one video line) and a reader can only take 80 MB/s continuously. During the line you accumulate (200−80)×40e-6 = 4.8 kB. After the line, blanking lets the reader catch up. The FIFO must hold at least 4.8 kB plus CDC margin. If the next line starts before the FIFO drains, occupancy ratchets up until you overflow — that is a system-rate bug, not a FIFO-depth typo you can hide forever.
Interviews like the phrase: the FIFO stores energy in the rate mismatch; it does not generate energy. Clock-crossing adds extra apparent occupancy because full/empty lag by a few destination clocks.
UART at 115200 baud, 8N1, is one byte every 86.8 µs. A 100 MHz core that reads one byte per cycle could empty a 16-deep FIFO in 160 ns. Depth is set by software interrupt latency, not by the UART bit rate: if firmware can take 200 µs to service RX, you need about 200e-6 / 86.8e-6 ≈ 3 bytes plus margin — 8 or 16 is typical. The async FIFO exists because the UART APB clock is not the CPU clock; Gray pointers carry occupancy across that boundary.
Contrast a 10 GbE MAC writing 64-byte packets every 67 ns into a 200 MHz DMA that sometimes stalls for 2 µs on a DRAM bubble: occupancy can jump by dozens of beats. That is why NIC designs use deep async FIFOs or store-and-forward packet memories, not a 4-entry toy queue.
A FIFO is an elastic First-In First-Out queue that absorbs bursts and applies backpressure with full and empty flags. It matches two rates over a finite window. It does not create extra bandwidth if the writer is permanently faster than the reader.
Use a 1–2 deep skid buffer when you only need to cut a combinational ready path or survive a single-cycle stall. Use a FIFO when occupancy can grow over many beats, or when the producer and consumer use different clocks.
Identify the worst-case burst length, subtract what the reader can drain during that window, add margin for pointer-synchronizer delay on an async FIFO, then round up to a power of two if you use Gray pointers. If average write rate exceeds average read rate, no finite depth works.
No. Full means the producer must stall (wr_fire stays 0). Overflow means a write still committed while occupancy was already DEPTH, destroying unread data. A correct RTL never overflows.
full / empty are the contract. Overflow and underflow are bugs, not “retry later”.