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.
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.
ref arguments — no shared write-backreturn statement — only exit was endfunctionoutput or inout portsref — pass by reference (zero copy, shared write-back)const ref — read-only pass by referencevoid functions — no return valuereturn [expr] — early exit with optional valuearg = default_expr).name(value))output and inout portsThe 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.
// 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) endtaskEDARun on EDA Playground
// 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; endtaskEDARun on EDA Playground
task automatic send_pkt(input int payload[]); if (payload.size() == 0) return; // exit immediately — no value transmit(payload); endtaskEDARun on EDA Playground
// 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); endtaskEDARun on EDA Playground
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.
// 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; endfunctionEDARun on EDA Playground
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.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] endfunctionEDARun on EDA Playground
// 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 endfunctionEDARun on EDA Playground
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 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' castEDARun on EDA Playground
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));EDARun on EDA Playground
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.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 (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 unchangedEDARun on EDA Playground
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 endtaskEDARun on EDA Playground
ref — nets (wire, tri) cannot.ref arguments. Fixed arrays cannot be mixed with dynamic arrays.ref cannot be combined with any other direction qualifier — ref input int a is a compile error.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.automatic (or inside automatic programs/classes).automatic tasks/functions when using ref arguments.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; endfunctionEDARun on EDA Playground
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.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)EDARun on EDA Playground
output ports cannot have default values — only input, inout, and ref arguments can.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 positionalEDARun on EDA Playground
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 fineEDARun on EDA Playground
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"EDARun on EDA Playground
// 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()EDARun on EDA Playground
pure requires a non-void function with no output or inout arguments.| 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 |
input (copy, cheap for scalars)const ref (zero copy, read-only, legal everywhere)ref (requires automatic subroutine)inoutoutputthis, inheritance with extends, super, virtual methods, polymorphism, abstract classes, parameterised classes, and scope resolution.