Case Splitting and Assume-Guarantee
Split a proof into cases that finish, show that the cases cover everything, avoid the split that hides interleavings, and compose two blocks without circular logic.
- Lesson
- F4.3
- Time
- 25 min
- Runs on
- SymbiYosys
- Check
- 6 questions
Three proofs, one per burst type, all passed. In silicon an INCR burst right after a WRAP crossed a 4 KB page, because the INCR reused the WRAP’s beat size. Each proof had restricted the whole run to one burst type, so no proof ever saw one type follow another.
Split by Burst Type
Read the diagram as text
- every legal burst: FIXED, INCR or WRAP, any order
- case FIXED: busy && burst_q == FIXED |-> in page
- case INCR: busy && burst_q == INCR |-> in page
- case WRAP: busy && burst_q == WRAP |-> in page
- union: the three conditions cover every busy cycle
- every legal burst → case FIXED
- every legal burst → case INCR
- every legal burst → case WRAP
- case FIXED → union
- case INCR → union
- case WRAP → union
Soft split: the case is the assertion’s condition, so each run still allows any sequence of bursts.
Proof (abc pdr unless noted) | len 4 bits (16 beats) | len 8 bits (256 beats) |
|---|---|---|
| all types together | Prove PASS, 6.0 s | time limit (20 s), no verdict |
| case FIXED | Prove PASS, 0.6 s | Prove PASS, 0.6 s |
| case INCR | Prove PASS, 0.8 s | time limit (20 s), no verdict |
case INCR, one helper, smtbmc yices prove 2 | – | Prove PASS, 0.5 s |
| case WRAP | Prove PASS, 5.7 s | Prove PASS, 10.1 s |
The property: every beat stays in the 4 KB page of its start address. WRAP lengths are 2, 4, 8 or 16 beats in both columns.
With 16-beat bursts the split costs about what the whole proof costs. With AXI4’s 256-beat INCR the whole proof runs out of time, and the split shows where: FIXED and WRAP finish, INCR does not. The hard case is now small enough to think about.
One helper for INCR, "the beats still to come fit below the page end", closes it at depth 2 (F4.6 shows how to find such helpers). The three cases together are every busy cycle, since the master never sends burst type 3.
wire [15:0] f_off = {4'd0, addr[11:0]} & ~(step - 16'd1);
wire [16:0] f_end = {1'b0, f_off} + {1'b0, step} * ({9'd0, left} + 17'd1);
always @(posedge clk) if (!rst && busy && burst_q == INCR) assert(f_end <= 17'd4096);Soft Split and Hard Split
Soft split: the case is a condition
if (!rst && busy && burst_q == INCR)
assert(addr[15:12] == page[15:12]);
Faulty design: FAIL at step 7, 0.6 s (bmc 20).
Hard split: the inputs are restricted
if (!busy && start) restrict(burst == INCR);
Faulty design: Prove PASS, 0.8 s (abc pdr).
| Faulty design: INCR keeps an old beat size | Measured result |
|---|---|
hard split, FIXED only, abc pdr | Prove PASS, 0.6 s |
hard split, INCR only, abc pdr | Prove PASS, 0.8 s |
hard split, WRAP only, abc pdr | Prove PASS, 5.3 s |
soft split, case INCR, bmc 20 | FAIL at step 7, 0.6 s |
no split, bmc 20 | FAIL at step 7, 0.6 s |
Three hard-split passes on a design with a real bug.
A soft split keeps every input sequence; only the assertion narrows. A hard split restricts the inputs themselves: here, every burst of the run has one type. That deletes every interleaving, and the faulty design passes all three runs.
Measured counterexample of the faulty design with no split (bmc, depth 20): FAIL at step 7, 0.6 s.
The escape is a WRAP followed by an INCR. A split proves the whole only under two conditions. The cases are exhaustive, and the case variable is fixed for the run, like a strap or a configuration written once. restrict is the IEEE statement for such constraints; simulation ignores it.
Faulty design, burst type a static configuration ($anyconst) | Measured result (abc pdr) |
|---|---|
split: restrict(f_cfg == FIXED) | Prove PASS, 0.6 s |
split: restrict(f_cfg == INCR) | Prove PASS, 0.7 s |
split: restrict(f_cfg == WRAP) | Prove PASS, 4.7 s |
| no split | Prove PASS, 4.3 s |
With the type fixed for the run, the three slices are exhaustive and sound, and they agree with the unsplit run.
Here the same faulty RTL is correct, because a WRAP can never be followed by an INCR. Whether a hard split is sound is a fact about the environment: write it as an assumption and cite the specification row that justifies it.
Assume-Guarantee
Read the diagram as text
- snd: two credits; sends only with a credit
- rcv: two buffer slots; registered credit return
- G_A: no send while two words are outstanding
- G_B: no credit while nothing is outstanding
- out_cnt: interface monitor: sends minus credits
- snd → rcv: send
- rcv → snd: ret
- G_A → rcv: assumed by
- G_B → snd: assumed by
- out_cnt → G_A
- out_cnt → G_B
G_A is proved on snd under G_B; G_B is proved on rcv under G_A.
Run (abc pdr) | Correct receiver | Faulty receiver |
|---|---|---|
| snd alone, G_B assumed | Prove PASS, 0.5 s | not involved |
| rcv alone, G_A assumed | Prove PASS, 0.5 s | FAIL at step 3, 0.6 s |
| both, nothing assumed | Prove PASS, 0.5 s | FAIL at step 3, 0.6 s |
| both, G_A and G_B assumed and asserted in one run | Prove PASS, 0.5 s | Prove PASS, 0.5 s |
| both, G_A and G_B assumed one cycle late | Prove PASS, 0.5 s | FAIL at step 3, 0.6 s |
The faulty receiver returns a credit when asked to pop an empty buffer.
Each block is proved alone, with the other block’s guarantee assumed on its inputs. It looks circular: G_A needs G_B and G_B needs G_A. It is sound here because ret is a register, so G_B at cycle t uses G_A only up to t − 1. Neither guarantee can be the first to break.
The trap is the shortcut of one run that assumes both guarantees in the same cycle it asserts them. The faulty receiver passes, because the assumption deletes the cycle where it breaks. Assuming them one cycle late restores the induction over time, and the bug is found again.
Asked in Interviews
| Question | Answer |
|---|---|
| What is case splitting? | Proving a property once per case of the inputs or modes, so each run has a smaller cone. Prove that the cases cover everything, prefer the soft split, and use a hard split only on values that cannot change during a run. |
| What is assume-guarantee reasoning? | Proving each block alone, assuming its neighbours’ guarantees on its inputs, then composing. Each guarantee must be proved somewhere. |
| When is it circular? | When a guarantee at cycle t needs the other guarantee at the same cycle t, for example through a combinational path, or when both are assumed and asserted in one run. A one-cycle delay on every dependency makes the argument an induction over time. |
Try It
The lab is the INCR case of the soft split, 16-beat lengths, abc pdr. Measured: Prove PASS, 0.8 s.
assert property (@(posedge clk) disable iff (rst)
busy |-> addr[15:12] == page[15:12]);Write the other cases
Change the case condition to FIXED, then WRAP, and record the three times.
Hard split the faulty design
Capture
size_qonly on WRAP bursts (the faulty line), drop the case condition, and addrestrict(burst == INCR);underif (!busy && start). Run, then remove the restrict and runbmcat depth 20.Read the escape
In the
bmctrace, find the burst types of the two starts before the failure.
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 case split shrinks each run’s cone and shows which case is hard.
- Prove that the cases cover everything.
- Soft split keeps every input sequence; a hard split hides interleavings unless the case variable is fixed for the run.
- Assume-guarantee is sound when each guarantee depends on the other only through earlier cycles.