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.

The data-order bug: glue green, end-to-end red
0123clkfreepushfreepopfreewdata00countrdata80sb000push 00pop: rdata 80, sb0 00SBY reports FAIL

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

Where Each Kind of Property AttachesGlue properties read internal signals and check one piece of mechanism. The end-to-end check reads only ports and the bench's shadow copy. Arrows mean "data flows to".
data: where the transaction goes
Where each kind of property attachesGlue properties read internal signals and check one piece of mechanism. The end-to-end check reads only ports and the bench's shadow copy. Arrows mean "data flows to". component push, wdata port component wr_ptr, rd_ptr,count glue: pointers equalwhen empty or full,count <= 4, countmatches the pointers component mem[0:3] four bytes component pop, rdata port component Scoreboardsb0..sb3 modelling code in thebench: everyaccepted byte, oldestfirst component End-to-end check pop && !empty |->rdata == sb0 writereadrecordhead
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

The count bug: glue red first
01234clkfreepushfreepopcount012wr_ptr012rd_ptrf_ptr_diffpush and popcount 2, pointers 1 apartSBY reports FAIL

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.

BugGlue onlyEnd-to-end onlyBoth
Read takes the entry after the headBMC PASS to depth 10not runFAIL at reported step 3
Count misses its decrement on push and popFAIL at reported step 4FAIL at reported step 5not run
No bugnot runnot runBMC 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

The scoreboard's price in depth

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

QuestionAnswer
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.

Depth 10 on purpose: the scoreboard makes each step expensive.
[options]
mode bmc
depth 10
[engines]
smtbmc yices
[script]
read_verilog -formal -sv design.sv props.sv
prep -top fifo4sb
[files]
design.sv
props.sv
  1. Run the glue set

    Run as it is, with the order bug: BMC PASS to depth 10.

  2. Add the end-to-end check

    Append the end-to-end line to props.sv: FAIL at reported step 3. Compare rdata with sb0 at the pop.

  3. 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.

  4. 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

Predict the outputfifo4sb with the read-order bug, mode bmc, depth 10, smtbmc yices, reset assumed in step 0, the four glue properties only. The summary verdict is:
module fifo4sb(input clk, rst, push, pop, input [7:0] wdata,
  output [7:0] rdata, output full, empty);
  reg [7:0] mem [0:3];
  reg [1:0] wr_ptr=0, rd_ptr=0;
  reg [2:0] count=0;
  assign full  = count == 3'd4;
  assign empty = count == 3'd0;
  wire wr_en = push && !full, rd_en = pop && !empty;
  assign rdata = mem[rd_ptr + 2'd1];   // bug: the pointer moves before the read
  always @(posedge clk)
    if (rst) begin wr_ptr <= 0; rd_ptr <= 0; count <= 0; end
    else begin
      if (wr_en) begin mem[wr_ptr] <= wdata; wr_ptr <= wr_ptr + 2'd1; end
      if (rd_en) rd_ptr <= rd_ptr + 2'd1;
      count <= count + wr_en - rd_en;
    end
`ifdef FORMAL
  always @(posedge clk) begin
    if ($initstate) begin assume(rst); end else begin assume(!rst); end
  end
  wire [1:0] f_ptr_diff = wr_ptr - rd_ptr;   // entries between the pointers, modulo 4
  // Modelling code: a shadow copy of the FIFO's contents, in order, oldest in sb0.
  reg [7:0] sb0=0, sb1=0, sb2=0, sb3=0;
  reg [2:0] sb_n=0;
  wire [2:0] slot = sb_n - (rd_en ? 3'd1 : 3'd0);   // where an accepted push lands
  always @(posedge clk)
    if (rst) sb_n <= 0;
    else begin
      if (rd_en) begin sb0 <= sb1; sb1 <= sb2; sb2 <= sb3; end
      if (wr_en) case (slot)
        3'd0: sb0 <= wdata;  3'd1: sb1 <= wdata;  3'd2: sb2 <= wdata;  default: sb3 <= wdata;
      endcase
      sb_n <= sb_n + wr_en - rd_en;
    end
`endif
endmodule
Read included file: props.sv
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);
ConceptWhich property sentence for a FIFO is end-to-end?
ConceptWhy do leads want end-to-end properties first?
Read the waveformIn the order-bug trace, what should the pop have returned, and what did it return?
The data-order bug: glue green, end-to-end red
0123clkfreepushfreepopfreewdata00countrdata80sb000push 00pop: rdata 80, sb0 00SBY reports FAIL

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.

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.