Clocks and sampling
Know exactly which values an assertion sees at each clock edge, and use $rose, $fell, $stable and $past to compare one edge with the edges before it.
- Lesson
- A1.2
- Time
- 25 min
- Runs on
- Verilator
- Check
- 5 questions
An engineer wrote $fell(rready) |-> ... and saw it fire one cycle "late". The waveform showed rready falling exactly at a rising edge, but the assertion reported the fall at the following edge. The engineer filed a bug against the simulator. There was no bug: the testbench changed rready at the edge itself, and an assertion reads every signal from just before the edge.
The sample is the value from just before the edge, so a change made at the edge appears one edge later.
Properties are checked only at the clock
A concurrent assertion names a clocking event, usually @(posedge clk). The property is evaluated at each such event and nowhere else, so a pulse that rises and falls between two edges is invisible to it. In the example below, a 3 ns pulse on err between the edges at 35 and 45 never fails !err, while an always @(posedge err) block counts it.
Writing the clock in every assertion is repetitive. A default clocking cb @(posedge clk); endclocking declaration in the module lets later assertions leave it out.
Sampled values
An assertion does not read signals at the moment it is evaluated. It reads their sampled values: the values from the Preponed region, before anything has changed in the time step of the edge. That is the same view a #1step clocking block gives, as Module 6 showed.
Testbench writes rready = 0 | Value the assertion samples at that edge | Edge where the change is seen |
|---|---|---|
| at the falling edge before the edge | 0 | this edge |
exactly at the edge, after @(posedge clk) | 1 (the old value) | the next edge |
| with a nonblocking assignment at the edge | 1 (the old value) | the next edge |
What an assertion sees for a change made at different times.
Because the samples are taken before any process runs, the result never depends on process order. Even a testbench that writes a signal at the edge with a blocking assignment gives the same sampled value on every simulator.
One time step, region by region
$fell(rready) rule of the example. Arrows show the order of the regions, and who writes or reads rready.- Preponed
Before any process runs at 15, the assertion takes the sampled value of
rready: 0, written right after the edge at 5. - Active
The testbench resumes after
@(posedge clk)and writesrready = 1. The sample taken a moment ago does not change.Follow the calls, values and waits
- data sample: rready = 0: Preponed → Active and NBA
- data rready = 1: Active and NBA → rready
- Observed
The property is evaluated on samples only: 1 at 5, 0 at 15.
$fell(rready)is true, so!$fell(rready)fails.Follow the calls, values and waits
- data sample: rready = 0: Active and NBA → Observed
- Reactive
The fail action prints
now=1 sampled=0. Readingrreadygives the value written in Active;$sampled(rready)gives the one the assertion used.More context
A monitor that prints the current value at this point reports a different value from the one the rule checked. Print
$sampled(x)next to it.Follow the calls, values and waits
- data $fell: 1 at 5, 0 at 15: Observed → Reactive
- data now=1: rready → Reactive
Read the diagram as text
- Preponed: Assertion samples taken
- Active and NBA: Design and testbench run
- Observed: Properties evaluated on samples
- Reactive: Action blocks run
- rready: The variable itself
- Preponed → Active and NBA: then
- Active and NBA → Observed: then
- Observed → Reactive: fails
- Active and NBA → rready: writes 1
- rready → Reactive: now=
Samples are taken first and evaluated after the design has moved on, so the result never depends on process order.
Reading a waveform the way an assertion does
Find the edge
Locate the rising edge of the assertion's clock that you are interested in.
Look just left of it
Read each signal's value immediately before the edge. A value that changes exactly at the edge counts as its old value.
Compare with the previous edge
For
$rose,$fell,$stableand$past, repeat the same reading at the edge before, and compare the two samples.
Many waveform viewers draw a change at an edge on top of the edge, which invites reading the new value. Zoom in, or move the cursor slightly left of the edge, before deciding what an assertion saw.
Why assertions cannot race
Module 6 showed a monitor in an always block reading a signal at the same edge where another process writes it, with no defined order. A concurrent assertion has no such problem, because its samples are taken before any process of that time step runs.
That makes assertions a reliable second opinion when a monitor and a waveform disagree. If the assertion and the monitor report different values for the same edge, the monitor is reading at the wrong moment.
Comparing edges: $rose, $fell, $stable, $changed
Sampled value functions compare a signal's sample at this edge with its sample at the previous edge. $rose(x) is true when the sample went from 0 to 1, $fell(x) when it went from 1 to 0, $stable(x) when it did not change, and $changed(x) when it did.
They work on whole vectors too: $stable(rdata) is true when every bit matches the previous sample. For $rose and $fell, only the least significant bit counts.
These functions are how most rules about change are written. The rule "data does not move during a stall" becomes rvalid && !rready |=> $stable(rdata).
$past, and the first edges
$past(x) returns the sample of x at the previous edge, and $past(x, n) the sample n edges back. It lets a property compare with an older value directly, as in rdata == $past(rdata).
At the start of simulation there may not be n earlier edges. Then $past returns the value the variable was declared with, such as the 1 in logic rready = 1;, not a value later written by an initial block. $rose and $fell at the first edge compare with the same declared value. Simulators do not all agree on these early values, so start such checks only after reset.
Checks that use $past should therefore start only after reset, or after enough edges, so that they never compare with a value nobody intended.
Printing what the assertion saw
Action blocks run later in the time step than the sampling, so a signal printed there may already hold a newer value. At 15 in the example, the fail message shows rready as 1 while the rule used 0. Print $sampled(x) to show the value the assertion actually used.
Try it
rready was written right after the edge at 5, so the fall is seen at 15. By the time the fail action runs at 15, the testbench has already written 1, which is why now and sampled differ. rdata was written between edges, so its changes appear at the very next edges, 45 and 55.
At 45, two edges back is before the first change, so $past(rdata, 2) returns the declared value 10. The err pulse came and went between two edges: counted once, never seen by no_err. Change #3 to #6, so the pulse covers the edge at 45, and predict which rule reports.
t=15 $fell(rready): now=1 sampled=0 t=45 rdata changed: sampled=20 past=10 past2=10 t=55 rdata changed: sampled=30 past=20 past2=10 err pulses: 1Check yourself
Remember
- A concurrent assertion is evaluated only at its clocking event; changes between edges are invisible.
- It reads sampled values from before the edge, so a change made at the edge appears at the next one.
$rose,$fell,$stableand$changedcompare this edge's sample with the previous edge's.$past(x, n)looks n edges back; before n edges exist, it returns the declared value.- Print
$sampled(x)in an action block to show the value the assertion used.