Skip to content
code0.HDL

Registers and nonblocking assignment

Describe a register with always @(posedge clock) and <=, learn the order of work inside one time step, read a counter trace to see the value before and after an edge, and watch a swap break when = replaces <=.

In this chapter

  • Write a register as an always @(posedge clock) block with nonblocking assignment and say why that is the idiom synthesis tools accept
  • Place $display, blocking and nonblocking updates, and $strobe in the regions of one time step and predict which value each prints at an edge
  • Explain why two registers swap with <= and collapse to one value with =, and why statement order matters only for =
  • Choose where a testbench changes inputs and reads outputs so that no race decides the result
Concepts in this chapter
  • always @(posedge), nonblocking assignment
Prerequisite concepts

1. always @(posedge clock) is the register

A combinational block runs whenever an input changes. A register changes only at a clock edge, and Verilog says so in the sensitivity list: always @(posedge clock) runs its body once per rising edge and at no other time. Whatever the body assigns is stored until the next edge. This is the standard idiom synthesis tools turn into flip-flops; the course assumes a single clock and a positive-edge synchronous design throughout.

Form

always @(posedge clock) begin
  count <= count + 4'd1;   // read the current value, store the next one
end

The right-hand side reads the current value, Q in the fundamentals. The assignment stores the next value, D, and that value becomes visible only after the edge. count has to be declared reg; here the keyword is finally paired with storage, but it is the edge in the sensitivity list that makes it a register, not the keyword.

Note. Delays such as #5, system tasks such as $display, and an initial block that steps through a test sequence exist for simulation; synthesis tools ignore or reject them, and which ones they accept depends on the tool. @(posedge clock) is different: as the sensitivity of an always block it is the standard way to describe a synthesizable register, which is exactly how Counter.v below uses it. It is only in a testbench, as a wait in the middle of a sequence, that it is a simulation device. Keep the DUT to the register and combinational idioms, and let the testbench use any simulation construct it needs. This chapter runs everything in simulation with Icarus 11; nothing here is synthesized.

2. Nonblocking <= reads now and updates after everything else at the edge

The nonblocking assignment <= evaluates its right-hand side immediately and postpones the update of its target to a later step of the same simulation time, the nonblocking-update region. Every <= triggered by one edge, in every block of the design, reads values from before the edge and updates afterwards, together. That is the one-cycle rule you met in the fundamentals: in the cycle after the edge, a register shows what was computed from the values before it.

AssignmentSyntaxRight-hand sideTarget changesThe next statement in the block sees
Blockingtarget = expression;evaluated nowimmediatelythe new value
Nonblockingtarget <= expression;evaluated nowin the nonblocking-update region, after the active statements of this time stepthe old value
reg [3:0] x;
initial begin
  x = 1;
  x <= 2;
  $display("x=%0d", x);      // 1: the <= update has not happened yet
  #1 $display("x=%0d", x);   // 2
end

Clocked logic means "every register computes its next value from the values before the edge", and <= is the assignment that says exactly that. Reading a register in the same cycle gives its old value because it is an edge-triggered register written with <=. It is not a law for every signal: a wire, or a variable assigned with =, shows a new value at once.

3. The order of work inside one time step

3.1 Scheduling regions

The simulator advances time in steps and processes the work inside each step in a fixed order of regions. Three of them matter for testbenches.

RegionWhat runs there
ActiveProcedural statements that are ready, blocking = assignments, continuous-assignment updates, $display
Nonblocking updateThe updates of every <= that ran in the active region
Postponed$strobe and $monitor output, after everything else in the step has settled

Two procedural blocks that wake up on the same rising edge both run in the active region, and the standard does not say which runs first. Most testbench races come from that undefined order. Follow these rules and the order stops mattering:

WhereRule
Clocked logicAssign registers with <=, so every block reads the values from before the edge
Testbench inputsChange them away from the sampling edge, for example on the falling edge
Reading outputs in the testbenchEither read at the edge knowing you see the old value, or read after the edge has settled

3.2 $display, $strobe and $monitor

All three take the same format string; they differ in when they print.

Form

$display("format", value, ...);
$strobe("format", value, ...);
$monitor("format", value, ...);
TaskPrints whenHow often
$displayThe moment the statement runs, in the active regionOnce per call
$strobeAt the end of the current time step, in the postponed regionOnce per call
$monitorAt the end of every time step in which one of its arguments changedCalled once, keeps printing
reg [3:0] a = 0, b = 0;
initial $monitor("t=%0t a=%0d b=%0d", $time, a, b);
initial begin
  #10 a = 1;
  #10 b = 2;
  #5  a = 3; b = 4;   // two changes in the same time step
end

Here $monitor prints one line each at times 0, 10, 20, and 25. At time 25 both a and b change, but it prints only once, after that time step has finished.

