always_ff, old state and next state

Explain what a blocking assignment does inside a clocked block, show the two failures it causes, and write every register with a nonblocking assignment.

Lesson
R2.1
Time
25 min
Runs on
Your browser
Check
5 questions

A two-stage pipeline was meant to delay its input by two clock edges. The simulation showed a one-edge delay, and so did the netlist: eight flops where sixteen were drawn. The block was written with =, so the second line read the value the first line had just written. Simulation and synthesis agreed with each other and with the language; only the designer had a different picture.

The pipeline written with blocking assignmentsd counts up from edge 2. Each line is printed after the edge has settled. With =, q equals d at every edge: one stage, not two.
01234567clkrst_nd0123456s10123456q0123456
  1. Cycle 2

    Reset is released after edge 1 and d becomes 1 before edge 2. At the edge, s1 = d runs first: s1 is 1 now. Then q = s1 reads that new 1.

  2. Cycle 3

    d is 2 and q is 2 in the same edge. The second stage never sees the old s1; nothing in the block keeps it.

  3. Cycle 5

    Every edge repeats the same thing: q follows d with one edge of delay, exactly as if s1 were a wire. That is what synthesis built: eight flops and no s1 register.

Measured on the bench. Written with <=, the same bench shows q two edges behind d (the example below).

Old state and next state in one edge

A clocked block runs once per rising edge. At that moment every register has an old state, the value it held before the edge, and the block computes each register's next state from the old states. A blocking assignment (=) breaks that picture: the variable changes at once, and the next statement in the block reads the changed value. A nonblocking assignment (<=) reads every right-hand side first and updates every variable afterwards, in the same time step, so each statement sees old state only.

Blocking: s1 is a wire

always_ff @(posedge clk)
begin
  s1 = d;
  q  = s1;   // reads the new s1
end

One register. q takes d in the same edge, and synthesis drops s1 as a wire: 8 flops.

Nonblocking: s1 is a register

always_ff @(posedge clk)
begin
  s1 <= d;
  q  <= s1;  // reads the old s1
end

Two registers. q shows the d of two edges ago: 16 flops.

What each version buildsThe nonblocking version has two flops in series: d into s1, s1 into q. The blocking version has one flop: d into q, with s1 a name for the wire between d and the flop.
What each version buildsThe nonblocking version has two flops in series: d into s1, s1 into q. The blocking version has one flop: d into q, with s1 a name for the wire between d and the flop. manager d[7:0] input bridge s1 8 flops (only with <=) bridge q 8 flops completer q[7:0] output edge kedge k+1
  1. Nonblocking, edge k

    d = 3 is captured into s1. q still holds the value s1 had before the edge.

    Follow the calls, values and waits
    1. data 3: d[7:0] → s1
  2. Nonblocking, edge k+1

    The 3 moves from s1 to q. Two edges after it arrived, 3 is at the output.

    Follow the calls, values and waits
    1. data 3: s1 → q
    2. data 3: q → q[7:0]
  3. Blocking, any edge

    s1 is assigned and read in the same statement list, so it is a wire; d reaches q in one edge. The middle box is not built.

    Follow the calls, values and waits
    1. data 3: d[7:0] → s1
    2. data 3: s1 → q
    3. data 3: q → q[7:0]
Read the diagram as text
  • d[7:0]: input
  • s1: 8 flops (only with <=)
  • q: 8 flops
  • q[7:0]: output
  • d[7:0] → s1: edge k
  • s1 → q: edge k+1
  • q → q[7:0]

Measured: 16 flops with nonblocking assignments, 8 with blocking ones, on this flow.

Note what did not happen: simulation and synthesis did not disagree. Both follow the language, which says a blocking assignment completes before the next statement. The failure is that the code did not say what the designer meant. Verilator says so at lint time: %Warning-BLKSEQ: Blocking assignment '=' in sequential logic process, once per =.

