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. Portable Registered-Output FIFO

Part 7 · Memory & Buses · Intermediate

Portable Registered-Output FIFO

A parameterized arbitrary-depth FIFO with explicit pointer wrap, registered read data, and memory-friendly reset behavior.

On this page

    Why this matters

    A registered-output FIFO keeps old bits on dout after a pop, so a consumer that treats dout as a new word every cycle repeats data. Gating the valid with not-empty instead drops the last word.

    After this lesson you can: build a counter-based FIFO with a registered pop result, qualify the result with dout_valid_o, and reserve credit before issuing a pop.

    Architecture

    The baseline uses an occupancy counter and explicit pointer wrap, so depth need not be a power of two. Output data is registered on an accepted pop, matching the synchronous-read behavior expected by many SRAM and FPGA memory blocks.

    Those choices now turn the telemetry contract into RTL: the count generates status, bump_ptr enforces the five-entry address range, and dout_valid_o marks the registered pop result.

    • The memory array is never reset.

    • Read and write pointers advance only on accepted operations.

    • Simultaneous accepted push and pop leave count unchanged.

    • The conservative full policy rejects a push observed while full, even if a pop occurs on that edge.

    • dout_valid_o pulses for one cycle after an accepted pop and qualifies dout_o.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // snippet: complete architecture appears in the verification lesson
    localparam int AW = (DEPTH <= 1) ? 1 : $clog2(DEPTH);
    localparam int CW = $clog2(DEPTH + 1);
    
    function automatic logic [AW-1:0] bump_ptr(input logic [AW-1:0] ptr);
      return (ptr == DEPTH-1) ? '0 : ptr + 1'b1;
    endfunction
    
    assign push_take = push_i && !full_o;
    assign pop_take  = pop_i  && !empty_o;
    assign full_o    = (count == DEPTH);
    assign empty_o   = (count == 0);

    Why registered output needs a valid indication

    After a pop, dout_o contains the removed word. Before the first pop, after reset, and in cycles without a pop, its bits alone carry no transaction meaning. dout_valid_o prevents downstream logic from treating a held old value as a new word.

    Each slot is one clock cycle. The FIFO holds words A and B before slot 0. pop_i stays high for three slots, and the three pop edges are the page's E0, E1 and E2: E0 ends slot 0, E1 ends slot 1 and E2 ends slot 2. The table lists the values after each edge.

    Edge

    Pre-edge words

    Accepted pop

    empty after

    dout_valid / dout after

    E0

    A, B

    yes

    0

    1 / A

    E1

    B

    yes

    1

    1 / B

    E2

    none

    no

    1

    0 / B (bits held)

    • Slot 1: the pop at E0 has landed. dout_valid is 1 and dout shows A. The consumer captures A at E1, using the pre-edge valid and data.

    • Slot 2: the pop at E1 has landed and emptied the FIFO. empty is 1, yet dout_valid is still 1 and dout shows B. Consecutive pops replace the result each cycle, so valid may stay high. pop_i is still high, but E2 rejects it because the FIFO is empty. The consumer captures B at E2.

    • Slot 3: dout_valid drops to 0 while dout holds B. Those held bits mean nothing; only valid marks a new word.

    This is a pop-result pulse interface with no ready input. Reserve capacity before requesting a pop; capture each result even when empty_o is also high. Never gate result-valid with !empty_o: that discards the final word. A stallable consumer needs a result buffer and credits for both buffered words and accepted pops whose results have not yet been captured. Issue a pop only when buffered + pending < result_capacity (or account explicitly for a same-edge delivery). Its valid/ready offer must then remain stable through the accepting edge.

    The credit check drives pop into the FIFO. The FIFO's one-cycle result goes into a result buffer, which offers valid and ready to the consumer. The buffer reports words held and the FIFO reports pops in flight back to the credit check.
    A stallable consumer behind a pop-result FIFO: a result buffer catches every pulse, and a credit check issues a pop only when the buffer has room for it

    Read the picture from the top: a pop is issued only when the buffer can still take its result. The dashed arrows are the two counts the check adds up.

    • The FIFO cannot wait. Its result arrives one cycle after the pop, with no ready input, so something must catch it.

    • The buffer can wait. It holds the word and offers it to the consumer with valid and ready for as long as the consumer stalls.

    • The credit check counts both. Words already in the buffer and pops whose results have not yet landed both use buffer space.

    Inference boundary

    A generic RTL FIFO cannot guarantee that every ASIC or FPGA tool maps the same array to the desired memory macro. The coding template expresses one read and one write per clock, but the target library's read-during-write mode and port shape still need a synthesis report check.

    Warning

    At full, write and read pointers address the same location. Allowing full-boundary replacement creates a same-address read/write on one edge. The baseline rejects that push so it does not depend on a memory macro's collision mode.

    Check yourself Why does the pointer increment compare against DEPTH-1 instead of relying on bit overflow?

    It makes arbitrary depths safe. A 10-entry FIFO wraps 9 to 0 rather than walking through invalid addresses 10 through 15.

    Check yourself Why qualify registered dout_o with dout_valid_o?

    Because dout_o holds its previous bits when no pop occurs. Valid marks the cycle containing a newly removed FIFO word.

    Remember

    • dout_valid_o marks the cycle of a newly removed word. The held bits afterwards mean nothing.

    • Never gate result-valid with !empty_o, because the final word is lost.

    • The memory array is never reset. Reset the pointers and the count, and empty hides the old contents.

    Rule of thumb: treat the pop as a result pulse: reserve capacity before you ask, and capture every result.

    Keep going

    • Practice: Synchronous FIFO RTL design, blocking on full and empty

    • Code lab: Synchronous FIFO, 16 entries

    • Interview: run a mock interview on this topic

    Related topics

    • Simultaneous push and pop

    • Synchronous RAM inference

    Previous

    218 / 350

    Next: Simultaneous Push and Pop

    Up next

    Up nextSimultaneous Push and Pop

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Contract and Invariants
    • Standard vs Fall-Through Output
    • Almost Flags and Parameter Edges
    clkpop_iemptydout_validdout0ab
    A registered-output FIFO holding words A and B (hex a and b) is popped on three consecutive edges: valid pulses with each accepted pop and the data bus holds B after the last word