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
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
Reachable states share four units of space between credits and buffer entries. The induction start has eight.
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.
| Round | Assertions | Measured result (prove depth 2) | Induction start |
|---|---|---|---|
| 1 | occ <= 4 | UNKNOWN at depth 2, 0.5 s | cred 4, occ 4, r1 0, r2 0 |
| 2 | plus cred + occ <= 4 | UNKNOWN at depth 2, 0.5 s | cred 4, occ 0, r1 0, r2 1 |
| 3 | plus cred + occ + r1 + r2 == 4 | Prove PASS, 0.5 s | none |
| – | occ <= 4, prove depth 12 | UNKNOWN at depth 12, 0.5 s | not shown |
| – | occ <= 4, abc pdr | Prove PASS, 0.5 s | none |
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
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 8 | UNKNOWN at depth 8, 0.6 s |
| no invariants, prove depth 20 | time limit (20 s), no verdict |
five invariants, prove depth 8, smtbmc yices | Prove PASS, 0.6 s |
five invariants, prove depth 8, smtbmc boolector | Prove PASS, 0.6 s |
five invariants, prove depth 8, smtbmc bitwuzla | Prove PASS, 0.6 s |
five invariants, prove depth 8, smtbmc z3 | Prove PASS, 0.7 s |
five invariants, prove depth 2, smtbmc yices | Prove PASS, 0.5 s |
| five invariants, BUG_RD_ORDER, prove depth 8 | FAIL 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.
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 2 | Measured result | Induction start |
|---|---|---|
count <= 16 only | UNKNOWN at depth 2, 0.5 s | count 1, wr_ptr 28, rd_ptr 28 |
count == wr_ptr - rd_ptr only | Prove PASS, 0.5 s | none |
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
| Question | Answer |
|---|---|
| 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.
assert property (@(posedge clk) disable iff (rst) occ <= 4);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.
Round 2
Run again. Which register in the new step 0 holds space that round 1 did not count? Assert the exact total.
Soundness
Make the receiver return a credit on every pop request (
r1 <= pop;) and runbmcat 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
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.