Many rules compare now with before: a flag that was set must still be set, a value may only change after a write, an output rises only after a request. SVA has a small family of functions for this, the sampled value functions $past, $rose, $fell, $stable and $changed. This article explains each one, uses them on a sticky error flag with a real bug, and shows the first-cycle trap that catches almost every beginner.
9 min read | Beginner to Intermediate
What You'll Learn
- Use
$past,$rose,$fell,$stableand$changedin properties - Look several cycles back with
$past(x, n) - Write "stays until" and "changes only after" rules for a register
- Avoid the first-cycle trap, where a check compares with a cycle that never existed
The Sampled Value Functions
Each function compares the value of an expression at the current clock edge with its value at an earlier edge. "Value" here means the sampled value, the one just before the edge, as for every concurrent assertion.
| Function | Returns |
|---|---|
$past(x) | the value of x at the previous clock edge |
$past(x, n) | the value of x n clock edges ago |
$rose(x) | true if the lowest bit of x changed to 1 |
$fell(x) | true if the lowest bit of x changed to 0 |
$stable(x) | true if x did not change |
$changed(x) | true if x changed (the opposite of $stable) |
Two details are worth remembering. $rose and $fell look only at the lowest bit, so on a vector $rose(count) means "bit 0 of count went from 0 to 1", which is rarely what you want. Use $changed or compare with $past for vectors. And $past takes an optional third argument, a gating condition: $past(x, 1, en) is the value of x at the last clock edge where en was true.
Quick Check
Q: A 4-bit counter goes from 1 to 2. What do $rose(count) and $changed(count) return?
Show Answer
$rose(count) is false: bit 0 goes from 1 to 0, so if anything $fell(count) is true. $changed(count) is true, because the value changed.
The Design: a Sticky Error Flag
Many blocks report errors through a sticky flag: a single error pulse sets it, and it stays set until software clears it. That way an error is not lost when software reads the status late. Here is one with a bug:
module sticky (input clk, input rst, input err, input clr, output reg flag);
always @(posedge clk)
if (rst) flag <= 1'b0;
else if (clr) flag <= 1'b0;
else flag <= err; // bug: should be "else if (err) flag <= 1'b1;"
endmodule
The last line copies err into the flag instead of setting it. The flag follows the error pulse and forgets it one cycle later. In a simulation where err stays high for a while, or where software reads the flag at the right moment, this goes unnoticed.
Properties That Look Back
Three rules describe a sticky flag:
// once set, the flag stays set until a clear
a_hold: assert property (@(posedge clk) disable iff (rst) flag && !clr |=> flag);
// the flag only rises after an error
a_rise: assert property (@(posedge clk) disable iff (rst) $rose(flag) |-> $past(err));
// the flag only falls after a clear
a_fall: assert property (@(posedge clk) disable iff (rst) $fell(flag) |-> $past(clr));
Each rule says something different. a_hold is about keeping the value. a_rise and a_fall say what is allowed to cause a change. Together they pin down the behavior without restating the RTL. Notice that a_hold uses |=> to look forward one cycle, while a_rise uses $past to look back. Both forms describe a relation between two neighboring cycles. You pick whichever reads more naturally.
Running It in ChipVerify Lab
The lab engine reads $past, $rose, $fell and $stable inside clocked blocks. The forward-looking |=> becomes a backward look with $past: "if the flag was set and there was no clear in the previous cycle, the flag is set now".
`ifdef FORMAL
reg init = 1'b1;
always @(posedge clk) init <= 1'b0;
always @(*) if (init) assume (rst);
// once set, it stays set until clr or rst
always @(posedge clk) if (!init && $past(flag) && !$past(clr) && !$past(rst)) assert (flag);
// it only rises after an err
always @(posedge clk) if (!init && $rose(flag)) assert ($past(err));
// it only falls after clr or rst
always @(posedge clk) if (!init && $fell(flag)) assert ($past(clr) || $past(rst));
`endif
The run fails at step 4:
An error pulse in step 1 sets the flag in step 2. In step 2 there is no error and no clear, so the flag should stay set, but in step 3 it is 0 again. Both a_hold and a_fall fail on this trace. With the fix, else if (err) flag <= 1'b1;, all three pass.
The First-Cycle Trap
Look again at the lab form of a_hold. It starts with !init. Remove it, so that the check also runs in the first cycle, and run the fixed design. It fails at step 1, and the waveform shows nothing wrong at all: reset in step 0, every signal 0, no error, no clear.
The reason is the cycle before step 0. A clocked assertion in the lab is evaluated at a clock edge and reported in the step after it, as the earlier chapters showed. The failure reported in step 1 comes from the very first clock edge, and at that edge $past refers to a cycle before step 0. That cycle never existed. The tool may choose any values for it, and it chooses ones that break the rule: a flag that "was" set with no clear and no reset. The waveform cannot show them, because they are not part of the trace.
The same problem exists in SVA. In the first clock tick, a sampled value function compares with the default value of the signal, which is X for a 4-state logic. The usual protection is disable iff (rst) together with a reset in the first cycle, so the check is off until there is a real history. In the lab form, the init register does the same job.
Whenever a counterexample fails in step 0 or 1 and the trace looks innocent, suspect a $past that reaches before the start.
Quick Check
Q: An assertion uses $past(x, 3). In how many cycles at the start of a trace is the result unreliable?
Show Answer
Three. $past(x, 3) needs three earlier cycles. The guard must skip the first three cycles, not just the first one, for example with a small counter or with !$past(rst), !$past(rst, 2) and !$past(rst, 3) when the reset is assumed in step 0.
Common Mistakes
Mistake #1: Using $rose or $fell on a vector
They only look at bit 0. $rose(count) is true whenever the counter moves from an even to an odd value. Use $changed(count) or count != $past(count) for vectors.
Mistake #2: Checking $past in the first cycle
The value before the first cycle is unknown or arbitrary. Guard every check that uses $past so it starts only after the history it needs exists.
Mistake #3: Writing only the "stays" rule
"Once set, stays set" does not say the flag cannot rise on its own. Add the rules for the causes of every change, as a_rise and a_fall do. Otherwise a flag that sets itself randomly passes.
Practice Exercise
Challenge: Which Property Finds the Bug?
Open the example, run it, and fix the bug. Then add a fourth rule: "if err is high and clr is low, the flag is set in the next cycle". Put the bug back, remove the three original assertions, and run with only your new rule. Does it fail? Explain why.
Hint
In the buggy design, what is the value of the flag one cycle after err is high?
Solution
always @(posedge clk) if (!init && !$past(rst) && $past(err) && !$past(clr)) assert (flag);
In SVA: assert property (@(posedge clk) disable iff (rst) err && !clr |=> flag);. On its own it passes on the buggy design, because the bug copies err into the flag, so the flag is indeed set one cycle after an error. The bug is in what happens two cycles later, which only the "stays set" rule checks. The exercise shows that a property can be correct and still blind to a bug, which is why a block needs a set of properties that together describe its behavior.
Open the ready-made example in the lab and run the formal check from the formal panel. The free tier allows a depth of up to 10 cycles, and PRO allows up to 100.
Summary
$past(x, n)gives the value n clock edges ago.$rose,$fell,$stableand$changeddetect changes between two edges.$roseand$felllook at bit 0 only. Use$changedor$pastcomparisons for vectors.- A sticky register needs a rule for holding its value and rules for every allowed cause of a change.
- The lab engine reads these functions inside clocked blocks. Forward rules with
|=>become backward rules with$past. - A
$pastthat reaches before the start of the trace gives a false failure with a quiet waveform. Guard such checks withdisable iffand a reset, or aninitregister.