Skip to practice questions

Part 1 · Subroutines and lifetime

Functions, tasks, arguments, and lifetime

Choose the right subroutine contract, then make argument direction, storage lifetime, reentrancy, and side effects explicit.

Reading path
1 of 3
Concept chapters
2
Practice links
4

Question-first preparation

Practice the mechanisms on this page.

Each question maps directly to one of the chapters below, so you can test the contract before reading the explanation.

Recommended start

One representative prompt from each major mechanism. Open any card in the live question bank.

  1. Q158Distinguish SystemVerilog Functions and TasksTiming · EasyPractice
  2. Q417SystemVerilog function timing ruleSystemVerilog · EasyPremium
  3. Q413Classify synthesizable and testbench-only constructsSystemVerilog · EasyPremium
  4. Q148Direct Programming Interface (DPI) 4-state data mappingVerification Utilities · MediumPractice
Complete question directoryFilter every stable practice link by mechanism.4 questions
Showing 4 questions across every track.Choose a track to narrow the practice set without changing its stable question links.
  1. Q158Distinguish SystemVerilog Functions and TasksTiming · EasyPractice
  2. Q417SystemVerilog function timing ruleSystemVerilog · EasyPremium
  3. Q413Classify synthesizable and testbench-only constructsSystemVerilog · EasyPremium
  4. Q148Direct Programming Interface (DPI) 4-state data mappingVerification Utilities · MediumPractice

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.

Rule to say firstUse a function for zero-time computation and a task for an activity. A SystemVerilog function cannot enable a user-defined task, even when that task appears to contain no timing control.

Subroutine selector

Return value, time, and side effects

  1. 01Typed functionone return value

    Zero-time computation that can appear in an expression.

  2. 02function voidno return value

    Zero-time procedure, commonly used for model updates.

  3. 03taskactivity

    May wait, delay, synchronize, and use output/inout arguments.

Typed functionone return value

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.

01

Observe

List outputs, side effects, timing controls, imported calls, and whether the caller uses the result inside an expression.

02

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.

03

Failure

Hiding time in a function breaks the expression model, while a task used as a pure calculator obscures ownership and makes composition harder.

04

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.

Make timing visible in the declarationsystemverilog
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;
endtask

Synthesis 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.

Function and task review
QuestionFunctionTask
Consumes time?NoMay
Used in expression?Yes, if non-voidNo
Return valuesOne typed value plus argumentsArguments only
Can callFunctions onlyFunctions and tasks
Automatic recommended?Yes for reentrancyYes for concurrent calls
Synthesizable?If body is synthesizableIf 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.
Frequently tested built-ins
OperationContractCommon review point
$clog2(n)Ceiling log2 as an integerGuard n <= 1 and resulting zero-width declarations
$countones(expr)Count bits that are exactly 1X and Z are not counted as ones
$onehot(expr)Exactly one bit is 1Use $onehot0 when all-zero is legal
$onehot0(expr)At most one bit is 1Unknown control bits still need an explicit policy
$cast(dst, src)Checked runtime conversionTest success before consuming dst
$sampled(expr)Value sampled for the current assertion attemptDo not confuse it with an arbitrary procedural read
array.shuffle()Randomize element order in placeThe receiver mutates; preserve a copy if order matters later
array.find_index with (...)Queue of matching indicesIt does not return matching element values

Explain it out loud

Interview reasoning checkpoints

Strong answer

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.

Reason it through
  1. Check for #, @, wait, or time-consuming calls.
  2. Check whether the caller needs a composable expression value.
  3. Choose automatic lifetime when calls can overlap.
Interviewer lens

The expected answer is about time and calling semantics, not just the number of outputs.

Common trap

Saying tasks are always unsynthesizable or that a task has a void return type.

Strong answer

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.

Reason it through
  1. A function can appear inside an expression whose evaluation must finish in the current region.
  2. The language restriction is on calling a user-defined task, not only on whether the task body visibly blocks.
  3. Move synchronization to the task caller and pass computed values through a function.
Interviewer lens

Look for awareness of transitive timing, not only syntax in the immediate function body.

Common trap

Assuming an apparently zero-time task is a portable escape hatch.

02 · Arguments and storage lifetime

Direction says who writes; lifetime says whether calls can overlap.

