always @(*)
Every signal a combinational block reads has to be in its list. That is easy for two inputs and hopeless for a two-hundred-line decoder with a dozen inputs, some of them buried in nested conditions. Verilog-2001 removed the problem with @(*).
The problem with manual lists
`timescale 1ns / 1ps
`default_nettype none
module mux4 (
input wire [1:0] sel,
input wire a, b, c, d,
output reg y
);
always @(sel or a or b or c or d) begin
case (sel)
2'b00: y = a;
2'b01: y = b;
2'b10: y = c;
2'b11: y = d;
endcase
end
endmodule
Correct as written. Now someone adds a fifth input and a new arm, and forgets the list. It compiles, it simulates, and it is wrong for one sequence of inputs that the tests may or may not try. The list lives at the top of the block and the logic lives in the body, and the two drift apart.
The automatic list
`timescale 1ns / 1ps
`default_nettype none
module mux4_star (
input wire [1:0] sel,
input wire a, b, c, d,
output reg y
);
always @(*) begin
case (sel)
2'b00: y = a;
2'b01: y = b;
2'b10: y = c;
2'b11: y = d;
endcase
end
endmodule
@(*), or @* without parentheses, means “sensitive to everything this block reads”. The two modules are identical to the simulator and the synthesiser, and the second cannot go stale, because there is nothing to update. Use it for every combinational block.
What it includes
Everything read in the block: every signal on the right of an assignment, every signal in an if, case or ?: condition, every index into an array or part-select, and signals read inside functions the block calls. Signals that appear in branches which may not execute are included too; if a changes while mode is 0 in
always @(*) begin
y = 1'b0;
if (mode)
y = a & b;
else
y = c | d;
end
the block runs, recomputes c | d, and nothing changes, which costs a little simulation time and no correctness.
What it leaves out: signals only ever written, and edge events. @(*) cannot express posedge; a flip-flop has to be written as @(posedge clk), which is a useful restriction rather than a limitation.
The same hardware
// manual list
always @(en or req) begin
valid = 1'b0;
grant = 2'b00;
if (en) begin
if (req[3]) begin grant = 2'b11; valid = 1'b1; end
else if (req[2]) begin grant = 2'b10; valid = 1'b1; end
else if (req[1]) begin grant = 2'b01; valid = 1'b1; end
else if (req[0]) begin grant = 2'b00; valid = 1'b1; end
end
end
// automatic list, same netlist
always @(*) begin
valid = 1'b0;
grant = 2'b00;
if (en) begin
if (req[3]) begin grant = 2'b11; valid = 1'b1; end
else if (req[2]) begin grant = 2'b10; valid = 1'b1; end
else if (req[1]) begin grant = 2'b01; valid = 1'b1; end
else if (req[0]) begin grant = 2'b00; valid = 1'b1; end
end
end
Same waveforms, same gates. The default assignments at the top are the latch-prevention pattern of the fourth article.
Converting
The conversion is mechanical: delete the list, write *.
always @(a or b or sel) begin → always @(*) begin
y = sel ? a : b; y = sel ? a : b;
end end
always @(state or din or valid or ready or count) → always @(*) begin
Some teams keep manual lists as documentation of what a block depends on. The intent is fair and the mechanism is wrong, because a stale list documents nothing; a comment does the job without the risk.
always_comb
SystemVerilog goes one step further:
always_comb begin
y = a & b & c;
end
It runs once at time zero, where @(*) waits for the first change, so initial values are right before the first edge; and a synthesiser that finds a latch in an always_comb must report an error rather than a warning. Module 9 moves to it. Until then, @(*).
Practice Exercise
Convert each block and list what @(*) infers.
always @(x or y or z) begin
out = (x & y) | (~x & z);
end
always @(addr or wen or wdata or mem_out) begin
rdata = wen ? wdata : mem_out;
ack = (addr < 8'hFF);
end
always @(state or req or ack or timeout) begin
case (state)
2'b00: next_state = req ? 2'b01 : 2'b00;
2'b01: next_state = ack ? 2'b10 : 2'b01;
2'b10: next_state = timeout ? 2'b00 : 2'b10;
default: next_state = 2'b00;
endcase
end
Solution
In each case the header becomes always @(*) and the body is unchanged. The inferred lists are x, y, z; addr, wen, wdata, mem_out, with rdata and ack excluded as outputs; and state, req, ack, timeout, with next_state excluded.
The next article traces exactly what goes wrong when a signal is missing, cycle by cycle, and why the synthesiser does not see the problem the simulator has.