The race between two blocks

The second failure is older and nastier. Put the two stages in two clocked blocks, both with blocking assignments. At one edge both blocks are ready to run, and the language lets the simulator run them in either order. Run q = s1 first and q gets the old s1: two stages. Run s1 = d first and q gets the new s1: one stage. The same source can behave either way in two simulators, or in one simulator after an unrelated edit, and a netlist always has two registers. This is the simulation-against-synthesis mismatch blocking assignments are famous for, and it needs two blocks, not one.

Measured on Icarus 12 with this lesson's bench and d stepping 1 to 5: with the s1 block written first, q read 1, 1, 3, 3, 5 at edges 2 to 6; with the q block written first, 0, 2, 2, 4, 4. Neither is a pipeline. Yosys 0.70 builds 16 flops from either file.

Two blocks, two blocking assignments, no defined order between them.
always_ff @(posedge clk) s1 = d;    // which runs first?
always_ff @(posedge clk) q  = s1;   // old s1 or new s1: the simulator decides

Nonblocking assignments end the question. A simulator splits each time step into regions. In the active region every ready block runs its statements and reads the old state. Nonblocking updates wait for the nonblocking-assignment region, after the active statements, so every register updates afterwards, whatever the order the blocks ran in. That is the whole reason for the rule: registers are written with <=, always, and a clocked block never reads a value another clocked block wrote in the same edge.

Reading a clocked block

  1. Find the registers

    Every variable assigned in an always_ff block is a register, one flop per bit. Count them; the netlist will agree.

  2. Read right-hand sides as old state

    With <=, every right-hand side in the block is the value before the edge. The order of the statements does not matter for the result.

  3. Treat a repeated target as the last word

    Two nonblocking assignments to the same variable in one block: the last one wins. That is how a reset or a clear takes priority (R2.6).

  4. Treat a blocking assignment as a flag

    An = in a clocked block is either a temporary (a named wire the block uses on the same edge) or a bug. Verilator warns on every one; make the temporary an always_comb variable instead.

Design brief
PortDirectionWidthMeaning
clkinput1clock
rst_ninput1reset, asynchronous, active-low
dinput[7:0]data in
qoutput[7:0]q at edge k shows d as it was before edge k-1: a two-edge delay
Reset
both stages 0 while rst_n is 0; the first edge after release captures d
Parameters
Memory: 16 flops: two stages of 8
Corner cases
d changing every edge; reset released between edges

The lab ships the pipeline with =. Run it: q equals d at every edge, and the Gates tab shows eight flops. Change the two assignments to <=, run again, and q trails d by two edges, with sixteen flops. The reset assignments may stay blocking or become nonblocking; the netlist is the same either way, and nonblocking everywhere is the house style.

Asked in interviews
  1. Blocking or nonblocking in sequential logic, and why?

    Nonblocking. It reads old state and updates after the whole time step, so the result does not depend on statement order inside a block or on the order of blocks. Blocking assignments in two clocked blocks race, and the simulator may give a result the netlist never shows.

  2. Does a blocking assignment in an always_ff block cause a simulation-synthesis mismatch?

    In one block, no: both see one register, because the next statement reads the new value in both. Between two blocks, yes: the simulator picks an order and synthesis builds two registers regardless.

  3. When is a blocking assignment in a clocked block acceptable?

    For a temporary that is computed and consumed inside the same block on the same edge and never read elsewhere. Most teams forbid even that and move the temporary to an always_comb block, because lint cannot tell the two cases apart.

Try it

Edges 0 and 1 are in reset. From edge 2 the input counts up, and at every edge q already equals d: the second statement read the s1 the first statement had just written. One stage.

Change s1 = d; q = s1; to s1 <= d; q <= s1; and run again. Now s1 takes d at each edge and q takes it one edge later: at edge 4 the line reads d=3 s1=3 q=2. Synthesize both versions and compare the flop counts: 8 and 16.