4. Read a counter trace at the edge and after it

Counter.v is a 4-bit counter with a synchronous reset: at each rising edge, reset high stores 0, otherwise the current value plus one. The reset is synchronous: it acts only at an edge, as Reset, enable and priority works out.

Counter.v
// A 4-bit counter with a synchronous, active-high reset.
module counter (
  input            clock,
  input            reset,
  output reg [3:0] count
);
  always @(posedge clock) begin
    if (reset) count <= 4'd0;
    else       count <= count + 4'd1;
  end
endmodule

The testbench makes a clock with always #5 clock = ~clock;, so rising edges fall at 5, 15, 25 and so on. reset starts at 1 and is lowered on the falling edge at time 20. A second block prints two lines at every rising edge: EDGE with $display, which runs at once, and AFTER with $strobe, which prints at the very end of the same time step.

PartBehavior
clockStarts at 0 and inverts every 5 units
resetStarts at 1
always @(posedge clock)At every rising edge, prints an EDGE line with $display and an AFTER line with $strobe
initialWaits two rising edges, lowers reset on the next falling edge, waits four more rising edges, and finishes one unit later
Tb.v
module tb;
  reg        clock = 0;
  reg        reset = 1;
  wire [3:0] count;

  counter dut (.clock(clock), .reset(reset), .count(count));

  // Period 10: rising edges at 5, 15, 25, ...
  always #5 clock = ~clock;

  // $display runs at the edge; $strobe waits until the edge's updates are done.
  always @(posedge clock) begin
    $display("EDGE t=%0t reset=%b count=%0d", $time, reset, count);
    $strobe("AFTER t=%0t count=%0d", $time, count);
  end

  initial begin
    repeat (2) @(posedge clock);
    // Change inputs on the falling edge, half a period away from sampling.
    @(negedge clock) reset = 0;
    repeat (4) @(posedge clock);
    #1 $finish;
  end
endmodule

Predict before you run.

  1. What does the first EDGE line, at time 5, show for count?
  2. At time 25, reset is low for the first time. Do the EDGE and AFTER lines show 0 or 1?
  3. How many times does the counter increment before $finish?

Check

What does the EDGE line at time 5, the first rising edge, show for count?

Your answer

Check

At time 25 reset is 0 for the first time. What do the EDGE and AFTER lines show?

Your answer

Check the result. Press Run and compare each pair of lines with this table.

TimeEventEDGEAFTER
5First rising edge, reset = 1x: nothing has assigned count yet0: reset applied
15Second rising edge, reset = 100
20Falling edge: the testbench lowers reset
25Rising edge, reset = 001
35, 45, 55Rising edges1, 2, 32, 3, 4

The EDGE line shows the value from before the edge for one precise reason: $display runs in the active region of the time step, and the counter's <= updates in the nonblocking-update region that follows. $strobe waits for the postponed region, so AFTER shows the value the edge produced. A $display after a #1 would also show the new value. The x on the first line is why reset matters: until the first edge with reset high, the register holds x.

Try it yourself. Change both <= in Counter.v to =. The counter counts in the same sequence, but its update now happens in the active region, racing the testbench's $display. With the Icarus version in this lab, the EDGE lines at times 35 and 55 print the new values 2 and 4, while the one at 45 prints the old value 2. Nothing in the source decides that order. The AFTER lines stay correct, because $strobe waits for the end of the time step.

Note. A testbench that reads values at the edge is only trustworthy when the design uses <=.

5. Two registers that swap, written twice

Swap.v holds two modules that differ by a single character. Both load x = 1, y = 2 while load is high, then swap x and y at every rising edge: one with <=, one with =. Read the two else branches side by side.

Swap.v
// Two registers that trade values on every rising edge.
module swap_nonblocking (
  input            clock,
  input            load,
  output reg [3:0] x,
  output reg [3:0] y
);
  // <= reads both old values first, then updates both registers.
  always @(posedge clock) begin
    if (load) begin x <= 4'd1; y <= 4'd2; end
    else      begin x <= y;    y <= x;    end
  end
endmodule

// The same text with = in place of <=.
module swap_blocking (
  input            clock,
  input            load,
  output reg [3:0] x,
  output reg [3:0] y
);
  // = updates x at once, so the next line reads the new x.
  always @(posedge clock) begin
    if (load) begin x = 4'd1; y = 4'd2; end
    else      begin x = y;    y = x;    end
  end
endmodule

Tb.v instantiates both modules, keeps load high for the first edge, and prints both pairs at every falling edge, half a period after the registers changed. Reading there avoids the race of section 3, so each line shows the value after the edge.

