Blocking Assignments
= looks like the assignment in C and behaves like it on the surface: evaluate the right side, store it on the left, carry on. The resemblance is exactly close enough to be dangerous. This article is about what = does inside an always block, where it is the right tool, and where it is not.
What blocks
The word refers to the flow inside one block:
always @(*) begin
temp = a & b; // line 1
y = temp | c; // line 2
end
Line 1 finishes, and temp has its new value, before line 2 starts, so line 2 reads the value line 1 just wrote. Each assignment blocks the next line until it is done. Within one block, statements run top to bottom and each sees the effect of the ones above it.
That is procedural, and for combinational logic it is useful. You can name intermediate results and build an expression in steps:
always @(*) begin
c1 = a[0] & b[0];
c2 = (a[1] & b[1]) | (a[1] & c1) | (b[1] & c1);
sum[0] = a[0] ^ b[0];
sum[1] = a[1] ^ b[1] ^ c1;
sum[2] = a[2] ^ b[2] ^ c2;
end
Sequential text, combinational circuit
The block above reads like steps. What the synthesiser takes from it is a function: y equals (a & b) | c. The temp does not exist in the netlist. It is substituted away,
$$y = temp \mid c, \quad temp = a ,&, b \quad \Rightarrow \quad y = (a ,&, b) \mid c$$
and the result is two gates. The sequential reading is a convenience for the writer; the hardware is a combinational function of the inputs, with no memory in it.
A mux, done right
`timescale 1ns / 1ps
`default_nettype none
module mux4to1 #(
parameter WIDTH = 8
) (
input wire [WIDTH-1:0] d0, d1, d2, d3,
input wire [1:0] sel,
output reg [WIDTH-1:0] y
);
always @(*) begin
case (sel)
2'b00: y = d0;
2'b01: y = d1;
2'b10: y = d2;
2'b11: y = d3;
default: y = {WIDTH{1'bx}}; // cannot happen; keeps the case full
endcase
end
endmodule
Every assignment is =, the block runs whenever any input changes, and the result is a mux tree with no state. The default matters, and section 5.3 says why: a case that leaves y unassigned for some value of sel becomes a latch.
A priority encoder
An encoder that reports the index of the highest active request is the natural use of the top-to-bottom order:
`timescale 1ns / 1ps
`default_nettype none
module priority_encoder #(
parameter N = 8
) (
input wire [N-1:0] req,
output reg [$clog2(N)-1:0] grant,
output reg valid
);
integer i;
always @(*) begin
grant = {$clog2(N){1'b0}}; // defaults: no request
valid = 1'b0;
for (i = 0; i < N; i = i + 1) begin
if (req[i]) begin
grant = i[$clog2(N)-1:0]; // a later hit overwrites an earlier one
valid = 1'b1;
end
end
end
endmodule
The loop runs from 0 upwards and each active request overwrites grant, so the last one found, the highest index, wins. Run the loop downwards and the lowest wins. The order of the statements is the priority, and the hardware is a chain of muxes in which the later ones override the earlier. The two defaults at the top guarantee that every output is assigned on every path, which is the pattern that keeps latches out of combinational blocks.
Where it goes wrong
always @(posedge clk) begin
q = d;
end
Alone, with nothing else touching q, this simulates correctly. Add a second block that reads q:
always @(posedge clk) begin
q = d;
end
always @(posedge clk) begin
r = q + 1;
end
Both fire on the same edge. If the first runs first, r gets d + 1. If the second runs first, r gets the old q plus one. The order is the simulator’s choice, can differ between tools and between runs of the same tool, and the failures are intermittent. This is the race from the first article, and the tool that removes it is the non-blocking assignment of the next.
Put mathematically: a combinational block describes $y = f(a, b, c, \ldots)$, a function of the present inputs. A clocked block describes $q[n] = f(q[n-1], d[n])$, a state update from one cycle to the next. Blocking assignment gives the first its sequential convenience. Non-blocking assignment gives the second the “read old, then update all” behaviour that a flip-flop has.
A testbench for the encoder
`timescale 1ns / 1ps
`default_nettype none
module tb_priority_encoder;
parameter N = 8;
reg [N-1:0] req;
wire [$clog2(N)-1:0] grant;
wire valid;
priority_encoder #(.N(N)) uut (.req(req), .grant(grant), .valid(valid));
initial begin
$dumpfile("tb.vcd");
$dumpvars(0, tb_priority_encoder);
req = 8'b0000_0000; #10; $display("req=%b grant=%0d valid=%b", req, grant, valid);
req = 8'b0000_0001; #10; $display("req=%b grant=%0d valid=%b", req, grant, valid);
req = 8'b0010_0000; #10; $display("req=%b grant=%0d valid=%b", req, grant, valid);
req = 8'b0001_0110; #10; $display("req=%b grant=%0d valid=%b", req, grant, valid); // 1, 2, 4: expect 4
req = 8'b1111_1111; #10; $display("req=%b grant=%0d valid=%b", req, grant, valid); // expect 7
$finish;
end
endmodule
req=00000000 grant=0 valid=0
req=00000001 grant=0 valid=1
req=00100000 grant=5 valid=1
req=00010110 grant=4 valid=1
req=11111111 grant=7 valid=1
Practice Exercise
For each block, is the blocking assignment right, wrong, or a race?
// A
always @(*) begin
t1 = x ^ y;
t2 = x & y;
z = t1 | t2;
end
// B
always @(posedge clk) begin
if (en)
q = d;
end
// C
always @(posedge clk) begin
stage1 = data_in;
stage2 = stage1;
stage3 = stage2;
end
// D
always @(*) begin
if (sel)
out = in_a;
end
Solution
A is right: a combinational block building z = (x ^ y) | (x & y) through two named intermediates; the synthesiser reduces it to x | y.
B is wrong: = in a clocked block. It works alone and races as soon as another block reads q. Use <=.
C is wrong in a way that shows up immediately. Each line reads the value the previous line just wrote, so all three stages take data_in in the same cycle and the pipeline is three flip-flops with no delay between them. With <= each stage takes the old value of the one before it.
D is a latch: when sel is 0, out is not assigned and has to hold. Give it a default at the top of the block.
The next article is the non-blocking assignment, and the two-phase time step that makes it work.