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 16-bit timer: depth 40 against the depth expiry needs

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

The same watchdog with a 4-bit timer

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

A kick in the expiry cycle, and the bark anyway
0123456clkfreekicktmr111213141501barktmr = 15 and kickbarkreported step 18

Measured with smtbmc yices: FAIL at reported step 18, 0.5 s. Steps 12 onward; steps 1 to 11 count up with kick low.

AbstractionWhat it keepsWhat it costs
Reduce: TMR_W = 4 by parameterEvery control decision, because the timer is only compared with its maximumA design that compares the timer with a middle value does not reduce this way
Free: an expired input with an assumptionEvery behaviour the real timer has, and moreA PASS carries over; a FAIL may be a trace the real timer cannot make
Neither: prove at full widthEverythingWorks 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

Where the Induction Step Starts, and How Far Reset Is from Itbmc walks forward from reset. The induction step starts anywhere the property held, so it can start one cycle before expiry.
Where the induction step starts, and how far reset is from itbmc walks forward from reset. The induction step starts anywhere the property held, so it can start one cycle before expiry. component reset tmr = 0 component 65535 quietcycles bmc would have tounroll every one component induction start tmr = 65535, kick =1: chosen by thesolver bad bark with a kick BUG_KICK fails here;the fixed bark stayslow reachable, but farone step
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.

RunMeasured result
Fixed, TMR_W = 16, prove k = 2Prove PASS (k = 2), 0.5 s
BUG_KICK, TMR_W = 16, prove k = 2UNKNOWN at k = 2 (base case passed, induction step failed), 0.5 s
Fixed, TMR_W = 4, prove k = 2Prove PASS (k = 2), 0.5 s
Fixed, TMR_W = 16, bmc 40BMC 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

QuestionAnswer
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

The lab task. The twins change the width, the bug, the mode and the depth.
[options]
mode bmc
depth 20
[engines]
smtbmc yices
[script]
read_verilog -formal -sv design.sv
prep -top wdt16
[files]
design.sv
The properties in concurrent SVA.
assert 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);
As the engine runs them: registered copies of last cycle's expiry and kick.
  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);
  end
  1. Worksheet first

    For each width, write the required depth for cover(bark) before you run anything.

  2. Run both widths

    16 bits at depth 40: BMC PASS to depth 40. 4 bits at depth 20: FAIL at reported step 18.

  3. 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

Predict the outputwdt16 with TMR_W = 16, BUG_KICK on, mode bmc, depth 40, smtbmc yices, reset assumed in step 0. The verdict is:
module wdt16 #(parameter W = 16)(input clk, rst, kick, output reg bark = 0);
  localparam [W-1:0] MAX = {W{1'b1}};
  reg [W-1:0] tmr = 0;
  always @(posedge clk)
    if (rst) begin tmr <= 0; bark <= 0; end
    else begin
      tmr <= kick ? {W{1'b0}} : tmr == MAX ? MAX : tmr + 1'b1;   // holds at MAX until kicked
      bark <= tmr == MAX;            // bug: the kick term is missing
    end
`ifdef FORMAL
  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
  end
`endif
endmodule
ConceptWhich abstraction note is wrong?
ConceptWhy does prove not need to reach step 65536?
Read the waveformIn the 4-bit trace, in which step does kick arrive, and why does bark still fire?
A kick in the expiry cycle, and the bark anyway
0123456clkfreekicktmr111213141501barktmr = 15 and kickbarkreported step 18

Measured with smtbmc yices: FAIL at reported step 18, 0.5 s. Steps 12 onward; steps 1 to 11 count up with kick low.

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.