01 · Subroutine contract
Pick a function for a value and a task for an activity.
The distinction is not stylistic: a function must complete without consuming simulation time, while a task can synchronize, delay, and return several results through arguments.
Subroutine selector
Return value, time, and side effects
- 01Typed functionone return value
Zero-time computation that can appear in an expression.
- 02function voidno return value
Zero-time procedure, commonly used for model updates.
- 03taskactivity
May wait, delay, synchronize, and use output/inout arguments.
Zero-time computation that can appear in an expression.
- Task return type
- None
- A task does not have a void return type; function void is different.
- Timing
- Function: zero
- No #, @, wait, or user-defined task call.
- Defaults
- input
- Argument directions should still be written explicitly for review.
Design a subroutine like a small interface: inputs, outputs, time, side effects, and ownership must all be visible.
Observe
List outputs, side effects, timing controls, imported calls, and whether the caller uses the result inside an expression.
Decide
Choose a typed function or function void for zero-time work; choose a task when event controls, delays, or activity-style completion are part of the API.
Failure
Hiding time in a function breaks the expression model, while a task used as a pure calculator obscures ownership and makes composition harder.
Prove
Lint timing controls and side effects, unit-test automatic reentrancy, and assert the caller-visible postconditions at the API boundary.
Functions compose; tasks sequence
A non-void function returns through its name or return statement and can participate in an expression. function void performs zero-time side effects without a value. A task represents an operation whose completion is a sequencing event for the caller.
function automatic logic [7:0] parity_mask(
input logic [63:0] data
);
logic [7:0] result;
foreach (result[i]) result[i] = ^data[i*8 +: 8];
return result;
endfunction
task automatic send_beat(
input logic [31:0] data,
output bit accepted
);
req <= 1'b1;
payload <= data;
do @(posedge clk); while (!ready);
accepted = 1'b1;
req <= 1'b0;
endtaskSynthesis depends on behavior, not the keyword alone
A statically bounded, zero-time task can be inlined by synthesis just as a function can. This describes analyzable behavior, not static storage lifetime; an automatic task can also synthesize. Delays, event controls, dynamic testbench objects, and simulator services make a subroutine testbench-only. Review the body and call context rather than declaring every task unsynthesizable.
| Question | Function | Task |
|---|---|---|
| Consumes time? | No | May |
| Used in expression? | Yes, if non-void | No |
| Return values | One typed value plus arguments | Arguments only |
| Can call | Functions only | Functions and tasks |
| Automatic recommended? | Yes for reentrancy | Yes for concurrent calls |
| Synthesizable? | If body is synthesizable | If behavior is statically bounded and zero-time |
Built-ins still carry precise return and mutation rules
System functions and array methods often look self-explanatory, but interview bugs hide in width, four-state treatment, sampling context, and whether the operation mutates its receiver. State those contracts before using the result.
- Array locator methods return queues; choose find, find_index, min, max, unique, or unique_index according to the required result type.
- Reduction and query functions can collapse or expose X/Z differently, so include unknown-state tests at verification boundaries.
- A function may call permitted non-time-consuming system subroutines, but it cannot use a user-defined task as a zero-time escape hatch.
| Operation | Contract | Common review point |
|---|---|---|
| $clog2(n) | Ceiling log2 as an integer | Guard n <= 1 and resulting zero-width declarations |
| $countones(expr) | Count bits that are exactly 1 | X and Z are not counted as ones |
| $onehot(expr) | Exactly one bit is 1 | Use $onehot0 when all-zero is legal |
| $onehot0(expr) | At most one bit is 1 | Unknown control bits still need an explicit policy |
| $cast(dst, src) | Checked runtime conversion | Test success before consuming dst |
| $sampled(expr) | Value sampled for the current assertion attempt | Do not confuse it with an arbitrary procedural read |
| array.shuffle() | Randomize element order in place | The receiver mutates; preserve a copy if order matters later |
| array.find_index with (...) | Queue of matching indices | It does not return matching element values |
Explain it out loud
Interview reasoning checkpoints
A function must complete in zero simulation time and may return a value for an expression. A task may consume time and represents an activity; results travel through output or inout arguments.
- Check for #, @, wait, or time-consuming calls.
- Check whether the caller needs a composable expression value.
- Choose automatic lifetime when calls can overlap.
The expected answer is about time and calling semantics, not just the number of outputs.
Saying tasks are always unsynthesizable or that a task has a void return type.
No. A SystemVerilog function cannot enable a user-defined task, regardless of whether that task happens to contain a timing control. Factor shared zero-time work into another function and keep synchronization in the task caller.
- A function can appear inside an expression whose evaluation must finish in the current region.
- The language restriction is on calling a user-defined task, not only on whether the task body visibly blocks.
- Move synchronization to the task caller and pass computed values through a function.
Look for awareness of transitive timing, not only syntax in the immediate function body.
Assuming an apparently zero-time task is a portable escape hatch.
