Induction Strengthening in Practice

Turn an UNKNOWN into a proof with a repeatable recipe: read step 0 of the induction trace, name what is impossible about it, assert that, and run again.

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

The credit counter’s overflow check came back UNKNOWN. The engineer added the obvious helper, credits plus occupancy at most four, and it came back UNKNOWN again with a different start state. That second state held the answer: one credit was still on its way back.

The Recipe

The Strengthening LoopRun prove; on UNKNOWN read step 0 of the induction trace, name the relation it breaks, assert that relation, and run again. Finish with the faulty twin.
The strengthening loopRun prove; on UNKNOWN read step 0 of the induction trace, name the relation it breaks, assert that relation, and run again. Finish with the faulty twin. step run prove k-induction, smalldepth step read step 0 the induction tracestarts anywhere step name what isimpossible a relation resetalways keeps and step0 breaks step assert it a helper, proven inthe same run result PASS then run the faultytwin: it must still fail UNKNOWNagainbase and step pass
Read the diagram as text
  • run prove: k-induction, small depth
  • read step 0: the induction trace starts anywhere
  • name what is impossible: a relation reset always keeps and step 0 breaks
  • assert it: a helper, proven in the same run
  • PASS: then run the faulty twin: it must still fail
  • run prove → read step 0: UNKNOWN
  • read step 0 → name what is impossible
  • name what is impossible → assert it
  • assert it → run prove: again
  • run prove → PASS: base and step pass

A helper is an assertion, never an assumption. An assumed helper would hide every state that breaks it, real bugs included.

k-induction fails when an unreachable state that satisfies every assertion leads to a bad one (F2.4). The induction trace hands you that state as step 0. Find the relation that reset always keeps and step 0 breaks: that is the next helper.

Write the helper as an assertion, so the same run proves it. Repeat until PASS. If step 0 could be reached from reset, the base case will find the failure at a larger depth: then it is a bug, not a missing helper.

Round 1

Round 1: a start state reset never reaches

Reachable states share four units of space between credits and buffer entries. The induction start has eight.

Round 1: a start state reset never reachesReachable states share four units of space between credits and buffer entries. The induction start has eight.Reachable from resetInductionc4 o0Depth 0c3 o1Depth 1c2 o2Depth 2c4 o4Unreachableo5Bad state

Measured start of round 1: cred 4, occ 4, r1 0, r2 0. A send from it makes occ 5. Reset keeps cred + occ at most 4, so the first helper is cred + occ <= 4.

Read the states and transitions
  • c4 o0: initial, depth 0.
  • c3 o1: reachable, depth 1.
  • c2 o2: reachable, depth 2.
  • c4 o4: unreachable, depth 0.
  • o5: bad, depth 1.
  • c4 o0 → c3 o1.
  • c3 o1 → c2 o2.
  • c2 o2 → c3 o1.
  • c3 o1 → c4 o0.
  • c4 o4 → o5 (induction candidate).

Illustration. c = credits held by the sender, o = occupied buffer entries. Reachable states drawn for the first two steps only.

RoundAssertionsMeasured result (prove depth 2)Induction start
1occ <= 4UNKNOWN at depth 2, 0.5 scred 4, occ 4, r1 0, r2 0
2plus cred + occ <= 4UNKNOWN at depth 2, 0.5 scred 4, occ 0, r1 0, r2 1
3plus cred + occ + r1 + r2 == 4Prove PASS, 0.5 snone
–occ <= 4, prove depth 12UNKNOWN at depth 12, 0.5 snot shown
–occ <= 4, abc pdrProve PASS, 0.5 snone

The credit counter: four credits, a four-entry buffer, credits returned two cycles after a pop. smtbmc yices unless noted.

Depth alone does not help: the start state can sit still for any number of steps. PDR finds an invariant by itself (F2.5), but the recipe gives you one you can read, review and reuse.

Round 2: The Trace Moves

012clkcred4occr1r2freewantfreepop

Measured induction trace of round 2 (smtbmc yices, prove depth 2): UNKNOWN at depth 2, 0.5 s.

The new start obeys round 1 (4 + 0 ≤ 4) but holds a credit in the return pipeline: r2 = 1. One cycle later the sender has five credits. The true invariant counts every unit of space wherever it is: with the sender, in the buffer, or on the way back.

With cred + occ + r1 + r2 == 4 the proof closes at depth 2, and round 1’s helper is implied (the second alone: Prove PASS, 0.5 s). The faulty twin returns a credit for an empty pop: FAIL at step 3, 0.5 s (bmc 12), on the round-2 helper, before any overflow.

The Spine FIFO, Closed

fifo4sb with its scoreboard (F3.4)Measured result
no invariants, prove depth 8UNKNOWN at depth 8, 0.6 s
no invariants, prove depth 20time limit (20 s), no verdict
five invariants, prove depth 8, smtbmc yicesProve PASS, 0.6 s
five invariants, prove depth 8, smtbmc boolectorProve PASS, 0.6 s
five invariants, prove depth 8, smtbmc bitwuzlaProve PASS, 0.6 s
five invariants, prove depth 8, smtbmc z3Prove PASS, 0.7 s
five invariants, prove depth 2, smtbmc yicesProve PASS, 0.5 s
five invariants, BUG_RD_ORDER, prove depth 8FAIL at step 3, 0.6 s

