A sequence is a simple building block in SystemVerilog assertions that can represent certain expressions to aid in creating more complex properties. Even a single signal name, on its own, is already a complete (if very simple) sequence.
11 min read | Intermediate Level
What You'll Learn
- How a single signal name can itself be a complete sequence
- How
$roseand$felldetect edges specifically, comparing against a default value when there's no earlier real sample yet - How
$stablediffers from both, checking for the absence of any transition at all - Why all three functions, applied to the exact same signal trace, classify each cycle differently
A Simple Sequence
module tb;
bit a;
bit clk;
// This sequence states that a should be high on every posedge clk
sequence s_a;
@(posedge clk) a;
endsequence
// When the above sequence is asserted, the assertion fails if 'a'
// is found to be not high on any posedge clk
assert property(s_a);
always #10 clk = ~clk;
initial begin
for (int i = 0; i < 10; i++) begin
a = $random;
@(posedge clk);
// Assertion is evaluated in the preponed region and
// use $display to see the value of 'a' in that region
$display("[%0t] a=%0d", $time, a);
end
#20 $finish;
end
endmodule

| Time (ns) | a | Result |
|---|---|---|
| 10 | 0 | FAIL |
| 30 | 1 | PASS |
| 50 | 1 | PASS |
| 70 | 1 | PASS |
| 90 | 1 | PASS |
| 110 | 1 | PASS |
| 130 | 1 | PASS |
| 150 | 0 | FAIL |
| 170 | 1 | PASS |
| 190 | 1 | PASS |
Output
Compiler version P-2019.06-1; Runtime version P-2019.06-1; Jan 14 06:32 2020 [10] a=0 "testbench.sv", 12: tb.unnamed$$_0: started at 10ns failed at 10ns Offending 'a' [30] a=1 [50] a=1 [70] a=1 [90] a=1 [110] a=1 [130] a=1 [150] a=0 "testbench.sv", 12: tb.unnamed$$_0: started at 150ns failed at 150ns Offending 'a' [170] a=1 [190] a=1 $finish called from file "testbench.sv", line 27. $finish at simulation time 210
This same a trace — 0, 1, 1, 1, 1, 1, 1, 0, 1, 1 across the ten sampled edges — is reused in every example below, so you can directly compare how each system function classifies the exact same signal differently.
$rose
The system function $rose detects a positive edge of the given signal. Because SystemVerilog assertions evaluate in the preponed region, $rose compares the signal's value across two samples — when the value is 0 on one and 1 on the next, a positive edge is assumed to have happened. Before the very first real sample, the "previous" value used for that comparison is the signal's default (0 for a 2-state bit), so a rise can in principle register immediately; in this particular trace, though, it genuinely takes until the second clock to see one:
module tb;
bit a;
bit clk;
// This sequence states that 'a' should rise on every posedge clk
sequence s_a;
@(posedge clk) $rose(a);
endsequence
// When the above sequence is asserted, the assertion fails if
// posedge 'a' is not found on every posedge clk
assert property(s_a);
// Rest of the testbench stimulus
endmodule

| Time (ns) | a | Transition | Result |
|---|---|---|---|
| 10 | 0 | FAIL | |
| 30 | 1 | 0->1 | PASS |
| 50 | 1 | FAIL | |
| 70 | 1 | FAIL | |
| 90 | 1 | FAIL | |
| 110 | 1 | FAIL | |
| 130 | 1 | FAIL | |
| 150 | 0 | 1->0 | FAIL |
| 170 | 1 | 0->1 | PASS |
| 190 | 1 | FAIL |
A positive edge is detected and the assertion passes at 30ns — a is 0 at 10ns and 1 at 30ns, so the rise completes right there. The same happens again at 170ns.
Compiler version P-2019.06-1; Runtime version P-2019.06-1; Jan 14 06:58 2020 [10] a=0 "testbench.sv", 12: tb.unnamed$$_0: started at 10ns failed at 10ns Offending '$rose(a)' [30] a=1 [50] a=1 "testbench.sv", 12: tb.unnamed$$_0: started at 50ns failed at 50ns Offending '$rose(a)' [70] a=1 "testbench.sv", 12: tb.unnamed$$_0: started at 70ns failed at 70ns Offending '$rose(a)' [90] a=1 "testbench.sv", 12: tb.unnamed$$_0: started at 90ns failed at 90ns Offending '$rose(a)' [110] a=1 "testbench.sv", 12: tb.unnamed$$_0: started at 110ns failed at 110ns Offending '$rose(a)' [130] a=1 "testbench.sv", 12: tb.unnamed$$_0: started at 130ns failed at 130ns Offending '$rose(a)' [150] a=0 "testbench.sv", 12: tb.unnamed$$_0: started at 150ns failed at 150ns Offending '$rose(a)' [170] a=1 [190] a=1 "testbench.sv", 12: tb.unnamed$$_0: started at 190ns failed at 190ns Offending '$rose(a)' $finish called from file "testbench.sv", line 27. $finish at simulation time 210
$fell
$fell works the same way as $rose, but detects the opposite transition — when the value is 1 on one edge and 0 on the next, a negative edge is assumed to have happened:
module tb;
bit a;
bit clk;
// This sequence states that 'a' should fall on every posedge clk
sequence s_a;
@(posedge clk) $fell(a);
endsequence
// When the above sequence is asserted, the assertion fails if
// negedge 'a' is not found on every posedge clk
assert property(s_a);
// Rest of the testbench stimulus
endmodule

