Automatic Checks
Generate covers and assertions from the RTL itself, read two kinds of verdict in one log, and see what lint can and cannot tell you about the same lines.
- Lesson
- F5.2
- Time
- 15 min
- Runs on
- SymbiYosys
- Check
- 4 questions
A controller listed five of its eight state encodings and sent the rest to a default that assigned x, "because it can never happen". Lint printed one informational note. Nobody asked the only useful question: can the state register reach an unlisted encoding, and what happens if it does?
Measured with smtbmc yices at depth 20. The default writes 3'bxxx, which this engine turns into a free value; the solver picks 6. Sampled at step 5, reported at 6.
Properties the Code Implies
Read the diagram as text
- case (state) with a default: listed states S0 to S4
- mem[idx]: eight entries, a four-bit index
- acc <= acc + in: a 9-bit sum stored in 8 bits
- cover(state == Sk): one per listed state: reachable?
- assert(state <= S4): never an unlisted encoding
- assert(!rd || idx < 8): every read in range
- assert(!acc_en || !sum[8]): no carry dropped
- case (state) with a default → cover(state == Sk)
- case (state) with a default → assert(state <= S4)
- mem[idx] → assert(!rd || idx < 8)
- acc <= acc + in → assert(!acc_en || !sum[8])
No spec was read. Each statement follows from the code, which is both the strength and the limit of the method.
Some properties need no spec. A case implies its state stays among the listed values. An array implies its index stays in range. A narrower destination implies the sum fits. A tool can write these statements for every such line in a design and run them all.
Two kinds of statement come out. Reachability is a cover: each listed state should be reachable. Safety is an assert: no index, encoding or carry should go wrong. One log holds both verdicts.
Two Verdicts in One Log
| Cover | Covers, x default | Covers, default to S0 |
|---|---|---|
state == S0 | reached, reported step 2 | reached, reported step 2 |
state == S1 | reached, reported step 3 | reached, reported step 3 |
state == S2 | reached, reported step 4 | reached, reported step 4 |
state == S3 | reached, reported step 4 | reached, reported step 4 |
state == S4 | reached, reported step 5 | reached, reported step 5 |
state > S4 | reached, reported step 6 | unreached within 20 |
Mode cover, depth 20, smtbmc yices, reset assumed in step 0. 582 ms and 579 ms on this machine.
Every listed state is reachable in both runs. The extra cover, state > S4, is the one that tells them apart: with the x default it is reached at step 6, with default: state <= S0 it is unreached. One arm changed (F1.3).
The asserts find the other two bugs. The index check fails at reported step 10: eight reads from reset take idx to 8. The carry check fails at step 3, the second add. With all three fixed, the set gives BMC PASS to depth 20 and abc pdr proves it in 496 ms.
Lint Says Could, Formal Says Does
| RTL construct | Lint (Verilator, default warnings) | Formal (generated property) |
|---|---|---|
mem[idx], four-bit index | WIDTHTRUNC, on the buggy and the fixed RTL alike | wrap at 9: FAIL at reported step 10; wrap at 7: BMC PASS |
S4 arm missing, default assigns x | nothing | FAIL at reported step 6 |
| 9-bit sum stored in 8 bits | nothing (-Wall reports sum[8] unused) | FAIL at reported step 3 |
| each listed state | nothing | five covers reached by reported step 5 |
Verilator 5.055 devel, --lint-only. Lint reads text and says a line could be wrong; formal reads the state space and says whether it is.
Lint warned about the index on both the buggy and the fixed RTL, because the text is the same: four bits for eight entries. Only the state space shows that the wrap at 9 reaches entry 8. Lint said nothing about the missing arm or the dropped carry.
The limit cuts the other way too. A generated check proves the code agrees with itself. If the spec wanted wrap-around arithmetic, the carry assert is noise; if a state should never be entered, its cover being reached is the bug. Read every generated verdict against the spec.
Asked in Interviews
| Question | Answer |
|---|---|
| What do automatic formal checks find? | Reachable out-of-range indexes, x assignments that matter, dropped carries, unlisted case values and unreachable states, from properties generated out of the RTL. |
| Why run them before writing properties? | They need no spec, they find real bugs and dead logic, and a reached cover per state shows the clock and reset setup works. |
| How is this different from lint? | Lint flags a line that could be wrong. Formal shows whether the bad state is reachable, with a trace. |
Try It
The generated set in SVA
cover property (@(posedge clk) !rst && state == S3);
assert property (@(posedge clk) disable iff (rst) state inside {S0, S1, S2, S3, S4});
assert property (@(posedge clk) disable iff (rst) rd |-> idx < 4'd8);
assert property (@(posedge clk) disable iff (rst) acc_en |-> !sum[8]);
A cover per state, then one assert per construct. inside is the IEEE form of the legal-state check.
As this engine runs them
always @(posedge clk) begin
if ($initstate) begin assume(rst); end else begin assume(!rst); end
end
// generated: state legal, index in range, no dropped carry
always @(posedge clk) if (!rst) begin
a_state: assert(state <= S4);
a_index: assert(!rd || idx < 4'd8);
a_carry: assert(!acc_en || !sum[8]);
end
Clocked immediate statements; the labels name each one in the log.
mode bmc with the same script.[options]
mode cover
depth 20
[engines]
smtbmc yices
[script]
read_verilog -formal -sv design.sv
prep -top lint5
[files]
design.svRun the covers
Run as it is: Cover reached, reported step 6. Read which covers the log names and at which step.
Fix the default
Change the default to
state <= S0and run: Cover unreached within 20. Which cover is unreached now, and why is that good news?Run the asserts
Replace the covers with the three asserts and run with the index bug, then with the overflow bug. Read the step at which each fails.
Fix and prove
With all three fixed, switch to
mode provewithabc pdr: Prove PASS (abc pdr).
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
- An automatic check is a property generated from an RTL construct: a cover per listed state, a legal-state assert, an index bound, a carry check.
- Covers and asserts come back in one log: reached or unreached, PASS or FAIL with a trace.
- Lint flags text that could be wrong; formal shows whether the bad state is reachable.
- Generated checks prove the code agrees with itself. Read each verdict against the spec.