input, output, inout, ref, const ref, automatic, and static control copying, aliasing, initialization, and reentrancy. Those are observable API properties.

Rule to say firstDefault reusable subroutines to automatic, use const ref for large read-only data, and reserve ref for intentional caller-visible mutation.

Call boundary

Copy, alias, then return

  1. 01inputcopy in

    Callee reads a snapshot; later caller changes are not observed.

  2. 02outputcopy out

    Formal is written locally and copied to actual on return.

  3. 03inoutcopy in/out

    Snapshot enters and final value returns.

  4. 04ref / const refalias

    Immediate shared object; const ref forbids callee writes.

inputcopy in

Callee reads a snapshot; later caller changes are not observed.

automatic
Per invocation
Arguments and locals are independent across concurrent calls.
static
Shared storage
State persists and overlapping calls can race.
Handles
Shallow copy
Value passing a class variable copies its handle, not the object.

Copy semantics and object-handle semantics coexist: the formal can be a copy while the referenced object remains shared.

01

Observe

For each formal, record whether the callee reads, writes, aliases, or retains it, plus whether concurrent invocations are possible.

02

Decide

Use value input/output for snapshot-and-return behavior, const ref for read-only aliasing, and ref only when changes must be immediate and type-exact.

03

Failure

A static local shared by overlapping calls creates a race; a ref formal can expose partial updates that a copy-out output would hide.

04

Prove

Run overlapping calls with distinct data, assert inputs remain unchanged for const APIs, and check that output values appear only at return when that is the contract.

Argument direction controls observation time

Value input is initialized on entry. Output is copied back on return. Inout does both. ref aliases the actual for the entire call, so writes are immediately visible; const ref keeps the performance and aliasing of ref but prevents assignments through the formal. Because a ref formal aliases live caller storage, it is legal only in an automatic-lifetime subroutine.

  • If a formal omits its direction, it inherits the preceding formal's direction; the first omitted direction defaults to input. Repeat directions when that sticky rule could surprise a reviewer.
  • Declare a task or function automatic before using ref or const ref formals; module-scope tasks otherwise default to static lifetime.
  • Use output for a final copy-back result and ref only when intermediate caller-visible mutation is part of the contract.
Use const ref for large read-only inputssystemverilog
function automatic int checksum(
  const ref byte unsigned data[],
  output bit valid
);
  int sum = 0;
  valid = (data.size() != 0);
  foreach (data[i]) sum = (sum + data[i]) & 'hFFFF;
  return sum;
endfunction

task automatic swap(ref int a, ref int b);
  int tmp = a;
  a = b;
  b = tmp;
endtask

Lifetime must match concurrency

Static subroutine storage persists for the containing scope and is shared among calls. Automatic storage is created for each invocation. Methods are normally automatic, but an explicit static local still shares state. Recursive and forked calls need automatic locals unless shared state is intentional and protected.

  • Initialize automatic variables before use; their per-call storage does not imply a desired value.
  • A static accumulator is hidden shared state and must be synchronized if calls overlap.
  • A ref actual must be a compatible variable, not an arbitrary expression.
  • ref and const ref formals require automatic subroutine lifetime because their aliases are invocation-specific.
  • Default arguments and named argument association improve call-site clarity but should not hide protocol-critical inputs.

Explain it out loud

Interview reasoning checkpoints

Strong answer

ref aliases the caller's variable for the duration of the call, so writes are immediate. output uses a local formal whose final value is copied back when the subroutine returns.

Reason it through
  1. Ask when the caller should observe intermediate values.
  2. Ask whether exact type and lvalue aliasing are required.
  3. Prefer output when only the final result belongs in the public contract.
Interviewer lens

A strong answer explains timing of visibility, not just “reference versus copy.”

Common trap

Using ref only as a performance optimization while ignoring caller-visible mutation.

Strong answer

Automatic gives each invocation independent arguments and locals, making recursion and overlapping calls reentrant. Static storage persists and is shared, which is useful only when that shared state is intentional.

Reason it through
  1. Determine whether calls can overlap through fork, callbacks, or parallel components.
  2. Identify every mutable local.
  3. Protect intentional shared state or move it into an explicit object.
Interviewer lens

Connect lifetime to races and reentrancy.

Common trap

Assuming a static local is safe because each source call appears in a different block.