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
  • Interview Prep15
    • Formal Whiteboard Framework
    • Scenario: A Proof That Will Not Converge
    • Scenario: Everything Proves, Nothing Is Reached
    • Scenario: A Late ECO Breaks Equivalence
    • Scenario: A Deadlock-Like Trace in Arbiter and Fabric
    • Rapid Fire by Theme
    • Numeric Drills
    • Spot the Bug in the Answer
  • Capstone: A Chip-Block Verification Lab24
Loading lesson

←Interview Prep
  1. Topics
  2. Formal Verification
  3. Interview Prep
  4. Scenario: Everything Proves, Nothing Is Reached
On this page

    Part 9 of 10, lesson 5 of 15Part 9 of 10/Interview Prep/Lesson 5 of 15All levels

    Scenario: Everything Proves, Nothing Is Reached

    A harness where every assertion passes and every cover is unreachable: find the assumption, repair it, prove the repair.

    Setup

    The dashboard says every assertion on a small counter block is proven in under a second. The cover report, on a side page nobody opened, says the activity cover was never reached. In the lab the block miscounts under back-to-back enables, and nobody saw it because the harness never let the enable assert.

    Job of this lesson: run the harness variants side by side: a clean one, three over-constrained ones that pass for no reason, and the repair with its mutation evidence.

    The three traps

    Trap

    What the candidate does

    Why it fails

    Silence a counterexample with an assumption

    Each false trace gets a new rule.

    The rules accumulate until the interesting behaviour is gone; a good proof needs fewer assumptions, each discharged.

    Trust the assertion column

    Reports proven without opening the covers.

    A vacuous pass is the fastest result the tool produces; speed without reached covers is suspicion, not evidence.

    Treat unreached covers as noise

    Leaves the cover column unread.

    An unreached cover on a required scenario fails signoff: each has an owner and a repair.

    The core idea

    An assumption removes behaviour; a cover shows that behaviour remains. The assertion column says nothing broke in what was checked; the cover column says something worth checking was exercised. A cover not reached within a depth is inconclusive on its own: it becomes a finding only when the assumption list shows why. So interrogate the harness before the design: list every assumption, remove those without an owner, and watch the covers return. Three harness faults empty the behaviour set: an assumption pinning the very signal under test, two rules pinning one signal to opposite values, and a reset held for ever.

    Proven assertions lead to reading the cover column. Unreached covers lead to listing assumptions. Relaxing the undocumented rule leads to reached covers and the bug being caught.
    From a false pass to a repaired harness: proven assertions lead to the cover column, then the assumption list, then the repair.

    Worked example: five runs on one counter

    The design is a 2-bit counter that steps on every enable. The bug variant sticks instead of stepping. Run A is the clean harness on the good design: the step property holds, including the wrap from 3 to 0, and the cover for the count reaching 2 is reached.

    ovc.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // formal: bmc 6 expect pass slang top ovc
    `define FORMAL
    // Lesson-local demo: 2-bit enable counter with a stuck-when-enabled bug variant.
    module ovc #(parameter BUG = 0) (
      input wire clk, input wire rst, input wire en, output reg [1:0] c
    );
      always @(posedge clk) begin
        if (rst) c <= 2'd0;
        else if (en) c <= (BUG == 1) ? c : c + 2'd1;
      end
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
      always @(posedge clk) if (!init && !$past(rst)) begin
        a_count: assert (!$past(en) || c == $past(c) + 2'd1);
      end
      always @(*) if (!init) c_two: cover (c == 2'd2);
    `endif
    endmodule
    ovc.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // formal: cover 6 expect reached slang top ovc
    `define FORMAL
    // Lesson-local demo: 2-bit enable counter with a stuck-when-enabled bug variant.
    module ovc #(parameter BUG = 0) (
      input wire clk, input wire rst, input wire en, output reg [1:0] c
    );
      always @(posedge clk) begin
        if (rst) c <= 2'd0;
        else if (en) c <= (BUG == 1) ? c : c + 2'd1;
      end
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
      always @(posedge clk) if (!init && !$past(rst)) begin
        a_count: assert (!$past(en) || c == $past(c) + 2'd1);
      end
      always @(*) if (!init) c_two: cover (c == 2'd2);
    `endif
    endmodule

    Run B adds one innocent-looking assumption, enable never asserted, to the buggy design. The step property passes because its antecedent needs a past enable that never comes. Only the cover column shows it: not reached within 6, inconclusive on its own, but here the assumption list explains it, since enable never asserted means the count can never reach 2.

    ovc.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // formal: bmc 6 expect pass slang top ovc define BUG=1
    `define FORMAL
    // Lesson-local demo: 2-bit enable counter with a stuck-when-enabled bug variant.
    module ovc #(parameter BUG = 0) (
      input wire clk, input wire rst, input wire en, output reg [1:0] c
    );
      always @(posedge clk) begin
        if (rst) c <= 2'd0;
        else if (en) c <= (BUG == 1) ? c : c + 2'd1;
      end
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
      always @(*) if (!init) assume (!en);
      always @(posedge clk) if (!init && !$past(rst)) begin
        a_count: assert (!$past(en) || c == $past(c) + 2'd1);
      end
      always @(*) if (!init) c_two: cover (c == 2'd2);
    `endif
    endmodule
    ovc.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // formal: cover 6 expect fail slang top ovc define BUG=1
    `define FORMAL
    // Lesson-local demo: 2-bit enable counter with a stuck-when-enabled bug variant.
    module ovc #(parameter BUG = 0) (
      input wire clk, input wire rst, input wire en, output reg [1:0] c
    );
      always @(posedge clk) begin
        if (rst) c <= 2'd0;
        else if (en) c <= (BUG == 1) ? c : c + 2'd1;
      end
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
      always @(*) if (!init) assume (!en);
      always @(posedge clk) if (!init && !$past(rst)) begin
        a_count: assert (!$past(en) || c == $past(c) + 2'd1);
      end
      always @(*) if (!init) c_two: cover (c == 2'd2);
    `endif
    endmodule

    Run C removes the assumption and reruns the buggy design. The stuck counter now fails the step property: depth 3 still passes and depth 4 fails, so the failing step is 3 (the property reads the previous cycle, so it reports a step after the stuck cycle). This is the mutation evidence the repaired harness must show.

    ovc.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // formal: bmc 3 expect pass slang top ovc define BUG=1
    `define FORMAL
    // Lesson-local demo: 2-bit enable counter with a stuck-when-enabled bug variant.
    module ovc #(parameter BUG = 0) (
      input wire clk, input wire rst, input wire en, output reg [1:0] c
    );
      always @(posedge clk) begin
        if (rst) c <= 2'd0;
        else if (en) c <= (BUG == 1) ? c : c + 2'd1;
      end
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
      always @(posedge clk) if (!init && !$past(rst)) begin
        a_count: assert (!$past(en) || c == $past(c) + 2'd1);
      end
      always @(*) if (!init) c_two: cover (c == 2'd2);
    `endif
    endmodule
    ovc.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // formal: bmc 4 expect fail slang top ovc define BUG=1
    `define FORMAL
    // Lesson-local demo: 2-bit enable counter with a stuck-when-enabled bug variant.
    module ovc #(parameter BUG = 0) (
      input wire clk, input wire rst, input wire en, output reg [1:0] c
    );
      always @(posedge clk) begin
        if (rst) c <= 2'd0;
        else if (en) c <= (BUG == 1) ? c : c + 2'd1;
      end
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
      always @(posedge clk) if (!init && !$past(rst)) begin
        a_count: assert (!$past(en) || c == $past(c) + 2'd1);
      end
      always @(*) if (!init) c_two: cover (c == 2'd2);
    `endif
    endmodule

    Runs D and E are the other two faults, both on the buggy design. D adds a contradictory pair, enable assumed high and enable assumed low: no behaviour satisfies both, so every assertion passes and every cover is unreached. E assumes reset for ever instead of for the first step: the design never leaves reset, the history guard never lets the step property fire, and the cover is unreached.

    ovc.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // formal: bmc 6 expect pass slang top ovc define BUG=1
    `define FORMAL
    // Lesson-local demo: 2-bit enable counter with a stuck-when-enabled bug variant.
    module ovc #(parameter BUG = 0) (
      input wire clk, input wire rst, input wire en, output reg [1:0] c
    );
      always @(posedge clk) begin
        if (rst) c <= 2'd0;
        else if (en) c <= (BUG == 1) ? c : c + 2'd1;
      end
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
      always @(*) assume (en);
      always @(*) assume (!en);
      always @(posedge clk) if (!init && !$past(rst)) begin
        a_count: assert (!$past(en) || c == $past(c) + 2'd1);
      end
      always @(*) if (!init) c_two: cover (c == 2'd2);
    `endif
    endmodule
    ovc.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // formal: cover 6 expect fail slang top ovc define BUG=1
    `define FORMAL
    // Lesson-local demo: 2-bit enable counter with a stuck-when-enabled bug variant.
    module ovc #(parameter BUG = 0) (
      input wire clk, input wire rst, input wire en, output reg [1:0] c
    );
      always @(posedge clk) begin
        if (rst) c <= 2'd0;
        else if (en) c <= (BUG == 1) ? c : c + 2'd1;
      end
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
      always @(*) assume (en);
      always @(*) assume (!en);
      always @(posedge clk) if (!init && !$past(rst)) begin
        a_count: assert (!$past(en) || c == $past(c) + 2'd1);
      end
      always @(*) if (!init) c_two: cover (c == 2'd2);
    `endif
    endmodule
    ovc.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // formal: bmc 6 expect pass slang top ovc define BUG=1
    `define FORMAL
    // Lesson-local demo: 2-bit enable counter with a stuck-when-enabled bug variant.
    module ovc #(parameter BUG = 0) (
      input wire clk, input wire rst, input wire en, output reg [1:0] c
    );
      always @(posedge clk) begin
        if (rst) c <= 2'd0;
        else if (en) c <= (BUG == 1) ? c : c + 2'd1;
      end
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) assume (rst);
      always @(posedge clk) if (!init && !$past(rst)) begin
        a_count: assert (!$past(en) || c == $past(c) + 2'd1);
      end
      always @(*) if (!init) c_two: cover (c == 2'd2);
    `endif
    endmodule
    ovc.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // formal: cover 6 expect fail slang top ovc define BUG=1
    `define FORMAL
    // Lesson-local demo: 2-bit enable counter with a stuck-when-enabled bug variant.
    module ovc #(parameter BUG = 0) (
      input wire clk, input wire rst, input wire en, output reg [1:0] c
    );
      always @(posedge clk) begin
        if (rst) c <= 2'd0;
        else if (en) c <= (BUG == 1) ? c : c + 2'd1;
      end
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) assume (rst);
      always @(posedge clk) if (!init && !$past(rst)) begin
        a_count: assert (!$past(en) || c == $past(c) + 2'd1);
      end
      always @(*) if (!init) c_two: cover (c == 2'd2);
    `endif
    endmodule

    Root cause: an assumption hid the bug, and the unread cover column hid the assumption. Fix: assert what the block owes, assume only documented input rules, require every required cover reached, and keep one injected bug per property class that the harness demonstrably catches.

    What the numbers say

    Assumptions are priced by the input space they remove. A 4-bit opcode restricted to 11 of 16 values plus a word-alignment rule on the address removes over four fifths of the space, and a bug living in the removed part is invisible by construction. Each excluded class needs a named verification home (figures illustrative).

    example.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    def remaining(opcodes_kept, opcodes_all, address_fraction):
        return (opcodes_kept / opcodes_all) * address_fraction
    
    left = remaining(11, 16, 1 / 4)
    assert abs(left - 11 / 64) < 1e-12, left
    excluded = 1 - left
    assert abs(excluded - 53 / 64) < 1e-12, excluded
    assert excluded > 0.8, excluded
    # The failing variant: the same call without the alignment rule understates the exclusion.
    try:
        assert abs(remaining(11, 16, 1) - left) < 1e-12
        raise SystemExit("priced the opcode rule but dropped the alignment rule")
    except AssertionError:
        pass
    print("PASS: 11/64 of the input space remains, 82.8 percent excluded by two rules")

    What a strong candidate says

    A strong answer opens the cover report before defending any proof, reads the assumption list aloud with an owner for each line, and names the run that separates the repaired harness from the false pass: the injected bug failing at a stated step. It closes with the signoff wording: proven for the stated model and assumptions, covers reached with steps, mutation evidence attached, excluded classes named with their verification home.

    Dimension

    Strong (3 to 4)

    Weak (0 to 2)

    Assumption

    Every assume listed, owned, discharged at a neighbour.

    Assumptions undescribed; the enable rule unmentioned.

    Mechanism

    Vacuity explained at cycle level: antecedent never fires.

    Proven means correct.

    Trade-off

    Fewer, documented assumptions versus quick convergence.

    More assumptions to make it pass.

    Evidence

    Cover steps plus the injected bug failing at step 3.

    The passing report alone.

    Enable assumed away. Symptom: every assertion proven and the activity cover unreached. Cause: an assume pins the signal under test. Fix: remove it, assert what the block owes, require the cover. Found by the cover column and by an injected bug that must fail.

    When the question says everything proves, reach for the cover column first and say so.

    Check yourself Two harnesses both report every assertion proven. How do you tell them apart in one minute?

    Open the cover column: the repaired harness reaches its activity covers at stated steps and fails its injected bug; the false pass shows unreached covers and has no failing partner.

    Related topics

    • Over-Constraint Seen Through Covers

    • Bounded Cover Miss Is Inconclusive

    • DV Puzzles: Vacuous Assertion Pass

    • Framework: Scoring Rubric and Structure

    • Rapid Fire: Assume, Assert, and Vacuity

    Previous

    149 / 183

    Next: Scenario: A Late ECO Breaks Equivalence

    Up next

    Up nextScenario: A Late ECO Breaks Equivalence

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Formal Whiteboard Framework
    • Scenario: A Deadlock-Like Trace in Arbiter and Fabric
    • Rapid Fire by Theme