Under-Constraint and Counterexample Triage
Watch a correct design fail with free inputs, walk the trace through four questions, and fix the environment with one assumption from the spec.
- Lesson
- F3.3
- Time
- 15 min
- Runs on
- SymbiYosys
- Check
- 4 questions
A read slave captured the address in the first cycle arvalid was high, to save a cycle when arready came late. The formal run failed with a trace in which the master changed araddr mid-stall, so the designer rewrote the capture logic. A week later the lead pointed at the AXI rule: a master holds its payload while VALID waits for READY. The trace was illegal, and the design had been right.
Measured with smtbmc yices at depth 20. The addresses are the solver's choice. The response at step 4 carries the captured FE plus one; the rule expects 7E plus one.
Too Few Assumptions
Read the diagram as text
- 1. Is step 0 a reset state?: no: fix the reset assumption (F1.6)
- 2. Is every shaded input legal?: check each value against the spec row, step by step
- 3. Does the check say what the spec says?: implication, delay, the signal it reads
- 4. RTL bug: only now is the design wrong
- Add one assumption: on inputs, with the spec row in its comment
- Fix the property: the design may be right
- 1. Is step 0 a reset state? → 2. Is every shaded input legal?: yes
- 2. Is every shaded input legal? → 3. Does the check say what the spec says?: yes
- 3. Does the check say what the spec says? → 4. RTL bug: yes
- 2. Is every shaded input legal? → Add one assumption: no
- 3. Does the check say what the spec says? → Fix the property: no
Run A stops at question 2: at step 3 araddr changed while step 2 had arvalid high and arready low. The flow is F1.4's table in order.
With no assumptions the solver may drive input sequences no legal neighbour produces. A FAIL built on one is a false failure: the design misbehaved in a world the spec forbids. That is under-constraint, the other end of the dial from F3.2.
The flow keeps you from fixing the wrong thing. Start state first, then inputs, then the check, and only then the RTL. Run A stops at question 2, and the hook's designer skipped straight to question 4.
The Handshake Rule People Get Backwards
| Signal, before the handshake | May it change? | So the assumption says |
|---|---|---|
ARVALID once high | No: it stays high until ARREADY is sampled high | arvalid && !arready |=> arvalid |
ARADDR while ARVALID waits | No: the payload holds until the transfer | ... |=> araddr == araddr_q |
ARREADY | Yes: it may rise and fall again before ARVALID arrives | nothing: never assume the receiver holds READY |
IHI 0022L A2.3, the Valid-Ready handshake (P1.3 draws it). A slave that relies on a held payload is correct; one that relies on a held READY is not.
One assumption, on the inputs arvalid and araddr, written from that row. The engine refuses $stable(araddr) on a primary input (it reports UNSUPPORTED_INPUT_HISTORY: araddr for $past(araddr) too), so the bench compares with araddr_q, last cycle's address. The response rule reads it the same way.
| Run | Measured result | Time |
|---|---|---|
| A: no assumption | FAIL at reported step 5 | 644 ms |
| B: the handshake assumption | BMC PASS to depth 20 | 687 ms |
| A and B, clocked immediate form | FAIL at reported step 5; BMC PASS to depth 20 | 516 ms; 627 ms |
| Capture bug, under the assumption | FAIL at reported step 3 | 620 ms |
rd_slave_sp; mode bmc, depth 20, smtbmc yices, reset assumed in step 0.
The capture bug fails under the honest assumption too, at reported step 3: a read accepted in its first cycle returns the stale copy. Walk that trace through the flow: step 0 is reset, every input is legal, the check matches the spec. Box 4: the RTL.
Did the Assumption Go Too Far?
| Cover | Handshake assumption | assume(!arvalid) | araddr never changes |
|---|---|---|---|
arvalid && !arready (a stall) | reached, step 3 | unreached | reached, step 3 |
arvalid && arready (a handshake) | reached, step 2 | unreached | reached, step 2 |
arvalid && arready && araddr == 8'hFF | reached, step 2 | unreached | unreached |
Mode cover, depth 20, the correct RTL. All three assumptions turn run A green in bmc: BMC PASS to depth 20, BMC PASS to depth 20, BMC PASS to depth 20.
Two wrong fixes silence run A just as well. assume(!arvalid) kills every cover: nothing happens. Freezing the address keeps the stall and the handshake but loses the top address, so a cover at each extreme of a data input earns its place.
Asked in Interviews
| Question | Answer |
|---|---|
| What is under-constraining? | Too few assumptions: the tool drives sequences the environment never produces and reports false failures. |
| How do you decide a failure is the environment's fault? | Reset state, then each shaded input against the spec row, then the check against the spec, and only then the RTL. |
Must READY stay high once asserted? | No. VALID must stay high and the payload stable until the handshake; READY may rise and fall freely. |
Try It
As IEEE SVA writes it
assume property (@(posedge clk) disable iff (rst) arvalid && !arready |=> arvalid && $stable(araddr));
assert property (@(posedge clk) disable iff (rst) arvalid && arready |=> rvalid && rdata == $past(araddr) + 8'd1);
assert property (@(posedge clk) disable iff (rst) rvalid && !rready |=> rvalid && $stable(rdata));
History on the inputs araddr: this engine refuses it.
props.sv, with the registered copy
assume property (@(posedge clk) disable iff (rst) arvalid && !arready |=> arvalid && araddr == araddr_q);
assert property (@(posedge clk) disable iff (rst) arvalid && arready |=> rvalid && rdata == araddr_q + 8'd1);
assert property (@(posedge clk) disable iff (rst) rvalid && !rready |=> rvalid && $stable(rdata));
araddr_q is last cycle's araddr, so the questions are the same from step 1.
mode cover for the cover runs.[options]
mode bmc
depth 20
[engines]
smtbmc yices
[script]
read_verilog -formal -sv design.sv props.sv
prep -top rd_slave_sp
[files]
design.sv
props.svRun free
Run as it is: FAIL at reported step 5. Walk the trace through the four questions and stop at the first "no".
Write one assumption
Add the handshake assumption as the first line of props.sv and run: BMC PASS to depth 20.
Check it with covers
Replace the two assertions with the three covers, mode cover: all reached.
Find a real bug
Make the response always use
addr_capand run under the assumption: FAIL at reported step 3. The flow ends at box 4.Try a wrong fix
Swap the assumption for
assume(!arvalid)in design.sv. Green in bmc; now run the covers.
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
- Under-constraint: free inputs drive sequences no legal neighbour sends, and the FAIL is false.
- Triage in order: reset state, each shaded input against the spec, the check, and only then the RTL.
- Write the missing assumption on inputs, from one spec row, and cite it in a comment.
- Then cover the stall, the handshake and each extreme of the payload, so the assumption did not go too far.