Design for Formal, and the Formal Engineer's Week
Know the design habits that make a block cheap to prove, see one measured before-and-after, and know what a formal engineer's week is made of.
- Lesson
- F9.3
- Time
- 20 min
- Runs on
- SymbiYosys
- Check
- 4 questions
Two engineers were given the same FIFO to prove. One got the simulation RTL: int counters, a debug timestamp in the control, two reset styles. The other got it after an afternoon of changes by its designer. The first spent two days writing helpers; the second proof closed on the first run, with the function unchanged.
Seven Habits
| Habit | Before | After |
|---|---|---|
| State as wide as it needs to be | int wp, rp, used; | reg [1:0] wp, rp; reg [2:0] used; |
| Control apart from data | payload bits steer the queue | the queue never reads the payload (data independence, F4.4) |
| No free-running counter in control | a 16-bit timer in every cone | a FORMAL switch narrows it to 4 bits (F8.3) |
| One reset style | async on half the flops, sync on the rest | one synchronous reset |
| Exposed state | the count is internal | the count is a port or a bindable signal |
| Small blocks, clean interfaces | one module with three jobs | three modules, each with a handshake |
| Parameters for depth and width | magic numbers | DEPTH, WIDTH that a bench can shrink (F4.5) |
Seven habits a designer can adopt in an afternoon. None changes the function.
Each habit removes states the design never reaches but the solver must still consider. A 32-bit pointer that only ever holds 0 to 2 leaves four billion values the induction step may start from.
The habits cost little when the RTL is first written: a parameter, a narrower declaration, a switch around debug logic. Ask for them at the design review, before the first UNKNOWN arrives.
One FIFO, Two Encodings
fifo_f, two- and three-bit state | Same FIFO, int state (A8.1) | |
|---|---|---|
| Flop bits in the RTL (Yosys) | 16 | 105 |
abc pdr, no helpers | Prove PASS (abc pdr), 1.3 s | Prove PASS (abc pdr), 1.5 s |
| k-induction, k = 12, no helpers | UNKNOWN at k = 12 (base case passed, induction step failed) | UNKNOWN at k = 12 (base case passed, induction step failed) |
| k-induction, k = 12, F8.1's helpers | Prove PASS (k = 12), 0.9 s | UNKNOWN at k = 12 (base case passed, induction step failed) |
DEPTH 3, WIDTH 8, the same F8.1 properties and tracker in both; reset assumed in step 0. Only the declarations of wp, rp and used differ.
IC3 closes both in about the same time: it builds its own invariant and the extra bits cost little here. k-induction tells them apart. The helpers that close fifo_f leave the int copy UNKNOWN: its induction step starts with wp = -4 and rp = -2, which pass wp < DEPTH because int is signed (F2.4, F4.6).
So the cost of wide state is not always seconds. It is the helper set. The narrow design needs F8.1's helpers; the wide one needs more, written by someone who knows the design well.
A Timer the Bench Can Shrink
wdt16 | Cover bark, depth 40 |
|---|---|
| 16-bit timer, as built | cover unreached within depth 40 |
4-bit timer, the FORMAL setting | Cover PASS, last cover reached at step 18 |
F8.3's watchdog: the bark needs a full count, 65,535 cycles at 16 bits. One parameter the bench can override brings it within reach.
A designer who writes the width as a parameter has done the abstraction for you; one who hard-codes 16 bits leaves you to cut the counter yourself (F4.5).
The Week
Read the diagram as text
- Monday: read: spec and RTL; plan rows and assumptions with the designer
- Tuesday: wiggle: covers before asserts; first traces shown to the designer
- Wednesday: debug: counterexamples, one by one: RTL, assumption, property or reset
- Thursday: converge: helpers, a split, an engine change for the UNKNOWNs
- Friday: regress: nightly runs, plan fields filled, assumptions handed to DV
- Monday: read → Tuesday: wiggle
- Tuesday: wiggle → Wednesday: debug
- Wednesday: debug → Thursday: converge
- Thursday: converge → Friday: regress
- Thursday: converge → Wednesday: debug: a new trace
Most hours go to reading traces with the designer. The proof is the last of them.
Formal work is reading: specs, RTL and traces, mostly with the designer in the room. Convergence work comes after the bench is right, and regressions after that.
The handoff to simulation is weekly (F7.4). The assumption list and the cover traces go out; the question "did the system keep our assumptions?" comes back. Ask designers to run covers on fresh RTL before any testbench exists.
A Third Block, Without Running It
module ctr_dbg(input clk, rst, arst_n, en, fast_in, ...);
always @(posedge clk) if (rst) count <= 0; else ... // synchronous
always @(posedge clk or negedge arst_n) // asynchronous,
if (!arst_n) dbg <= 0; else dbg <= count; // debug mirror onlyIt breaks "one reset style". The bench must say what the second reset is: tie it to the first, or let SBY model it with async2sync or multiclock on (F1.6). Either is a plan entry with a reason.
Asked in Interviews
| Question | Answer |
|---|---|
| What does a formal engineer do all day? | Plan from the spec, wiggle with covers, debug traces with the designer, converge proofs, regress, and hand assumptions and cover traces to DV. |
| What is design for formal? | Right-sized state, control apart from data, no free-running counters in control, one reset, exposed state, small blocks, parameters. |
| What would you ask a designer to change first? | Whatever the checklist flags, with a measured pair like the FIFO above as the reason. |
Try It
The lab is fifo_f with F8.1's helpers under k-induction: Prove PASS (k = 12), 0.9 s. The twins are its int copy and the two timers. Fill the table from the summaries.
fifo_f (F8.1)
reg [1:0] wp = 0, rp = 0;
reg [2:0] used = 0;
Prove PASS (k = 12) with the helpers.
As A8.1 declares it
integer wp = 0, rp = 0, used = 0;
UNKNOWN at k = 12 (base case passed, induction step failed) with the same helpers.
Run the pair
Run the lab and its
inttwin. Open the twin's induction trace and readwpandrpin step 0.Run IC3
Run both under
abc pdrand compare the times.Run the timers
Run the bark cover at both widths.
Name the habit
For the third block above, name the habit it breaks and the bench line it needs.
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
- Design habits that remove unreachable states: right-sized state, control apart from data, narrowed timers, one reset, exposed state, small blocks, parameters.
- Measured: IC3 closes both FIFO encodings in similar time; k-induction with the same helpers closes only the narrow one.
- A parameterised timer turns an unreachable cover into a reached one.
- A formal week is mostly reading traces with the designer; the proof comes last.