Tb.v
module tb;
  reg        clock = 0;
  reg        load = 1;
  wire [3:0] nx, ny, bx, by;
  integer    cycle = 0;

  swap_nonblocking good (.clock(clock), .load(load), .x(nx), .y(ny));
  swap_blocking    bad  (.clock(clock), .load(load), .x(bx), .y(by));

  always #5 clock = ~clock;

  // Sample on the falling edge, when every register has settled.
  always @(negedge clock) begin
    $display("CYCLE %0d load=%b nonblocking x=%0d y=%0d blocking x=%0d y=%0d",
             cycle, load, nx, ny, bx, by);
    cycle = cycle + 1;
    load = 0;
    if (cycle == 4) $finish;
  end
endmodule

Predict before you run. After the first swap edge, what are x and y in each module? What happens after the second swap?

Check

After the first swap edge, what do the two modules hold?

Your answer

Check the result. Press Run.

Output linenonblockingblocking
CYCLE 0x=1 y=2, right after loadingx=1 y=2
CYCLE 1x=2 y=1: both right-hand sides were read before either register changedx=2 y=2: x = y made x 2 first, and y = x read the new x
CYCLE 2, CYCLE 3Alternates between 1,2 and 2,1Stays at 2,2

The blocking result is not a simulator quirk. It describes different hardware, a circuit that copies y into both registers, and a synthesis tool would build exactly that. With <= the order of the two statements does not matter; with = it is the whole design.

6. Change one thing

In the blocking module, reverse the two statements to y = x; x = y; and predict before you run.

Check

With the blocking branch written y = x; x = y;, what do x and y settle to?

Your answer

y takes the old x, 1, and x copies the new y, so both registers stick at 1,1. With =, statement order changes the circuit; with <=, it does not. That is why clocked always blocks use <=, and = belongs in combinational always @* blocks.

7. Registers in a row delay by one edge each

Two registers written with <= in one clocked block form a two-stage delay: s1 <= in; s2 <= s1;. At each edge s2 takes the value s1 had before the edge, so a value on in sampled at one edge reaches s2 after the next edge, one edge later than it reached s1. Fill in the table; the compare button names the first cycle that differs from the model.

Predict, then compare · delay2

Fill in each cell before comparing. Empty cells count as a mismatch.

State table
Cycleininputs sampled at the edges1after the edges2after the edge
01
10
21
31
40
50

Had both stages been written with blocking assignments, s1 = in; s2 = s1;, s2 would read the new s1, and the two stages would print the same value on every line, as if there were one register. Changing only the second assignment to = would still read the old s1, because the first assignment's <= update has not happened yet. Register stages, observation and waveforms looks at such stages in a waveform, and the Practice problem delay-line of Recording waveforms asks you to repair exactly this defect.

8. Common mistakes

8.1 Changing inputs on the sampling edge

// Wrong: changes the input on the edge where the DUT reads it
@(posedge clock) add = 1;

// Right: change it on the falling edge, half a period away
@(negedge clock) add = 1;

When the testbench and the DUT both wake up on the rising edge, nothing says which runs first, so whether the DUT sees the new input or the old one depends on the simulator's ordering. Change inputs on the falling edge, as Tb.v of the counter trace does with reset.

9. Summary

ElementMeaning
always @(posedge clock)the block runs once per rising edge: a register, and the idiom synthesis tools accept
q <= expr;reads now, updates in the nonblocking-update region of the same time step
active, nonblocking update, postponedthe order of work inside one time step; two blocks woken by the same edge run in the active region in no defined order
$display at the edgeshows the value from before the edge
$strobe, $monitor, #1, falling edgeshow the value the edge produced; $monitor keeps printing whenever an argument changes
= in a clocked blockthe next statement reads the new value: a different circuit
clocked logic uses <=, combinational always @* uses =the one rule that makes a testbench reading at the edge trustworthy

10. Check your understanding

  1. In the counter trace, why does the EDGE line at time 25 show 0 while the AFTER line shows 1?

    Show answer

    The $display for the EDGE line runs in the active region, so it reads 0, the value before the counter's <= update. The $strobe for the AFTER line prints in the postponed region, after the update, so it reads 1.

  2. What would the counter trace show if the testbench never raised reset?

    Show answer

    count starts as x, and x + 1 is still x, so every line prints x. A register only gets a known value from a reset or an assignment.

  3. In swap_blocking, why does reordering the two statements change the result, while in swap_nonblocking it does not?

    Show answer

    = changes its target immediately, so the second statement reads the first one's result. <= evaluates both right-hand sides from the pre-edge values and updates both targets together, so either order reads the same values.

This chapter has no Practice problem; its checkpoint is running the two examples. Tasks, !== comparisons, $fatal and the watchdog that make a testbench check itself are in Testbench techniques.

Checkpoint

Run the example and compare it with your prediction

Sources

Licences and notices for the sources this site follows