Continuous assignment (assign) means the net always equals the expression. A procedural block (always @(*)) computes a variable with if, case, and temporaries.
Both can describe the same hardware. The table summarizes when each fits.
module mux_assign (input wire a, b, sel, output wire y);
assign y = sel ? b : a;
endmodule
module mux_always (input wire a, b, sel, output reg y);
always @(*) begin
if (sel) y = b;
else y = a;
end
endmoduleUse | Use |
|---|---|
The logic is a single expression or a short chain. | You need |
You are driving a | You compute several related outputs together from one decision. |
You want the dependency to be obvious. | You use temporary variables that make the logic clearer. |
A procedural block runs when a signal in its sensitivity list changes. Combinational logic must list every signal the block reads.
Hand-written lists are error-prone. For the simple mux above, @(*) includes a, b and sel automatically. First see what happens when b is missing from a hand-written list; the function caveat comes after that example.
The listing pairs a bad-sensitivity mux with a testbench that exposes it. The full code sits under Testbench below.
Use always @(*) (Verilog) or always_comb (SystemVerilog).
Use blocking assignments (=).
Assign every output on every path, or you infer a latch (next lesson).
One block should drive each signal; do not assign the same signal from two blocks.
assign y = a & b; and always @(*) y = a & b; are equivalent. Which one declares y as wire and which as reg?assign drives a wire (or logic); the always block assigns a reg (or logic). The hardware is the same AND gate.
A bad-sensitivity mux and the testbench that exposes its stale output.
// bad-example: 'b' is missing from the sensitivity list
module mux_bad_sens (input wire a, b, sel, output reg y);
always @(a or sel) y = sel ? b : a;
endmodule
module tb_bad_sens;
reg a, b, sel;
wire y;
mux_bad_sens dut (.a(a), .b(b), .sel(sel), .y(y));
initial begin
a = 0; b = 0; sel = 1; #1;
$display("b=0 y=%b (correct)", y);
b = 1; #1; // b changes, but the block is not sensitive to b
$display("b=1 y=%b (should be 1, stale value kept)", y);
$finish;
end
endmoduleb=0 y=0 (correct)
b=1 y=0 (should be 1, stale value kept)Verilog-2001 @(*) includes directly read expressions and function arguments, but not a module-level signal read only inside a function. SystemVerilog always_comb includes reads inside called functions too. Pass every dependency explicitly when using @(*). In the test below, changing only mask leaves y_hidden stale; the two fixes update. Return to this caveat after learning functions if the syntax is new.
module tb_function_sensitivity;
logic a = 0, mask = 1;
logic y_hidden, y_comb, y_explicit;
int errors = 0;
function automatic logic hidden_mask(input logic x);
return x & mask;
endfunction
function automatic logic explicit_mask(input logic x, m);
return x & m;
endfunction
always @(*) y_hidden = hidden_mask(a); // intentional contrast: mask is not an argument
always_comb y_comb = hidden_mask(a);
always @(*) y_explicit = explicit_mask(a, mask);
initial begin
#1 a = 1; #1;
if ({y_hidden, y_comb, y_explicit} !== 3'b111) errors++;
mask = 0; #1;
if (y_hidden !== 1'b1) errors++; // stale until a changes
if ({y_comb, y_explicit} !== 2'b00) errors++;
a = 0; #1;
if ({y_hidden, y_comb, y_explicit} !== 3'b000) errors++;
mask = 1; #1;
a = 1; #1;
if ({y_hidden, y_comb, y_explicit} !== 3'b111) errors++;
if (errors == 0) $display("PASS: function sensitivity distinguishes hidden and explicit dependencies");
else $display("FAIL: function-sensitivity errors=%0d", errors);
$finish;
end
endmodule