Tasks, functions and reusable definitions
- Use functions for reusable combinational calculations
- Use tasks for testbench procedures
- Choose parameters and localparams for constants
- Apply include files and macros without hiding design intent
zero-time calculation
Explanation
Functions calculate one value without consuming time
function [7:0] saturating_add8;
input [7:0] a;
input [7:0] b;
reg [8:0] wide_sum;
begin
wide_sum = {1'b0, a} + {1'b0, b};
if (wide_sum[8])
saturating_add8 = 8'hFF;
else
saturating_add8 = wide_sum[7:0];
end
endfunction
Use a function when the operation is conceptually combinational and returns one result. A synthesizable function must avoid delays, event controls, and simulation system tasks. Inputs and local variables should make widths explicit.
Call it as part of an expression:
assign sum_sat = saturating_add8(a, b);
Tasks organize procedures
Tasks can have input, output, and inout arguments and may return multiple results. In testbenches they can wait for events and simplify repeated stimulus.
task drive_and_check;
input [7:0] stimulus;
input [7:0] expected;
begin
@(negedge clk);
data_in = stimulus;
@(posedge clk);
#1;
if (data_out !== expected)
$fatal(1, "input=%h expected=%h got=%h",
stimulus, expected, data_out);
end
endtask
The @, #1, display, and fatal behavior make this a testbench task. Do not mix such code into synthesizable RTL.
Task and function lifetime
Traditional Verilog tasks and functions are static by default: local storage
can be shared across overlapping calls. That is harmless for a simple
combinational function called once at a time, but can corrupt results when a
task is re-entered or called concurrently. The automatic keyword gives each
active call its own local storage:
task automatic compare_byte;
input [7:0] actual;
input [7:0] expected;
begin
if (actual !== expected)
$display("Mismatch expected=%02h actual=%02h", expected, actual);
end
endtask
Use automatic lifetime for re-entrant testbench helpers when supported by the selected Verilog mode. Confirm simulator compatibility instead of assuming a construct accepted in SystemVerilog mode is accepted in every Verilog mode.
Constants have different ownership
parameter: parent-visible configuration.localparam: internal constant that cannot be overridden.- macro: global text substitution after preprocessing.
Prefer parameters, localparams, and functions when they express the requirement. A macro does not understand Verilog scope or types and can produce surprising precedence.
`define DOUBLE_BAD(x) x + x
For DOUBLE_BAD(a & b), substitution and operator precedence may not express the intended grouping. If a macro is required, parenthesize arguments and the whole result, and keep its scope controlled.
Include discipline
Use include files for shared declarations only under a clear project convention. Do not rely on a hidden compile-directory search path. File lists should specify include directories, and include guards should prevent accidental duplicate definitions.
Worked example
The saturating adder first widens both operands so carry cannot disappear. The function returns 8'hFF when carry is set. For 8'd200 + 8'd100, the mathematical sum is 300 and the returned saturated value is 255.
Check yourself
Why should a synthesizable function not contain # or @? When is a testbench task more readable than copied stimulus? Why can a text macro create precedence problems?