Why Proofs Do Not Converge

Name the three reasons a proof stalls, compute the depth a bounded proof needs, and watch a cut turn a timer that blocks every engine into a half-second answer.

Lesson
F4.1
Time
20 min
Runs on
SymbiYosys
Check
6 questions

The watchdog passed bmc at depth 40 and was signed off. In silicon, a kick that landed in the very cycle the timer expired still reset the chip. The shortest timeout was 65,536 cycles, so no 40-step run ever reached an expiry, and the report never said so.

The Cone of Influence

The Cone of Influence of the Watchdog AssertionEverything that can change the assertion, traced back from it: the FSM, the expiry compare, the 32-bit timer and the multiplier that sets the limit.
The cone of influence of the watchdog assertionEverything that can change the assertion, traced back from it: the FSM, the expiry compare, the 32-bit timer and the multiplier that sets the limit. input pre, ticks captured at start,each at least 256 datapath pre_q × ticks_q 16 × 16 multiplier counter timer 32-bit counter,cleared by a kick cut expired timer == product: thecut point input kick, start free inputs control state IDLE, RUN, BARK property assertion kick in RUN |=> !bark cut hereclears
Read the diagram as text
  • pre, ticks: captured at start, each at least 256
  • pre_q × ticks_q: 16 × 16 multiplier
  • timer: 32-bit counter, cleared by a kick
  • expired: timer == product: the cut point
  • kick, start: free inputs
  • state: IDLE, RUN, BARK
  • assertion: kick in RUN |=> !bark
  • pre, ticks → pre_q × ticks_q
  • pre_q × ticks_q → expired
  • timer → expired
  • expired → state: cut here
  • kick, start → state
  • kick, start → timer: clears
  • state → assertion

Every block drawn is in the cone. Yosys keeps 67 flop bits after prep; with expired cut, 3. The depth the timer adds is what no bound reaches.

The cone of influence of an assertion is every signal that can reach it through gates and flops, back to the inputs. Tools delete everything else before an engine starts. What matters is what the cone contains, and how far back in time it reaches.

Three things stall a proof. Sequential depth: a counter puts behaviour thousands of cycles away. Hard logic in the cone: a multiplier, a wide compare. And a property that is not inductive (F2.4, F4.6). This watchdog has the first two.

Depth: The Bound Against the Bug

Where the Bug Lives on the Real Timer, and Where Depth 40 StopsThe faulty watchdog's counterexample on the real timer: reset, start, 65,536 cycles of counting, then the kick that lands on the expiry. Depth 40 checks samples 0 to 38.
Where the bug lives on the real timer, and where depth 40 stopsThe faulty watchdog's counterexample on the real timer: reset, start, 65,536 cycles of counting, then the kick that lands on the expiry. Depth 40 checks samples 0 to 38. step step 0: reset assumed step step 1: start pre and tickscaptured, each atleast 256 wait steps 2 to65,537 RUN: timer counts 0to 65,535 step step 65,538: kickon expiry the faulty FSM teststhe expiry first bad step 65,539:bark the violation;reported at step65,540 bound depth 40 ends atsample 38 required depth:65,541 stops inside the wait
Read the diagram as text
  • step 0: reset: assumed
  • step 1: start: pre and ticks captured, each at least 256
  • steps 2 to 65,537: RUN: timer counts 0 to 65,535
  • step 65,538: kick on expiry: the faulty FSM tests the expiry first
  • step 65,539: bark: the violation; reported at step 65,540
  • depth 40 ends at sample 38: required depth: 65,541
  • step 0: reset → step 1: start
  • step 1: start → steps 2 to 65,537
  • steps 2 to 65,537 → step 65,538: kick on expiry
  • step 65,538: kick on expiry → step 65,539: bark
  • depth 40 ends at sample 38 → steps 2 to 65,537: stops inside the wait

Depth 40 covers 0.06 % of the bound the bug needs. The steps on the real timer follow from the measured cut trace plus 65,536 counting cycles.

Run on the faulty watchdogMeasured result
bmc, depth 20BMC PASS to depth 20, 1.2 s
bmc, depth 40BMC PASS to depth 40, 5.8 s
bmc, depth 60time limit (20 s), no verdict
cover of state == RUN && expired, depth 40cover unreached within depth 40, 5.4 s
prove, k-induction, depth 20UNKNOWN at depth 20, 1.1 s
prove, abc pdrtime limit (20 s), no verdict

