Repetition and counting events
Say that a level holds for n edges with [*n], know what a repetition range at the end of a sequence really checks, and count protocol events with a small auxiliary model.
- Lesson
- A2.2
- Time
- 30 min
- Runs on
- Verilator
- Check
- 5 questions
A four-beat burst must end with rlast on its fourth beat. The first check, req |-> ##4 rlast, looked four edges after the request and passed in every test without backpressure. When a sink started stalling the channel, it failed on correct bursts. After it was relaxed to stop the noise, a DUT that ended stalled bursts one beat early passed straight through.
One stall at edge 3 moves the fourth transfer to edge 6, but ##4 still looks at edge 5.
Consecutive repetition
b[*n] means b holds at n consecutive edges, the same as b ##1 b ##1 ... written out. $rose(rvalid) |-> rvalid[*4] says that once rvalid rises, it stays high for at least four edges, which every four-beat burst needs.
Whole sequences can repeat too: (rvalid ##1 !rvalid)[*2] describes valid going high and low twice in a row. Use this sparingly, because repeated sequences quickly become hard to read in a waveform.
A range at the end checks only its lower bound
b[*m:n] allows from m to n repetitions. At the end of a consequent it behaves like a delay range: the attempt passes at its first match, the shortest one. req |=> busy[*2:4] therefore passes as soon as busy has been high for two edges, and it still passes when busy stays high for six.
Measured in the predict question below: the only failure is at 115. Six edges of busy are not reported.
So the upper bound 4 checks nothing here. If the spec says "busy for two to four edges", the "at most four" half needs a rule of its own.
The upper bound needs its own rule
A fixed repetition can be a trigger. busy[*4] |=> !busy matches wherever busy has been high for four edges in a row, and then requires it low at the next edge. Each edge starts a new attempt, so a long stretch of busy starts several overlapping ones.
busy[*4] |=> !busy, on a stretch of six busy edges from 25 to 75.- Edge 3 · t=25
busyrises at 25, and an attempt starts matching four edges of it: 25, 35, 45 and 55. - Edge 6 · t=55
Four attempts are in flight, from 25, 35, 45 and 55; the figure follows the first three.
- Edge 7 · t=65
The attempt from 25 has seen four edges of
busy, sobusymust be low at 65. It is high, and that attempt fails. - Edge 8 · t=75
The attempt from 35 fails at 75 for the same reason: the fifth and sixth edges each add one failure.
- Edge 9 · t=85
busydrops at 85, and the attempt from 45 passes there. Attempts from later edges never see four edges ofbusy, so they succeed vacuously.More context
A stretch of six busy edges gives two failures. Debug from the first one; the second reports the same stretch.
Read the lanes as text
- from @25, from edge 3 (t=25): fails at edge 7 (t=65).
- from @35, from edge 4 (t=35): fails at edge 8 (t=75).
- from @45, from edge 5 (t=45): passes at edge 9 (t=85).
Sampled values, edge 1 to 13: busy 0 0 1 1 1 1 1 1 0 0 1 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 at_most_4 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 req = 0, busy = 0;
at_most_4: assert property (@(posedge clk) busy[*4] |=> !busy);
initial begin
@(negedge clk); req = 1; // request sampled at 15
@(negedge clk); req = 0; busy = 1; // busy sampled at 25, 35, ... 75
repeat (6) @(negedge clk);
busy = 0;
@(negedge clk); req = 1; // request sampled at 95
@(negedge clk); req = 0; busy = 1; // busy only at 105
@(negedge clk); busy = 0;
repeat (2) @(negedge clk); // edges at 115 and 125
$finish;
end
endmodule
One failure per edge beyond the limit, and a pass for the attempt whose fourth edge is the last one.
Counting edges or counting events
Ask what the spec counts. Repetition counts clock edges, so it suits levels: "valid stays high for four edges", "busy never lasts more than four". A delay counts edges too.
Protocol rules usually count events: transfers, beats, grants. Written as an edge count, such a rule is right only while nothing stalls, which is exactly the trap in the opening story.
The standard has event-counting forms, the goto repetition [->n] and the non-consecutive repetition [=n]. They are not covered yet: Verilator 5.052 misses goto failures and rejects [=n]. Count the events in a few lines of ordinary code instead.
Counting with auxiliary code
A counter that resets on each request and increments on each transfer turns "the fourth transfer carries rlast" into xfer && beat == 3 |-> rlast, with stalls anywhere.
Such auxiliary code is common in real assertion suites. Keep it small and obviously correct, update it with nonblocking assignments at the clock, and remember that assertions see its value from before the edge, like any other signal.
Count edges
assert property (@(posedge clk)
req |-> ##4 rlast);
Correct only without stalls; fails good bursts and can pass bad ones.
Count transfers
// beat: transfers since the request
assert property (@(posedge clk)
xfer && beat == 3 |-> rlast);
assert property (@(posedge clk)
xfer && beat < 3 |-> !rlast);
Checks the fourth transfer, wherever stalls put it.
- A request
A request resets
beatto 0. The counter is ordinary code, updated with a nonblocking assignment at the clock.Follow the calls, values and waits
- data req: beat = 0: Read channel pins → beat counter
- Early transfers
Each transfer adds one. While
beatis below 3,no_earlyrequiresrlastlow on every transfer.Follow the calls, values and waits
- data two transfers: beat = 2: Read channel pins → beat counter
- data beat = 2 sampled: beat counter → no_early
- A stall
A stalled edge has no transfer, so
beatholds. An edge count moves on here; the counter does not. - The fourth transfer
At the fourth transfer the sampled
beatis 3, solast_on_4threquiresrlast, wherever the stalls put that edge.More context
The assertion reads
beatfrom before the edge, so at the fourth transfer it sees 3, the count of the transfers before this one.Follow the calls, values and waits
- data beat = 3 sampled: beat counter → last_on_4th
- A burst with rlast too early
In the faulty burst,
rlastcame on the third transfer.no_earlyfails at 185, andlast_on_4thfails at 195 becauserlastis gone at the fourth.Follow the calls, values and waits
- data no_early at 185: no_early → Failure report
- data last_on_4th at 195: last_on_4th → Failure report
Read the diagram as text
- Read channel pins: req, xfer = rvalid && rready, rlast
- beat counter: 0 on req, +1 on each xfer
- last_on_4th: xfer && beat == 3 |-> rlast
- no_early: xfer && beat < 3 |-> !rlast
- Failure report: One line per broken rule
- Read channel pins → beat counter: req, xfer
- beat counter → last_on_4th: beat
- beat counter → no_early: beat
- last_on_4th → Failure report: fails
- no_early → Failure report: fails
Two rules over one counter: one for the beat that must carry rlast, one for every beat that must not.
The second rule matters as much as the first. Without !rlast on earlier beats, a design that raises rlast on every beat passes.
Try it
The fixed-delay check failed the correct stalled burst at 115. On the faulty burst, the stall happened to put rlast exactly four edges after the request, so the fixed delay passed it. The transfer-counting rules caught the early rlast at 185 and the missing one at 195.
four_edges passed every burst, as it should. Change it to rvalid[*5] and predict which bursts fail it.
t=115 fixed_delay failed t=185 no_early failed t=195 last_on_4th failedCheck yourself
Remember
b[*n]isbat n consecutive edges; use it for levels that must hold.- A repetition range at the end of a sequence passes at its shortest match, so its upper bound checks nothing.
- Check an upper bound with its own rule, such as
busy[*4] |=> !busy. - Protocol rules count events such as transfers, not clock edges.
- A small auxiliary counter turns an event-counting rule into a plain implication; check the earlier events too.