SystemVerilog $rose, $fell, $stable covered detecting an edge or the absence of one. This article covers three more sampled-value system functions: $past, for reaching back further than just "the previous cycle"; $sampled, for reading a signal's assertion-visible value from procedural code; and $changed, the direct logical complement of $stable.

11 min read | Intermediate Level

What You'll Learn

  • How $past retrieves a signal's value from a specific number of cycles ago, not just the immediately previous one
  • How $past's optional gating argument skips cycles where a condition wasn't true
  • What $sampled is for, and why it matters specifically inside procedural code
  • Why $changed is just the logical opposite of $stable

$past: Reaching Back Further Than One Cycle

$past(expr) returns the value expr had one clock tick ago — the classic use case is checking a registered signal against the value that fed it:


// Checks that out correctly holds whatever in was one cycle earlier -
// a straightforward 1-cycle pipeline register check
assert property (@(posedge clk) out == $past(in));

Given in sampled as 5, 7, 2, 9 across four consecutive cycles, a correctly-implemented 1-cycle delay register (out <= in; every cycle) produces out values of 5, 7, 2 starting from the second cycle onward — and checking out == $past(in) passes at every one of those cycles, since each out value exactly matches what in was one cycle before it.

Now suppose the design has a bug — an off-by-one delay, lagging by 2 cycles instead of 1. At the cycle where out should show in's value from one cycle back (7), it instead still shows the value from two cycles back (5). The assertion catches this immediately: $past(in) correctly evaluates to 7, but out is 5 — a clear failure, exactly identifying the pipeline timing bug.

Looking Back More Than One Cycle

A second argument specifies how many cycles back to look:


assert property (@(posedge clk) out2 == $past(in, 2));   // 2-cycle pipeline stage

Quick Check

Q: At the very first clock edge of a simulation, what would $past(in) return?

Show Answer

The default sampled value of in — not nothing. Per the LRM, when there's no prior clock tick to look back to yet, $past falls back to the expression's default sampled value (0 for a 2-state variable, X for a 4-state one) instead of being left undefined. It's a well-defined result, just not necessarily a meaningful one — it's being compared against a value nothing ever actually drove.

Skipping Cycles With a Gating Argument

A third, optional argument gates which cycles actually count as "history" — instead of strictly N ticks ago in absolute time, $past looks back to the most recent cycle where the gating condition was true, skipping over any cycles where it wasn't:


// Compares captured against data's value from the most recent cycle
// where enable was actually high - not just literally 1 cycle ago
assert property (@(posedge clk) captured == $past(data, 1, enable));

This is useful whenever a value is only meant to update on specific qualifying cycles — without the gating argument, $past would compare against whatever data happened to be exactly one cycle back, even on a cycle where enable was low and nothing was actually supposed to be captured.

$sampled: Reading a Signal's Assertion-Visible Value

$sampled(expr) returns the same preponed-region value an assertion would use, but callable from ordinary procedural code — useful when you want a $display or other procedural statement to show exactly the value an assertion actually evaluated, rather than whatever the signal's live value happens to be at the exact point that procedural code executes:


if (some_condition)
  $display ("Value seen by the assertion: %0d", $sampled(data));

Without $sampled, procedural code reading data directly could, in principle, see a value that's already moved on from what the assertion itself sampled in the preponed region — $sampled guarantees the two match.

$changed: The Opposite of $stable

$changed(expr) is true exactly when expr's value differs from its previous sampled value — the direct logical complement of $stable. Reusing the same signal trace verified in the $rose/$fell/$stable article:

Time (ns)a$stable(a)$changed(a)
100PASSFAIL
301FAILPASS
501PASSFAIL
701PASSFAIL
901PASSFAIL
1101PASSFAIL
1301PASSFAIL
1500FAILPASS
1701FAILPASS
1901PASSFAIL

At every one of these samples, $changed(a) is exactly the opposite of $stable(a) — wherever the value held steady, $stable passes and $changed fails, and wherever it moved, the reverse.

Even at 10ns — the very first sample, with no earlier posedge yet — $stable and $changed still land on exactly opposite results. Before any real prior tick exists, both functions compare against a's default sampled value (0 for a 2-state bit) instead of being left undefined, so the same complementary relationship holds there too.

Quick Check

Q: Would assert property (@(posedge clk) !$changed(config_reg)); and assert property (@(posedge clk) $stable(config_reg)); check the same thing?

Show Answer

Yes — from the second sample onward, !$changed(x) and $stable(x) are logically identical, since $changed is defined as the exact complement of $stable. Either form expresses "this value must not change" equally well.

Where Are These Used?

$past is the standard way to verify a pipeline stage's registered output against whatever fed it some number of cycles earlier — exactly the kind of check that catches an off-by-one timing bug in a multi-stage datapath. $changed is a natural fit anywhere the condition being checked reads more naturally as "this changed" than "this failed to stay the same," even though both express the same idea.

Common Beginner Mistakes

Mistake #1: Assuming $past returns nothing at all on the very first checked edge

It actually returns a well-defined value even there — the expression's default sampled value, not an undefined result. Why? Per the LRM, when there's no prior clock tick to look back to yet, $past falls back to that default (0 for a 2-state variable, X for a 4-state one) instead of being left unusable — it just may not be a meaningful comparison yet, since nothing real has actually been registered.

Mistake #2: Assuming $past(x, N) always means "N cycles ago" when a gating argument is present

Once a gating condition is added, $past instead looks back to the Nth most recent cycle where that condition was true — not a fixed number of cycles in absolute time. Why? Mixing these two interpretations up produces a check comparing against the wrong historical value entirely, especially on a signal that doesn't update every single cycle.

Mistake #3: Reading a signal directly in procedural code when $sampled was needed

A signal's live value at the point procedural code executes isn't always guaranteed to match what an assertion sampled for that same cycle. Why? If a display statement or debug check needs to show exactly what an assertion actually evaluated, $sampled is the way to guarantee that match.

Mistake #4: Treating $changed as if it were a different check from !$stable

They're the same check, just phrased in opposite terms. Why? Writing both a $changed-based and a $stable-based assertion for what's logically the same condition is redundant — pick whichever phrasing reads more naturally for the specific property being expressed.

Practice Exercise

Challenge: Check a 3-Stage Pipeline

Write an assertion checking that stage3_out correctly holds the value data_in had exactly 3 cycles earlier.

Hint

$past's second argument specifies how many cycles back to look — this is a direct extension of the basic 1-cycle example in this article.

Solution

assert property (@(posedge clk) stage3_out == $past(data_in, 3));

This follows the exact same pattern as the article's own 1-cycle and 2-cycle examples, just extended to a 3-cycle pipeline depth — $past's second argument scales to any fixed delay a design's datapath actually has.

Summary

In this tutorial, you learned:

  • $past(expr) returns a signal's value from one cycle ago by default, or N cycles ago with an explicit second argument
  • An optional gating argument makes $past look back to the most recent cycle where a condition was true, rather than a fixed number of cycles in absolute time
  • $sampled(expr) reads a signal's assertion-visible (preponed-region) value from ordinary procedural code
  • $changed(expr) is the exact logical complement of $stable(expr) — true whenever the value differs from its previous sample
  • Before a real prior sample exists, all of these functions fall back to the expression's default sampled value rather than being left undefined — a well-defined result, even if not yet a meaningful one
Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5