All smtbmc yices except the last row. The design has a real bug, and not one run says FAIL.

The shortest timeout is 256 × 256 = 65,536 cycles. The cut run below fails at step 4; on the real timer the same failure sits 65,536 steps later, so a bounded proof needs depth 65,541.

This is the proof-depth argument: derive the required bound from the deepest counter and the longest transaction before you trust a bounded PASS, and back it with covers. Here the expiry cover is unreached, so the runs never exercised the logic that matters.

Two Ways a Run Fails to Finish

bmc depth, smtbmc yices510152080
fifo4sb: 4 × 8 FIFO with its scoreboard (F3.4)BMC PASS to depth 5, 0.5 sBMC PASS to depth 10, 0.9 stime limit (20 s), no verdicttime limit (20 s), no verdict–
ctr10: a modulo-10 counter (F1.6)–––BMC PASS to depth 20, 0.5 sBMC PASS to depth 80, 0.6 s

Same tool, same mode, same depths. The cone decides.

The watchdog fails the first way: the bound is too deep to reach, and every affordable depth says PASS, which means nothing. The spine FIFO fails the second way: a modest depth is too slow to reach, and the run ends with no verdict at all.

Both are called "the proof does not converge", and they have different cures. A deep bound needs abstraction of the deep element (F4.2, F4.5). A slow solver needs an invariant (F4.6) or another engine (F4.7).

The Cone: What Every Step Carries

Design, bmc depth 40Measured result
faulty, limit pre_q * ticks_qBMC PASS to depth 40, 5.8 s
faulty, limit {ticks_q, pre_q}BMC PASS to depth 40, 0.7 s
fixed, limit pre_q * ticks_qBMC PASS to depth 40, 0.7 s

Same timer, same depth. Only the logic in the cone, or the need to reason through it, changes.

Each unrolled step copies the whole cone. To print PASS for the faulty design, the solver must show that timer never equals the product within 40 steps: arithmetic, in every copy. With a concatenated limit that claim is trivial. On the fixed design no failure depends on it, so the solver never asks.

Cut the Timer

Real timer

wire expired = timer == pre_q * ticks_q;

Faulty design: BMC PASS to depth 20, 1.2 s at depth 20.

Cut point

wire expired = $anyseq;

Faulty design: FAIL at step 4, 0.5 s.

01234clkfreerstfreestartfreekickfreeexpiredstateIDLERUNBARKbarkkick and expiry togetherbark: the violationreported

Measured counterexample with the timer cut (bmc, depth 20): FAIL at step 4, 0.5 s. expired is now a free input, shaded like the others.

A cut point makes expired a free input: any value in any cycle. Every behaviour of the real timer is still possible, plus more. In the trace the solver raises expired in the first RUN cycle together with a kick, and the FSM barks at step 3.

After the cutMeasured result
faulty, timer cut, bmc depth 20FAIL at step 4, 0.5 s
fixed (kick tested first), timer cut, prove depth 2Prove PASS, 0.5 s
fixed, real timer, prove depth 2Prove PASS, 0.5 s

The faulty twin still fails after the cut: the cut hid nothing.

Is the failure real? It needs only a kick in an expiry cycle, which the real timer reaches after 65,536 cycles. So it is a real bug, found in 0.5 s. The last row shows k-induction never needed the timer's value at all. F4.2 shows a cut whose failure is false.

Asked in Interviews

QuestionAnswer
Why does my proof not converge?Something in the cone is deep (counters, long transactions), hard (arithmetic, memories), or the property is not inductive. Find which: depth shows in unreached covers, hard logic in solver time, non-induction in an UNKNOWN with a strange step 0.
What is a proof-depth argument?The bound a bounded proof must reach, computed from the deepest counter and the longest transaction. A bounded PASS below it says nothing about the deep behaviour; here the bug needs depth 65,541.
What is the cone of influence?Every signal that can affect the property. Tools prune the rest automatically, so convergence depends on what is inside the cone, not on design size.

Try It

The lab is the faulty watchdog, bmc at depth 20 with smtbmc yices. Measured: BMC PASS to depth 20, 1.2 s. The rule in concurrent SVA is shown first; the design carries its immediate form.

The rule. The lab runs it as f_kick plus an immediate assertion.
assert property (@(posedge clk) disable iff (rst)
  state == RUN && kick |=> !bark);
  1. Read the claim

    Note what BMC PASS at depth 20 promises, and what the required bound is.

  2. Cut the timer

    Replace the right side of wire expired = ... with $anyseq; and run again. Read the step where kick and expired are both 1.

  3. Fix and prove

    Swap the two tests in the RUN branch so the kick wins, set mode prove and depth 2, and run with the cut and without it.