RTLIcarus Verilog · in your browser · Lint: Verilator service · Synthesize: Yosys service
Edit the design, then Run, Lint or Synthesize
Expected: e0 d=0 s1=0 q=0 e1 d=0 s1=0 q=0 e2 d=1 s1=1 q=1 e3 d=2 s1=2 q=2 e4 d=3 s1=3 q=3 e5 d=4 s1=4 q=4 e6 d=5 s1=5 q=5 e7 d=6 s1=6 q=6

Original design · 8 cells · 8 flip-flops · 0 latches · 0 logic levels. Synthesis uses Yosys; counts depend on the toolchain.

Read included file: tb.sv
`timescale 1ns/1ps
module tb;
  logic clk = 0;
  always #5 clk = ~clk;                   // rising edges at 5, 15, 25, ...
  logic rst_n = 1;                        // falls at 1 ns: a real edge, so an asynchronous reset acts before any clock
  int edge_n = -1;
  always @(posedge clk) edge_n = edge_n + 1;
  logic [7:0] d = 0;
  logic [7:0] q;
  pipe2 dut (.clk(clk), .rst_n(rst_n), .d(d), .q(q));
  // Advance to the next rising edge, wait for the design to settle, then print one line.
  task automatic tick();
    @(posedge clk); #1;
    $display("e%0d d=%0d s1=%0d q=%0d", edge_n, d, dut.s1, q);
  endtask
  initial begin
    #1 rst_n = 0; tick(); tick(); rst_n = 1;   // asserted at 1 ns, edges 0 and 1 in reset, released 1 ns after edge 1
    for (int i = 1; i <= 6; i++) begin
      d = i; tick();
    end
    $finish(0);                             // quiet: no "$finish called" line
  end
endmodule

Check yourself

Predict the outputTwo registers swap values every edge with nonblocking assignments. After reset a is 1 and b is 0; the program then applies three clock edges and prints. What is the output line?
module tb;
  logic clk = 0, rst_n = 1;
  logic [3:0] a, b;
  always_ff @(posedge clk or negedge rst_n)
    if (!rst_n) begin a <= 4'd1; b <= 4'd0; end
    else begin
      b <= a;        // nonblocking: both right-hand sides are read first ...
      a <= b;        // ... then both variables change together
    end
  initial begin
    #1 rst_n = 0;                 // reset pulse: a = 1, b = 0
    #1 rst_n = 1;
    #1 clk = 1; #1 clk = 0;
    #1 clk = 1; #1 clk = 0;
    #1 clk = 1; #1 clk = 0;
    $display("a=%0d b=%0d", a, b);
    $finish(0);
  end
endmodule
Spot the bugEach option is the body of the two-stage module. The bench applies d = 5, then d = 7, one edge each, and expects q = 5 after the second edge. Which body fails?
ConceptTwo clocked blocks each use a blocking assignment, one writing s1 from d and one writing q from s1. What does the language say about the result at one edge?
ConceptThe pipeline written with = synthesized to eight flops. The designer says the synthesizer dropped a register. Who is right?
Read the waveformThis is the bench run on the corrected pipeline, with nonblocking assignments. At which edge does q first show the value d had at edge 2?
The pipeline with nonblocking assignments
01234567rst_nd0123456s10123456q012345

One value per edge, printed after the edge settles. s1 shows the value just captured; q shows it one edge later.

Remember

  • In a clocked block a blocking assignment changes the variable at once, so the next statement reads the new value; two stages written with = are one register, in simulation and in gates.
  • A nonblocking assignment reads old state and updates after the whole time step, so statement order and block order stop mattering.
  • Blocking assignments in two clocked blocks race: the simulator may order them either way and the netlist never does. That is the real simulation-against-synthesis mismatch.
  • House rule: always_ff writes registers with <= only, always_comb uses = only; Verilator's BLKSEQ warning marks every exception.