After reset, read and write pointers are equal because no data is stored. After exactly DEPTH accepted pushes with no pops, their address portions are equal again because the write pointer has wrapped. Bare pointer equality therefore describes both empty and full.
Pointers are four bits wide here: one wrap bit, then three address bits. rd marks the oldest stored word and wr marks the next slot to write. Shaded slots hold data.
Empty: both pointers are 0_011. Every bit matches, so nothing is stored.
Part full: wr has moved from slot 3 to slot 6 with no wrap, so the three shaded slots 3, 4 and 5 hold data.
Full: wr has gone once around the ring and reached slot 3 again. The address bits match rd, but the wrap bit has flipped to 1, so all eight slots are stored.
Representation | Empty | Full | Strength | Constraint |
|---|---|---|---|---|
Occupancy counter |
|
| Direct level and almost flags | Counter width must represent |
Pointers plus wrap bit | Full pointers equal | Addresses equal, wrap bits differ | No separate occupancy counter | Cleanest for power-of-two depth |
Leave one slot unused | Pointers equal | Next write pointer equals read pointer | Simple pointer logic | Usable capacity is |
A counter is the clearest baseline and naturally supports arbitrary depth. It changes by +1 for push-only, -1 for pop-only, and 0 when both or neither are accepted.
Depth five makes the representation choice visible: the telemetry FIFO uses a count that reaches five and pointers that explicitly wrap from address four to zero.
// snippet: occupancy next-state rule
unique case ({push_take, pop_take})
2'b10: count <= count + 1'b1;
2'b01: count <= count - 1'b1;
default: count <= count;
endcase
assign empty_o = (count == 0);
assign full_o = (count == DEPTH);For a power-of-two depth, give each pointer one bit beyond the memory address. The low bits index memory; the high bit toggles on wrap.
// snippet: power-of-two pointer flags (DEPTH must be at least 2)
if (DEPTH < 2 || (DEPTH & (DEPTH - 1)) != 0) begin : g_bad_depth
$error("pointer flags require a power-of-two DEPTH >= 2");
end
localparam int AW = (DEPTH > 1) ? $clog2(DEPTH) : 1;
logic [AW:0] wr_ptr, rd_ptr;
assign empty_o = (wr_ptr == rd_ptr);
assign full_o = (wr_ptr[AW] != rd_ptr[AW]) &&
(wr_ptr[AW-1:0] == rd_ptr[AW-1:0]);This pointer template requires power-of-two depth at least two. A depth-one queue needs separate valid/phase logic; forcing its address width to one would incorrectly model two addresses. For non-power-of-two depth, explicit modulo wrapping is required; blindly truncating the pointer accesses nonexistent entries.
The simulator starts on an 8-entry FIFO with a 16-word burst written once per cycle and read once every two cycles. The writer obeys full, and full and empty are decoded from the registered state exactly as above, so a write is refused whenever the FIFO is full at the start of the cycle.
Loading fifo simulator…
Step 15 times: wr is 1_111 and rd is 0_111. The addresses match and the wrap bits differ, so full is 1 with 8 words stored.
Step once more: word 16 stalls although word 8 is read in the same cycle, because push_take = push_i && !full_o. The peak is 8, yet the smallest depth that never stalls is 9.
Set the depth to 9: no stall cycles. Nine is not a power of two, so the address wraps at 8 and the flags come from the count. Then set the writer to ignore full at depth 8: 1 of the 16 words is lost instead.
The wrap/phase bits. Equal complete pointers mean empty; equal address bits with different wrap bits mean full.
Count gives exact occupancy, makes almost flags direct, and supports arbitrary depth cleanly. It costs an adder and another state register, so the choice depends on timing and required status.