Describing combinational logic
Use assign and always @* to produce an output that depends only on the current inputs.
What a combinational circuit is
A combinational circuit does not keep state. For a given input combination, its outputs always have the same value after the physical propagation delay.
Multiplexers, decoders, and adders are common examples. In Verilog, they can be described with a continuous assignment or a combinational procedural block.
The continuous assign statement
assign works well for a short expression. Its target is a net, usually declared as wire.
module alarm_logic (
input wire i_smoke,
input wire i_overtemp,
input wire i_service_mode,
output wire o_alarm
);
assign o_alarm = (i_smoke | i_overtemp) & ~i_service_mode;
endmoduleThe expression is continuously active. When an input changes, the simulation recalculates o_alarm.
Several continuous assignments describe several logic paths in parallel:
module compare_byte (
input wire [7:0] i_a,
input wire [7:0] i_b,
output wire o_equal,
output wire o_a_greater
);
assign o_equal = (i_a == i_b);
assign o_a_greater = (i_a > i_b);
endmoduleThe always @* block
A decision with several branches is often easier to read inside an always block.
module command_decode (
input wire [1:0] i_command,
output reg [3:0] o_enable
);
always @* begin
o_enable = 4'b0000;
if (i_command == 2'b00)
o_enable = 4'b0001;
else if (i_command == 2'b01)
o_enable = 4'b0010;
else if (i_command == 2'b10)
o_enable = 4'b0100;
else
o_enable = 4'b1000;
end
endmoduleThe o_enable target is a reg because it is assigned inside a procedural block. The block is still combinational: it contains no clock and asks for no storage.
@* tells the simulator to make the block sensitive to signals read inside it. This Verilog-2001 notation avoids forgetting an input in a manually written list such as @(i_command).
Using blocking assignments
Blocking assignments with = are the usual choice inside a combinational block. A value is updated before the next statement in the same pass through the block.
module add_or_sub (
input wire [7:0] i_a,
input wire [7:0] i_b,
input wire i_subtract,
output reg [8:0] o_result
);
reg [8:0] v_a_ext;
reg [8:0] v_b_ext;
always @* begin
v_a_ext = {1'b0, i_a};
v_b_ext = {1'b0, i_b};
if (i_subtract)
o_result = v_a_ext - v_b_ext;
else
o_result =
The intermediate variables are calculated before o_result. Using nonblocking assignments here could make the simulation reuse their previous values instead of representing the expected circuit.
Assigning every output in every case
To remain combinational, every output and local variable must receive a value on every possible path.
A simple method is to set a default value at the beginning of the block, then replace it in selected branches:
always @* begin
o_grant = 1'b0;
if (i_request & i_ready)
o_grant = 1'b1;
endWithout the first assignment, o_grant would have to preserve its value when the condition is false. Synthesis would then need a latch.
assign or always?
Both styles can produce the same hardware.
- Use
assignfor a direct equation or a short selection. - Use
always @*for several outputs, default values, or nested branches. - Do not drive the same target with both styles.
- Keep
=in combinational blocks and<=in sequential blocks.
Key points
- Combinational logic depends only on the current inputs.
assigndrives awire.- An output assigned in
always @*is declared asregin Verilog. @*builds the sensitivity list from the signals being read.- Every output must receive a value on every path.
📝 Test your knowledge - Chapter quiz