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 $rose and $fell detect edges specifically, comparing against a default value when there's no earlier real sample yet
  • How $stable differs 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)aResult
100FAIL
301PASS
501PASS
701PASS
901PASS
1101PASS
1301PASS
1500FAIL
1701PASS
1901PASS

Output

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)aTransitionResult
100FAIL
3010->1PASS
501FAIL
701FAIL
901FAIL
1101FAIL
1301FAIL
15001->0FAIL
17010->1PASS
1901FAIL

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.

Output
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)aTransitionResult
100FAIL
3010->1FAIL
501FAIL
701FAIL
901FAIL
1101FAIL
1301FAIL
15001->0PASS
17010->1FAIL
1901FAIL

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.

Output
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)aTransitionResult
100PASS
3010->1FAIL
501PASS
701PASS
901PASS
1101PASS
1301PASS
15001->0FAIL
17010->1FAIL
1901PASS
Output
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)aPlain a$rose(a)$fell(a)$stable(a)
100FAILFAILFAILPASS
301PASSPASSFAILFAIL
501PASSFAILFAILPASS
701PASSFAILFAILPASS
901PASSFAILFAILPASS
1101PASSFAILFAILPASS
1301PASSFAILFAILPASS
1500FAILFAILPASSFAIL
1701PASSPASSFAILFAIL
1901PASSFAILFAILPASS

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
SampleaPlain a$rose(a)$fell(a)$stable(a)
11PASSPASSFAILFAIL
21PASSFAILFAILPASS
30FAILFAILPASSFAIL
40FAILFAILFAILPASS
51PASSPASSFAILFAIL
60FAILFAILPASSFAIL

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
  • $rose and $fell compare against a default sampled value when there's no earlier real sample yet, rather than simply failing outright on the very first checked edge
  • $stable checks for the absence of any change at all, regardless of direction
  • For a single-bit signal, exactly one of $rose, $fell, or $stable passes 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
Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5