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

  • SystemVerilog Language Foundations34
  • Object-Oriented SystemVerilog for Verification33
  • Constraint Randomization — Interview-Critical Deep Dive56
  • SystemVerilog Assertions — Interview-Critical Deep Dive54
    • SVA Fundamentals
    • Sequences: Temporal Building Blocks
    • Implication & Property Operators
    • Sampled Value Functions
      • $rose, $fell, $stable, $changed
      • $past: Looking Backward
      • $countones, $onehot, $onehot0, $isunknown
      • Sampled-Value Edge Cases
      • Sampled Functions with Clock & Gating Arguments
      • Building Data-Integrity Checks
    • Protocol Assertion Cookbook
    • Advanced SVA: Local Variables, Multiclock & More
    • Assertion Debug & Methodology
    • Interview SVA Patterns — Write-the-Assertion Drills
    • SVA Interview Q&A — Deep Answers
  • Functional Coverage in SystemVerilog28
  • Testbench Architecture in SystemVerilog32
  • Advanced Language & Integration30
  • Senior & Interview Preparation29
Loading lesson

‹Sampled Value Functions
  1. Topics
  2. SystemVerilog
  3. SystemVerilog Assertions — Interview-Critical Deep Dive
  4. Sampled Value Functions
  5. Building Data-Integrity Checks

Part 4 · Assertions (SVA) · Intermediate

Building Data-Integrity Checks

Combining $past and $stable into real checks: FIFO delayed-data, stable-while-busy, write-then-readback, and address/data phase matching.