| Time (ns) | a | Transition | Result |
|---|---|---|---|
| 10 | 0 | FAIL | |
| 30 | 1 | 0->1 | FAIL |
| 50 | 1 | FAIL | |
| 70 | 1 | FAIL | |
| 90 | 1 | FAIL | |
| 110 | 1 | FAIL | |
| 130 | 1 | FAIL | |
| 150 | 0 | 1->0 | PASS |
| 170 | 1 | 0->1 | FAIL |
| 190 | 1 | FAIL |
Notice 30ns and 170ns both show a marked transition (0->1), yet both still FAIL — a transition genuinely happened, just the wrong kind of transition for what $fell is checking. A negative edge is only actually detected, and the assertion passes, at 150ns.
Compiler version P-2019.06-1; Runtime version P-2019.06-1; Jan 14 07:09 2020 [10] a=0 "testbench.sv", 12: tb.unnamed$$_0: started at 10ns failed at 10ns Offending '$fell(a)' [30] a=1 "testbench.sv", 12: tb.unnamed$$_0: started at 30ns failed at 30ns Offending '$fell(a)' [50] a=1 "testbench.sv", 12: tb.unnamed$$_0: started at 50ns failed at 50ns Offending '$fell(a)' [70] a=1 "testbench.sv", 12: tb.unnamed$$_0: started at 70ns failed at 70ns Offending '$fell(a)' [90] a=1 "testbench.sv", 12: tb.unnamed$$_0: started at 90ns failed at 90ns Offending '$fell(a)' [110] a=1 "testbench.sv", 12: tb.unnamed$$_0: started at 110ns failed at 110ns Offending '$fell(a)' [130] a=1 "testbench.sv", 12: tb.unnamed$$_0: started at 130ns failed at 130ns Offending '$fell(a)' [150] a=0 [170] a=1 "testbench.sv", 12: tb.unnamed$$_0: started at 170ns failed at 170ns Offending '$fell(a)' [190] a=1 "testbench.sv", 12: tb.unnamed$$_0: started at 190ns failed at 190ns Offending '$fell(a)' $finish called from file "testbench.sv", line 27. $finish at simulation time 210
$stable
$stable checks something different from either edge-detection function: that the value hasn't changed at all between the current and previous sample, regardless of which direction a change would have gone:
module tb;
bit a;
bit clk;
// This sequence states that 'a' should be stable on every clock
// and should not have posedge/negedge at any posedge clk
sequence s_a;
@(posedge clk) $stable(a);
endsequence
// When the above sequence is asserted, the assertion fails if
// 'a' toggles at any posedge clk
assert property(s_a);
// Rest of the testbench stimulus
endmodule

