Tasks, Functions & Argument Passing
All SV additions to tasks and functions: new port directions including ref and const ref, void functions, the return statement, default argument values, named argument passing, optional parentheses, and the DPI import/export mechanism for calling C from SV and vice versa.
💡 What SV Adds to Tasks & Functions
Verilog-2001 tasks and functions were functional but had significant limitations — no pass-by-reference, no default arguments, no void functions, and no early exit with return. SystemVerilog brings all of these, plus named argument passing and the DPI bridge to C.
Verilog-2001 — what was missing
- Only pass-by-value — large arrays copied every call
- No
refarguments — no shared write-back - Functions always had to return a value
- No
returnstatement — only exit wasendfunction - No default argument values
- Arguments always positional
- Functions could not have
outputorinoutports - No C-language calling bridge
SystemVerilog additions (§10)
ref— pass by reference (zero copy, shared write-back)const ref— read-only pass by referencevoidfunctions — no return valuereturn [expr]— early exit with optional value- Default argument values (
arg = default_expr) - Named argument passing (
.name(value)) - Functions can now have
outputandinoutports - DPI import/export for C interoperability
► Tasks — New Capabilities
The core task model is unchanged: tasks can consume simulation time (timing controls are legal) and can have any number of input, output, inout, and now ref arguments. SystemVerilog adds three improvements.
1 — Default direction and type rules
// Verilog-2001 style (still works) task mytask1(output int x, input logic y); // ... endtask // SV rule 1: default direction is INPUT if none given // SV rule 2: once a direction is given, subsequent args inherit it task mytask3(a, b, output logic [15:0] u, v); // a: input logic (default direction, default type) // b: input logic (inherits both from a) // u: output logic [15:0] (explicit) // v: output logic [15:0] (inherits direction + type from u) endtask // SV rule 3: default type is 'logic' for task arguments task mytask_default(a, b); // a: input logic, b: input logic endtask // Arrays as formal arguments task mytask4(input [3:0][7:0] a, b[3:0], output [3:0][7:0] y[1:0]); // a: input [3:0][7:0] a (scalar of that packed type) // b: input [3:0][7:0] b[3:0] (4-element unpacked array of that type) // y: output [3:0][7:0] y[1:0] (2-element unpacked array) endtask
2 — begin…end is optional
// Verilog-2001: required begin...end for multiple statements task old_style; begin $display("a"); $display("b"); end endtask // SV: begin...end not required — still sequential without it task sv_style; $display("a"); $display("b"); endtask // Also legal: task with no statements at all task empty_task; endtask
3 — return exits early
task automatic send_pkt(input int payload[]); if (payload.size() == 0) return; // exit immediately — no value transmit(payload); endtask
4 — Per-argument lifetime override
// A static task can have individual automatic arguments or local vars task static mixed_lifetime(input int shared, automatic int private_copy); // shared: static — same storage across all active calls // private_copy: automatic — each call gets its own stack slot endtask // An automatic task can have individual static arguments or local vars task automatic with_counter(input int x); static int call_count = 0; // shared counter — survives across calls call_count++; $display("Call #%0d: x=%0d", call_count, x); endtask
▶ Functions — New Capabilities
Functions in Verilog-2001 could not have timing controls and always had to return a value by assigning to the function name. SystemVerilog extends functions in several important ways.
Functions now have output and inout ports
// SV: functions can have output and inout arguments // (illegal in Verilog-2001) function logic [15:0] myfunc3(int a, b, output logic [15:0] u, v); // a, b: input int (default direction + type) // u, v: output logic [15:0] // function returns logic [15:0] endfunction // Both ANSI and non-ANSI styles are legal function logic [15:0] myfunc1(int x, int y); // ANSI style endfunction function logic [15:0] myfunc2; // non-ANSI style input int x; input int y; endfunction
output, inout, or ref arguments is illegal in: an event expression (@), a procedural continuous assignment (assign x = f(y)), or any expression that is not inside a procedural statement. However, a const ref argument is legal in all those contexts.
Arrays as function arguments
function [3:0][7:0] myfunc4(input [3:0][7:0] a, b[3:0]); // a: scalar of packed [3:0][7:0] // b: 4-element unpacked array of [3:0][7:0] // return: packed [3:0][7:0] endfunction
∅ void Functions & the return Statement
Returning values — three legal patterns
// Pattern 1 (Verilog-2001 style): assign to function name function [15:0] myfunc_v(input [7:0] x, y); myfunc_v = x * y - 1; // return value is the function name endfunction // Pattern 2 (SV new): return statement function [15:0] myfunc_sv(input [7:0] x, y); return x * y - 1; // cleaner — same result endfunction // Pattern 3: early return with guard clause function automatic int find_first(int arr[], int target); foreach(arr[i]) if(arr[i] == target) return i; // early return on first match return -1; // not found endfunction
return, the return value takes precedence. You cannot mix them to produce different values for different paths — use either style consistently. For struct/union return types, hierarchical names starting with the function name inside the function refer to members of the return value.
void functions — no return value
// void function: declared as type void, no return value function void myprint(int a); $display("value = %0d", a); // no return statement needed // return; is legal but return expr; is illegal endfunction // Non-void function called as an expression result = myfunc1(c, d); // expression context — OK x = b + myfunc1(c, d); // inside expression — OK // void function called as a statement myprint(a); // statement — OK (type void) // myfunc1(c,d); // WARNING: result discarded — use void' cast
🗑 Discarding Function Return Values
In Verilog-2001, calling a non-void function as a statement (discarding the return value) generated a warning. SystemVerilog provides an explicit, clean way to discard a return value using a cast to void.
// Discard the return value explicitly — no warning void'(some_function()); // Common example: call randomize() but don't check the return (1=success) void'(pkt.randomize()); // Another example: $cast returns 1 on success, 0 on failure // If you don't care about the return, cast away void'($cast(derived_ptr, base_ptr));
void'(pkt.randomize()) is legal but risky — if randomisation fails (constraints are unsatisfiable), the return value is 0 and you never know. In testbench code, use assert(pkt.randomize()) so the simulation errors immediately on failure instead of continuing with un-randomised data.
↔ Argument Directions
Tasks and functions in SV share the same four argument directions. Functions gain output, inout, and ref as new capabilities compared to Verilog-2001.
| Direction | Copy at call? | Copy at return? | Changes visible when? | Tasks? | Functions? |
|---|---|---|---|---|---|
| input | Yes | No | Never visible to caller | ✓ | ✓ |
| output | No (default init) | Yes | At return | ✓ | ✓ (SV new) |
| inout | Yes | Yes | At return | ✓ | ✓ (SV new) |
| ref | No copy | No copy | Immediately | ✓ | ✓ (SV new) |
📋 Pass by Value
Pass by value (the Verilog-2001 default) copies every argument into the subroutine’s local storage area. For small data this is fine. For large arrays it copies megabytes of data on every call.
// 1000 bytes copied every call — expensive function int crc_slow(byte packet[1000:1]); for(int j=1; j<=1000; j++) crc_slow ^= packet[j]; endfunction // Changes to packet inside the function do NOT affect the caller's array byte my_pkt[1000:1]; int k = crc_slow(my_pkt); // my_pkt is unchanged
🔗 Pass by Reference (ref)
A ref argument passes a reference to the caller’s variable — no copy is made. Both the caller and the subroutine operate directly on the same memory location. Changes inside the subroutine are immediately visible to the caller (not just at return time).
// Same CRC — now with ref: zero-copy, and changes visible immediately function int crc_fast(ref byte packet[1000:1]); for(int j=1; j<=1000; j++) crc_fast ^= packet[j]; endfunction // The CALL SITE looks identical — ref is transparent to the caller byte packet1[1000:1]; int k = crc_fast(packet1); // same syntax as pass-by-value call // Immediate visibility example task automatic increment(ref int val); val++; // write-back visible to caller IMMEDIATELY (not at return) endtask int x = 5; increment(x); // x is now 6 — change happened immediately inside the task // ref vs inout — the timing difference matters for tasks with delays task automatic delayed_update(ref int r, inout int io); r++; // IMMEDIATELY visible outside #10; io++; // will be copied out when task returns — NOT immediately endtask
ref argument restrictions
- Only variables can be passed by
ref— nets (wire,tri) cannot. - Types must match exactly — no implicit casting or promotion for
refarguments. Fixed arrays cannot be mixed with dynamic arrays. refcannot be combined with any other direction qualifier —ref input int ais a compile error.- For class handles passed as
ref: changes to the handle itself (pointing it to a new object) are visible to the caller, in addition to changes to the object’s contents. - Only usable in tasks and functions declared as
automatic(or insideautomaticprograms/classes).
automatic tasks/functions when using ref arguments.
🔒 const ref
const ref gives the zero-copy performance of ref while preventing the subroutine from modifying the argument. It is ideal for large read-only inputs like packet arrays passed to display or check functions.
// const ref: zero-copy, read-only inside the function task show(const ref byte [] data); for(int j=0; j < data.size; j++) $display(data[j]); // read — OK // data[0] = 0; // COMPILE ERROR — const ref cannot be written endtask // Advantage over const ref over plain input for large arrays: // input: copies all 1000 bytes → expensive // const ref: no copy, enforces read-only → efficient and safe function int hash(const ref byte pkt[1000:1]); int h = 0; foreach(pkt[i]) h ^= pkt[i]; return h; endfunction
const ref arguments (and no output/inout) can be called inside @(f(x)) event expressions. This enables efficient per-element method calls in always_comb sensitivity lists.
📌 Default Argument Values
Arguments can declare a default value. When the call omits that argument (or passes an empty placeholder ,), the default expression is used. Default values are evaluated in the caller’s scope each time the subroutine is called.
task read(int j = 0, int k, int data = 1); endtask // j: default=0, k: no default (must be given), data: default=1 // All legal call forms: read( , 5 ); // j=0, k=5, data=1 (j and data use defaults) read(2, 5 ); // j=2, k=5, data=1 (data uses default) read( , 5, ); // j=0, k=5, data=1 (empty = default for j and data) read( , 5, 7); // j=0, k=5, data=7 read(1, 5, 2); // j=1, k=5, data=2 (all explicit) // read( , ); // ERROR: k has no default value // Default evaluated at call time in CALLER's scope int global_base = 100; function int adjust(int x, int base = global_base); return x + base; endfunction global_base = 200; adjust(5); // base = 200 (current value of global_base at this call)
output ports cannot have default values — only input, inout, and ref arguments can.
📌 Named Argument Passing
Arguments can be passed by name using the .name(value) syntax, mirroring named port connections on module instances. This makes it easy to pass non-consecutive defaults and to self-document which argument is which.
function int fun(int j = 1, string s = "no"); endfunction // All legal calling forms — named, positional, mixed fun(.j(2), .s("yes")); // fun(2, "yes") — fully named fun(.s("yes") ); // fun(1, "yes") — j uses default fun( , "yes" ); // fun(1, "yes") — j placeholder, s positional fun(.j(2) ); // fun(2, "no") — s uses default fun(.s("yes"), .j(2)); // fun(2, "yes") — named in any order fun(.s( ), .j()); // fun(1, "no") — empty = default for both fun(2 ); // fun(2, "no") — positional fun( ); // fun(1, "no") — all defaults // Mixed positional + named: positional must come FIRST fun(2, .s("yes")); // OK — 2 is positional (j), then .s is named // fun(.s("yes"), 2); // ILLEGAL — named before positional
() Optional Parentheses
When a task or function declares no arguments, or when all its arguments have default values, the trailing parentheses () in the call are optional.
task no_args; // no arguments endtask task all_defaults(int x=0, int y=0); endtask // All of these are legal: no_args; // no parentheses no_args(); // parentheses — also fine all_defaults; // all defaults — parentheses optional all_defaults(); // parentheses — also fine
⌮ Import and Export Functions (DPI)
The Direct Programming Interface (DPI) provides a bridge between SystemVerilog and C/C++. import brings a C function into SV scope; export makes an SV function callable from C.
// Import: call C from SV import "DPI" function int c_add(int a, int b); import "DPI" task c_delay(int ns); import "DPI" my_c_func = function int sv_name(int x); // last form: C function name is "my_c_func", SV sees it as "sv_name" // Export: call SV from C export "DPI" function sv_checker; export "DPI" c_name = function sv_func; // last form: C calls "c_name", which maps to SV function "sv_func"
Three import qualifiers
// Pure function: compiler may optimise duplicate calls import "DPI" pure function int c_sqrt(int x); // Context function: can access SV signals from C import "DPI" context function void c_dump_state(); // Call imported functions just like any SV function int root = c_sqrt(64); // calls C sqrt(64) = 8 c_dump_state(); // calls C function that accesses SV state // Export example function automatic int sv_check(int x); return x > 0; endfunction export "DPI" function sv_check; // C can now call sv_check()
DPI signature rules
- For any given C function name, all import/export declarations anywhere in the design must have identical signatures (return type, argument count, argument types, directions).
- Only one import or export of a given SV function name per scope.
- Only SV functions (not class methods, not tasks) can be exported.
purerequires a non-void function with nooutputorinoutarguments.- Context imports have a built-in scope: they always run in the SV scope of the module/interface where the import declaration appears, not where the call is made.
📋 Quick Reference
SV additions to tasks and functions at a glance
| Feature | Tasks | Functions | Notes |
|---|---|---|---|
| Default direction | input | input | Subsequent args inherit direction from previous |
| Default type | logic | logic | — |
| output ports | Always | New in SV | Cannot be used in event expressions or continuous assign |
| inout ports | Always | New in SV | Same restriction as output |
| ref ports | New in SV | New in SV | Automatic only; no implicit cast; no other qualifier combo |
| const ref ports | New in SV | New in SV | Legal everywhere including event expressions |
| void return | N/A | New in SV | Called as a statement |
| return statement | New in SV | New in SV | task: no expr; non-void function: expr required |
| Default arg values | New in SV | New in SV | ANSI style only; output cannot have defaults |
| Named arg passing | New in SV | New in SV | Named must come after positional in a mixed call |
| Optional () | New in SV | New in SV | When no args or all have defaults |
| begin…end optional | New in SV | New in SV | Statements still execute sequentially |
| DPI import/export | import only | Both | Only functions can be exported |
Argument direction quick guide
- Read-only input, small:
input(copy, cheap for scalars) - Read-only input, large array:
const ref(zero copy, read-only, legal everywhere) - Read-write sharing, immediate visibility:
ref(requires automatic subroutine) - Write-back at return, not immediate:
inout - Write-only output at return:
output
this, inheritance with extends, super, virtual methods, polymorphism, abstract classes, parameterised classes, and scope resolution.
