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.

systemverilog
// 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);
diagram
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 dies

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

systemverilog
// 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;
endproperty
diagram
WAVEFORM — 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".

systemverilog
// 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]);
diagram
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.

systemverilog
// 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;
endproperty
diagram
WAVEFORM — $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.