| Time (ns) | a | Transition | Result |
|---|---|---|---|
| 10 | 0 | PASS | |
| 30 | 1 | 0->1 | FAIL |
| 50 | 1 | PASS | |
| 70 | 1 | PASS | |
| 90 | 1 | PASS | |
| 110 | 1 | PASS | |
| 130 | 1 | PASS | |
| 150 | 0 | 1->0 | FAIL |
| 170 | 1 | 0->1 | FAIL |
| 190 | 1 | PASS |
Compiler version P-2019.06-1; Runtime version P-2019.06-1; Jan 14 07:12 2020 [10] a=0 [30] a=1 "testbench.sv", 12: tb.unnamed$$_0: started at 30ns failed at 30ns Offending '$stable(a)' [50] a=1 [70] a=1 [90] a=1 [110] a=1 [130] a=1 [150] a=0 "testbench.sv", 12: tb.unnamed$$_0: started at 150ns failed at 150ns Offending '$stable(a)' [170] a=1 "testbench.sv", 12: tb.unnamed$$_0: started at 170ns failed at 170ns Offending '$stable(a)' [190] a=1 $finish called from file "testbench.sv", line 27. $finish at simulation time 210
Comparing All Four on the Same Trace
Since every example in this article uses the exact same a trace, here's all four side by side:
| Time (ns) | a | Plain a | $rose(a) | $fell(a) | $stable(a) |
|---|---|---|---|---|---|
| 10 | 0 | FAIL | FAIL | FAIL | PASS |
| 30 | 1 | PASS | PASS | FAIL | FAIL |
| 50 | 1 | PASS | FAIL | FAIL | PASS |
| 70 | 1 | PASS | FAIL | FAIL | PASS |
| 90 | 1 | PASS | FAIL | FAIL | PASS |
| 110 | 1 | PASS | FAIL | FAIL | PASS |
| 130 | 1 | PASS | FAIL | FAIL | PASS |
| 150 | 0 | FAIL | FAIL | PASS | FAIL |
| 170 | 1 | PASS | PASS | FAIL | FAIL |
| 190 | 1 | PASS | FAIL | FAIL | PASS |
Quick Check
Q: At 10ns — the very first sampled edge, with no earlier posedge to compare against — $stable still reports a clean PASS. How can that be?
Show Answer
Before the first real clock tick, these functions compare against the expression's default sampled value rather than against nothing at all — for a 2-state bit, that default is 0. Since a's actual value at 10ns (0) matches that default, correctly sees no change. and both fail there for the same reason: compared against that same default of 0, neither a rise to 1 nor a fall to 0 actually happened.
Where Are These Used?
$rose and $fell are the standard way to detect an edge-triggered event — a handshake pulse, a mode-change strobe — without manually tracking a "previous value" register yourself. $stable is commonly used to check that a data or address bus doesn't glitch while a corresponding valid signal is asserted, since unexpected changes mid-transfer usually indicate a real bug.
Common Beginner Mistakes
Mistake #1: Assuming $rose or $fell can never register anything on the very first sampled edge
Before the first real clock tick, both functions compare against the signal's default sampled value instead of against nothing at all. Why? For a 2-state bit, that default is 0 — so if the signal's actual value at the very first sample is already 1, genuinely does report true right there, exactly like any later rise. It only looks like the first sample can never register anything because, in most of this article's own examples, that first value happens to match the default.
Mistake #2: Confusing "a transition happened" with "the right kind of transition happened"
As the $fell table shows, 30ns and 170ns both display a marked transition, yet both still fail. Why? The transition column just reports what actually happened to the signal — it doesn't mean that transition satisfies whichever specific function is being checked.
Mistake #3: Assuming the very first sampled edge is a special exception
As the corrected comparison table above shows, it isn't — and ! && ! agree at every single sample, including the very first one. Why? Before any real clock tick has occurred, these functions compare against the signal's default sampled value instead of simply failing outright, so a 2-state signal never actually produces this kind of edge case.
Mistake #4: Assuming a sequence has to combine multiple signals to be useful
The very first example in this article is a complete, legitimate sequence built from nothing but a single signal name. Why? A sequence can be as simple as one boolean expression — there's no minimum complexity required before it becomes a usable building block for a property.
Practice Exercise
Challenge: Classify a New Trace
Given the signal a sampled at six consecutive edges as 1, 1, 0, 0, 1, 0, fill in the result (PASS/FAIL) for plain a, $rose(a), $fell(a), and $stable(a) at each of the six samples.
Hint
Before the first real clock tick, $rose/$fell/$stable compare against a's default sampled value (0 for a 2-state bit) rather than against nothing at all — work out what that comparison gives when the very first sampled value is 1.
Solution
| Sample | a | Plain a | $rose(a) | $fell(a) | $stable(a) |
|---|---|---|---|---|---|
| 1 | 1 | PASS | PASS | FAIL | FAIL |
| 2 | 1 | PASS | FAIL | FAIL | PASS |
| 3 | 0 | FAIL | FAIL | PASS | FAIL |
| 4 | 0 | FAIL | FAIL | FAIL | PASS |
| 5 | 1 | PASS | PASS | FAIL | FAIL |
| 6 | 0 | FAIL | FAIL | PASS | FAIL |
Even at sample 1, with no earlier posedge yet, $rose can still register a genuine rise: compared against a's default sampled value (0 for a 2-state bit), the actual value of 1 here counts as a rise, so $rose correctly passes alongside plain a. $fell and $stable both fail for the same reason — exactly one of the three still passes, the same pattern as every other sample in this article.
Summary
In this tutorial, you learned:
- A sequence can be as simple as a single signal name, checked directly against a clocking event
$roseand$fellcompare against a default sampled value when there's no earlier real sample yet, rather than simply failing outright on the very first checked edge$stablechecks for the absence of any change at all, regardless of direction- For a single-bit signal, exactly one of
$rose,$fell, or$stablepasses at any given sample, including the very first one, since these functions compare against a well-defined default value rather than against nothing at all