A function must execute in zero simulation time. No # delays, no @ event controls, no wait, and no task calls, which is what lets functions appear inside expressions.
A task may consume time and block on events, so bus drivers and monitors are tasks. A void function returns nothing but keeps the zero-time guarantee, which suits pure side effects such as printing or counting.
Aspect | Function | Task |
|---|---|---|
May consume time | Never | Yes: delays, events, |
Callable from | Expressions and other functions | Procedural code, never from a function |
Typical use | CRC, address math, | Bus drivers, monitors, checkers |
Routines in modules and interfaces default to static lifetime: one shared copy of every argument and local. Two concurrent calls overwrite each other's state with no warning of any kind.
Declaring the routine automatic gives every call a private stack frame. Class methods are always automatic; module and interface routines are the danger zone.
// snippet: two parallel calls share one frame, so the second call corrupts the first
task send_pkt(input int id, input int len);
int beats;
beats = len;
repeat (beats) begin
@(posedge clk);
end
endtaskThe fix is one keyword. The body stays identical; only the storage class changes.
output int got while it runs for 6 clocks. The caller reads its variable after 3 clocks. What does it see?Its old value. output arguments copy out only when the task returns, so the caller sees 6 at the end and nothing before.
Inputs copy in at call time, while output and inout copy out at return. The caller sees nothing until the routine returns, so an output never streams live updates.
A ref argument is a true alias: reads and writes touch the caller variable directly, and large arrays avoid an expensive copy. It requires an automatic routine. const ref keeps the no-copy efficiency with a read-only contract.
Direction | Caller sees the value | Cost for large arrays |
|---|---|---|
| N/A: copies in at call | One full copy |
| Only at return | One full copy each way for |
| Immediately, it is an alias | No copy |
| N/A: read-only | No copy |
It cannot wait: functions run in zero time. Split the work so a task handles the waiting and the function handles the pure computation.
ref arguments only in automatic routines?A ref names the caller's variable for one call. A static routine keeps one shared frame that outlives every call, so it has nowhere safe to hold a per-call alias.
Concurrent automatic calls proving independent frames, plus a const ref checksum that avoids copying the array. The first bench uses fork ... join, which starts the two calls at the same time and waits for both; the OOP part teaches it properly.
module tb_auto_task;
timeunit 1ns; timeprecision 1ps;
logic clk = 0;
always #5 clk = ~clk;
int got0 = -1, got1 = -1;
int errors = 0;
task automatic send_pkt(input int id, input int len, output int got);
int beats;
beats = len;
got = 0;
repeat (beats) begin
@(posedge clk);
got = got + 1;
end
endtask
initial begin
fork
send_pkt(0, 6, got0);
send_pkt(1, 2, got1);
join
if (got0 !== 6) errors++;
if (got1 !== 2) errors++;
if (errors != 0) $fatal(1, "FAIL: automatic task calls show unexpected beat counts");
$display("PASS: concurrent automatic calls return 6 and 2 beats with no crosstalk");
$finish;
end
endmodulePASS: concurrent automatic calls return 6 and 2 beats with no crosstalkfunction automatic int unsigned checksum(const ref byte data[]);
int unsigned total = 0;
foreach (data[i]) total += int'(data[i]);
return total;
endfunction
module tb_checksum;
byte frame[];
int unsigned got = 0;
int errors = 0;
initial begin
frame = new[4];
frame[0] = 8'h01; frame[1] = 8'h02;
frame[2] = 8'h03; frame[3] = 8'h04;
got = checksum(frame);
if (got !== 10) errors++;
if (errors != 0) $fatal(1, "FAIL: checksum gave an unexpected total");
$display("PASS: const ref checksum of 01 02 03 04 is 10 with no array copy");
$finish;
end
endmodulePASS: const ref checksum of 01 02 03 04 is 10 with no array copyRun with Verilator 5.048 (--binary --timing) on 2026-10-18; every bench printed the PASS line shown, with no warnings. Icarus 13 cannot run the const ref checksum bench; the parallel-task bench runs on both.