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
    • Model Checking in Plain Terms
    • Bounded and Unbounded Results
    • K-Induction in Depth
    • IC3, PDR and Interpolation, Concept Level
    • Fairness, Lassos and Liveness-to-Safety
    • Proof Convergence Tactics
      • Diagnose Before You Tune
      • Case Splitting and Helper Lemmas
      • Proof Structure and Lemma Stacks
      • When to Stop and What Inconclusive Means
  • Formal Applications20
  • Equivalence Checking14
  • Formal Coverage and Signoff15
  • Methodology and Debug13
  • Interview Prep15
  • Capstone: A Chip-Block Verification Lab24
Loading lesson

←Proof Convergence Tactics
  1. Topics
  2. Formal Verification
  3. Proof Engines and Convergence
  4. Proof Convergence Tactics
  5. Diagnose Before You Tune
On this page

    Part 4 of 10, lesson 20 of 23Part 4 of 10/Proof Engines and Convergence/Lesson 20 of 23All levels

    Diagnose Before You Tune

    Separating a modelling error, a non-inductive property and true complexity from the run report, then choosing the matching tactic.

    Why it matters

    An inconclusive run tempts the team to lengthen the timeout, which works precisely when the proof was about to close and wastes the budget otherwise. The five-minute alternative is diagnosis: read what the run reported and match it to the cause. A counterexample from an illegal start is a modelling error, fixed in the harness. A passing base with a failing step is a non-inductive property, fixed with helpers. A run whose cone holds tens of thousands of bits is true complexity, fixed by reduction. Each cause has its tactic, and the wrong tactic costs the week the right one saves.

    The core idea

    Bounded is a result, not a branch of non-convergence: it says no violation within N, honestly reported with its justification. Non-convergence tactics start after that sentence. Measure first: the cone bit count per property, the depth reached against the depth needed, engine progress in frames or clauses, and which obligation failed. Then choose. Reduce when the cone dwarfs the property: abstraction, cut points, smaller parameters. Split when one property covers several behaviours: cases by value range or mode. Strengthen when the step fails from unreachable states: helper invariants from the CTI. Assume last, when nothing else closes the gap: one justified assumption with an owner and a review, never a quiet constraint.

    Flowchart from the question what the run reported: a counterexample is classified, and an illegal start means a modelling error; an inconclusive run with a passing base and a failing step is strengthened with helpers; otherwise a large cone is reduced, several modes are split, and only then is a justified assumption used.
    The diagnosis flow: the first branch asks what the run reported, and the leaves are modelling error, strengthen, reduce, split, and a last-resort assumption.

    Worked example: eight reports classified

    The script classifies eight run reports by the rule the prose states. A counterexample that does not start at reset is a modelling error; one that does is classified as a trace. A proof is filed with vacuity evidence. An inconclusive run with a passing base and a failing step is strengthened, a very large cone is reduced, several behaviours in one property are split, a depth shortfall with none of those is answered by induction or reduction and not by splitting, and only a run with none of these causes reaches the last-resort assumption. The cone limit of 5,000 bits is illustrative. A malformed report is rejected rather than guessed at. The clause-growth check prices depth with illustrative arithmetic: 50,000 gates at depth 30 need 4.56 million clauses and depth 300 needs 45.6 million, and where memory ends a run depends on the tool and the machine.

    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
    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
    CONE_LIMIT = 5000  # illustrative: the cone size above which reduction comes first
    
    def triage(r):
        outcome = r["outcome"]
        if outcome == "counterexample":
            # a trace that does not start at a state reset can produce is a modelling error, not a bug
            return "classify-trace" if r["starts_at_reset"] else "fix-reset-model"
        if outcome == "proven":
            return "file-with-vacuity-evidence"
        if outcome != "inconclusive":
            raise ValueError("unknown outcome: " + outcome)
        if r["base"] == "pass" and r["step"] == "fail":
            return "strengthen"             # non-inductive: read the CTI, write helpers
        if r["cone_bits"] > CONE_LIMIT:
            return "reduce"                 # true complexity: abstract, cut, shrink parameters
        if r["modes"] > 1:
            return "split"                  # one property, several behaviours
        if r["depth_reached"] < r["depth_needed"]:
            return "induct-or-reduce"       # a depth shortfall is answered by induction or reduction, not by splitting
        return "last-resort-assume"
    
    reports = [
        ({"outcome": "counterexample", "starts_at_reset": False}, "fix-reset-model"),
        ({"outcome": "counterexample", "starts_at_reset": True}, "classify-trace"),
        ({"outcome": "proven"}, "file-with-vacuity-evidence"),
        ({"outcome": "inconclusive", "base": "pass", "step": "fail", "cone_bits": 400,
          "modes": 1, "depth_reached": 10, "depth_needed": 10}, "strengthen"),
        ({"outcome": "inconclusive", "base": "running", "step": "not run", "cone_bits": 20000,
          "modes": 1, "depth_reached": 6, "depth_needed": 34}, "reduce"),
        ({"outcome": "inconclusive", "base": "pass", "step": "timeout", "cone_bits": 400,
          "modes": 3, "depth_reached": 12, "depth_needed": 12}, "split"),
        ({"outcome": "inconclusive", "base": "pass", "step": "timeout", "cone_bits": 400,
          "modes": 1, "depth_reached": 12, "depth_needed": 34}, "induct-or-reduce"),
        ({"outcome": "inconclusive", "base": "pass", "step": "timeout", "cone_bits": 400,
          "modes": 1, "depth_reached": 34, "depth_needed": 34}, "last-resort-assume"),
    ]
    for report, expected in reports:
        assert triage(report) == expected, (report, triage(report))
    
    # injected data: a malformed report must be rejected, not guessed at
    caught = False
    try:
        triage({"cone_bits": 10})
    except KeyError:
        caught = True
    assert caught, "triage guessed at a malformed report"
    
    # injected data: a non-reset counterexample must not be classified as a design bug
    assert triage({"outcome": "counterexample", "starts_at_reset": False}) != "classify-trace"
    
    def clauses(gates, flops, depth):
        return (3 * gates + flops) * depth   # illustrative: 3 clauses per gate, one link per state bit per step
    
    assert clauses(50000, 2000, 30) == 4560000
    assert clauses(50000, 2000, 300) == 45600000
    print("PASS: eight reports classified, malformed rejected, 4.56M to 45.6M clauses (illustrative)")

    Measurement

    Reading

    Tactic

    Counterexample from an illegal start

    Modelling error (free initial state)

    Fix reset and initial state

    Base passes, step fails (CTI)

    Non-inductive property

    Strengthen with helpers

    Cone tens of thousands of bits

    True complexity

    Reduce: abstract, cut, shrink

    One property, several behaviours

    Case-shaped problem

    Split and prove coverage

    Depth reached below depth needed

    Bound too short for the state space

    Induct or reduce; splitting does not buy depth

    Nothing closes the gap

    Model too big to decide

    One justified assumption, last

    What the review looks for

    • Every inconclusive run files its measurements: cone bits, depth reached, failed obligation.

    • The chosen tactic matches the measurement, and a changed tactic is re-measured, not assumed better.

    • Assumptions carry owners and justifications; timeouts carry new measurements or they are not extended.

    Verify and debug

    Rerun the classifier after each tactic: a reduction should shrink the cone bits, a split should move the failure to one case, a helper should move the step failure. If no measurement moves, the tactic is not biting: stop and re-diagnose rather than stacking more of the same.

    State explosion met with a longer timeout: the run never finishes because the cone holds the full memory and every extra minute searches the same space. The symptom is depth stuck and memory growing. The fix is parameter reduction or a tracked-entry abstraction, found by reading the state-bit count before touching the timeout.

    If asked to triage a proof that will not converge, answer in order: read the report, separate modelling error from non-induction from complexity, then reduce, split or strengthen, with assumptions last. Quote the measurements that decide each branch: cone bits, depth reached, the failing obligation.

    Check yourself An inconclusive run has 400 cone bits, a passing base, a failing step and adequate depth. Which tactic, and what measurement confirms it worked?

    Strengthen: write helpers from the CTI. The rerun should move the failure: the step passes or names a new, smaller gap. If the step failure does not move, re-diagnose.

    Related topics

    • Next: Case Splitting and Helper Lemmas

    • Formal: Complexity Management

    • Formal: Debugging Counterexamples

    Previous

    79 / 183

    Next: Case Splitting and Helper Lemmas

    Up next

    Up nextCase Splitting and Helper Lemmas

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Proof Structure and Lemma Stacks
    • When to Stop and What Inconclusive Means