Modelling Code and Helper Assertions
Count outstanding requests in the bench, bind the counter as an assumption and as an assertion, and track one symbolic tag that stands for all of them.
- Lesson
- F3.6
- Time
- 15 min
- Runs on
- SymbiYosys
- Check
- 5 questions
A block allowed four outstanding requests, and its bench counted them in a helper register that someone bounded with assume(f_outstanding <= 4). The block's own counter was two bits wide and wrapped on the fourth accept, so it took a fifth request. The assumption deleted every world with five, the proof passed, and the design ran out of buffer in the lab.
Measured with smtbmc yices at depth 20. Five accepts on steps 1 to 5, no responses taken. Note resp falling at step 5: the design thinks it is empty.
Numbers the Design Keeps to Itself
Read the diagram as text
- Solver: req, req_tag, resp_take
- req4: four credits, a tag queue
- Modelling code: f_outstanding, the f_tag tracker
- Helper assertions: claims about the DUT, proven
- ack, resp, resp_tag: ports
- anyconst f_tag: one value, fixed at step 0
- Solver → req4
- req4 → ack, resp, resp_tag
- ack, resp, resp_tag → Modelling code: reads
- Solver → Modelling code: reads
- anyconst f_tag → Modelling code
- Modelling code → Helper assertions
No arrow runs from the modelling code back into the design. If one did, the checker would be changing what it checks.
Properties often need a number the design does not show: how many requests are outstanding, which byte was pushed fourth. Modelling code computes it in the bench, behind FORMAL: counters, trackers, shadow registers. It reads the design's ports, and for glue properties its state. It never drives anything. In a real project it lives in the bound checker module beside the properties (A7.1), so the RTL file never changes.
A Helper Is a Claim, so Assert It
Run A: the helper as a promise
assert property (@(posedge clk) disable iff (rst) ack |-> req);
assume property (@(posedge clk) disable iff (rst) f_outstanding <= 3'd4);
BMC PASS to depth 20, with the wrap bug in the RTL.
Run B: the helper as a claim
assert property (@(posedge clk) disable iff (rst) ack |-> req);
assert property (@(posedge clk) disable iff (rst) f_outstanding <= 3'd4);
FAIL at reported step 7.
f_outstanding is a claim about the design: accepts minus responses never exceed four. Written as an assumption, it constrains the design through the bench's own arithmetic, the master rule broken by a side door (F3.1). Written as an assertion, it is proven, and later it becomes an invariant for induction (F4.6).
| Run | Measured result | Time |
|---|---|---|
| A: tracker assumed (bmc) | BMC PASS to depth 20 | 537 ms |
| B: tracker asserted (bmc) | FAIL at reported step 7 | 539 ms |
| Cover of a fifth accept, under the assumption | cover reached, reported step 6 | 516 ms |
| Cover of a fifth accept, no assumption | cover reached, reported step 6 | 519 ms |
| Fixed: a three-bit counter, tracker asserted | BMC PASS to depth 20 | 563 ms |
req4 with the two-bit counter; depth 20, smtbmc yices, reset assumed in step 0.
The cover of a fifth accept is reached even under the assumption, at step 6. A cover trace stops at the covered sample, and an assumption binds only the samples inside it. There f_outstanding is still 4; the 5 it forbids is the next sample, after the trace ends. A cover you expected impossible came back reached: a bug report the bmc PASS was hiding.
One Symbolic Tag for All of Them
The tag-swap bug at depth 10. The solver chose f_tag = 00, the tag of the request it could break; any other choice is equally legal. The wrong response tag comes from a slot never written.
(* anyconst *) reg [7:0] f_tag; is a value the solver picks once, at step 0, and holds for the whole run. A tracker that follows the request carrying f_tag follows every request at once, because the solver may pick the worst one. (* anyseq *) is its partner: a fresh value every step, an input without a port.
| Tag tracker run | Measured result | Time |
|---|---|---|
| Fixed design, depth 10 | BMC PASS to depth 10 | 712 ms |
| Tag-swap bug, depth 10 | FAIL at reported step 3 | 516 ms |
| Fixed design, depth 14 | BMC PASS to depth 14 | 18091 ms |
A tracker over data costs depth: compare the two depths on the same fixed design, measured on the authoring machine. On the shared server depth 14 exceeds the 20-second budget, so the editor keeps depth 10. F4.4 builds the formal scoreboard on this idea.
Asked in Interviews
| Question | Answer |
|---|---|
| What is modelling code in a formal testbench? | Bench logic that computes what the properties need and the design does not export. It is part of the checker. |
| Should a helper be an assume or an assert? | An assert. It is a claim about the design, it gets proven, and it strengthens induction. |
What is anyconst for? | A symbolic constant: one tracked value that stands for every value. |
Try It
In concurrent SVA over bench signals
assert property (@(posedge clk) disable iff (rst) f_outstanding <= 3'd4);
assert property (@(posedge clk) disable iff (rst) f_tag_in && f_ahead == 0 && resp && resp_take |-> resp_tag == f_tag);
cover property (@(posedge clk) !rst && req && ack && f_outstanding == 3'd4);
Claims about the design, read through the modelling code.
The tracker as the editor runs it
reg [2:0] f_outstanding=0;
always @(posedge clk)
if (rst) f_outstanding <= 0;
else f_outstanding <= f_outstanding
+ (req && ack) - (resp && resp_take);
Accepts minus responses, as the ports see them.
[options]
mode bmc
depth 20
[engines]
smtbmc yices
[script]
read_verilog -formal -sv design.sv
prep -top req4
[files]
design.svRead the counter
Find
f_outstandingin the bench and say in one sentence what it counts.Run it as a promise
Run as it is: BMC PASS to depth 20. Add the fifth-accept cover and run in mode cover.
Change the verb
Make the helper an
assert: FAIL at reported step 7. Find the step whereusedreads 0 with four requests outstanding.Fix the width
Widen
usedto three bits: BMC PASS to depth 20.Track one tag
Add the
f_tagtracker at depth 10, then break the response read. Which tag did the solver pick?
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
- Modelling code computes what properties need and the design does not export; it reads, it never drives.
- A helper is a claim about the design: assert it. As an assumption it hides bugs through the bench's arithmetic.
- A cover trace stops at the covered sample; an assumption that only the next sample would break cannot hide it. A cover you expected impossible, reached, is a bug report.
anyconstis a value fixed once by the solver; one tracked value stands for all values.