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. Verification and Signoff

Part 7 · Memory & Buses · Intermediate

Verification and Signoff

A complete arbitrary-depth FIFO, reference scoreboard, assertions, parameter matrix, and implementation handoff checklist.

On this page

    Why this matters

    A FIFO that passes a short directed test can still lose a word after the pointers wrap, or at a depth that is not a power of two. Signoff needs an independent reference queue, boundary assertions and a parameter matrix.

    After this lesson you can: build a self-checking FIFO test with a reference queue, and list the parameter sets and evidence needed for signoff.

    Complete baseline and self-checking test

    The design below implements the conservative registered-output contract. The parameterized finite harness defaults to depth 5, runs directed boundaries and reset epochs, forces pointer wrap, and compares 3000 pseudo-random cycles against an independent reference queue.

    Stimulus feeds the DUT and a reference queue. The DUT's popped word and the reference queue's expected word meet in an in-order compare that ends in PASS or a fatal error. Assertions on pointers and flags attach to the DUT.
    The verification harness: one stimulus drives the FIFO and an independent reference queue, every popped word is compared in order, and assertions watch the internal rules

    Read the picture from the top: the same accepted operations reach both the DUT and the reference, so any difference shows up at the compare.

    • The reference is independent. It is a plain queue, not a copy of the RTL, so a shared bug cannot hide.

    • The compare checks order. Each result is matched against the next expected word, which catches loss, duplication and reordering.

    • Assertions see inside. They check rules the outputs alone cannot show, such as a pointer holding when its request is refused.

    sync_fifo.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    module sync_fifo #(
      parameter int DW = 8,
      parameter int DEPTH = 16,
      parameter int AF_MARGIN = 2,
      parameter int AE_MARGIN = 1
    ) (
      input  logic          clk,
      input  logic          rst_n,
      input  logic          push_i,
      input  logic          pop_i,
      input  logic [DW-1:0] din_i,
      output logic [DW-1:0] dout_o,
      output logic          dout_valid_o,
      output logic          full_o,
      output logic          empty_o,
      output logic          almost_full_o,
      output logic          almost_empty_o,
      output logic [$clog2(DEPTH+1)-1:0] level_o
    );
      localparam int AW = (DEPTH <= 1) ? 1 : $clog2(DEPTH);
      localparam int CW = $clog2(DEPTH + 1);
    
      logic [DW-1:0] mem [0:DEPTH-1];
      logic [AW-1:0] wr_ptr, rd_ptr;
      logic [CW-1:0] count;
      logic push_take, pop_take;
    
      // Elaboration-time guards: an illegal configuration fails to build.
      if (DEPTH < 2) begin : g_bad_depth
        $error("DEPTH must be at least 2");
      end
      if (DW < 1) begin : g_bad_dw
        $error("DW must be positive");
      end
      if (AF_MARGIN < 1 || AF_MARGIN > DEPTH) begin : g_bad_af
        $error("AF_MARGIN must be in 1..DEPTH");
      end
      if (AE_MARGIN < 0 || AE_MARGIN >= DEPTH) begin : g_bad_ae
        $error("AE_MARGIN must be in 0..DEPTH-1");
      end
    
      function automatic logic [AW-1:0] bump_ptr(input logic [AW-1:0] ptr);
        if (ptr == DEPTH-1) return '0;
        return ptr + 1'b1;
      endfunction
    
      assign full_o         = (count == DEPTH);
      assign empty_o        = (count == 0);
      assign almost_full_o  = (count >= DEPTH - AF_MARGIN);
      assign almost_empty_o = (count <= AE_MARGIN);
      assign level_o        = count;
      assign push_take      = push_i && !full_o;
      assign pop_take       = pop_i && !empty_o;
    
      always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
          wr_ptr       <= '0;
          rd_ptr       <= '0;
          count        <= '0;
          dout_o       <= '0;
          dout_valid_o <= 1'b0;
        end else begin
          dout_valid_o <= pop_take;
          if (push_take) begin
            mem[wr_ptr] <= din_i;
            wr_ptr      <= bump_ptr(wr_ptr);
          end
          if (pop_take) begin
            dout_o <= mem[rd_ptr];
            rd_ptr <= bump_ptr(rd_ptr);
          end
          unique case ({push_take, pop_take})
            2'b10: count <= count + 1'b1;
            2'b01: count <= count - 1'b1;
            default: count <= count;
          endcase
        end
      end
    
    endmodule
    

    The course simulator does not run SVA, so the properties are a snippet. The testbench above checks the same rules procedurally.

    Reset deliberately cancels queued words and any uncaptured pop result; the reference counters restart at zero for the new epoch. The two midstream cases check pointers, flags and result-valid directly during reset, then compare fresh data and reject a stale empty pop. A system wrapper must account for cancelled buffered/pending results across its coordinated reset boundary; epoch-local FIFO occupancy alone is not end-to-end delivery conservation.

    sync_fifo_props.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // snippet: the same rules as SVA properties
    module sync_fifo_props #(parameter int DEPTH = 5) (
      input logic clk, rst_n, push_i, pop_i, push_take, pop_take,
      input logic full_o, empty_o, dout_valid_o,
      input logic [$clog2(DEPTH)-1:0] wr_ptr, rd_ptr,
      input logic [$clog2(DEPTH+1)-1:0] count
    );
      assert property (@(posedge clk) disable iff (!rst_n) count <= DEPTH);
      assert property (@(posedge clk) disable iff (!rst_n) full_o |-> !empty_o);
      assert property (@(posedge clk) disable iff (!rst_n)
        push_i && full_o |=> $stable(wr_ptr));
      assert property (@(posedge clk) disable iff (!rst_n)
        pop_i && empty_o |=> $stable(rd_ptr));
      assert property (@(posedge clk) disable iff (!rst_n)
        !push_take |=> $stable(wr_ptr));
      assert property (@(posedge clk) disable iff (!rst_n)
        !pop_take |=> $stable(rd_ptr));
      assert property (@(posedge clk) disable iff (!rst_n)
        push_take && !pop_take |=> count == $past(count) + 1'b1);
      assert property (@(posedge clk) disable iff (!rst_n)
        pop_take && !push_take |=> count == $past(count) - 1'b1);
      assert property (@(posedge clk) disable iff (!rst_n)
        pop_take |=> dout_valid_o);
    endmodule

    Coverage matrix

    Run the same top with parameter tuples (DW, DEPTH, AF_MARGIN, AE_MARGIN): (8,5,2,1) retains telemetry; (1,2,1,0), (1,3,3,2), (8,16,1,0), (8,16,16,15) cover widths, wrap and threshold limits. Override the top parameters in the simulator rather than editing the reference. Expected-failure guard configurations, run separately, are (8,1,1,0), (0,5,2,1), (8,5,0,1), (8,5,6,1), (8,5,2,-1), (8,5,2,5); each must fail elaboration through the generate-if guards. Random occupancy coverage is checked per configuration and may require additional directed traffic if the finite seed misses a level.

    Scenario

    Required evidence

    Reset

    Empty asserted, full deasserted, level zero, pointers zero, no valid output

    Fill and drain

    Every level from 0 through DEPTH, in-order data

    Overflow and underflow requests

    No pointer, level, or output transaction change

    Simultaneous operations

    Partial occupancy holds level; full and empty follow the documented policy

    Pointer wrap

    Several wraps with no loss, duplication, or invalid address

    Parameter matrix

    Depth 2, 3, 5, 16; width 1 and a normal data width; threshold limits

    Implementation

    Expected memory inferred; no reset on array; timing paths and read latency reviewed

    Signoff questions

    • Is the accepted-operation policy identical in RTL, assertions, scoreboard, and integration documentation?

    • Does the selected memory implementation provide the required read latency and collision behavior?

    • Are almost thresholds derived from real reaction latency rather than an arbitrary constant?

    • Have non-power-of-two depths and pointer wrap been tested?

    • Does downstream logic use dout_valid_o rather than treating held data bits as a transaction?

    Interview

    Support FIFO signoff with a conservation/order scoreboard, boundary assertions, directed and random wrap-around traffic, a parameter matrix, and the memory-inference report.

    Check yourself What single test catches the most FIFO bugs?

    A long random push/pop run checked by an independent ordered reference queue, provided it also reaches empty, full, simultaneous operations, and multiple pointer wraps. Directed boundary tests ensure those states are not left to chance.

    Remember

    • Compare every popped word with an independent ordered reference queue, and reach empty, full, simultaneous operations and several wraps.

    • Run the same top over a matrix of width, depth and margin. Illegal settings must fail at elaboration.

    • Reset discards queued words and any uncaptured result. A wrapper must account for them.

    Rule of thumb: signoff is a scoreboard, boundary assertions, wrap-around traffic, a parameter matrix and the memory-inference report.

    How this was checked

    Parameterised bench (default depth 5; directed boundaries, reset epochs, forced pointer wrap, 3000 pseudo-random cycles against a reference queue), run with Icarus Verilog 13.0 (2026-10-16): prints PASS. It says nothing about physical memory inference.

    Parameter matrix, run with Icarus Verilog 13.0 (2026-10-16): the five legal tuples print PASS and the six guard tuples fail elaboration as required.

    Icarus does not run SVA, so the properties are shown as a snippet and the bench checks the same rules procedurally.

    Keep going

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

    • Code lab: Synchronous FIFO, 64 entries

    • Interview: run a mock interview on this topic

    Related topics

    • Synchronous FIFO whiteboard drill

    • Asynchronous FIFO for unrelated clocks

    Previous

    222 / 350

    Next: Register-Map and CSR RTL

    Up next

    Up nextRegister-Map and CSR RTL

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Contract and Invariants
    • Pointers, Occupancy and Flags
    • Portable Registered-Output FIFO