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

  • Start Here6
  • SystemVerilog Language Foundations28
  • Object-Oriented SystemVerilog for Verification30
  • Constraint Randomization — Interview-Critical Deep Dive36
  • SystemVerilog Assertions — Interview-Critical Deep Dive40
    • SVA Fundamentals
    • Sequences: Temporal Building Blocks
    • Implication and Property Operators
    • Sampled Value Functions
    • Protocol Assertion Cookbook
    • Advanced SVA: Local Variables, Multiclock & More
    • Assertion Debug & Methodology
      • Debugging Assertion Failures
      • When Passing Is the Bug: Vacuity & Coverage
      • Assertion Performance & Noise Control
      • Where Assertions Live
      • The Assertion Review Checklist
    • SVA Interview Drills
  • Functional Coverage in SystemVerilog22
  • Testbench Architecture in SystemVerilog23
  • Advanced Language & Integration25
  • Senior & Interview Preparation21
  • Principal-Level Verification28
Loading lesson

‹Assertion Debug & Methodology
  1. Topics
  2. SystemVerilog
  3. SystemVerilog Assertions — Interview-Critical Deep Dive
  4. Assertion Debug & Methodology
  5. Debugging Assertion Failures

Part 4 · Assertions (SVA) · Intermediate

Debugging Assertion Failures

Read failure messages, mind the preponed off-by-one, and print sampled values.

On this page

    Why this matters

    An engineer spent a morning on the cycle where an assertion failed. Nothing happened there: the window had simply run out, and the unanswered request sat four cycles earlier.

    After this lesson you can: read a failure report's two times, read signals as the assertion saw them, and print sampled values in action blocks.

    Attempt start and fail time

    A concurrent assertion failure carries two times. The attempt start time is the edge where the attempt began, usually where the antecedent matched.

    The fail time is the edge where the consequent became impossible. The protocol violation sits somewhere inside that window.

    Two times in every failure report.

    The message above is a schematic illustration, not a verbatim tool format. Neither Icarus nor Verilator prints attempt-start time or the offending term; commercial thread viewers do.

    Timestamp

    Meaning

    Where to look

    Attempt start

    Edge where the antecedent matched

    Begin the waveform search here

    Fail time

    Edge where the last live thread died

    End the search here; the cause is earlier

    Window between

    One attempt, possibly several threads

    Walk each delay value against the signals

    Each thread dies for its own reason.

    With an attempt starting every cycle, several attempts are mid-flight at once. Attribute the failure to its own attempt before theorizing.

    Check yourself An attempt starts at 1150ns for $rose(req) |-> ##[1:4] ack and ack never comes. At what time does it fail, and where do you debug?

    It fails at 1190ns, when the ##4 thread dies. Debug starts at 1150ns: the request was never answered.

    The waveform off-by-one

    Assertions sample in the Preponed region: the value a signal held just before the clock edge. A waveform cursor parked on the edge shows the value after updates instead.

    When a signal changes at edge N, the assertion at edge N still saw the old value. The assertion looks one cycle behind the waveform, and it is the cursor placement that is wrong.

    The assertion used the value before the edge.

    Common pitfall

    Reading waveform values on the edge and declaring the assertion broken is the preponed off-by-one. Move the cursor just left of the edge and the views agree. Fixing a correct assertion to match a misread waveform enshrines the bug.

    The window lane marks the live attempt. Ack arrives after the window closes, so the attempt fails.

    • Slot 0: idle; req 0, ack 0, window 0.

    • Slot 1: req rises; the attempt starts and the window opens.

    • Slot 2: ack still 0; the ##1 thread dies.

    • Slot 3: ack still 0; the ##2 thread dies.

    • Slot 4: ack still 0; the ##3 thread dies.

    • Slot 5: ack still 0; the ##4 thread dies, the window closes, FAIL.

    • Slot 6: ack finally rises, one cycle outside the window. The bug is the unanswered request at slot 1.

    1. Take attempt start time and fail time from the message; load only that window.

    2. Read every signal just before each clock edge; that is what the assertion used.

    3. Identify which boolean term broke; binary-search the property terms if the tool does not name it.

    4. Decide RTL bug versus assertion bug: wrong window, missing qualifier, or bad reset gating are the usual assertion bugs.

    Action blocks that print the truth

    An action block runs after the design updates for that edge. Printing a signal directly shows the post-edge value, not the value the assertion tested.

    $sampled returns the preponed value, so the message matches the assertion view. $sampled takes only the expression; its clock is inferred from the assertion.

    design.sv
    1
    2
    3
    4
    // bad-example: prints post-edge values, contradicting the failure
    ap_gnt_bad: assert property (@(posedge clk) disable iff (!rst_n)
      req |-> gnt)
      else $error("req=%b gnt=%b", req, gnt);   // may print gnt=1 on a fail
    design.sv
    1
    2
    3
    4
    5
    // snippet: prints what the assertion actually sampled
    ap_gnt: assert property (@(posedge clk) disable iff (!rst_n)
      req |-> gnt)
      else $error("[%0t] saw req=%b gnt=%b (sampled)",
                  $time, $sampled(req), $sampled(gnt));

    Invest in action blocks while writing the assertion, not during the debug. A message with every relevant sampled value plus the time often closes the bug without opening a viewer.

    Interview

    Likely question: “Your assertion failed but the waveform shows the signal high at that edge, explain.” Answer: assertions sample preponed values from just before the edge, while the cursor shows the post-edge value. Move the cursor just left of the edge. Follow-ups name the two timestamps and $sampled in action blocks.

    Check yourself An else action prints gnt=1 for a property req |-> gnt that just failed. Is the assertion broken?

    No. The action block ran after the edge updates, so it printed the new value. The assertion tested the preponed value 0. Print $sampled(gnt) instead.

    Testbench

    A request-to-ack pipeline with a stall input, plus the assertion with a sampled message. Phase A is healthy and fires nothing; phase B stalls through the whole window and fires exactly once.

    ack_gen.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    module ack_gen (
      input logic clk, rst_n,
      input logic req, stall,
      output logic ack
    );
      logic [2:0] sr;
      always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
          sr <= '0;
          ack <= 1'b0;
        end else begin
          sr <= {sr[1:0], (req && !stall)};
          ack <= sr[2];
        end
      end
    endmodule
    
    Terminal
    caught late ack at 145000: sampled req=0 ack=0
    PASS: healthy ack arrived at +4 and the stalled window stayed quiet

    Remember

    • A windowed failure reports at the window end; the cause is at the start.

    • The assertion used the value just before the edge.

    • Print $sampled values, not raw signals, in action blocks.

    Rule of thumb: start every assertion debug at the attempt's start time.

    How this was checked

    Run with Verilator 5.048 (--binary --timing --assert, plus --coverage when a listing has a cover property) on 2026-10-18; every bench printed the PASS line shown. Verilator stops at the first $error or failing assertion without an action block, so the benches count failures in the action block. Icarus 13 has no concurrent assertions.

    Related topics

    • When Passing Is the Bug

    Previous

    128 / 259

    Next: When Passing Is the Bug: Vacuity & Coverage

    Up next

    Up nextWhen Passing Is the Bug: Vacuity & Coverage

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Assertion Performance & Noise Control
    • Where Assertions Live
    • The Assertion Review Checklist
    clkreqackwindow
    A request answered one cycle too late: the window closes at slot 5, ack lands at slot 6