SVA Examples: $past, $stable, disable iff and Repetition [*], [->], [=] (Pass and Fail)
In short: $stable(x) is true when x has the same value as at the previous clock edge. $past(x, N) gives the value x had N clock edges ago. disable iff (rst) cancels any check in progress while reset is high, so it neither passes nor fails. The repetition operators count: b[*3] means 3 cycles in a row, b[->3] means the 3rd time b is true (gaps allowed) with the next step right after it, and b[=3] means 3 times with gaps allowed and idle cycles allowed after the 3rd.
Part 1 covered the basics (##, implication, edges). See SVA with examples.
This part covers the operators you reach for next: history, stability, reset and counting.
Every example below has a stimulus you can read cycle by cycle, the real output from a simulator, and a waveform.
Think of it like a security guard with a camera
The guard doesn’t watch nonstop. The camera takes one snapshot per clock edge, and the guard only judges snapshots.
- **
$stable(x):** “Is the parcel still exactly where it was in the last snapshot?” - **
$past(x, 2):** “Rewind two snapshots. What did it look like then?” - **
disable iff (rst):** a fire drill. The guard drops every check in progress. Nobody gets written up for what happened during the drill. - **
[*3]: “The door was open in 3 snapshots in a row**.” - **
[->3]: “On the 3rd badge swipe, the door must open in the very next** snapshot.” - **
[=3]: “After the 3rd swipe, the door may open later**, as long as nobody swipes a 4th time first.”
One detail makes all of this work: the snapshot is taken just before the clock edge. So a signal you change at the edge is seen at the next snapshot.
Example 1: $stable, data must hold while waiting
A classic handshake rule (AXI has the same one): once valid is up and ready is low, data must not change until the handshake.
// $stable: data must not change while valid is waiting for ready
module tb;
logic clk = 1, valid = 0, ready = 0;
logic [7:0] data = 0;
always #5 clk = ~clk; // rising edges at 10, 20, 30 ... = cycles 1, 2, 3 ...
a_hold: assert property (@(posedge clk) (valid && !ready) |=> $stable(data))
else $display("cycle %0t: FAIL a_hold, data went %0d -> %0d while waiting",
$time/10, $past(data), data);
initial begin // each line sets the values seen at that cycle
#5 valid = 1; data = 10; // cycle 1: waiting
#10 ; // cycle 2: waiting, data held
#10 ready = 1; // cycle 3: handshake
#10 valid = 0; ready = 0; // cycle 4: idle
#10 valid = 1; data = 20; // cycle 5: waiting
#10 data = 21; // cycle 6: waiting, data CHANGED (the bug)
#10 ready = 1; // cycle 7: handshake
#10 valid = 0; ready = 0; // cycle 8: idle
#20 $finish;
end
endmodule
Output (Verilator 5.020 with --assert, 11 Nov 2026):
cycle 6: FAIL a_hold, data went 20 -> 21 while waiting
| Cycle | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|---|---|---|---|---|---|---|---|---|
| valid | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 0 |
| ready | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 0 |
| data | 10 | 10 | 10 | 10 | 20 | 21 | 21 | 21 |
| Result | pass | pass | FAIL | pass |
- Cycles 1 and 2: waiting, data held at 10. Fine.
- Cycle 5 was waiting with data 20. At cycle 6, data is 21. One failure, at the exact cycle it happened.
|=>means “check at the next edge”, which is why the rule looks one cycle ahead.
Example 2: $past, ack must come exactly 2 cycles after req
// $past: every ack must have a req exactly 2 cycles earlier
module tb;
logic clk = 1, req = 0, ack = 0;
always #5 clk = ~clk; // cycle N = rising edge at time 10*N
a_ack: assert property (@(posedge clk) ack |-> $past(req, 2))
else $display("cycle %0t: FAIL a_ack, no req 2 cycles earlier", $time/10);
initial begin
#15 req = 1; // cycle 2: req
#10 req = 0; // cycle 3
#10 ack = 1; // cycle 4: ack, 2 cycles after req -> good
#10 ack = 0; // cycle 5
#10 req = 1; // cycle 6: req
#10 req = 0; // cycle 7
#20 ack = 1; // cycle 9: ack, 3 cycles after req -> bad
#10 ack = 0; // cycle 10
#20 $finish;
end
endmodule
Output (Verilator 5.020 with --assert):
cycle 9: FAIL a_ack, no req 2 cycles earlier
- The ack at cycle 4 looks back 2 snapshots, finds req at cycle 2. Pass.
- The ack at cycle 9 looks back to cycle 7. No req there (it was at 6). Fail.
$pastlets you write the rule from the effect back to the cause. That’s often easier than starting from req and looking forward.

Example 3: disable iff, the same rule with and without a reset guard
// disable iff: the same rule, with and without a reset guard
module tb;
logic clk = 1, rst = 0, valid = 0, ready = 0;
logic [7:0] data = 0;
always #5 clk = ~clk; // cycle N = rising edge at time 10*N
a_no_guard: assert property (@(posedge clk)
(valid && !ready) |=> $stable(data))
else $display("cycle %0t: FAIL a_no_guard", $time/10);
a_guarded: assert property (@(posedge clk) disable iff (rst)
(valid && !ready) |=> $stable(data))
else $display("cycle %0t: FAIL a_guarded", $time/10);
initial begin
#5 valid = 1; data = 10; // cycle 1: waiting
#10 ; // cycle 2: still waiting
#10 rst = 1; valid = 0; data = 0;// cycle 3: reset clears everything
#10 rst = 0; // cycle 4
#20 $finish;
end
endmodule
Output (Verilator 5.020 with --assert):
cycle 3: FAIL a_no_guard
- Cycle 2 started a check: “data must still be 10 at cycle 3”.
- At cycle 3, reset cleared data to 0. Without a guard, that’s a failure: a false alarm, because a reset is allowed to clear data.
a_guardedsawrsthigh and dropped the check. No pass, no fail. That’s the fire drill.

Rule of thumb: almost every concurrent assertion in a real design wants disable iff (rst). Without it, the first reset in every test can raise a failure that isn’t a bug.
Example 4: [*3], three cycles in a row
// [*3]: busy must be high for exactly 3 cycles in a row, then done
module tb;
logic clk = 1, start = 0, busy = 0, done = 0;
always #5 clk = ~clk; // cycle N = rising edge at time 10*N
a_busy3: assert property (@(posedge clk) $rose(start) |=> busy[*3] ##1 done)
else $display("cycle %0t: FAIL a_busy3", $time/10);
initial begin
// attempt 1: start at 1, busy 2-4, done 5 -> pass
#5 start = 1; // cycle 1
#10 start = 0; busy = 1; // cycle 2
#20 ; // cycles 3, 4 busy
#10 busy = 0; done = 1; // cycle 5
#10 done = 0; // cycle 6
// attempt 2: start at 8, busy 9-10, gap at 11 -> fail at 11
#20 start = 1; // cycle 8
#10 start = 0; busy = 1; // cycle 9
#10 ; // cycle 10
#10 busy = 0; // cycle 11: busy dropped early
#10 busy = 1; // cycle 12
#10 busy = 0; done = 1; // cycle 13
#10 done = 0;
#20 $finish;
end
endmodule
Output (Synopsys VCS 2025.06, 11 Nov 2026):
"testbench.sv", 6: tb.a_busy3: started at 80ns failed at 110ns
Offending 'busy'
cycle 11: FAIL a_busy3
VCS adds its own line on top of ours: the attempt started at 80 ns (cycle 8, the rise of start) and failed at 110 ns (cycle 11), and it names the signal that broke the rule: busy.
- Attempt 1: busy at 2, 3, 4, then done at 5. Three in a row. Pass.
- Attempt 2: busy at 9 and 10, then a gap at 11.
[*3]needs them back to back, so the check fails the moment the run breaks, not later at done. - Picture it: the door must show open in 3 snapshots in a row. One closed snapshot in the middle and the rule fails right there.

Example 5: [->3] vs [=3] on the same signals
This is the pair everyone mixes up, so here they are side by side, watching the same wires.
// [->3] vs [=3]: same stimulus, two rules
module tb;
logic clk = 1, req = 0, gnt = 0, enable = 0;
always #5 clk = ~clk; // cycle N = rising edge at time 10*N
a_goto: assert property (@(posedge clk) $rose(req) |-> gnt[->3] ##1 enable)
else $display("cycle %0t: FAIL a_goto ([->3])", $time/10);
a_nonc: assert property (@(posedge clk) $rose(req) |-> gnt[=3] ##1 enable)
else $display("cycle %0t: FAIL a_nonc ([=3])", $time/10);
task automatic pulse_gnt(); gnt = 1; #10 gnt = 0; endtask
initial begin
// attempt 1: req at 1, gnt at 3, 5, 7, enable at 8 -> both pass
#5 req = 1; // cycle 1
#10 req = 0; // cycle 2
#10 pulse_gnt(); // cycle 3
#10 pulse_gnt(); // cycle 5
#10 pulse_gnt(); // cycle 7
enable = 1; #10 enable = 0; // cycle 8
// attempt 2: req at 12, gnt at 14, 16, 18, enable at 21 (2 idle cycles)
#30 req = 1; // cycle 12
#10 req = 0; // cycle 13
#10 pulse_gnt(); // cycle 14
#10 pulse_gnt(); // cycle 16
#10 pulse_gnt(); // cycle 18
#20 enable = 1; // cycle 21
#10 enable = 0;
#30 $finish;
end
endmodule
Output (Synopsys VCS 2025.06, 11 Nov 2026):
"testbench.sv", 6: tb.a_goto: started at 120ns failed at 190ns
Offending 'enable'
cycle 19: FAIL a_goto ([->3])
Only a_goto failed, at cycle 19, and VCS points at enable. a_nonc printed nothing, so it passed both attempts. Attempt 1 passed for both rules.
| Attempt 1: enable right after 3rd gnt | Attempt 2: enable 3 cycles after 3rd gnt | |
|---|---|---|
gnt[->3] ##1 enable | Pass | Fail at cycle 19 |
gnt[=3] ##1 enable | Pass | Pass at cycle 21 |
- **
[->3](goto): the count ends on** the 3rd gnt. So##1 enablemeans “enable in the very next cycle”. At cycle 19 it wasn’t there. - **
[=3](non-consecutive): the count can stretch past** the 3rd gnt over idle cycles. So enable at cycle 21 is fine, as long as gnt doesn’t come a 4th time first. - Badge picture: goto says “the door opens on the next snapshot after the 3rd swipe”. Non-consecutive says “the door opens some time after the 3rd swipe, before anyone swipes a 4th time”.

Quick reference
| Operator | Means | Picture it as |
|---|---|---|
$stable(x) | Same value as last edge | Parcel hasn’t moved since the last snapshot |
$past(x, N) | Value N edges ago | Rewind N snapshots |
disable iff (c) | Drop checks while c is true | Fire drill: no pass, no fail |
b[*N] | b true N cycles in a row | Open in N snapshots in a row |
b[->N] | N-th time b is true, ends on it | Door opens on the snapshot right after the N-th swipe |
b[=N] | N times b is true, idle cycles allowed after | Door opens later, before a 4th swipe |
Common mistakes (and the fix for each)
These come from real Verification Academy threads, retold in my own words with links.
1. $stable failing on the first clock
- What happened: in 2016 an engineer wrote
assert property (@(posedge clk) $stable(d));and it failed on the very first edge (Verification Academy, 2016). The same question came back in 2020 for a 12-bit bus (Verification Academy, 2020). - Why: at the first edge there is no real “last snapshot”.
$stablecompares against the starting value, which may not match. - Fix: don’t check stability on its own. Give it a reason to start, like example 1’s
(valid && !ready) |=>, or skip the first cycle with##1 $stable(d).
2. Treating [->] and [=] as the same
- What happened: in 2017 an engineer asked what the difference is, since both seemed to behave the same (Verification Academy, 2017).
- Why: they only differ in where the count ends. With
[->3], the next step must come right after the 3rd match. With[=3], idle cycles may sit in between. - Fix: run example 5. Same signals, one passes, one fails.
3. Reading a[=3] ##1 b as “b any time after”
- What happened: in 2021 someone read
a[=3] ##1 bas “b can come any time after the 3rd a”, and asked if##5would mean the same (Verification Academy, 2021). - Why: close, but
amust stay low in the gap. A 4thabefore b breaks the match. Anda[=3] ##5 bis different again: it allowsato go high during those 5 cycles. - Fix: say it out loud: “3 a’s, then quiet, then b”.
4. Using a gated clock instead of disable iff
- What happened: an engineer wrote
@(posedge test_clk iff rstb)and expected the check to stop whenrstbdropped. It kept going and failed after reset (Verification Academy). - Why:
iffon the clock only skips snapshots. It doesn’t cancel checks already running.disable iffdoes. - Fix:
@(posedge test_clk) disable iff (!rstb) ..., like example 3.
5. Failures from X values right after reset
- What happened: in 2014 a
$stablecheck failed in the cycle reset was released, because the previous value was X (Verification Academy, 2014). In 2017 another check failed at start-up because counters were X (Verification Academy, 2017). - Why:
$stableand$pastlook at the snapshot before, and that snapshot may still hold reset junk. - Fix: guard with
disable iff (rst)and start the check from a real event, not from “every cycle”. In 2017 Ben Cohen suggested areadysignal that goes high a few cycles after reset.
FAQ
What does $stable do in SystemVerilog assertions?
It returns true when a signal has the same value as at the previous clock edge.
What is the difference between $past and $stable?
$past(x, N) gives you the old value itself. $stable(x) just tells you whether it changed since the last edge.
What does disable iff do?
While its condition is true, any check in progress is dropped: no pass, no fail.
What is the difference between [*], [->] and [=]?
[*N]: N in a row. [->N]: N times with gaps, ending on the N-th. [=N]: N times with gaps, idle cycles allowed after the N-th.
Why does my assertion fail on the first clock?
Usually $stable or $past comparing against a start-up value. Add a trigger with |-> or |=>, or disable iff (rst).
References
- IEEE 1800-2017, SystemVerilog standard: section 16.9.2 (repetition in sequences), 16.9.3 (sampled value functions:
$past,$stable,$rose), 16.12 (properties, includingdisable iffand implication). - Verification Academy forum threads linked in the mistakes section (accessed 11 Nov 2026).
- Examples 1 to 3 tested on Verilator 5.020 (
--assert), examples 4 and 5 on Synopsys VCS 2025.06, on 11 Nov 2026.
What to read next
- SVA bind: attach these checks to a design without editing it
- Immediate vs concurrent assertions
- AXI protocol explained: where the valid/ready stable rule comes from
Part of the learning path Assertions (SVA)
