HDLbits
Start Practicing

RTL Design Patterns/FIFOs

Circular Buffer (Overwrite-on-Full)

medium
fifocircular-bufferoverwritering-buffer

Why this is different from a FIFO (and why that matters)

A FIFO’s contract is “never lose data — if the consumer can’t keep up, make the producer wait.” That’s right for a bus interface or a DMA queue. But plenty of real hardware wants the opposite contract: a debug trace buffer that always holds the most recent N cycles of history, a sensor sample window for a moving-average filter, or a “last N events” log for postmortem debugging. In all of these, losing the oldest data when the buffer is full is correct behavior, and blocking the producer would be actively wrong (you’d stall the pipeline just to protect data nobody needs anymore).

That’s the whole design delta from the Synchronous FIFO problem: wr_en is always accepted. When the buffer is already full, a write overwrites the oldest entry and the read pointer silently advances along with it, keeping the window exactly DEPTH entries wide.

Interface

Signal Direction Width Description
clk input 1 Clock.
rst input 1 Synchronous, active-high reset.
wr_en input 1 Write wr_data this cycle — always succeeds.
wr_data input WIDTH Data to push.
rd_en input 1 Pop the oldest entry this cycle (ignored while empty).
rd_data output WIDTH Current oldest entry (fall-through).
full output 1 High when the window already holds DEPTH entries.
empty output 1 High when there is nothing to read.
overwrite output 1 Pulses high the cycle a write evicts the oldest entry.

Cycle-by-cycle example (DEPTH = 4)

Cycle wr_en wr_data Buffer contents (oldest → newest) overwrite
0 1 A A 0
1 1 B A, B 0
2 1 C A, B, C 0
3 1 D A, B, C, D (now full) 0
4 1 E B, C, D, E (A dropped) 1
5 1 F C, D, E, F (B dropped) 1

Correctness constraints

  • wr_en must never be blocked or gated by full — a write always writes.
  • When a write happens while full, the oldest entry is dropped and overwrite pulses; the buffer’s occupancy stays at exactly DEPTH.
  • A read while empty must have no effect (no pointer movement, no false data).
  • full and empty must never be asserted simultaneously.
  • If a consumer read and an evicting write land in the same cycle, the read must observe the value being evicted (it was still the oldest live entry going into that cycle), and the read pointer must advance by exactly one — not two — for that cycle.