HDLbits
Start Practicing

RTL Design Patterns/FIFOs

Synchronous FIFO

medium
fifosynchronousqueuepointer-arithmetic

Why this shows up in real designs

Almost every block boundary in a chip needs a little elasticity: a DMA engine bursts 64 words into a bus interface that can only accept one word every few cycles, or a decoder produces symbols in irregular bursts that a downstream consumer drains at its own pace. When producer and consumer share the same clock, a synchronous FIFO is the standard way to absorb that mismatch without ever losing or corrupting data.

This is the single most common building block in RTL design, and its “extra pointer bit” trick for computing full/empty is something you should be able to derive from memory in an interview.

Interface

Signal Direction Width Description
clk input 1 Single clock shared by producer and consumer.
rst input 1 Synchronous, active-high reset.
wr_en input 1 Assert to write wr_data this cycle.
wr_data input WIDTH Data to push.
rd_en input 1 Assert to pop the front entry this cycle.
rd_data output WIDTH Current front-of-queue entry (fall-through).
full output 1 High when the FIFO cannot accept another write.
empty output 1 High when there is nothing left to read.

DEPTH must be a power of two so a simple binary pointer wraps correctly.

Read semantics: fall-through (FWFT)

rd_data is combinational: it always shows the current head-of-queue value whenever empty is low, with no extra latency. Asserting rd_en for one cycle “pops” that entry — on the next clock edge, rd_data advances to the next entry. This is sometimes called first-word-fall-through (FWFT), as opposed to a design where rd_data is registered and lags rd_en by one cycle.

Cycle-by-cycle example (DEPTH = 4)

Cycle wr_en wr_data rd_en rd_data (this cycle) full empty
0 1 A1 0 — 0 1
1 1 A2 0 A1 0 0
2 1 A3 1 A1 0 0
3 1 A4 0 A2 1 0
4 1 (dropped) A5 1 A2 1 0
5 0 — 1 A3 0 0

At cycle 4 the FIFO is already full, so the write is silently ignored — A5 never enters the queue.

Correctness constraints

  • full and empty must never be asserted at the same time (except transiently at DEPTH == 0, which is not a legal configuration).
  • A write attempted while full must be dropped without corrupting the queue or advancing wr_ptr.
  • A read attempted while empty must not advance rd_ptr or produce a spurious element.
  • Order must be strictly FIFO: the first word written is the first word read.
  • The design must work for any power-of-two DEPTH, using only $clog2(DEPTH)+1-bit pointers — no separate occupancy counter is required (though one is a valid alternative implementation).