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

AboutRoadmapCareersInternshipsFAQContactPrivacyTerms
Skip to lesson

Course contents

  • How to Use This Course
  • Reading Paths
  • Glossary
  • Metrics Reference
  • Formal Cheat Sheet
  • Formal Foundations19
  • Properties and SVA16
    • SVA as a Formal Tool Reads It
    • Implication, Edges and Windows
    • Safety, Liveness and What a Tool Can Say
    • Debugging and Reviewing Assertions
      • Reading a Failing Assertion in a Trace
      • Vacuous and Trivially True Assertions
      • Mutation Check of a Property
      • Property Review Checklist
  • Property Development and Constraints19
  • Proof Engines and Convergence23
  • Formal Applications20
  • Equivalence Checking14
  • Formal Coverage and Signoff15
  • Methodology and Debug13
  • Interview Prep15
  • Capstone: A Chip-Block Verification Lab24
Loading lesson

←Debugging and Reviewing Assertions
  1. Topics
  2. Formal Verification
  3. Properties and SVA
  4. Debugging and Reviewing Assertions
  5. Reading a Failing Assertion in a Trace
On this page

    Part 2 of 10, lesson 13 of 16Part 2 of 10/Properties and SVA/Lesson 13 of 16All levels

    Reading a Failing Assertion in a Trace

    Three steps from the failing cycle back to the attempt: which attempt, which cycle, which condition; shown on the bridge word that is dropped under stall.

    Why it matters

    A counterexample names a failing assertion and a cycle. That is where most engineers start simulating, and where the confusion starts: the failing cycle is where the monitor gave up, not where the design went wrong. The design went wrong at the attempt, up to several cycles earlier. This lesson's one job is the three-step walk backwards.

    The core idea

    Read a failure in three steps. First, find the attempt: which antecedent fired, and in which cycle, to start the check that failed. For an invariant such as conservation there is no antecedent, so the attempt is the arming event: the first cycle in which the design did something that made the state wrong. Second, walk the window forward from the attempt and name the first cycle where a required condition is missing. Third, classify: a design bug if the environment kept its contract, a missing assumption if it did not, an abstraction artefact if the trace cannot happen in the unabstracted design. The routine is the same for a finite counterexample and for a step counterexample from an induction failure.

    Steps are numbered as in every proof run in this section: step 0 is the reset step, and a run of depth D checks steps 0 to D minus 1.

    Worked example: the word dropped under stall

    The bridge mutant that never captures into the skid accepts a word while stalled and drops it. The proof below fails; the table reads the failing run the way the routine demands, attempt first. It is a trace built by hand from the mutant's code that shows the same failure the solver reports, with each row giving the state at the start of the step:

    hs_bridge.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
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    // formal: bmc 4 expect fail slang top hs_bridge define BUG=1
    `define FORMAL
    // hs_bridge: valid/ready register stage with a skid buffer (2-entry elastic stage).
    // Output stage `ovld/odat` plus skid `svld/sdat`. in_ready = !svld, so it depends on a
    // register only (no combinational path from out_ready to in_ready). Synchronous
    // active-high reset empties both stages. A transfer happens on a cycle where valid
    // and ready are both 1.
    // BUG: 0 none, 1 data dropped on stall (skid never captures), 2 valid dropped
    // (stalled output loses valid when the input idles), 3 data changes under backpressure
    // (stalled output overwritten by new input).
    module hs_bridge #(parameter BUG = 0, parameter HELPER = 1) (
      input  wire       clk,
      input  wire       rst,
      input  wire       in_valid,
      output wire       in_ready,
      input  wire [7:0] in_data,
      output wire       out_valid,
      input  wire       out_ready,
      output wire [7:0] out_data
    );
      reg       ovld, svld;
      reg [7:0] odat, sdat;
    
      assign in_ready  = !svld;
      assign out_valid = ovld;
      assign out_data  = odat;
    
      wire in_xfer  = in_valid && in_ready;
      wire out_xfer = ovld && out_ready;
      wire stalled  = ovld && !out_ready;
    
      always @(posedge clk) begin
        if (rst) begin
          ovld <= 1'b0;
          svld <= 1'b0;
        end else if (stalled) begin
          // output holds; a new input is parked in the skid register
          if (in_xfer && BUG != 1) begin svld <= 1'b1; sdat <= in_data; end
          if (BUG == 2 && !in_valid) ovld <= 1'b0;
          if (BUG == 3 && in_xfer)   odat <= in_data;
        end else begin
          // output empty or consumed this cycle
          if (svld) begin
            odat <= sdat; ovld <= 1'b1; svld <= 1'b0;
          end else begin
            odat <= in_data; ovld <= in_valid;
          end
        end
      end
    
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
    
      // ---- conservation: accepted - delivered = items held (counters wrap at 16) ----
      reg [3:0] acc, dlv;
      always @(posedge clk) begin
        if (rst) begin acc <= 4'd0; dlv <= 4'd0; end
        else begin
          if (in_xfer)  acc <= acc + 4'd1;
          if (out_xfer) dlv <= dlv + 4'd1;
        end
      end
    
      always @(*) if (!init) begin
        a_skid_needs_out: assert (!svld || ovld);                       // skid only used behind a full output
        a_conserve:       assert (acc - dlv == {3'b0, ovld} + {3'b0, svld});
      end
    
      // ---- protocol: a stalled output must hold valid and data ----
      always @(posedge clk) if (!init && !$past(rst)) begin
        a_valid_hold: assert (!($past(out_valid) && !$past(out_ready)) || out_valid);
        a_data_hold:  assert (!($past(out_valid) && !$past(out_ready)) || out_data == $past(out_data));
        // ready is a registered function: it cannot depend on out_ready in the same cycle
        a_no_ready_comb: assert (in_ready == !svld);
      end
    
      // ---- data integrity by tracked-item abstraction: follow the n-th accepted word ----
      // free constant (see sync_fifo.sv): anyconst wire plus an equal-to-its-old-copy assumption
      (* anyconst *) wire [3:0] tn;
      reg [3:0] tn_q;
      always @(posedge clk) tn_q <= tn;
      always @(*) if (!init) assume (tn == tn_q);
      reg       tv;      // the tracked word has been accepted and not yet delivered
      reg [7:0] td;
      always @(posedge clk) begin
        if (rst) tv <= 1'b0;
        else begin
          if (in_xfer && acc == tn) begin tv <= 1'b1; td <= in_data; end
          else if (out_xfer && dlv == tn) tv <= 1'b0;
        end
      end
      always @(*) if (!init && out_xfer && tv && dlv == tn) a_data: assert (out_data == td);
      generate if (HELPER) begin : g_helper
        always @(*) if (!init) begin
          // where the tracked word lives: output stage if it is next to leave, else skid
          h_in_out:  assert (!(tv && dlv == tn) || (ovld && odat == td));
          h_in_skid: assert (!(tv && dlv + 4'd1 == tn) || (svld && sdat == td));
          h_pos:     assert (!tv || (dlv == tn) || (dlv + 4'd1 == tn));
        end
      end endgenerate
    
      // ---- covers ----
      always @(*) if (!init) begin
        c_stall:     cover (stalled);
        c_skid_used: cover (svld);
        c_backpress_full: cover (!in_ready);
        c_flow:      cover (in_xfer && out_xfer);
      end
    `endif
    endmodule

    Step

    in_valid, out_ready

    Accepted minus delivered

    Stored (output, skid)

    Reading

    0

    reset step

    0

    0, 0

    Not checked.

    1

    1, 0

    0

    0, 0

    Word A is accepted into the output stage. Legal.

    2

    1, 0

    1

    1, 0

    Arming event: the output is stalled and word B is accepted, but the skid never captures it. The handshake is legal; the capture is missing.

    3

    any

    2

    1, 0

    Conservation breaks: two words owed, one stored. The monitor reports here.

    The root cause is the missing capture in step 2: the stalled branch takes the input's handshake without parking the word. The fix restores the skid write. Note the classification: the environment kept its contract throughout, so this is a design bug, not a missing assumption, and the trace replays on the unabstracted design, so it is not an abstraction artefact either.

    What the review looks for

    • The failure write-up names the attempt cycle, not just the failing cycle: which antecedent fired, or which event armed the invariant, and what it demanded.

    • The classification is stated: design bug, missing assumption, or abstraction artefact, with the test that distinguishes them.

    • The fix is paired with the failure: the same trace, rerun after the fix, must pass, and a cover must show the scenario still happens.

    Verify and debug

    Replay the failing inputs in simulation against the same RTL: the replayer drives the trace as stimulus and the waves must show the same broken condition in the same cycle. If simulation disagrees, the harness differs from the bench: reset, initial state, or an assumption the bench does not keep. Minimise next: drop inputs the failure does not need until the shortest failing trace remains, then fix from the attempt, not from the failing cycle.

    Debugging from the failing cycle: the conservation check failed at step three, so the fix tried to repair the counters instead of asking what armed the failure. The arming event was a legal stalled accept at step two; the counters were never the problem. Found by the three-step walk: the arming event was legal, the environment kept its contract, and the missing capture was one branch up. Fixed at the arming event, in the skid write.

    Walk me through a counterexample: say the failing label and cycle, then step back to the attempt that armed it, then name the first cycle where a required condition is missing, then classify. Never start at the design: start at the attempt. Evidence: the conservation table above, where the arming event in step two explains the failure reported in step three.

    Check yourself The failing cycle is three but the bug is in cycle two. Why does the routine insist on starting at the attempt?

    Because the failing cycle is where the monitor gave up, which moves with the monitor: a different check reports the same bug a cycle later. The attempt is where the design committed, and it does not move. Fixes written at the failing cycle chase the monitor; fixes at the attempt fix the design.

    Related topics

    • Next: Vacuous and Trivially True Assertions

    • Hardware Debug: Formal Counterexamples as Traces

    • SystemVerilog: Assertion Debug

    Previous

    37 / 183

    Next: Vacuous and Trivially True Assertions

    Up next

    Up nextVacuous and Trivially True Assertions

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Mutation Check of a Property
    • Property Review Checklist