Part 4 · Assertions (SVA) · Intermediate
Consecutive Repetition: [*N], [*M:N], [*0]
Exact and ranged consecutive repeat, the empty-match [*0], and unbounded [*1:$].
Exact consecutive repetition: [*N]
a [*3] means a holds for 3 consecutive clock ticks — it is pure shorthand for a ##1 a ##1 a. The repeated item can be any sequence, not just a boolean: (a ##1 b)[*2] expands to a ##1 b ##1 a ##1 b. Repetition of a multi-cycle sequence concatenates copies with ##1 between them — the next copy starts the cycle after the previous one ends.
// Busy must stay asserted exactly 4 cycles after start
sequence s_busy4;
start ##1 busy [*4] ##1 done;
endsequence
// Equivalent expansion:
// start ##1 busy ##1 busy ##1 busy ##1 busy ##1 done
property p_busy4;
@(posedge clk) start |-> ##1 busy [*4] ##1 done;
endproperty
a_busy4: assert property (p_busy4);WAVEFORM — start ##1 busy[*4] ##1 done
clk : 1 2 3 4 5 6 7 8
start : 0 1 0 0 0 0 0 0
busy : 0 0 1 1 1 1 0 0
done : 0 0 0 0 0 0 1 0
A r1 r2 r3 r4 M
A = attempt (start @2); r1..r4 = four consecutive busy ticks
M = match: done at tick 7, one tick after the 4th busy
FAIL case — busy drops one cycle early:
clk : 1 2 3 4 5 6
start : 0 1 0 0 0 0
busy : 0 0 1 1 1 0
r1 r2 r3 X
X = busy sampled 0 at tick 6 where r4 was required → thread diesRanged repetition: [*M:N]
a [*2:4] matches a run of 2, 3, or 4 consecutive ticks of a — like ranged delay, it forks one match thread per legal count. A subtle consequence: a longer run contains shorter matches. If a holds 4 cycles, the [*2], [*3], and [*4] threads all match at different end ticks. What follows the repetition decides which threads survive: a [*2:4] ##1 b only completes on threads where b appears right after the run-length that thread chose.
// Wait-states: between 1 and 3 wait cycles, then ready
sequence s_waits;
sel ##1 wait_n [*1:3] ##1 ready;
endsequence
property p_waits;
@(posedge clk) sel |-> ##1 wait_n [*1:3] ##1 ready;
endpropertyWAVEFORM — sel ##1 wait_n[*1:3] ##1 ready (threads)
clk : 1 2 3 4 5 6 7
sel : 0 1 0 0 0 0 0
wait_n : 0 0 1 1 0 0 0
ready : 0 0 0 0 1 0 0
A
thread [*1]: wait @3, ready @4? wait_n=1 not ready → dies
thread [*2]: wait @3,4, ready @5? ready=1 → MATCH @5
thread [*3]: wait @3,4,5... wait_n=0 @5 → run broken → dies
One surviving thread is enough — sequence matches at tick 5.The empty match: [*0]
a [*0] is the empty repetition : it matches zero occurrences, consuming zero clock ticks. An empty match has no cycles, so a sequence that can only match empty cannot stand alone — assert property (a [*0]); is illegal because there is no tick at which anything is observed. Empty matches only make sense as optional middles glued to neighbors, and the standard defines the gluing carefully: s1 ##1 a[*0] ##1 s2 collapses to s1 ##1 s2, while s1 ##0 a[*0] ##0 s2 collapses to s1 ##0 s2. The real use is [*0:N]: an optional run, including "not there at all".
// 0 to 2 wait states allowed: ready may come immediately
sequence s_opt_waits;
sel ##1 wait_n [*0:2] ##1 ready;
endsequence
// [*0] thread: sel ##1 ready (no wait cycle at all)
// [*1] thread: sel ##1 wait_n ##1 ready
// [*2] thread: sel ##1 wait_n ##1 wait_n ##1 ready
// ILLEGAL — empty match cannot be a whole sequence:
// a_bad: assert property (@(posedge clk) wait_n [*0]);WAVEFORM — sel ##1 wait_n[*0:2] ##1 ready, zero-wait case
clk : 1 2 3 4
sel : 0 1 0 0
wait_n : 0 0 0 0
ready : 0 0 1 0
A M
[*0] thread: the wait_n run is EMPTY (consumes no ticks), so
sel ##1 [*0] ##1 ready collapses to sel ##1 ready → match @3.
[*1] and [*2] threads die (wait_n never 1). Empty thread saves it.Unbounded repetition: [*1:$]
a [*1:$] is an unbounded run: one or more consecutive ticks of a, with no upper limit — the repetition analog of ##[1:$]. It is weak in simulation: a run that never terminates into the following item just never completes, with no failure. The idiomatic use is "hold until": busy [*1:$] ##1 done reads as busy stays up for some cycles, then done. Be careful what ends the run — the repetition itself never requires a to drop; only the next sequence element anchors the end.
// Busy holds (any length >= 1) and the run ends with done
property p_busy_until_done;
@(posedge clk) $rose(busy) |-> busy [*1:$] ##1 done;
endproperty
// NOTE: in simulation, if done never comes and busy stays high,
// the attempt never finishes and never fails (weak).
// Bounded alternative for simulation sign-off:
property p_busy_bounded;
@(posedge clk) $rose(busy) |-> busy [*1:256] ##1 done;
endpropertyWAVEFORM — $rose(busy) |-> busy[*1:$] ##1 done
clk : 1 2 3 4 5 6
busy : 0 1 1 1 1 0
done : 0 0 0 0 1 0
A . . M
A = attempt on $rose(busy) @2
Threads: [*1]→done@3? no. [*2]→done@4? no. [*3]→done@5? YES → MATCH
Note busy is still 1 at tick 5 — the run does not require a drop;
done arriving is what terminates the matching thread.Interview angle and review
Interview angle
"Expand a[*3]" — a ##1 a ##1 a. Then the follow-up: "(a ##1 b)[*2]?" — a ##1 b ##1 a ##1 b. Knowing repetition of multi-cycle sequences is the differentiator.
"Why can't a[*0] stand alone?" — an empty match consumes zero ticks; there is nothing to observe, so the standard forbids it as a complete sequence/property.
"busy[*1:$] — when does it fail?" — trick question in simulation: it cannot fail by itself; it is weak. It only constrains via what follows it.
"a holds 5 cycles; how many matches does a[*2:4] have?" — three (run lengths 2, 3, 4 ending at different ticks) per attempt start; thread thinking again.
Key takeaways
[*N] is sugar for N copies chained with ##1 — and works on whole sequences, not just booleans.
[*M:N] forks a thread per run length; longer runs contain shorter matches, and the following element selects survivors.
[*0] matches empty, consuming zero ticks — legal only glued between neighbors, never standalone.
[*1:$] is weak: it never fails alone in simulation; the next element (or a bound) supplies the teeth.
Common pitfalls
Writing busy[*4] and expecting busy to DROP after 4 cycles — repetition checks presence, not the trailing edge; add ##1 !busy if you need it.
assert property (s [*0]) or a standalone [*0:N] that admits empty — illegal or vacuous-ish; keep empty matches glued.
Using [*1:$] as a liveness check in simulation — it silently never completes when the terminator never comes.
Forgetting that [*M:N] yields multiple matches — pair with first_match before using the endpoint elsewhere.
Off-by-one in [*N] boundaries: start ##1 busy[*4] ##1 done spans 6 ticks total, not 4 — count the waveform.