Holding until an event: until and throughout
Say that a condition holds until an event with the right operator: until or until_with, weak or strong, or throughout over a goto or a window, including the case where the event is already there.
- Lesson
- A3.2
- Time
- 30 min
- Runs on
- Verilator
- Check
- 5 questions
A bus bridge had the rule "once req rises, it stays high until gnt", written $rose(req) |-> req until gnt. A request that was dropped early passed anyway, because gnt from the previous transfer was still high at the edge where req rose. until ended the check at its very first edge, before it had looked at req at all.
Two questions for every hold
Every "p holds until q" rule has to answer two questions. Must p still hold at the edge where q arrives? And must q arrive before the test ends? Get either answer wrong, and the rule passes a real bug or fails on legal traffic. The four until operators are the four combinations of answers.
| Operator | p needed at the edge of q? | q never comes |
|---|---|---|
p until q | no (non-overlapping) | no failure (weak) |
p until_with q | yes (overlapping) | no failure (weak) |
p s_until q | no | fails at the end of the run (strong) |
p s_until_with q | yes | fails at the end of the run (strong) |
Measured on Verilator 5.052; the end-of-run case is in the simulator note below.
In each form, p must hold at every edge from the start of the attempt until q is seen. The difference is only the last edge, and what happens if that edge never comes.
One trace, two operators
The two figures below follow three attempts on one trace. In A, v drops exactly where r arrives. In B, r is already high at the start. In C, r never comes before the test stops.
start |-> v until r.- Edge 2 · t=15
A starts at 15 with
vhigh.vmust hold untilris seen. - Edge 4 · t=35
rarrives at 35.untildoes not look atvon this edge, so A passes althoughvis already low. - Edge 6 · t=55
B starts at 55 with
ralready high. The attempt passes at once, andvis never checked. - Edge 8 · t=75
C starts at 75 with
vhigh and keeps it high. - End of the run · t=100
rnever came.untilis weak, so C ends with no verdict.
Read the lanes as text
- start @15, from edge 2 (t=15): passes at edge 4 (t=35).
- start @55, from edge 6 (t=55): passes at edge 6 (t=55).
- start @75, from edge 8 (t=75): is still running at the end of the run: weak, so no failure.
Sampled values, edge 1 to 10: start 0 1 0 0 0 1 0 1 0 0; v 0 1 1 0 0 0 0 1 1 1; r 0 0 0 1 0 1 0 0 0 0.
The program behind the lanes
The lanes come from running this program on Verilator 5.052. Each lane is a separate copy of hold_until that can start only at that lane's edge, so every verdict and its edge are read from that copy's own output.
module tb;
logic clk = 0;
always #5 clk = ~clk; // rising edges at 5, 15, 25, ...
logic start = 0, v = 0, r = 0;
hold_until: assert property (@(posedge clk) start |-> v until r);
initial begin
@(negedge clk); start = 1; v = 1; // A starts at 15
@(negedge clk); start = 0;
@(negedge clk); v = 0; r = 1; // r at 35, v already low
@(negedge clk); r = 0;
@(negedge clk); start = 1; r = 1; // B starts at 55 with r already high
@(negedge clk); start = 0; r = 0;
@(negedge clk); start = 1; v = 1; // C starts at 75; r never comes
@(negedge clk); start = 0;
@(negedge clk);
@(negedge clk);
$finish; // the test stops at 100
end
endmodule
start |-> v until_with r.- Edge 2 · t=15
A starts at 15 with
vhigh, exactly as in the first figure. - Edge 4 · t=35
At 35
rarrives, anduntil_withneedsvon this edge as well.vis low, so A fails. - Edge 6 · t=55
B starts with
rhigh, so its first edge is also its last.vis low there: B fails. - End of the run · t=100
C is still waiting at the end.
until_withis weak too, so no verdict.
Read the lanes as text
- start @15, from edge 2 (t=15): fails at edge 4 (t=35).
- start @55, from edge 6 (t=55): fails at edge 6 (t=55).
- start @75, from edge 8 (t=75): is still running at the end of the run: weak, so no failure.
Sampled values, edge 1 to 10: start 0 1 0 0 0 1 0 1 0 0; v 0 1 1 0 0 0 0 1 1 1; r 0 0 0 1 0 1 0 0 0 0.
The program behind the lanes
The lanes come from running this program on Verilator 5.052. Each lane is a separate copy of hold_until_with that can start only at that lane's edge, so every verdict and its edge are read from that copy's own output.
module tb;
logic clk = 0;
always #5 clk = ~clk; // rising edges at 5, 15, 25, ...
logic start = 0, v = 0, r = 0;
hold_until_with: assert property (@(posedge clk) start |-> v until_with r);
initial begin
@(negedge clk); start = 1; v = 1; // A starts at 15
@(negedge clk); start = 0;
@(negedge clk); v = 0; r = 1; // r at 35, v already low
@(negedge clk); r = 0;
@(negedge clk); start = 1; r = 1; // B starts at 55 with r already high
@(negedge clk); start = 0; r = 0;
@(negedge clk); start = 1; v = 1; // C starts at 75; r never comes
@(negedge clk); start = 0;
@(negedge clk);
@(negedge clk);
$finish; // the test stops at 100
end
endmodule
Lane A is the whole difference between the two. For a hold that must cover the transfer itself, such as data that must be right when rready is seen, only the overlapping form works: rvalid && !rready |=> (rvalid && $stable(rdata)) until_with rready.
The hold rule, edge by edge
Edge 1: the offer stalls
The antecedent
rvalid && !rreadymatches, so an attempt starts. Because of|=>, the hold begins at the next edge.Edge 2: still stalled
rvalidis high andrdataequals its value at edge 1, so$stableholds.rreadyis still low, so the attempt keeps going.Edge 3: the transfer
rreadyis seen, so this is the last edge the attempt checks. Because the operator isuntil_with,rvalidand stable data must hold here as well.
Each stalled edge also starts its own attempt, so a three-edge stall is checked by overlapping attempts that all end at the transfer. A data change anywhere in the stall fails at least one of them.
When the event is already there
Lane B is the trap from the opening story. If q is true at the first edge of the attempt, until passes without checking anything, and until_with checks p on that single edge only. A leftover gnt, a sticky done or a level that stays high between transfers all end the hold before it starts.
Decide whether the first edge counts. If the event must be a new one, skip that edge: req throughout (##1 gnt[->1]) waits for the first gnt after the start edge. Writing the event as an edge, req until $rose(gnt), has the same effect when gnt is a level that stays high.
throughout
c throughout s requires the boolean c at every edge of a match of the sequence s, from its first edge to its last. s can be fixed, en throughout (##2 done), or a window, en throughout (##[1:3] d). Unlike the operands of until, which are properties, the left side here must be a plain boolean expression.
s |-> en throughout (##[1:3] d). Each delay of the window is a thread.- Edge 2 · t=15
The start at 15 forks three threads, one per delay. Each needs
enon every edge it covers. - Edge 4 · t=35
darrives at 35 withenstill high, so the two-edge thread passes and takes the whole attempt with it. - Edge 6 · t=55
A second start at 55 forks three new threads.
- Edge 7 · t=65
No
dat 65, so the shortest thread dies. - Edge 8 · t=75
endrops at 75. Both remaining threads fail on this edge, and so does the attempt, althoughdcomes at 85.
Read the lanes as text
- s @15, from edge 2 (t=15): thread 1 (to edge 3 (t=25)) dies at edge 3 (t=25); thread 2 (to edge 4 (t=35)) passes at edge 4 (t=35); thread 3 (to edge 5 (t=45)) is dropped at edge 4 (t=35), because the attempt has already passed.
- s @55, from edge 6 (t=55): thread 1 (to edge 7 (t=65)) dies at edge 7 (t=65); thread 2 (to edge 8 (t=75)) fails at edge 8 (t=75); thread 3 (to edge 8 (t=75)) fails at edge 8 (t=75).
Sampled values, edge 1 to 10: s 0 1 0 0 0 1 0 0 0 0; en 0 1 1 1 0 1 1 0 0 0; d 0 0 0 1 0 0 0 0 1 0.
The program behind the lanes
The lanes come from running this program on Verilator 5.052. Each lane is a separate copy of en_until_d that can start only at that lane's edge, so every verdict and its edge are read from that copy's own output.
module tb;
logic clk = 0;
always #5 clk = ~clk; // rising edges at 5, 15, 25, ...
logic s = 0, en = 0, d = 0;
en_until_d: assert property (@(posedge clk) s |-> en throughout (##[1:3] d));
initial begin
@(negedge clk); s = 1; en = 1; // start at 15
@(negedge clk); s = 0;
@(negedge clk); d = 1; // d at 35, en still high
@(negedge clk); d = 0; en = 0;
@(negedge clk); s = 1; en = 1; // start at 55
@(negedge clk); s = 0;
@(negedge clk); en = 0; // en drops at 75
@(negedge clk); d = 1; // d at 85: too late
@(negedge clk); d = 0;
@(negedge clk);
$finish;
end
endmodule
A window under throughout is a bounded hold: "en stays high, and d comes within three edges". It fails at a definite edge, which makes it the simulation-friendly cousin of s_until.
Over a goto, v throughout r[->1] gives the same verdicts as v until_with r. The example measures all five operators on one trace.
Because throughout builds a sequence, the standard also lets more sequence follow it. That use is not covered yet.
Choosing the operator
| The spec says | Write |
|---|---|
p holds while waiting; the event edge itself is free | p until q |
p holds through the event edge as well | p until_with q or p throughout q[->1] |
| the event must come within N edges | p throughout (##[0:N] q) |
| the event must come before the test ends | p s_until q, p s_until_with q, with a drain |
| the event may already be true at the start | skip that edge: p throughout (##1 q[->1]) |
Prefer a bound when the specification gives one; it fails at a definite edge.
Keep "what must hold while waiting" and "how long the wait may last" as two rules. A weak hold rule plus a bounded latency rule, such as rvalid && !rready |-> ##[0:16] rready, gives two failure messages, and each names the requirement that broke. One strong rule that mixes both reports only at the end of the run, and says less.
Try it
Attempts A and B are the lanes of the figures. The non-overlapping forms pass both; the overlapping forms, and throughout over a goto, fail both. Attempt C here gets its r at 95 with v still high, so every operator passes it.
Delete the line that raises r at 95. The weak forms then stay silent, and Verilator stops at $finish with an error for the two strong ones.
until failed at: none until_with failed at: 35 55 s_until failed at: none s_until_with failed at: 35 55 throughout failed at: 35 55Check yourself
Remember
untilleaves the edge ofqfree;until_withcheckspthere too.- The
s_forms also fail ifqnever comes before the end of the run; the plain forms are weak. - If
qis already true at the first edge,untilpasses at once: decide whether that edge counts. c throughout sneedscon every edge ofs; overq[->1]it matchesuntil_with, over a window it is a bounded hold.- Prefer a bounded hold when the specification gives a limit.
Practice
- Hold the offer Verify · Easy