Counters, Timers and Shift Registers
Read a bounded PASS against the depth a counter needs, shrink the counter soundly, and let induction prove the full-width timer without ever reaching expiry.
- Lesson
- F8.3
- Time
- 20 min
- Runs on
- SymbiYosys
- Check
- 4 questions
A watchdog had one rule: a kick in the same cycle as expiry stops the bark. The formal bench ran bmc to depth 40 and reported PASS, and the team shipped. Expiry was 65535 cycles from reset, so the run had never seen it. The bark fired on a kicked system after hours of uptime.
The timer leaves reset at 0 in step 1 and reaches 65535 at step 65536. The bark that depends on it is sampled at step 65537 and reported at 65538, so a bmc run needs depth 65539 to hold that trace.
Logarithmic scale: position uses log(1 + bound); values below are exact. 0 to 65539 steps.
- Checked bound
- 40
- Required bound
- 65539
- Claim
- Bounded check only
Measured: BUG_KICK at depth 40, BMC PASS to depth 40; cover(bark) cover unreached within depth 40. Unreached within the bound, never "unreachable".
Required Depth Comes First
Expiry is 15 kick-free cycles from reset; the bug is reported at step 18, so bmc needs depth 19, inside the default bound of 20.
Linear scale. 0 to 20 steps.
- Checked bound
- 20
- Required bound
- 19
- First reported failure
- 18
- Claim
- Counterexample found
Measured: BUG_KICK, FAIL at reported step 18; cover(bark) on the fixed design reached at step 18.
A counter in the control path sets the depth a bounded run needs (F7.3). A property about expiry needs the timer to expire: step 65536 here, then one step to sample the bark and one to report it, so bmc needs depth 65539. Depth 40 says nothing about it.
The cover says the same thing in the tool's own words: cover(bark) comes back unreached within 40. Read that as a warning about the bound, not as dead logic.
Shrink the Counter Soundly
Measured with smtbmc yices: FAIL at reported step 18, 0.5 s. Steps 12 onward; steps 1 to 11 count up with kick low.
| Abstraction | What it keeps | What it costs |
|---|---|---|
Reduce: TMR_W = 4 by parameter | Every control decision, because the timer is only compared with its maximum | A design that compares the timer with a middle value does not reduce this way |
Free: an expired input with an assumption | Every behaviour the real timer has, and more | A PASS carries over; a FAIL may be a trace the real timer cannot make |
| Neither: prove at full width | Everything | Works only when the property is inductive |
Counter abstraction (F4.5). Each choice is written down with its soundness note in the sign-off.
With four bits the bug appears at reported step 18: kick and expiry in the same cycle, and the bark one cycle later. The argument that carries this back to 16 bits is about the RTL, not the tool: the timer feeds only tmr == MAX.
The free route deletes the timer instead. An expired input chosen by the solver replaces it, with an assumption that it rises only after a kick-free cycle and a cover that it rises at all. Every real behaviour is still possible, so a PASS carries over to the real timer.
Induction Needs No Bound
Read the diagram as text
- reset: tmr = 0
- 65535 quiet cycles: bmc would have to unroll every one
- induction start: tmr = 65535, kick = 1: chosen by the solver
- bark with a kick: BUG_KICK fails here; the fixed bark stays low
- reset → 65535 quiet cycles
- 65535 quiet cycles → induction start: reachable, but far
- induction start → bark with a kick: one step
The measured induction start of the buggy run. Its reachability is your argument, not the tool's output.
| Run | Measured result |
|---|---|
Fixed, TMR_W = 16, prove k = 2 | Prove PASS (k = 2), 0.5 s |
BUG_KICK, TMR_W = 16, prove k = 2 | UNKNOWN at k = 2 (base case passed, induction step failed), 0.5 s |
Fixed, TMR_W = 4, prove k = 2 | Prove PASS (k = 2), 0.5 s |
Fixed, TMR_W = 16, bmc 40 | BMC PASS to depth 40, 0.6 s |
smtbmc yices. The watchdog has 17 flops: the timer and bark.
"Bark only after an unkicked expiry" is about one transition, so induction proves it at k = 2 for the 16-bit timer. On the buggy design the induction step fails from a start with tmr = 65535 and kick = 1. Is that state reachable? Yes, after 65535 quiet cycles: you argue it, the tool cannot show it in budget.
A shift register is the same lesson in another shape. A 32-stage register needs 32 steps before its last stage sees the first input: cover(dout) is reached at reported step 34.
Asked in Interviews
| Question | Answer |
|---|---|
| Why does a long timer block a proof? | It sets the required depth beyond any bound. A bmc PASS below it says nothing about expiry. |
| What is counter abstraction? | A smaller counter, or a free "expired" signal under assumptions, with a written reason why the control behaviour is unchanged. |
| When does prove make the abstraction unnecessary? | When the property is about one transition and is inductive at full width. |
Try It
[options]
mode bmc
depth 20
[engines]
smtbmc yices
[script]
read_verilog -formal -sv design.sv
prep -top wdt16
[files]
design.svassert property (@(posedge clk) disable iff (rst) bark |-> $past(tmr == MAX && !kick));
assert property (@(posedge clk) disable iff (rst) kick |=> tmr == 0);
cover property (@(posedge clk) disable iff (rst) bark); always @(posedge clk) begin
if ($initstate) begin assume(rst); end else begin assume(!rst); end
end
reg f_expire_q = 0, f_kick_q = 0; // registered copies: last cycle's expiry and kick
always @(posedge clk) begin
f_expire_q <= !rst && tmr == MAX && !kick;
f_kick_q <= !rst && kick;
end
always @(posedge clk) if (!rst) begin
assert(!bark || f_expire_q); // bark |-> $past(tmr == MAX && !kick)
assert(!f_kick_q || tmr == 0); // kick |=> tmr == 0
c_bark: cover(bark);
endWorksheet first
For each width, write the required depth for
cover(bark)before you run anything.Run both widths
16 bits at depth 40: BMC PASS to depth 40. 4 bits at depth 20: FAIL at reported step 18.
Prove
Fix the bark, set the width back to 16 and run prove at k = 2: Prove PASS (k = 2). Write the soundness note that links the two results.
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
- A counter sets the required depth; compare every bounded PASS with it.
- An unreached cover within the bound is a warning about the bound, not proof of dead logic.
- Reduce a counter only with a written reason; a free signal under assumptions is the safe over-approximation.
- A one-transition property proves at full width by induction, with no bound at all.