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
  • Property Development and Constraints19
  • Proof Engines and Convergence23
  • Formal Applications20
  • Equivalence Checking14
  • Formal Coverage and Signoff15
  • Methodology and Debug13
    • The Formal Testplan
    • Managing Complexity in a Project
    • Debugging Formal Results
      • Triage a Counterexample
      • Minimising and Replaying in Simulation
      • CEX, CTI, and Lasso Compared
      • Four Counterexamples Worked
    • Formal in Regression
  • Interview Prep15
  • Capstone: A Chip-Block Verification Lab24
Loading lesson

←Debugging Formal Results
  1. Topics
  2. Formal Verification
  3. Methodology and Debug
  4. Debugging Formal Results
  5. Triage a Counterexample
On this page

    Part 8 of 10, lesson 7 of 13Part 8 of 10/Methodology and Debug/Lesson 7 of 13All levels

    Triage a Counterexample

    Decide in a fixed order: legal start, missing assumption, abstraction artefact, or design bug; then fix and regress.

    Why it matters

    Two engineers read the same conservation failure on the skid stage. One spent a week in the RTL before noticing the trace began with the counters at arbitrary values, a harness gap. The other spent ten minutes: unreachable start, so fix the harness, rerun, and only then ask whether the design is guilty. Triage is that ten minutes, done the same way every time.

    Job of this lesson: classify any failure into one of four classes and prescribe the fix plus its regression.

    The core idea

    Four classes, checked in order. An illegal initial state means the harness is wrong: the trace is discarded, not debugged. A trace the assumptions should have excluded means the environment model is wrong: add the documented rule, and check the neighbour owes it. A trace that does not replay on the unabstracted design means the abstraction is wrong: constrain the cut or the black box with a proven contract. A trace that survives all three is a design bug: fix the RTL, keep the failing property, and add a cover that pins the scenario. Each class leaves a regression behind, an assert plus a cover, so it cannot silently return:

    Class

    Test that names it

    Fix

    Regression left behind

    Illegal start

    First cycle unreachable from reset

    Fix reset and initial state

    Reset cover, no step-0 failure

    Missing assumption

    A spec rule the environment owes would have excluded it

    Add the documented rule, exactly as written

    Neighbour assert discharging it, plus a cover that legal traffic survives

    Abstraction artefact

    No replay on the real design

    Constrain with a proven contract

    Contract asserted on the real block

    Design bug

    Survives all three tests

    Fix the RTL

    Failing property plus scenario cover

    First ask whether the start is legal: if not, fix the harness. Then ask whether a documented rule should have excluded the trace: if so, add the rule and make the neighbour owe it. Then ask whether the trace replays on the real design: if not, fix the abstraction; if it does, fix the design.
    The triage flow: each question has a yes and a no, and each answer either names a fix or passes to the next question.

    Worked example: the assumption checker

    The script below encodes the skid stage's environment rules as data and tests a trace against them. The rule is the valid/ready rule: a source whose valid was high while ready was low must keep valid high in the next cycle. It is not the rule that valid is held while the output is stalled, because while the output is stalled the skid register may be empty, ready is high, the word is accepted, and the source may then legally stop. The test uses that legal trace to show the difference: the right rule passes it, the over-strong rule flags it, and so would over-constrain the proof. A trace with a real drop is flagged by the right rule with its name, and a checker that inspects only the first cycle misses it:

    example.py
    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
    def valid_held(t):
        # valid/ready rule: valid and not ready in one cycle means valid again in the next
        return all(not (a["in_valid"] and not a["in_ready"]) or b["in_valid"] for a, b in zip(t, t[1:]))
    
    def valid_held_while_output_stalled(t):
        # too strong: it also forbids a legal stop after a word was accepted during a stall
        return all(not (a["out_stalled"] and a["in_valid"]) or b["in_valid"] for a, b in zip(t, t[1:]))
    
    rules = [
        ("reset-first", lambda t: t[0]["rst"] == 1),
        ("valid-held", valid_held),
    ]
    
    def broken_rules(trace, rule_list=rules):
        return [name for name, holds in rule_list if not holds(trace)]
    
    def cycle(rst, v, r, s):
        return {"rst": rst, "in_valid": v, "in_ready": r, "out_stalled": s}
    
    legal = [cycle(1, 0, 1, 0), cycle(0, 1, 1, 0), cycle(0, 1, 1, 1), cycle(0, 0, 0, 1)]   # word accepted, then valid stops
    assert broken_rules(legal) == []
    assert broken_rules(legal, [("too-strong", valid_held_while_output_stalled)]) == ["too-strong"]
    
    drop = [cycle(1, 0, 1, 0), cycle(0, 1, 1, 0), cycle(0, 1, 0, 0), cycle(0, 0, 0, 0)]    # offer waiting, then valid stops
    assert broken_rules(drop) == ["valid-held"]
    
    def blind_checker(trace):
        return [] if trace[0]["rst"] == 1 else ["reset-first"]
    
    assert blind_checker(drop) == [] and blind_checker(drop) != broken_rules(drop)
    print("PASS: the legal stop is accepted, the real drop is named, the first-cycle checker is blind to it")

    What the review looks for

    • Every triaged failure names its class and the test that decided it.

    • Missing assumptions map to a neighbour guarantee and are written as the documented rule, not as a tighter local constraint.

    • Each fix carries a regression: the property plus a cover pinning the scenario, and for an assumption a cover showing legal traffic is still reachable.

    Verify and debug

    Replay the trace on the unabstracted design before changing anything: if it does not replay, the abstraction is the suspect, not the RTL. Record the classification in the ledger next to the outcome; a failure with no class is a failure that will be triaged twice. Adding an assumption is the one repair that can hide a bug, so every added rule is listed with its owner: the assumptions lesson covers the register.

    Debugging the design from a trace with an unreachable start: days spent on RTL that was never guilty. Symptom is a first-cycle failure in unreachable state. Cause is reading the trace before gating the stages. Fix: the fixed order of this lesson, covers and reset first. Found by checking the first trace cycle against the reset state.

    “A counterexample appears. How do you triage?” Answer in fixed order: legal start, missing assumption, replay on the real design, then classify as bug, assumption gap, or artefact. For each class name the fix and the regression it leaves. Mention the assumption register: every assumption added here is owed an assert elsewhere.

    Check yourself The trace replays on the real design and the start is legal, but an environment rule in the spec would have excluded it. Which class?

    Missing assumption: add the documented rule, check the neighbour owes it, and prove the property again under the corrected environment.

    Check yourself Two failures look identical but one replays and the other does not. What differs?

    Everything downstream: the replaying one is a candidate design bug; the other is an abstraction artefact until its cut or black box carries a proven contract.

    Related topics

    • Next: Minimising and Replaying in Simulation

    • Hardware Debug: Formal Counterexamples as Traces

    • Assumptions and Constraints

    Previous

    138 / 183

    Next: Minimising and Replaying in Simulation

    Up next

    Up nextMinimising and Replaying in Simulation

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • CEX, CTI, and Lasso Compared
    • Four Counterexamples Worked