design.sv

props.sv

Use assert property (@(posedge clk) ...);. Leave blank for immediate assertions in the design.

Inputs are free unless constrained. Run to find a counterexample or check a proof.

Model: one positive-edge clock, binary data. Sampled history requires declaration-initialized variables; primary-input history is unsupported.

SBY summary and log

Check Yourself

Predict the outputThe faulty watchdog with expired cut to $anyseq, mode bmc, depth 20, smtbmc yices, reset assumed in step 0. At which step does SBY report the failure?
module wdt(input clk, rst, start, kick, input [15:0] pre, ticks, output bark);
  localparam IDLE = 2'd0, RUN = 2'd1, BARK = 2'd2;
  reg [1:0] state = IDLE;
  reg [15:0] pre_q = 0, ticks_q = 0;
  reg [31:0] timer = 0;
  wire expired = $anyseq;                     // cut point: any value, every cycle
  assign bark = state == BARK;
  always @(posedge clk)
    if (rst) begin state <= IDLE; timer <= 0; end
    else case (state)
      IDLE: if (start) begin pre_q <= pre; ticks_q <= ticks; timer <= 0; state <= RUN; end
      RUN: if (expired) state <= BARK;     // bug: expiry tested first
           else if (kick) timer <= 0;
           else timer <= timer + 32'd1;
      default: if (kick) state <= IDLE;
    endcase
  always @(posedge clk) begin
    if ($initstate) begin assume(rst); end else begin assume(!rst); end
  end
  always @(*) assume(pre >= 16'd256 && ticks >= 16'd256);   // the shortest timeout
  // a kick while running means no bark in the next cycle
  reg f_kick = 0;
  always @(posedge clk) begin
    f_kick <= !rst && state == RUN && kick;
    if (!rst && f_kick) assert(!bark);
  end
endmodule
Predict the outputThe faulty watchdog with the real timer, mode bmc, depth 20, smtbmc yices, reset assumed in step 0. The verdict is:
module wdt(input clk, rst, start, kick, input [15:0] pre, ticks, output bark);
  localparam IDLE = 2'd0, RUN = 2'd1, BARK = 2'd2;
  reg [1:0] state = IDLE;
  reg [15:0] pre_q = 0, ticks_q = 0;
  reg [31:0] timer = 0;
  wire expired = timer == pre_q * ticks_q;
  assign bark = state == BARK;
  always @(posedge clk)
    if (rst) begin state <= IDLE; timer <= 0; end
    else case (state)
      IDLE: if (start) begin pre_q <= pre; ticks_q <= ticks; timer <= 0; state <= RUN; end
      RUN: if (expired) state <= BARK;     // bug: expiry tested first
           else if (kick) timer <= 0;
           else timer <= timer + 32'd1;
      default: if (kick) state <= IDLE;
    endcase
  always @(posedge clk) begin
    if ($initstate) begin assume(rst); end else begin assume(!rst); end
  end
  always @(*) assume(pre >= 16'd256 && ticks >= 16'd256);   // the shortest timeout
  // a kick while running means no bark in the next cycle
  reg f_kick = 0;
  always @(posedge clk) begin
    f_kick <= !rst && state == RUN && kick;
    if (!rst && f_kick) assert(!bark);
  end
endmodule
ConceptThe faulty watchdog passes bmc at depth 40. What has been shown?
ConceptOne run returns PASS at every depth it can afford; another ends at the time limit at depth 15. Which cure fits which?
ConceptWhich statement about the required depth is wrong?
Read the waveformIn the measured trace with the timer cut, at which step do the kick and the free expiry coincide?
01234clkfreerstfreestartfreekickfreeexpiredstateIDLERUNBARKbarkkick and expiry togetherbark: the violationreported

Measured counterexample with the timer cut (bmc, depth 20): FAIL at step 4, 0.5 s. expired is now a free input, shaded like the others.

Remember

  • Convergence depends on what is in the cone, not on design size.
  • Depth, hard logic and non-inductive properties are the three causes; each has its own symptom.
  • Compute the required bound before trusting a bounded PASS, and prove covers that reach it.
  • A cut point adds behaviour: it never hides a bug, and its failures must be checked for realism.