The design whose bounded run hit the time limit at depth 15 in F4.1.

Step 0 of the plain induction trace has count 1 with the scoreboard holding 0: the ring and the shadow copy disagree. The invariants state what the design already does: the counts match, and each occupied ring slot holds its scoreboard byte.

Asserted, so the same run proves them. The faulty twin still fails.
assert(count == sb_n);
if (count > 3'd0) assert(mem[rd_ptr] == sb0);
if (count > 3'd1) assert(mem[rd_ptr + 2'd1] == sb1);
if (count > 3'd2) assert(mem[rd_ptr + 2'd2] == sb2);
if (count > 3'd3) assert(mem[rd_ptr + 2'd3] == sb3);

Back to the FIFO of F2.4

fifo16 helper set, prove depth 2Measured resultInduction start
count <= 16 onlyUNKNOWN at depth 2, 0.5 scount 1, wr_ptr 28, rd_ptr 28
count == wr_ptr - rd_ptr onlyProve PASS, 0.5 snone

F2.4 added both helpers. The recipe shows which one the proof needed.

With count <= 16 alone, step 0 has count 1 and equal pointers, which breaks count == wr_ptr - rd_ptr. With that relation alone the proof closes. Add what step 0 breaks; extra helpers cost review time and can slow the solver.

Asked in Interviews

QuestionAnswer
Why did induction fail when BMC found no bug?The induction step started in an unreachable state that satisfies the property and reaches a bad state. Read its step 0.
What is a helper invariant and how do I find one?An asserted fact about internal state that excludes those states. Name the relation reset keeps and step 0 breaks, assert it, and repeat until PASS.
Why assert helpers instead of assuming them?An asserted helper is proven in the same run. An assumed one removes every state that breaks it, including real bugs.

Try It

The lab is the credit counter in prove mode, depth 2, smtbmc yices. Measured: UNKNOWN at depth 2, 0.5 s.

The rule in concurrent SVA.
assert property (@(posedge clk) disable iff (rst) occ <= 4);
  1. Round 1

    Read step 0 of the induction trace. How many units of space do the credits and the buffer hold together? Assert the bound.

  2. Round 2

    Run again. Which register in the new step 0 holds space that round 1 did not count? Assert the exact total.

  3. Soundness

    Make the receiver return a credit on every pop request (r1 <= pop;) and run bmc at depth 12.

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 credit counter with only occ <= 4, mode prove, depth 2, smtbmc yices, reset assumed in step 0. The verdict is:
module crd(input clk, rst, want, pop, output send);
  reg [2:0] cred = 3'd4;      // sender credits
  reg [2:0] occ = 3'd0;       // receiver buffer, four entries
  reg r1 = 0, r2 = 0;         // credit return pipeline
  assign send = want && cred != 3'd0;
  wire do_pop = pop && occ != 3'd0;
  always @(posedge clk)
    if (rst) begin cred <= 3'd4; occ <= 0; r1 <= 0; r2 <= 0; end
    else begin
      cred <= cred - send + r2;
      occ <= occ + send - do_pop;
      r1 <= do_pop; r2 <= r1;
    end
  always @(posedge clk) begin
    if ($initstate) begin assume(rst); end else begin assume(!rst); end
  end
  wire [3:0] f_sum1 = cred + occ, f_sum = cred + occ + r1 + r2;
  always @(posedge clk) if (!rst) begin
    assert(occ <= 3'd4);
  end
endmodule
Predict the outputThe credit counter with occ <= 4 and cred + occ <= 4, mode prove, depth 2, smtbmc yices. The verdict is:
module crd(input clk, rst, want, pop, output send);
  reg [2:0] cred = 3'd4;      // sender credits
  reg [2:0] occ = 3'd0;       // receiver buffer, four entries
  reg r1 = 0, r2 = 0;         // credit return pipeline
  assign send = want && cred != 3'd0;
  wire do_pop = pop && occ != 3'd0;
  always @(posedge clk)
    if (rst) begin cred <= 3'd4; occ <= 0; r1 <= 0; r2 <= 0; end
    else begin
      cred <= cred - send + r2;
      occ <= occ + send - do_pop;
      r1 <= do_pop; r2 <= r1;
    end
  always @(posedge clk) begin
    if ($initstate) begin assume(rst); end else begin assume(!rst); end
  end
  wire [3:0] f_sum1 = cred + occ, f_sum = cred + occ + r1 + r2;
  always @(posedge clk) if (!rst) begin
    assert(occ <= 3'd4);
    assert(f_sum1 <= 4'd4);               // round 1
  end
endmodule
ConceptThe induction trace starts in a state you can reach from reset. What do you conclude?
ConceptWhy read step 0 of the induction trace rather than the failing step?
ConceptWhich line should you not add to close the credit proof?
Read the waveformWhich value in step 0 of the round-2 trace makes it unreachable?
012clkcred4occr1r2freewantfreepop

Measured induction trace of round 2 (smtbmc yices, prove depth 2): UNKNOWN at depth 2, 0.5 s.

Remember

  • The induction trace’s step 0 is the evidence: name what it breaks.
  • Assert helpers; never assume them.
  • Repeat: each round moves the start state until the step passes.
  • Keep only the helpers the proof needs, and rerun the faulty twin.