Cover: Reachability, Witnesses and Dead Code
Make covers the first run on a new bench, read a cover trace as a directed test, and sort an unreached cover into one of three causes.
- Lesson
- F3.5
- Time
- 15 min
- Runs on
- SymbiYosys
- Check
- 4 questions
A new FIFO bench was green on its first run, with twenty assertions. A second engineer added three covers before reading any of them, and full came back unreachable. A debugging assume(!push) left in the file had turned twenty proofs into proofs about an empty FIFO.
Measured with smtbmc yices at depth 20. One reset step, sixteen pushes on steps 1 to 16, full at step 17, reported at 18. No pop: a shortest trace never wastes a step.
The First Run on Every Bench
Read the diagram as text
- cover(full): the state you want to see
- trace.vcd: every signal, step by step
- trace_tb.v: the same inputs as a Verilog testbench
- Any simulator: replays the inputs on the same RTL
- The same waveform: count climbs to 16, full rises
- cover(full) → trace.vcd: solver
- cover(full) → trace_tb.v: solver
- trace_tb.v → Any simulator
- Any simulator → The same waveform
The cheapest directed test there is: one line of SVA, no sequence to write.
A cover asks the tool to reach a condition and to show the shortest sequence that does (F1.3). From reset, full on a sixteen-entry FIFO needs sixteen pushes, so count before you run: step 0 is reset, and sixteen pushes end at step 16. The measured trace has full at step 17, reported at 18.
Run the covers before you trust any assertion. They show the design moving under the assumptions you wrote. This is the "wiggling" stage of a new bench (F1.4).
One Leftover Line, Every Cover Gone
| Cover | No assumption | Leftover assume(!push) |
|---|---|---|
full | reached, reported step 18 | Unreached cover statement at top: c_full |
push && pop && full | reached, reported step 18 | Unreached cover statement at top: c_both_full |
overflow | Unreached cover statement at top: c_overflow | Unreached cover statement at top: c_overflow |
fifo16 with an overflow flag; mode cover, depth 20, smtbmc yices, reset assumed in step 0. 622 ms and 537 ms on this machine.
With the leftover assumption, nothing can enter the FIFO and all three covers fail. Every assertion in that bench was checked against an empty FIFO only. The covers took one line each.
Write the first covers from the spec, not from the RTL: each queue empty and full, each state of each machine, each input at its extremes, each documented flow. One sentence of the spec, one cover.
Why a Cover Is Unreached
Read the diagram as text
- 1. The depth: the trace needs more steps than the task allows
- 2. An assumption: it removed every sequence that reaches the state (F3.2)
- 3. Dead logic: no input sequence reaches it: a bug, or a register to delete
- Raise the depth: count the steps from reset first
- Read the assumptions: delete leftovers; justify the rest
- Read the RTL: find the condition that contradicts itself
- 1. The depth → 2. An assumption
- 2. An assumption → 3. Dead logic
- 1. The depth → Raise the depth
- 2. An assumption → Read the assumptions
- 3. Dead logic → Read the RTL
Each reason has its own fix, and the depth is the cheapest one to rule out.
The overflow flag (dead)
wire wr_en = push && !full;
...
if (wr_en && full) overflow <= 1;
wr_en already requires !full, so the set condition contradicts itself.
What the designer meant
if (push && full) overflow <= 1;
Report the push the write gate refused.
overflow is unreached with no assumption in the way: dead logic. The depth is the boring third reason. At depth 10, cover(full) gives "Cover unreached within the depth", because sixteen pushes do not fit in ten steps.
Dead logic is either a bug or a register to delete, and only the designer can say which. Here the designer meant to report a refused push, so the flag is a bug. Either way, an unreached cover on it was the first sign.
Asked in Interviews
| Question | Answer |
|---|---|
| What is a cover property for in formal? | A reachability witness and a free directed test; the first run on any bench. |
| What does an unreachable cover tell you? | Within this depth, under these assumptions, no input sequence reaches it. Rule out the depth, then look for an over-constraint or dead logic. |
Try It
The covers in SVA
cover property (@(posedge clk) !rst && full);
cover property (@(posedge clk) !rst && push && pop && full);
cover property (@(posedge clk) !rst && overflow);
Three states the bench should be able to reach.
As the editor runs them
always @(posedge clk) if (!rst) begin
c_full: cover(full);
c_both_full: cover(push && pop && full);
c_overflow: cover(overflow);
end
The labels name each cover in the log.
[options]
mode cover
depth 20
[engines]
smtbmc yices
[script]
read_verilog -formal -sv design.sv
prep -top fifo16
[files]
design.svRun the covers
Run as it is: Cover unreached within the depth. Two covers are reached at reported step 18; read which one the log names.
Count the pushes
Delete the other two covers and run
cover(full)alone. Count the pushes in the trace.Plant the leftover
Add
assume(!push);above the covers and run. Which assumption would you look for first?Read the overflow RTL
Decide which of the three reasons applies to
overflow, and what the designer meant.Shrink the depth
Set depth 10 and run
cover(full): Cover unreached within the depth.
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 cover asks the tool to reach a condition and returns the shortest trace: a directed test it wrote for you.
- Run covers first on any new bench, before trusting an assertion.
- An unreached cover has three causes: the depth, an assumption or dead logic. Check them in that order.
- One leftover assumption can empty a whole bench; one cover per key state exposes it.