Writing out a ##1 a ##1 a to say "a should be true for 3 cycles in a row" gets unwieldy fast, especially once the count grows. SystemVerilog provides three repetition operators that express this compactly — and two of them, despite looking similar, behave in a genuinely different way that's worth understanding precisely.
13 min read | Intermediate Level
What You'll Learn
- How
[*n]compactly expresses n consecutive repetitions of the same condition - How
[->n](goto repetition) finds the nth occurrence of a condition, with gaps allowed in between - How
[=n](non-consecutive repetition) differs from goto in exactly where its match is allowed to end - Why the exact same signal trace can pass under
[=n]but fail under[->n]
Consecutive Repetition: [*n]
expr[*n] requires expr to be true for n consecutive clock cycles in a row. req2[*3] is exactly equivalent to writing req2 ##1 req2 ##1 req2 — the repetition operator just avoids spelling that out by hand:
module tb;
bit req1, req2;
bit clk;
always #10 clk = ~clk;
// Once req1 is seen, req2 must stay high for 3 consecutive
// cycles starting the very next cycle
sequence s_consec;
@(posedge clk) req1 ##1 req2[*3];
endsequence
assert property (s_consec)
$display ("[%0t] Match: req2 held high for 3 consecutive cycles", $time);
initial begin
req1 <= 1; req2 <= 0;
@(posedge clk); // 10ns: req1=1
req1 <= 0; req2 <= 1;
@(posedge clk); // 30ns: req2's 1st consecutive cycle
@(posedge clk); // 50ns: req2's 2nd consecutive cycle
@(posedge clk); // 70ns: req2's 3rd consecutive cycle - match completes
req2 <= 0;
@(posedge clk); // 90ns
#20 $finish;
end
endmodule
req1 is seen at 10ns, and req2 stays high for exactly three cycles afterward — 30ns, 50ns, and 70ns — so the sequence completes and the assertion's action block fires at 70ns, printing "Match: req2 held high for 3 consecutive cycles". Every other independent attempt starting at 30ns, 50ns, 70ns, or 90ns fails immediately, since req1 is 0 at each of those points — only the attempt that started with req1=1 at 10ns has any chance of succeeding.
Range Repetition: [*n:m] and [*n:$]
A range works the same way, just with flexible bounds instead of a fixed count:
req2[*2:4] // req2 true for anywhere between 2 and 4 consecutive cycles
req2[*2:$] // req2 true for 2 or more consecutive cycles, with no upper limit
Quick Check
Q: In the worked example above, if req2 had only stayed high for 2 cycles (30ns and 50ns) before dropping at 70ns, would req1 ##1 req2[*3] still match?
Show Answer
No. [*3] requires exactly 3 consecutive cycles — dropping after only 2 breaks the requirement, and this specific attempt fails. A range like [*2:3] would have accepted either count, but a plain [*3] does not.
Goto Repetition: [->n]
expr[->n] waits for expr to become true a total of n times — the occurrences don't need to be consecutive, gaps are allowed in between. The important detail: the sequence's own match point is fixed exactly at the cycle of the nth occurrence, with no flexibility about where that match "ends."
sequence s_goto;
@(posedge clk) a[->2] ##1 b;
endsequence
This says: wait for a to occur twice (with any gap in between), and b must be true in the cycle immediately following that 2nd occurrence — no later.
Non-Consecutive Repetition: [=n]
expr[=n] also waits for expr to become true n times with gaps allowed, but differs from goto in one specific way: its match point does not have to be exactly the nth occurrence — it can be any later cycle too, as long as expr stays false the whole way there:
sequence s_nonconsec;
@(posedge clk) a[=2] ##1 b;
endsequence
Because [=n]'s match point can slide forward through any number of additional false cycles, this is effectively evaluated as several candidate possibilities in parallel — the property succeeds overall if any valid placement makes the rest of the sequence hold true.
Comparing goto and non-consecutive on the Same Trace
Here's a trace where a occurs at cycles 1 and 3 (with a gap at cycle 2), and b doesn't show up until cycle 5, staying false at cycles 2 and 4:
| Cycle | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|
| a | 1 | 0 | 1 | 0 | 0 |
| b | 0 | 0 | 0 | 0 | 1 |
a[->2] ##1 b (goto): a's 2nd occurrence is at cycle 3, so the goto match ends exactly there — no flexibility. ##1 b then requires b to be true precisely at cycle 4. But b=0 at cycle 4, so this fails.
a[=2] ##1 b (non-consecutive): the match point for the [=2] portion doesn't have to be cycle 3 — it can also be cycle 4, since a stays false there. Using that later match point, ##1 b now requires b at cycle 5 instead — and b=1 there, so this passes.
This exact behavior — goto and non-consecutive repetition genuinely disagreeing on the same trace — is one of the most subtle corners of SVA syntax, and it's easy to find experienced engineers who mix the two up. If this distinction matters for a real check you're writing, it's worth verifying the exact behavior against your specific simulator rather than relying on this explanation alone, since matching semantics for repetition operators are a known area of subtlety even among production tools.
Quick Check
Q: Using the same trace, if b were high at cycle 4 instead of cycle 5, would both a[->2] ##1 b and a[=2] ##1 b pass?
Show Answer
Yes. If b is true exactly one cycle after a's 2nd occurrence (cycle 3), both the rigid goto match point and one of the non-consecutive repetition's candidate match points land on the same answer — the two operators only disagree when the correct answer specifically depends on that extra flexibility.
Where Are These Used?
[*n] is common for checking a signal holds steady for a required minimum duration — a reset pulse width, or a data-valid window. [->n] and [=n] show up in protocols where an event needs to happen a specific number of times before something else is expected, like waiting for a fixed number of acknowledge pulses across an arbitrary number of cycles before checking that a completion signal follows.
Common Beginner Mistakes
Mistake #1: Confusing [*n] with [->n]
[*n] requires consecutive cycles; [->n] allows gaps between occurrences. Why? Using [*n] when gaps are actually expected will fail the moment the condition drops even briefly — the wrong operator here doesn't just give an unexpected result, it changes what's actually being checked.
Mistake #2: Assuming goto and non-consecutive repetition are interchangeable
As the comparison trace shows, they can produce different results on the exact same signal behavior. Why? Goto rigidly anchors its match to the nth occurrence; non-consecutive repetition allows that match point to slide later through additional false cycles — picking the wrong one changes what a downstream ##1 continuation actually requires.
Mistake #3: Forgetting a range's upper bound still needs to be reachable
[*2:4] requires the repeated condition to genuinely hold for somewhere between 2 and 4 consecutive cycles. Why? If the condition drops after only 1 cycle, that's below the range's minimum, and the attempt fails — a range doesn't make the minimum optional.
Mistake #4: Overusing [=n] or [->n] when [*n] would already do the job
If the intended behavior genuinely requires consecutive cycles, reaching for the more flexible non-consecutive or goto operators adds unnecessary ambiguity. Why? The more permissive operators accept traces a consecutive check would correctly reject — use the most restrictive operator that actually matches your intended check.
Practice Exercise
Challenge: Predict a Longer Trace
Given a occurring at cycles 1, 3, and 5 (with gaps at 2 and 4), and b true only at cycle 7 (staying false at cycle 6), predict whether a[->3] ##1 b and a[=3] ##1 b each pass or fail.
Hint
a's 3rd occurrence is at cycle 5. Work out exactly which cycle each operator's match point can land on, then check what ##1 b requires from there.
Solution
a[->3] ##1 b fails. The 3rd occurrence of a is at cycle 5, so goto's match ends exactly there, requiring b at cycle 6 — but b=0 at cycle 6.
a[=3] ##1 b passes. The non-consecutive match point can slide forward to cycle 6 (since a stays false there), which then requires b at cycle 7 instead — and b=1 there. Same underlying logic as the article's own example, just one cycle further along.
Summary
In this tutorial, you learned:
[*n]requires a condition to hold true for n consecutive cycles, and[*n:m]/[*n:$]extend that to a range[->n](goto repetition) waits for the nth occurrence of a condition, allowing gaps, but ends its match exactly at that nth occurrence[=n](non-consecutive repetition) also allows gaps, but its match point can slide forward through additional cycles where the condition stays false- This flexibility means goto and non-consecutive repetition can genuinely disagree on the same trace, specifically when a later match point would succeed but the exact nth-occurrence point would not
- Always use the most restrictive operator that matches your intended check — reaching for extra flexibility you don't actually need makes a property accept traces it should reject