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.
=, q equals d at every edge: one stage, not two.- Cycle 2
Reset is released after edge 1 and
dbecomes 1 before edge 2. At the edge,s1 = druns first:s1is 1 now. Thenq = s1reads that new 1. - Cycle 3
dis 2 andqis 2 in the same edge. The second stage never sees the olds1; nothing in the block keeps it. - Cycle 5
Every edge repeats the same thing:
qfollowsdwith one edge of delay, exactly as ifs1were a wire. That is what synthesis built: eight flops and nos1register.
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.
- 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
- data 3: d[7:0] → s1
- 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
- data 3: s1 → q
- data 3: q → q[7:0]
- 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
- data 3: d[7:0] → s1
- data 3: s1 → q
- 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.
always_ff @(posedge clk) s1 = d; // which runs first?
always_ff @(posedge clk) q = s1; // old s1 or new s1: the simulator decidesNonblocking 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
Find the registers
Every variable assigned in an
always_ffblock is a register, one flop per bit. Count them; the netlist will agree.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.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).
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 analways_combvariable instead.
| Port | Direction | Width | Meaning |
|---|---|---|---|
| clk | input | 1 | clock |
| rst_n | input | 1 | reset, asynchronous, active-low |
| d | input | [7:0] | data in |
| q | output | [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
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.
Does a blocking assignment in an
always_ffblock 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.
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.
Synthesize the design to measure its cells and inspect the mapped gates.
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=6Original 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
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_ffwrites registers with<=only,always_combuses=only; Verilator's BLKSEQ warning marks every exception.