Sign in on the Premium hub to save lesson progress. This lesson is open to read so the page is a complete article, not an empty login wall.
Lesson 2.1

Ring buffer, extra MSB, full vs empty

When write and read pointers are equal, the FIFO is either empty or full. That ambiguity is the entire pointer design problem. Solve it on one clock first.

1

Circular memory

Address = low ADDR_W bits of the pointer. After the last location, the pointer wraps. Occupancy is the number of unread beats, not “how far the pointer is from 0”.

0 1 rd 2 3 4 wr 5 6 DEPTH = 8, occupied = locations 1,2,3 (rd=1, wr=4) Next write goes to 4. Next read comes from 1. If wr == rd at address 1: either 0 entries or 8 entries — need extra bit.

Occupied slots sit strictly between rd and wr in ring order.

2

Three correct full/empty methods

MethodEmptyFullNotes
Count registercount == 0count == DEPTHIncrement on write-only, decrement on read-only, hold on both. Width = $clog2(DEPTH)+1.
Extra MSB (n+1 bit pointers)wptr == rptrwptr == {~rptr[MSB], rptr[MSB-1:0]}Address bits equal, wrap bits differ → full. Industry default for async FIFOs too.
Leave one slot emptywptr == rptrnext write would equal rptrUsable depth = DEPTH−1. Simple, wastes one entry.
Wrong

empty = (wptr == rptr); full = (wptr == rptr); is always a bug. Equal pointers cannot mean both.

3

Extra-MSB worked example (DEPTH = 4)

ADDR_W = 2. Pointers are 3 bits. Address = ptr[1:0]. Wrap bit = ptr[2].

Eventwptrrptraddr equal?MSB differ?Flag
Reset000000yesnoEMPTY
4 writes100000yes (addr 00)yesFULL
1 read100001no—neither
4 more writes (wrap)000 after 8 writes total vs 4 reads…track wrap bit

After four writes from reset: wptr=100, rptr=000. Addresses match (00), MSBs differ → full, occupancy = 4. After four reads: both 100 → equal including MSB → empty.

pointer_flags.v
// DEPTH power of two. PTR_W = ADDR_W + 1
wire empty = (wptr == rptr);
wire full  = (wptr == {~rptr[PTR_W-1], rptr[PTR_W-2:0]});
4

Simultaneous read+write

Count method: count holds. Pointer method: both pointers increment; difference stays the same. If empty, a same-cycle write+read is a design choice (FWFT vs standard): this course uses standard — you cannot read while empty, so rd_fire is 0 even if wr_en=1 on that cycle.

5

Standard vs first-word fall-through

Standard (this course): rdata is a flop loaded on rd_fire. After reset, empty=1 and rdata is don’t-care until the first successful read. Downstream must pulse rd_en to obtain each beat, then wait a cycle.

FWFT / show-ahead: the RAM read port is combinational or a lookahead pointer, so rdata presents the next byte whenever the FIFO is not empty. empty (often named !data_valid) falls as soon as the first write is visible. FWFT is nicer for ready/valid but creates a combinational path from write to read data in a sync FIFO, and in an async FIFO it needs a small output register stage in the read domain. Do not mix the two contracts in one testbench.

6

Count-register method — what goes wrong

A saturating counter of occupancy looks easy: +1 on write-only, −1 on read-only, hold on both. Width must be $clog2(DEPTH)+1 so DEPTH itself is representable. Bugs: updating the count from ungated wr_en; using a WIDTH that wraps at DEPTH (so full looks like empty); computing full/empty from the count in the same cycle as the pointer update without a consistent blocking assignment order. Extra-MSB pointers avoid a third sequential element that can disagree with the RAM addresses.

Async FIFOs almost never share a count register across clocks — that would be another multi-bit CDC. Occupancy in one domain is reconstructed from local binary minus converted synced Gray.

Q

Frequently asked questions

Why are equal pointers ambiguous?

With only ADDR_W bits, write and read addresses match both when the FIFO is empty and when it has wrapped and is full. You add a wrap/MSB bit or a separate occupancy counter to distinguish those two states.

What is the extra-MSB full formula?

empty when the entire (ADDR_W+1)-bit pointers are equal. full when the address bits match and the wrap bits differ: wptr == {~rptr[MSB], rptr[MSB-1:0]}.

Can you read and write in the same cycle?

Yes on a sync FIFO when it is neither empty nor full: occupancy holds. This course’s standard FIFO still forbids a read while empty, even if a write is happening on that cycle.

Why not leave one slot empty?

That method is simple (full when the next write address would equal the read address) but wastes one location. Extra-MSB uses the whole DEPTH and is what Gray async FIFOs generalize.

Key takeaways

  • Equal n-bit addresses are ambiguous. Add a wrap bit or a count.
  • Full: addresses equal, wrap bits differ. Empty: entire pointers equal.
  • Always gate: wr_fire = wr_en && !full, rd_fire = rd_en && !empty.