On this page

    From timing rules to data rules

    Most assertion tutorials stop at handshake timing — request gets a grant, valid waits for ready. The checks that actually catch corrupting bugs are data-integrity checks: the value that comes out equals the value that went in, suitably delayed, selected, or held. The sampled value functions are the raw material: $past supplies the historical reference value, $stable pins values that must not move, and the gating argument aligns histories to qualifying beats. This lesson assembles them into four checks you will write on real projects — and a preview of where they run out of steam and local variables take over.

    Diagram
    DATA-INTEGRITY CHECK FAMILIES
    
      ┌─────────────────────┬──────────────────────────────────────────┐
      │ delayed-equality    │ out == $past(in, N [, qualifier])        │
      │                     │ FIFOs (in-order), fixed-latency pipes    │
      ├─────────────────────┼──────────────────────────────────────────┤
      │ hold / stability    │ qualifier |-> $stable(value)             │
      │                     │ config-while-busy, data-while-stalled    │
      ├─────────────────────┼──────────────────────────────────────────┤
      │ readback            │ read_resp |-> rdata == $past(wdata, ...) │
      │                     │ register files, write-then-read rules    │
      ├─────────────────────┼──────────────────────────────────────────┤
      │ phase matching      │ data-phase fields == $past(addr-phase)   │
      │                     │ split-transaction buses (AHB/AXI style)  │
      └─────────────────────┴──────────────────────────────────────────┘

    FIFO: data out equals data in, delayed

    For a FIFO in the simple regime — in-order, one element per push/pop — the element popped now is the element pushed OCCUPANCY pushes ago. With the gating argument, $past(wr_data, occupancy, push) reaches back exactly that many push beats. The catch: $past requires a constant depth argument on most tools, so the elegant dynamic form is illegal. The standard compromises: assert at a fixed occupancy (e.g. a flush-through test where occupancy is known), or generate one assertion per legal occupancy value.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // Legal pattern: one assertion per occupancy, generated
    genvar k;
    generate
      for (k = 1; k <= DEPTH; k++) begin : g_fifo_chk
        assert property (@(posedge clk) disable iff (!rst_n)
          (pop && occupancy == k) |->
            (rd_data == $past(wr_data, k, push)))
          else $error("FIFO integrity: occ=%0d", k);
      end
    endgenerate
    
    // ILLEGAL on most tools — $past depth must be elaboration-constant:
    // rd_data == $past(wr_data, occupancy, push)   // do not write this
    Diagram
    DEPTH-2 trace, occupancy shown after each tick's push/pop
    
      tick           :  1    2    3    4    5    6
      push           :  1    1    0    0    1    0
      wr_data        :  A    B    -    -    C    -
      pop            :  0    0    1    1    0    1
      rd_data        :  -    -    A    B    -    C
      occupancy@tick :  0    1    2    1    0    1   (sampled, pre-update)
    
      tick 3: pop with occupancy==2 → check rd_data == $past(wr_data,2,push)
              push history (newest→oldest): [B, A] → 2 back = A → A==A PASS
      tick 4: occupancy==1 → 1 push-beat back = B → B==B PASS
      tick 6: occupancy==1 → 1 back = C → C==C PASS

    This generate-per-occupancy trick works but scales poorly and only covers in-order, no-simultaneous-push-pop regimes cleanly. The general solution — capture wr_data into a local variable at push time and compare at the matching pop — handles dynamic depth and out-of-order tags, and is the subject of the local-variables lesson. Knowing why $past alone cannot do it (non-constant depth) is itself a standard interview point.


    Stable-while-busy and write-then-readback

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // 1. Stability: configuration frozen for the whole operation.
    //    Level antecedent is CORRECT here — the rule holds every cycle.
    assert property (@(posedge clk) disable iff (!rst_n)
      busy |-> $stable({cfg_mode, cfg_len, cfg_base}));
    
    // 2. Hold under stall: output data must not move while consumer stalls
    assert property (@(posedge clk) disable iff (!rst_n)
      (out_valid && !out_ready) |-> ##1 ($stable(out_data) && out_valid));
    
    // 3. Write-then-readback: a read that follows a write to the SAME
    //    address with no intervening write returns the written data.
    //    (Single outstanding write; reg-file with 1-cycle read.)
    property p_readback;
      logic [7:0]  a;
      logic [31:0] d;
      @(posedge clk) disable iff (!rst_n)
        (wr_en, a = wr_addr, d = wr_data) ##1
        (!(wr_en && wr_addr == a)) [*0:$] ##0
        (rd_en && rd_addr == a)
        |-> ##1 (rd_data == d);
    endproperty
    assert property (p_readback);
    Diagram
    Readback trace (1-cycle read latency)
    
      tick      :   1     2     3     4     5
      wr_en     :   1     0     0     1     0
      wr_addr   :  0x10   -     -    0x10   -
      wr_data   :  AAAA   -     -    BBBB   -
      rd_en     :   0     0     1     0     1
      rd_addr   :   -     -    0x10   -    0x10
      rd_data   :   -     -     -    AAAA  ...   ← tick-3 read returns at 4
                                           BBBB  ← tick-5 read returns at 6
    
      attempt from tick 1: a=0x10, d=AAAA; no intervening write at 2,3;
      read at tick 3 → consequent at tick 4: rd_data==AAAA  PASS.
      attempt from tick 4: d=BBBB; read at 5 → check at 6: BBBB  PASS.

    Check 3 quietly introduced local variables (a, d captured at the write) because $past cannot express “the write that happened an unbounded number of cycles ago”. The boundary is worth internalizing: fixed, constant distance → $past; variable distance or per-transaction matching → local variables.


    Address/data phase matching

    Pipelined buses split a transfer into an address phase and a later data phase. A one-phase-deep pipeline (AHB-style, data phase immediately follows address phase) is the perfect $past use case: every data-phase attribute must equal the corresponding address-phase value from exactly one transfer beat ago — gated on transfer beats, since the bus can insert wait states between them.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // AHB-flavored: data phase attributes match prior address phase.
    // trans_beat = address-phase handshake (htrans active && hready)
    logic trans_beat;
    assign trans_beat = (htrans inside {2'b10, 2'b11}) && hready;
    
    // Write data presented in the data phase belongs to the write
    // command issued in the previous transfer beat
    assert property (@(posedge clk) disable iff (!rst_n)
      (data_phase_active && hready && $past(hwrite, 1, trans_beat)) |->
        !$isunknown(hwdata));
    
    // Response checks aligned to the command one beat earlier
    assert property (@(posedge clk) disable iff (!rst_n)
      (data_phase_active && hready) |->
        (cur_resp_addr == $past(haddr, 1, trans_beat)));
    Diagram
    Address/data phase pipeline with one wait state
    
      tick        :    1       2       3       4
      haddr       :   A1      A2       -       -
      trans_beat  :    1       1       0       0
      hready      :    1       1       0       1
      data phase  :    -      D(A1)   D(A2)~  D(A2)   (~ = stretched)
      check at 2  : resp addr == $past(haddr,1,trans_beat) = A1  PASS
      check at 4  : gated history skipped the tick-3 wait state →
                    still returns A2 (last trans beat)            PASS
    
      Ungated $past(haddr) at tick 4 would return tick-3's haddr ('-')
      → spurious FAIL. The gate IS the correctness here.

    Interview angle

    “Write an assertion that FIFO output equals input delayed” is a deliberately underspecified favorite. Strong answers interrogate the regime first: in-order? constant latency? Then: constant delay → $past(in, N); valid-qualified → add the gate; depth varies at runtime → $past cannot (non-constant argument), switch to local variables or per-occupancy generate. For stable-while-busy, expect the follow-up “edge or level antecedent?” — level, because the rule re-applies every cycle of the window. Being able to produce the wait-state phase-matching waveform above distinguishes candidates who have verified a real bus from those who have read about one.

    Key takeaways

    • Delayed-equality, stability, readback, and phase-matching cover most practical data checks.

    • $past handles constant-distance lookback; gate it with the beat qualifier on elastic or wait-stated buses.

    • $past's depth must be an elaboration-time constant — dynamic distance needs local variables.

    • Stability rules want level antecedents: the obligation renews every cycle of the busy window.

    • Concatenate related fields inside one $stable to check a whole config group atomically.

    Common pitfalls

    • Writing $past(wr_data, occupancy, push) with a run-time occupancy — illegal non-constant depth.

    • Ungated $past across wait states — compares against an idle-cycle value and fails spuriously.

    • Readback checks without the 'no intervening write' clause — a second write legally changes the data.

    • Checking out_data stability under stall without re-asserting out_valid — data held but valid dropped.

    • Reaching for local variables when the latency is constant — $past is simpler, cheaper, and clearer.

    Previous

    147 / 296

    Next: Protocol Assertion Cookbook

    Up next

    Up nextProtocol Assertion Cookbook

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • $rose, $fell, $stable, $changed
    • $past: Looking Backward
    • $countones, $onehot, $onehot0, $isunknown