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
    • Formal versus Simulation
      • Three Outcomes of a Formal Run
      • A Proof Is Relative to Its Assumptions
      • A Bug Simulation Misses
      • The Formal Engineer's Split
    • Transition Systems, Invariants and the Three Property Roles
    • Reset, Initial State and the First Cycle
    • Cone of Influence
    • The Formal Tool Flow, Vendor-Neutral
  • Properties and SVA16
  • 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

←Formal versus Simulation
  1. Topics
  2. Formal Verification
  3. Formal Foundations
  4. Formal versus Simulation
  5. Three Outcomes of a Formal Run
On this page

    Part 1 of 10, lesson 1 of 19Part 1 of 10/Formal Foundations/Lesson 1 of 19All levels

    Three Outcomes of a Formal Run

    Proven, counterexample, or inconclusive: what each formal result means and what bounded to N promises.

    Why it matters

    A signoff sheet said the decade counter was proven, yet in the lab its terminal pulse arrived one count late. The sheet had recorded a bounded check at depth 10 as a proof; the bug needs depth 11. The tool was fine; the reader misread the result. This lesson fixes the vocabulary so that cannot happen.

    Job of this lesson: define proven, counterexample and inconclusive once, with what each demands next, and show four statuses on one design.

    The core idea

    A formal run checks a property against the model: the design plus the harness (the reset sequence, the free inputs, every assumption). The answer is relative to that model and is one of three outcomes.

    Outcome

    Meaning

    What it demands next

    Proven

    Holds for every behaviour of the stated model and assumptions.

    Keep the assumption list; rerun on every RTL change.

    Counterexample

    A concrete trace that breaks the property, ending at a stated step.

    Triage: design bug, missing assumption or model artefact.

    Inconclusive

    Undetermined: bound reached, time-out, or the induction step (a check from any state) failed.

    Justify the bound, extend the run or strengthen the proof.

    A bounded pass, “no counterexample up to depth N”, written “bounded to N”, is a sound and stated result that proves nothing unbounded, so it is reported under the third row. A counterexample always wins: if a bounded check finds one, no run on the same model, assumptions and reset can report a proof.

    Each block starts with a proof header (How to Use This Course explains the words): prove N is a bounded check to depth N plus an induction step (a check that starts from any state, not only from reset); bmc N is the bounded check alone; cover N searches for a witness; expect is the outcome the runner must see. define FORMAL switches on the property part of the file.

    Worked example: one counter, four statuses

    The running design is a 4-bit decade counter that counts 0 to 9 and wraps, with a terminal pulse at 9: 10 of its 16 states are reachable, 6 (10 to 15) are not. Run A is a full proof of the good design: the base case and the induction step both pass (the set needs induction depth 2).

    mod10_counter.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
    // formal: prove 4 expect pass slang top mod10_counter
    `define FORMAL
    // mod10_counter: 4-bit decade counter, counts 0..9 and wraps to 0.
    //   rst (synchronous, active high) : count <= 0
    //   en  = 1                        : count <= (count == 9) ? 0 : count + 1
    //   en  = 0                        : count holds
    //   tc  = en && (count == 9)       : terminal-count pulse (combinational)
    // States 10..15 exist in the 4-bit register but are unreachable from reset.
    // BUG: 0 none, 1 wraps at 10 instead of 9 (counts 0..10, a mod-11 counter).
    // HELPER: 1 (default) includes the helper invariant count <= 9; 0 leaves count != 15 alone.
    module mod10_counter #(parameter BUG = 0, parameter HELPER = 1) (
      input  wire       clk,
      input  wire       rst,
      input  wire       en,
      output reg  [3:0] count,
      output wire       tc
    );
      wire [3:0] last = (BUG == 1) ? 4'd10 : 4'd9;
      assign tc = en && (count == last);
    
      always @(posedge clk) begin
        if (rst)      count <= 4'd0;
        else if (en)  count <= (count == last) ? 4'd0 : count + 4'd1;
      end
    
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
    
      // P2: count is never 15. True, but NOT inductive on its own: the unreachable states
      // 10..14 satisfy it and lead to 15 (and en = 0 lets a state repeat forever, so no k
      // of k-induction closes it).
      always @(*) if (!init) a_not15: assert (count != 4'd15);
      // P1 / helper: count <= 9. Inductive on its own (k = 1), and it makes a_not15 inductive.
      generate if (HELPER) begin : g_helper
        always @(*) if (!init) a_range: assert (count <= 4'd9);
      end endgenerate
    
      always @(*) if (!init) a_tc: assert (tc == (en && count == 4'd9));
      always @(posedge clk) if (!init && !$past(rst)) begin
        a_step: assert (!$past(en) || count == (($past(count) == 4'd9) ? 4'd0 : $past(count) + 4'd1));
        a_hold: assert ($past(en) || count == $past(count));
      end
    
      // sticky flag: the counter has wrapped at least once
      reg wrapped;
      always @(posedge clk) begin
        if (rst) wrapped <= 1'b0;
        else if (tc) wrapped <= 1'b1;
      end
      always @(*) if (!init) begin
        c_nine: cover (count == 4'd9);
        c_tc:   cover (tc);
        c_wrap: cover (wrapped && count == 4'd3);
      end
    `endif
    endmodule

    Run B plants a bug, a deliberate defect that shows the proof can fail: the wrap constant is 10, not 9.

    mod10_counter.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
    // formal: bmc 11 expect fail slang top mod10_counter define BUG=1
    `define FORMAL
    // mod10_counter: 4-bit decade counter, counts 0..9 and wraps to 0.
    //   rst (synchronous, active high) : count <= 0
    //   en  = 1                        : count <= (count == 9) ? 0 : count + 1
    //   en  = 0                        : count holds
    //   tc  = en && (count == 9)       : terminal-count pulse (combinational)
    // States 10..15 exist in the 4-bit register but are unreachable from reset.
    // BUG: 0 none, 1 wraps at 10 instead of 9 (counts 0..10, a mod-11 counter).
    // HELPER: 1 (default) includes the helper invariant count <= 9; 0 leaves count != 15 alone.
    module mod10_counter #(parameter BUG = 0, parameter HELPER = 1) (
      input  wire       clk,
      input  wire       rst,
      input  wire       en,
      output reg  [3:0] count,
      output wire       tc
    );
      wire [3:0] last = (BUG == 1) ? 4'd10 : 4'd9;
      assign tc = en && (count == last);
    
      always @(posedge clk) begin
        if (rst)      count <= 4'd0;
        else if (en)  count <= (count == last) ? 4'd0 : count + 4'd1;
      end
    
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
    
      // P2: count is never 15. True, but NOT inductive on its own: the unreachable states
      // 10..14 satisfy it and lead to 15 (and en = 0 lets a state repeat forever, so no k
      // of k-induction closes it).
      always @(*) if (!init) a_not15: assert (count != 4'd15);
      // P1 / helper: count <= 9. Inductive on its own (k = 1), and it makes a_not15 inductive.
      generate if (HELPER) begin : g_helper
        always @(*) if (!init) a_range: assert (count <= 4'd9);
      end endgenerate
    
      always @(*) if (!init) a_tc: assert (tc == (en && count == 4'd9));
      always @(posedge clk) if (!init && !$past(rst)) begin
        a_step: assert (!$past(en) || count == (($past(count) == 4'd9) ? 4'd0 : $past(count) + 4'd1));
        a_hold: assert ($past(en) || count == $past(count));
      end
    
      // sticky flag: the counter has wrapped at least once
      reg wrapped;
      always @(posedge clk) begin
        if (rst) wrapped <= 1'b0;
        else if (tc) wrapped <= 1'b1;
      end
      always @(*) if (!init) begin
        c_nine: cover (count == 4'd9);
        c_tc:   cover (tc);
        c_wrap: cover (wrapped && count == 4'd3);
      end
    `endif
    endmodule

    Step 0 is the power-up state with reset asserted, step 1 the first state after reset. A check on the current state reports the step where the bad state exists; a check that looks back with $past reports one step later. So the pulse check fails at step 10, the range check at step 11 and the step check at step 12.

    Step

    en

    count

    tc (spec says)

    Note

    0

    -

    arbitrary

    -

    Power-up; reset asserted. A dash means the input is free.

    1

    1

    0

    0

    First state after reset.

    2 to 9

    1

    1 to 8

    0

    Counting up, one per cycle.

    10

    1

    9

    1

    The design shows 0: a_tc fails here.

    11

    1

    10

    0

    Count outside 0 to 9: a_range fails.

    Run C is the same buggy design one step shorter and it passes: a counterexample at step K needs depth K + 1.

    mod10_counter.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
    // formal: bmc 10 expect pass slang top mod10_counter define BUG=1
    `define FORMAL
    // mod10_counter: 4-bit decade counter, counts 0..9 and wraps to 0.
    //   rst (synchronous, active high) : count <= 0
    //   en  = 1                        : count <= (count == 9) ? 0 : count + 1
    //   en  = 0                        : count holds
    //   tc  = en && (count == 9)       : terminal-count pulse (combinational)
    // States 10..15 exist in the 4-bit register but are unreachable from reset.
    // BUG: 0 none, 1 wraps at 10 instead of 9 (counts 0..10, a mod-11 counter).
    // HELPER: 1 (default) includes the helper invariant count <= 9; 0 leaves count != 15 alone.
    module mod10_counter #(parameter BUG = 0, parameter HELPER = 1) (
      input  wire       clk,
      input  wire       rst,
      input  wire       en,
      output reg  [3:0] count,
      output wire       tc
    );
      wire [3:0] last = (BUG == 1) ? 4'd10 : 4'd9;
      assign tc = en && (count == last);
    
      always @(posedge clk) begin
        if (rst)      count <= 4'd0;
        else if (en)  count <= (count == last) ? 4'd0 : count + 4'd1;
      end
    
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
    
      // P2: count is never 15. True, but NOT inductive on its own: the unreachable states
      // 10..14 satisfy it and lead to 15 (and en = 0 lets a state repeat forever, so no k
      // of k-induction closes it).
      always @(*) if (!init) a_not15: assert (count != 4'd15);
      // P1 / helper: count <= 9. Inductive on its own (k = 1), and it makes a_not15 inductive.
      generate if (HELPER) begin : g_helper
        always @(*) if (!init) a_range: assert (count <= 4'd9);
      end endgenerate
    
      always @(*) if (!init) a_tc: assert (tc == (en && count == 4'd9));
      always @(posedge clk) if (!init && !$past(rst)) begin
        a_step: assert (!$past(en) || count == (($past(count) == 4'd9) ? 4'd0 : $past(count) + 4'd1));
        a_hold: assert ($past(en) || count == $past(count));
      end
    
      // sticky flag: the counter has wrapped at least once
      reg wrapped;
      always @(posedge clk) begin
        if (rst) wrapped <= 1'b0;
        else if (tc) wrapped <= 1'b1;
      end
      always @(*) if (!init) begin
        c_nine: cover (count == 4'd9);
        c_tc:   cover (tc);
        c_wrap: cover (wrapped && count == 4'd3);
      end
    `endif
    endmodule

    Run D removes the helper invariant count <= 9 (an extra true statement that gives the induction step something to stand on). The bounded check passes but the induction step fails: inconclusive.

    mod10_counter.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
    // formal: prove 10 expect unproven slang top mod10_counter define HELPER=0
    `define FORMAL
    // mod10_counter: 4-bit decade counter, counts 0..9 and wraps to 0.
    //   rst (synchronous, active high) : count <= 0
    //   en  = 1                        : count <= (count == 9) ? 0 : count + 1
    //   en  = 0                        : count holds
    //   tc  = en && (count == 9)       : terminal-count pulse (combinational)
    // States 10..15 exist in the 4-bit register but are unreachable from reset.
    // BUG: 0 none, 1 wraps at 10 instead of 9 (counts 0..10, a mod-11 counter).
    // HELPER: 1 (default) includes the helper invariant count <= 9; 0 leaves count != 15 alone.
    module mod10_counter #(parameter BUG = 0, parameter HELPER = 1) (
      input  wire       clk,
      input  wire       rst,
      input  wire       en,
      output reg  [3:0] count,
      output wire       tc
    );
      wire [3:0] last = (BUG == 1) ? 4'd10 : 4'd9;
      assign tc = en && (count == last);
    
      always @(posedge clk) begin
        if (rst)      count <= 4'd0;
        else if (en)  count <= (count == last) ? 4'd0 : count + 4'd1;
      end
    
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
    
      // P2: count is never 15. True, but NOT inductive on its own: the unreachable states
      // 10..14 satisfy it and lead to 15 (and en = 0 lets a state repeat forever, so no k
      // of k-induction closes it).
      always @(*) if (!init) a_not15: assert (count != 4'd15);
      // P1 / helper: count <= 9. Inductive on its own (k = 1), and it makes a_not15 inductive.
      generate if (HELPER) begin : g_helper
        always @(*) if (!init) a_range: assert (count <= 4'd9);
      end endgenerate
    
      always @(*) if (!init) a_tc: assert (tc == (en && count == 4'd9));
      always @(posedge clk) if (!init && !$past(rst)) begin
        a_step: assert (!$past(en) || count == (($past(count) == 4'd9) ? 4'd0 : $past(count) + 4'd1));
        a_hold: assert ($past(en) || count == $past(count));
      end
    
      // sticky flag: the counter has wrapped at least once
      reg wrapped;
      always @(posedge clk) begin
        if (rst) wrapped <= 1'b0;
        else if (tc) wrapped <= 1'b1;
      end
      always @(*) if (!init) begin
        c_nine: cover (count == 4'd9);
        c_tc:   cover (tc);
        c_wrap: cover (wrapped && count == 4'd3);
      end
    `endif
    endmodule

    The step starts from any state, including the unreachable values 10 to 14, which satisfy “count is never 15” and lead to 15. That trace is spurious; the design is fine and the proof is unfinished. Run A, with the helper restored, is its partner.

    What the review looks for

    • Every result names the model, the assumption list, and the depth or proof form.

    • A bounded pass is written “bounded to N”; time-outs are counted apart from passes.

    Verify and debug

    Read the status per property, not the summary line: one failing property among twenty passing ones is still a counterexample. When a run ends without a proof, record which part gave up: the bound, the clock, or the induction step.

    Bounded pass or time-out filed as proven. Symptom: a lab escape after a clean sheet. Cause: reading “no counterexample up to N” as “no counterexample”, or a summary that counts anything without FAIL as a pass. Fix: write “bounded to 10”, keep three counts with the depth beside each bounded line. Found by planting a deep bug and watching the short bound pass, as in Runs B and C.

    “Your block proves in 3 seconds. Do you believe it?” Answer: not yet. Ask for the trigger cover of every implication, the assumption list, and a planted bug the proof catches. A fast pass can be a short bound; only the failing partner shows the proof is looking.

    Check yourself Run C passes and Run B fails on the same design. What may you claim?

    Only the depth changed: 10 versus 11. Claim “bounded to 10”, never “proven”.

    Check yourself Run D passes its bounded check but is inconclusive. Why?

    The induction step fails from unreachable states, so there is no unbounded argument.

    Related topics

    • On-ramp: Formal in One Lesson

    • Verification First Principles: Bounded Proofs

    • Verification First Principles: What Formal Does

    • Later: K-Induction

    Previous

    6 / 183

    Next: A Proof Is Relative to Its Assumptions

    Up next

    Up nextA Proof Is Relative to Its Assumptions

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • A Bug Simulation Misses
    • The Formal Engineer's Split