Scheduling regions and nonblocking updates
Follow what a simulator does inside one time step, see why nonblocking assignments make clocked logic predictable, and recognise a race when you write one.
- Lesson
- 6.2
- Time
- 30 min
- Runs on
- Your browser
- Check
- 5 questions
A testbench drives the DUT input with din = next; immediately after @(posedge clk). On one simulator the DUT captures the old value at that edge. After an unrelated file is added to the compile list, it captures the new one, and every expected value is off by a cycle. Neither simulator is wrong: the standard does not say which of two processes woken by the same edge runs first.
With a blocking write, q depends on which process runs first. With a nonblocking write, the DUT always sees the value from before the edge.
One time step has several phases
Simulation time advances in time steps, one for each moment at which something happens. Within a time step, the standard sorts pending work into regions and runs them in a fixed order, going back to earlier regions whenever later work creates new events.
Preponed
Values are sampled before anything changes in this time step. Clocking-block inputs with
#1stepskew and concurrent assertions read these values.Active
Processes woken in this time step run: blocking assignments, continuous assignments,
$display. Nonblocking assignments evaluate their right-hand side here, but do not update yet.Inactive
Processes delayed by
#0resume here, then the scheduler returns to Active if that created new work.NBA
All pending nonblocking updates are applied, in the order they were scheduled. Processes waiting on those signals wake, and the scheduler loops back to Active.
Observed
Concurrent assertions evaluate, using the Preponed samples. Clocking-block events trigger here, after the design has settled.
Reactive
Code in program blocks and the action blocks of concurrent assertions run; they can create new Active work in the same time step.
Postponed
Read-only:
$strobeand$monitorprint the final values of the time step. Nothing may change any more.
The full list also has Re-Inactive and Re-NBA regions, which mirror Inactive and NBA for testbench code. Clocking-block drives with #0 skew land in Re-NBA, as lesson 6.3 shows.
Nonblocking assignments split read from write
A nonblocking assignment, q <= d;, reads d in the Active region and writes q later, in the NBA region. Every process woken by the same clock edge therefore reads the values from before the edge, whatever order the processes run in.
That is what makes a pipeline written with <= behave like flip-flops. With a1 <= d; a2 <= a1;, stage 2 receives the old value of stage 1, so data moves one stage per edge.
A blocking assignment, q = d;, writes immediately. Inside one process the order of statements is fixed, so b1 = d; b2 = b1; always copies the new d into both stages: the pipeline collapses into a single register, every time.
Where the order is left open
The standard fixes the order of statements inside one begin-end block, and the order of the regions. It does not fix the order in which separate processes woken in the same region run.
Suppose one process writes a signal with a blocking assignment at a clock edge, and another process reads it at the same edge. The reader may see either the old or the new value. That is a race. Different simulators, or the same simulator after a harmless-looking edit, may choose differently.
A race is a bug in the code, not a property of a simulator. Never test which value "wins"; remove the race instead.
Blocking write at the edge
@(posedge clk);
din = next; // DUT reads din
// at this same edge
The DUT may capture the old or the new value; the standard does not say which.
Nonblocking write at the edge
@(posedge clk);
din <= next; // updated in the
// NBA region
Every Active-region reader at this edge sees the old value; the DUT captures next at the following edge.
Three ways to keep a testbench race-free
Write clocked state with nonblocking assignments, in the DUT and in testbench models alike. Then everything that reads at the edge sees the values from before it.
Change testbench stimulus away from the sampling edge, for example at the falling edge, as lesson 6.1 did. Or drive and sample through a clocking block, which places both at fixed points in the time step; that is the next lesson.
Avoid #0 and small #1 delays as fixes. They move work into another region or time step, but the result then depends on details that the next edit can change.
Seeing the regions in output
$display prints in the Active region, so right after @(posedge clk) it shows the values from before the edge's nonblocking updates. $strobe waits until the Postponed region and shows the values at the end of the time step.
Printing the same register both ways at one edge is a quick way to see whether it is updated by a nonblocking assignment. In the example below, $display shows 00 and $strobe shows 11 for the same signal at the same time.
Try it
At t=5, $display ran in the Active region and saw the pipeline before its nonblocking updates, so both stages read 00. $strobe printed in the Postponed region, after the updates.
The nonblocking pipeline moves data one stage per edge, while the blocking one copies each new value into both stages. Swap the two lines inside the blocking always block, so it reads b2 = b1; b1 = d;, and predict the new b values.
t=5 Active: a1=00 a2=00 t=5 Postponed: a1=11 a2=00 | b1=11 b2=11 t=15 Postponed: a1=22 a2=11 | b1=22 b2=22 t=25 Postponed: a1=33 a2=22 | b1=33 b2=33Check yourself
Remember
- A time step runs its regions in order: Preponed, Active, Inactive, NBA, Observed, Reactive, Postponed.
- A nonblocking assignment reads in Active and writes in NBA, so every reader at an edge sees pre-edge values.
- Statements inside one block run in order; separate processes in the same region run in an unspecified order.
- A blocking write and a read of the same signal at the same edge, in different processes, is a race.
$displayshows Active-region values;$strobeshows the final values of the time step.
Practice
- The pipeline that lost a stage Fix · Easy
- Catch the broken pipelines Verify · Medium