Unreachability and Coverage Closure
Ask three different questions of a coverage bin, prove the dead ones dead, reach the live ones, and write a waiver that survives review.
- Lesson
- F5.7
- Time
- 15 min
- Runs on
- SymbiYosys
- Check
- 4 questions
A regression sat at 99.4 % coverage for a month while three engineers chased the last twelve bins. One bin was waived as unreachable after a formal run, and it was not. The run had been a bounded cover at depth 20, the bin needed 30 cycles of setup, and nobody had written the depth down.
Measured with smtbmc yices, mode cover, depth 30. The last steps shown; steps 1 to 24 are all idle. A depth-20 run cannot hold this trace.
Three Questions About One Bin
Read the diagram as text
- A coverage bin: a Boolean condition on the design
- cover(bin), reached: reachable: here is the trace, a directed test
- assert(!bin), Prove PASS: unreachable from reset, for ever, under these assumptions
- cover(bin), unreached at N: not within N steps: a hint about depth
- Waive with the proof attached: mode, engine, assumptions, revision
- Raise N or try a proof: never a waiver by itself
- A coverage bin → cover(bin), reached
- A coverage bin → assert(!bin), Prove PASS
- A coverage bin → cover(bin), unreached at N
- assert(!bin), Prove PASS → Waive with the proof attached
- cover(bin), unreached at N → Raise N or try a proof
The story's team waived a bin on the bottom row and called it the middle one.
A coverage bin is a condition, and formal can ask three things of it. Does some trace reach it (a cover)? Can no trace ever reach it (an assert of its negation under prove)? Does a trace reach it within N steps (a cover at depth N)? Only the second supports a waiver.
The third is the trap. SBY's wording for it is "Unreached cover statement", and the task status is FAIL. That is a fact about N, not about the design (F3.5).
Two Bins on seq25
| Bin | Question | Measured result | Time |
|---|---|---|---|
A: idle_cnt > 24 | assert(!bin), abc pdr | Prove PASS (abc pdr) | 489 ms |
| A | assert(!bin), k-induction | UNKNOWN at k = 1 (base case passed, induction step failed); Prove PASS (k = 2) | 492 ms |
B: idle_cnt == 24 && start | cover(bin), depth 20 | Unreached cover statement at top: bin_b | 510 ms |
| B | cover(bin), depth 30 | Cover reached, reported step 26 | 554 ms |
B, leftover assume(!start) | cover(bin), depth 30 | Unreached cover statement at top: bin_b | 532 ms |
seq25 as fixed in F1.5; smtbmc yices unless named, reset assumed in step 0.
Bin A is the top of a five-bit counter's range: values 25 to 31 that a covergroup would list. The proof shows no trace from reset reaches them. Bin B is live: 25 steps of setup, beyond depth 20 and inside 30 (F1.5).
The last row is the dangerous one. With a leftover assume(!start) the live bin is unreached at any depth: the environment, not the design, removed it (F3.2).
The Waiver Card
A waiver that survives review
bin: idle_cnt > 24 (idle_cnt[25:31])
proof: assert(!bin), mode prove, abc pdr
result: Prove PASS (abc pdr)
assumptions: reset in step 0; none on inputs
revision: seq25 as fixed in F1.5, 2026-11-07
Bin, proof, assumptions, revision: anyone can rerun it.
A waiver that should be refused
bin: idle_cnt == 24 && start
proof: cover not hit, bmc depth 20
result: unreachable
Bounded, and the bin is reached at step 26 of a deeper run.
A proof holds only for the environment it ran in. Running the unreachability check with no input assumptions at all is the safe default: a bin unreachable even when the inputs may do anything is unreachable for real. Every assumption you add must be listed on the card.
Asked in Interviews
| Question | Answer |
|---|---|
| How does formal help close coverage? | It proves the dead bins dead with assert(!bin) under prove, so they can be waived with a proof, and writes traces to the live ones that replay as directed tests. |
| What does a bounded "unreached" prove? | Nothing beyond the depth. |
| What does an unreachable cover tell you? | Dead logic or an over-constraint; the assumption list decides which. |
Try It
The two bins in SVA
assert property (@(posedge clk) disable iff (rst) !(idle_cnt > 5'd24));
cover property (@(posedge clk) !rst && idle_cnt == 5'd24 && start);
Bin A as a proof obligation, bin B as a reachability question.
As this engine runs them
always @(posedge clk) begin
if ($initstate) begin assume(rst); end else begin assume(!rst); end
end
always @(posedge clk) if (!rst) begin
bin_b: cover(idle_cnt == 5'd24 && start);
end
Swap the cover line for the assert to ask about bin A.
mode prove with abc pdr.[options]
mode cover
depth 20
[engines]
smtbmc yices
[script]
read_verilog -formal -sv design.sv
prep -top seq25
[files]
design.svCover at 20
Run as it is and read the exact log line for the unreached cover.
Cover at 30
Set depth 30 and run: Cover reached, reported step 26. Count the idle steps in the trace.
Prove bin A
Replace the cover with
assert(!(idle_cnt > 5'd24))and runmode provewithabc pdr: Prove PASS (abc pdr).Plant an assumption
Add
always @(posedge clk) assume(!start);and rerun the depth-30 cover. Did bin B become unreachable?Write the card
Write the waiver card for bin A from the measured log: bin, proof line, assumptions, revision.
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
- Formal can ask three things of a bin: reachable (cover), unreachable for ever (assert of its negation under prove), unreached within N (bounded cover).
- Only a proof supports a waiver; a bounded unreached cover is a hint about depth.
- A waiver card carries the bin, the proof line, the assumptions and the revision.
- An over-constraint makes a live bin look dead; run unreachability with as few assumptions as possible.