Functions, tasks, and loops
Factor out a calculation, write synthesizable loops, and keep RTL separate from test utilities.
A function computes one value
A Verilog function can group a combinational calculation that is used several times. It executes without delay, contains no timing control, and returns one value.
module popcount_pair #(
parameter WIDTH = 8
) (
input wire [WIDTH-1:0] i_a,
input wire [WIDTH-1:0] i_b,
output wire [31:0] o_count_a,
output wire [31:0] o_count_b
);
function [31:0] count_ones;
input [WIDTH-1:0] value;
integer index;
begin
count_ones = 0;
for (index = 0; index < WIDTH; index = index + 1)
count_ones = count_ones + value[index];
end
endfunction
assign o_count_a = count_ones(i_a);
assign o_count_b = count_ones(i_b);
endmoduleThe loop bound is known during elaboration. Synthesis unrolls it and creates the required adders. The calculation does not wait for WIDTH clock cycles.
Result width still matters
The largest population count is WIDTH. The 32-bit output makes this example easy to configure, but it may be unnecessarily wide in a real block. A polished interface would use a derived width and check the allowed values of WIDTH.
A function does not automatically fix sizing and signedness rules. Its inputs, return value, and intermediate calculations must still be sized deliberately.
A task can have several outputs
A task can use several input, output, or inout arguments. In synthesizable RTL, it must remain purely structural: no #, no @, no wait, and only bounded loops.
module min_max_pair (
input wire [7:0] i_a,
input wire [7:0] i_b,
output reg [7:0] o_min,
output reg [7:0] o_max
);
task order_pair;
input [7:0] a;
input [7:0] b;
output [7:0] low;
output [7:0] high;
begin
if (a <= b) begin
low = a;
Current tools can synthesize this style, but a function or a small module can be easier to review and reuse. Follow the project rules and inspect the synthesis result.
Tasks are especially useful in a testbench
In a testbench, a task can wait for clock edges, drive inputs, and check an output. Those timing controls belong to simulation, not to the circuit.
task check_sum;
input [7:0] a;
input [7:0] b;
input [8:0] expected;
begin
i_a = a;
i_b = b;
#1;
if (o_sum !== expected)
$display("ERROR: %0d + %0d", a, b);
end
endtaskThe #1 delay and $display call make this task non-synthesizable. That is expected because it belongs to the testbench.
for, repeat, while, and forever
In RTL, a for loop with constant bounds normally describes a finite amount of repeated hardware. A loop whose end depends on input data may be impossible to unroll or may produce an unpredictable structure.
repeat, while, and forever are mainly useful in simulation. Some bounded uses are synthesizable, but they are less obvious to readers and tools. An operation that must actually run for several cycles needs a counter and a state machine.
A loop does not automatically mean sequential processing
This distinction is essential:
- fixed-bound loop in combinational logic: several hardware operations in parallel;
- counter-driven operation in clocked logic: one operation repeated over several cycles;
- timing loop using
@or#: simulation behavior.
The source may look similar, but the resulting architecture is different.
Key points
- A function returns one value and must not consume simulation time.
- A task can have several outputs.
- Timing controls stay in the testbench.
- A constant-bound loop is generally unrolled into hardware.
- A multi-cycle operation needs explicit state and a counter.
📝 Test your knowledge - Chapter quiz