End-to-End Versus Glue Properties
Prove a FIFO's pointers and count, miss a data-order bug, and see which bugs each kind of property catches first.
- Lesson
- F3.4
- Time
- 15 min
- Runs on
- SymbiYosys
- Check
- 4 questions
A FIFO had twelve proven properties on its pointers, its count and its flags. The read path took the entry after the head, so every pop returned the wrong byte, and not one of the twelve mentioned data. The block went to integration proven and failed its first two-word transfer.
Measured at depth 10 with smtbmc yices. The solver needed no second push: the slot after the head was never written, so its power-up value (80) is the solver's choice.
Inside the Box, or Across It
Read the diagram as text
- push, wdata: port
- wr_ptr, rd_ptr, count: glue: pointers equal when empty or full, count <= 4, count matches the pointers
- mem[0:3]: four bytes
- pop, rdata: port
- Scoreboard sb0..sb3: modelling code in the bench: every accepted byte, oldest first
- End-to-end check: pop && !empty |-> rdata == sb0
- push, wdata → wr_ptr, rd_ptr, count
- push, wdata → mem[0:3]: write
- mem[0:3] → pop, rdata: read
- push, wdata → Scoreboard sb0..sb3: record
- Scoreboard sb0..sb3 → End-to-end check: head
- pop, rdata → End-to-end check
Glue attaches inside the box. End-to-end attaches across it: what goes in comes out, in order, once, unchanged.
A glue property, also called local or white-box, checks one piece of mechanism. An end-to-end property checks the block's purpose at its ports. Here that needs modelling code: a shadow copy of the contents, so the check knows which byte must come out next (F3.6).
A quick test: if the property names an internal signal, it is glue. If it names only ports and the bench's own model, it is end-to-end. Both kinds belong in a finished bench.
Each Kind Misses a Different Bug
Glue fails at reported step 4; the end-to-end check alone fails later, at 5, when a pop past the real data reaches the port.
| Bug | Glue only | End-to-end only | Both |
|---|---|---|---|
| Read takes the entry after the head | BMC PASS to depth 10 | not run | FAIL at reported step 3 |
| Count misses its decrement on push and pop | FAIL at reported step 4 | FAIL at reported step 5 | not run |
| No bug | not run | not run | BMC PASS to depth 10 |
fifo4sb; mode bmc, depth 10, smtbmc yices. The immediate forms agree: BMC PASS to depth 10 and FAIL at reported step 3 on the order bug, FAIL at reported step 4 on the count bug.
The order bug leaves every pointer and count relation true; only the end-to-end check sees it. The count bug breaks the glue relation between count and the pointers one step after the cause, before any wrong byte reaches a port.
So end-to-end finds the bugs a user would see, and glue finds bugs earlier, closer to the cause. Glue properties also become the invariants that induction needs (F4.6).
End-to-End First, and What It Costs
Correct FIFO, glue and end-to-end together. Depth 10 (samples 0 to 8) passed in 1.0 s. Depth 20 ended at the 20-second budget: time limit, no verdict.
Linear scale. 0 to 20 steps.
- Checked bound
- 10
- Required bound
- 20
- Claim
- Bounded check only
Required here means the depth the task asked for. Measured on this machine; F4 is about making such proofs finish.
Write end-to-end properties first: they restate the specification, and a sign-off argues about them (F7.3). Add glue second, to localise bugs and help proofs converge. A block with only glue properties is a block whose spec nobody wrote down.
The scoreboard is why the lab runs at depth 10: tracking data multiplies the work at every step.
Asked in Interviews
| Question | Answer |
|---|---|
| End-to-end or glue: which should I write? | Both, end-to-end first. End-to-end states the spec and catches what users see; glue localises bugs and feeds induction. |
| Which bug does each kind miss? | Glue misses an ordering bug that leaves the mechanism intact. A bounded end-to-end run can miss internal drift that has not reached a port yet. |
Try It
Glue: the mechanism
assert property (@(posedge clk) disable iff (rst) empty |-> wr_ptr == rd_ptr);
assert property (@(posedge clk) disable iff (rst) full |-> wr_ptr == rd_ptr);
assert property (@(posedge clk) disable iff (rst) count <= 3'd4);
assert property (@(posedge clk) disable iff (rst) count == f_ptr_diff || count == 3'd4 && f_ptr_diff == 2'd0);
Pointers, count and their relation.
End-to-end: the purpose
assert property (@(posedge clk) disable iff (rst) pop && !empty |-> rdata == sb0);
The byte popped is the oldest byte pushed and not yet popped.
[options]
mode bmc
depth 10
[engines]
smtbmc yices
[script]
read_verilog -formal -sv design.sv props.sv
prep -top fifo4sb
[files]
design.sv
props.svRun the glue set
Run as it is, with the order bug: BMC PASS to depth 10.
Add the end-to-end check
Append the end-to-end line to props.sv: FAIL at reported step 3. Compare
rdatawithsb0at the pop.Swap the bug
Fix the read to
mem[rd_ptr], then break the count update so a push and pop together count up. Run glue alone, then end-to-end alone.Ask for depth 20
On the correct design set depth 20. Expect the 20-second budget to end the run with no verdict; that result claims nothing.
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
- Glue properties check mechanism inside the block; end-to-end properties check its purpose at the ports.
- Glue misses bugs that leave the mechanism intact; end-to-end within a bound can miss drift that has not reached a port.
- Write end-to-end first, then glue to localise bugs and feed induction.
- End-to-end checks need modelling code, and data tracking costs depth.