Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • RTL Foundations36
  • Combinational Design25
  • Sequential Design27
  • FSM & Control27
  • Timing & Clocking39
  • Reset & CDC34
  • Memory & Buses57
    • SRAM and ROM
    • Register Files
    • Valid/Ready Handshake
    • AXI-Lite
    • AXI-Stream
    • Bus Integration
    • Synchronous FIFO
      • Contract and Invariants
      • Pointers, Occupancy and Flags
      • Portable Registered-Output FIFO
      • Simultaneous Push and Pop
      • Standard vs Fall-Through Output
      • Almost Flags and Parameter Edges
      • Verification and Signoff
    • Register-Map and CSR RTL
    • Memory Reliability
    • Peripheral RTL Integration
    • Full AXI4: RTL Roadmap
  • Advanced RTL28
  • Senior & Interview76
  • Glossary and Notation
Loading lesson

‹Synchronous FIFO
  1. Topics
  2. RTL Design
  3. Memory & Buses
  4. Synchronous FIFO
  5. Pointers, Occupancy and Flags

Part 7 · Memory & Buses · Intermediate

Pointers, Occupancy and Flags

Count-based and pointer-based FIFO state, including the extra-wrap-bit rule that distinguishes full from empty.

On this page

    Why this matters

    Read and write pointers are equal both when the FIFO is empty and when it is full, so a design that compares them alone cannot tell the two apart. At depth 10, a 4-bit pointer also walks into addresses 10 to 15 that do not exist.

    After this lesson you can: separate empty from full with a wrap bit or a counter, and wrap a non-power-of-two depth explicitly.

    Why pointer equality is ambiguous

    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.

    An eight-slot FIFO ring in three states: empty, three words stored, and full. The address bits of rd and wr are equal in the empty and full pictures; only the wrap bit tells them apart

    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

    count == 0

    count == DEPTH

    Direct level and almost flags

    Counter width must represent DEPTH

    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 DEPTH - 1

    Counter-based state

    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.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 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);

    Pointer-based state

    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.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 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.

    Common pitfall

    localparam AW = $clog2(DEPTH) does not make every depth safe. At DEPTH = 10, a 4-bit pointer naturally visits 10 through 15 unless the RTL explicitly wraps at 9.

    Try it: fill and drain a FIFO

    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.

    FIFO simulator

    Loading fifo simulator…

    Shaded slots hold data, numbered by word. rd points at the oldest word and wr at the next slot to write; each pointer is a wrap bit and three address bits. The lower chart is the occupancy in every cycle.
    • 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.

    Check yourself In an 8-entry power-of-two FIFO, read address and write address are both 3. What additional state distinguishes empty from full?

    The wrap/phase bits. Equal complete pointers mean empty; equal address bits with different wrap bits mean full.

    Check yourself Why keep a count when pointers can generate full and empty?

    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.

    Remember

    • An extra wrap bit makes equal pointers mean empty, and equal address bits with different wrap bits mean full.

    • A counter gives exact occupancy and any depth, at the cost of an adder and a register.

    • For non-power-of-two depth, wrap explicitly at DEPTH-1. Truncating the pointer reaches nonexistent entries.

    Keep going

    • Practice: Synchronous FIFO with full and empty flags

    • Code lab: Synchronous FIFO, 8 entries

    • Interview: run a mock interview on this topic

    Related topics

    • Portable registered-output FIFO

    Previous

    217 / 350

    Next: Portable Registered-Output FIFO

    Up next

    Up nextPortable Registered-Output FIFO

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Simultaneous Push and Pop
    • Standard vs Fall-Through Output
    • Almost Flags and Parameter Edges