Skip to content

SystemVerilog · Module 6

Default Argument Values

Default values for function/task arguments, the ordering rule, named-argument calls, defaults from parameters, and API design patterns.

Module 6 · Page 6.6

Default argument values turn a complex 8-parameter task into something you can call with just one argument for the common case — and still have full control when you need it. This is how you write APIs that are both powerful and easy to use.

What Are Default Argument Values?

When you declare a task or function argument, you can give it a default value using an = sign. If the caller omits that argument, the default value is used automatically. If the caller provides a value, the provided value overrides the default.

This is the same concept as default function parameters in Python, C++, or Java — and it is equally transformative for SystemVerilog testbench APIs. A single protocol driver task can serve all test scenarios with minimal typing for the common case, while remaining fully configurable for edge cases.

task automatic apb_write ( input logic [31:0] addr, input logic [31:0] data, output logic err,REQUIRED — must provide input bit verbose = 0, input int timeout_cyc = 1000, input logic strobe = 1'b1);DEFAULT — omit to use default value
Figure 1 — Required arguments (red) must always be supplied. Default arguments (yellow) are optional — if omitted, the declared default value is used automatically.

Basic Syntax — How to Declare Defaults

Add = default_value after the argument name in the argument list. The default can be a literal, a constant, or an expression involving other arguments or parameters.

SystemVerilog — default argument syntax for tasks and functions
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── Function with default arguments ──────────────────────────────
function automatic logic [7:0] calc_crc (
    input logic [7:0] data,         // ← required: no default
    input logic [7:0] poly = 8'hD5, // ← optional: default polynomial
    input logic [7:0] init = 8'hFF  // ← optional: default init value
);
    return data ^ poly ^ init;      // simplified
endfunction
 
// ── Calling with defaults ──────────────────────────────────────────
calc_crc(8'hAA);                    // uses poly=0xD5, init=0xFF
calc_crc(8'hAA, 8'h07);             // uses poly=0x07, init=0xFF
calc_crc(8'hAA, 8'h07, 8'h00);      // all three provided
 
 
// ── Task with multiple defaults ───────────────────────────────────
task automatic apb_write (
    input  logic [31:0] addr,             // required
    input  logic [31:0] data,             // required
    output logic        err,              // required (output can't have default)
    input  int          timeout = 1000,   // optional: 1000-cycle timeout
    input  bit          verbose = 0,      // optional: silent by default
    input  bit          check   = 1       // optional: check SLVERR by default
);
    int cnt = 0;
    @(posedge clk); psel = 1; paddr = addr; pwdata = data;
    @(posedge clk); penable = 1;
    @(posedge clk);
    while (!pready && cnt < timeout) begin @(posedge clk); cnt++; end
    err = pslverr;
    if (verbose) $display("APB write 0x%h = 0x%h err=%b", addr, data, err);
    if (check && err) $error("APB SLVERR on write to 0x%h", addr);
    psel = 0; penable = 0;
endtask
 
// ── All three calling styles ───────────────────────────────────────
logic e;
apb_write(32'h0, 32'hFF, e);                  // timeout=1000, silent, check
apb_write(32'h0, 32'hFF, e, 500);             // timeout=500, silent, check
apb_write(32'h0, 32'hFF, e, 500, 1);          // timeout=500, verbose, check
apb_write(32'h0, 32'hFF, e, 500, 1, 0);       // timeout=500, verbose, no-check

The Ordering Rule — Defaults Must Come Last

Arguments with default values must be placed after all required arguments in the list. You cannot have a required argument after a default one — the compiler cannot determine which argument to apply when the caller omits a middle one.

WRONG — default before requiredaddrrequiredverbose=0default ← middledatarequired ← after default!If verbose is omitted, which argument is data? Ambiguous → compile errorCORRECT — required first, defaults at endaddrrequireddatarequiredverbose=0default ← lastfn(addr, data) → verbose uses default=0. Unambiguous// WRONG calling attempt (ambiguous):fn(addr, data); // ← which argument goes to 'data'?// CORRECT calls (clear):fn(addr, data); // verbose=0 (default)fn(addr, data, 1); // verbose=1 (explicit)
Figure 2 — All required arguments must appear before any defaults. The compiler must be able to determine argument positions unambiguously.
SystemVerilog — argument ordering: wrong vs correct
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ❌ WRONG: required arg after default arg
task automatic bad_order (
    input logic [31:0] addr,
    input bit          verbose = 0,  // default
    input logic [31:0] data          // ❌ required AFTER default — illegal!
);
endtask
 
 
// ✅ CORRECT: all required args before all default args
task automatic good_order (
    input logic [31:0] addr,          // required ─┐
    input logic [31:0] data,          // required  │ all required first
    output logic       err,           // required ─┘
    input bit          verbose = 0,   // optional ─┐
    input int          timeout = 1000 // optional  │ defaults last
);                                    //          ─┘
endtask

Named Argument Passing — Skip to Any Default

SystemVerilog allows you to name arguments when calling a task or function — just like named port connections on a module instantiation. This lets you override a specific default argument without providing all the ones before it.

SystemVerilog — named argument calling (skip to any default)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Task declaration
task automatic apb_write (
    input  logic [31:0] addr,
    input  logic [31:0] data,
    output logic        err,
    input  int          timeout = 1000,
    input  bit          verbose = 0,
    input  bit          check   = 1
);
endtask
 
logic e;
 
// ── Positional (must provide all preceding args) ──────────────────
apb_write(32'h0, 32'hFF, e, 1000, 1);  // have to spell out timeout to get verbose
 
 
// ── Named (jump directly to any argument) ─────────────────────────
apb_write(.addr(32'h0), .data(32'hFF), .err(e), .verbose(1));
// ↑ timeout and check use defaults — verbose overridden to 1
 
apb_write(.addr(32'h0), .data(32'hFF), .err(e), .check(0));
// ↑ jump straight to 'check' — timeout=1000, verbose=0 by default
 
apb_write(.addr(32'h0), .data(32'hFF), .err(e), .timeout(500), .check(0));
// ↑ override timeout and check, verbose stays at default
 
 
// ── Named arguments can be in any order ───────────────────────────
apb_write(
    .err     (e),
    .data    (32'hFF),    // order doesn't matter with named args
    .addr    (32'h0),
    .verbose (1)
);  // ✅ legal — same as the positional version above

Practical Examples — Real Testbench APIs

Here are four real-world patterns where default arguments dramatically simplify testbench code.

SystemVerilog — four practical default-argument patterns
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── Pattern 1: Wait with configurable timeout ─────────────────────
task automatic wait_for_done (
    input int timeout_cyc = 10_000   // almost always fine with 10k cycles
);
    int cnt = 0;
    while (!done && cnt < timeout_cyc) begin
        @(posedge clk); cnt++;
    end
    if (cnt == timeout_cyc) $fatal(1, "Timeout waiting for done");
endtask
 
wait_for_done();          // 10k cycle timeout
wait_for_done(500);       // strict 500-cycle timeout for fast path
wait_for_done(100_000);   // long timeout for slow peripheral
 
 
// ── Pattern 2: UART with configurable baud and stop bits ──────────
task automatic uart_send (
    input logic [7:0] data,
    input int         baud_div  = 434,  // 115200 at 50 MHz
    input int         stop_bits = 1,    // standard: 1 stop bit
    input bit         parity_en = 0     // no parity by default
);
    // start bit
    tx = 0; repeat(baud_div) @(posedge clk);
    // data bits
    for (int i = 0; i < 8; i++) begin
        tx = data[i]; repeat(baud_div) @(posedge clk);
    end
    // parity bit (optional)
    if (parity_en) begin tx = ^data; repeat(baud_div) @(posedge clk); end
    // stop bits
    tx = 1; repeat(stop_bits * baud_div) @(posedge clk);
endtask
 
uart_send(8'h41);                       // 'A' at 115200, no parity
uart_send(8'h41, 5208);                 // 'A' at 9600 baud
uart_send(8'h41, 434, 2, 1);            // 115200, 2 stop bits, with parity
 
 
// ── Pattern 3: Memory transaction helper ─────────────────────────
function automatic logic [31:0] read_reg (
    input logic [11:0] addr,
    input int          offset = 0,      // byte offset into register
    input logic [3:0]  mask   = 4'hF    // all 4 bytes active
);
    return regfile[addr + offset] & {{8{mask[3]}},{8{mask[2]}},{8{mask[1]}},{8{mask[0]}}};
endfunction
 
logic [31:0] v = read_reg(12'hA00);             // offset=0, all bytes
logic [31:0] b = read_reg(12'hA00, 4);          // 4-byte offset
logic [31:0] m = read_reg(12'hA00, 0, 4'h3);    // low 2 bytes only
 
 
// ── Pattern 4: $display wrapper with severity control ─────────────
function automatic void log (
    input string msg,
    input string level = "INFO",    // INFO/WARN/ERROR/DEBUG
    input bit    show  = 1          // suppress if 0 (e.g. debug build)
);
    if (show)
        $display("[%s @ %0t] %s", level, $time, msg);
endfunction
 
log("Boot complete");                   // [INFO @ 0] Boot complete
log("Parity mismatch", "WARN");         // [WARN @ 200] Parity mismatch
log("Debug trace", "DEBUG", DEBUG_EN);  // shown only if DEBUG_EN=1

Default Values from Parameters and Expressions

A default value is not limited to a literal constant. It can be any compile-time expression — including module parameters, localparams, $clog2, or even other arguments declared earlier in the same list.

SystemVerilog — default values from parameters and expressions
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── Module-level constant used as default ─────────────────────────
module tb;
    localparam int DEFAULT_TIMEOUT = 5_000;
    localparam int MAX_RETRIES     = 3;
 
    task automatic smart_write (
        input logic [31:0] addr, data,
        output logic       err,
        input int          timeout = DEFAULT_TIMEOUT,  // from localparam
        input int          retries = MAX_RETRIES       // from localparam
    );
        // ... driver body
    endtask
 
    // Change DEFAULT_TIMEOUT once — all tasks using it update automatically
endmodule
 
 
// ── $clog2 as a default ───────────────────────────────────────────
parameter int DEPTH = 1024;
 
task automatic init_memory (
    input int start_addr  = 0,
    input int num_entries = DEPTH,          // from parameter
    input int addr_width  = $clog2(DEPTH)   // computed at compile time
);
    $display("Init %0d entries, %0d-bit addr", num_entries, addr_width);
endtask
 
init_memory();              // start=0, entries=1024, width=10
init_memory(512);           // start=512, rest at defaults
init_memory(0, 256);        // first 256 entries

Default Values and output Arguments

output arguments technically can have defaults in SystemVerilog, but the default value has no practical effect — an output is written by the task and read by the caller after return. A caller that "omits" an output argument cannot receive the result. In practice, output arguments should always be required. The exception is input and inout which both support defaults meaningfully.

SystemVerilog — defaults with input and inout (not output)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── input: most common use of defaults ───────────────────────────
task automatic send_burst (
    input logic [31:0] start_addr,          // required
    input int          burst_len = 4,       // optional — 4 beats
    input bit          incr_addr = 1,       // optional — auto-increment
    input logic [1:0]  size      = 2'b10    // optional — 32-bit transfers
);
    for (int i = 0; i < burst_len; i++) begin
        @(posedge clk);
        paddr = start_addr + (incr_addr ? i*4 : 0);
        psize = size;
    end
endtask
 
send_burst(32'h1000);           // 4-beat, auto-increment, 32-bit
send_burst(32'h1000, 8);        // 8-beat burst
send_burst(32'h1000, 1, 0);     // single fixed-address transfer
 
 
// ── inout with default: counter with optional starting value ──────
task automatic count_errors (
    inout int err_count = 0    // start at 0 if no argument given
);
    if (pslverr) err_count++;
endtask
 
int my_errs;
count_errors(my_errs);     // my_errs used as-is (must be initialised)

Common Mistakes

SystemVerilog — default argument mistakes and fixes
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ════ MISTAKE 1: Required arg after default ════════════════════════
task automatic bad1 (
    input logic a,
    input bit   verbose = 0,  // default
    input logic b             // ❌ required after default — illegal
);
endtask
// ✅ FIX: move required before default
task automatic good1 (
    input logic a,
    input logic b,
    input bit   verbose = 0   // default at end
);
endtask
 
 
// ════ MISTAKE 2: Omitting a middle positional arg ══════════════════
task automatic send (
    input logic [7:0] data,
    input int         delay = 10,
    input bit         check = 1
);
endtask
// Positional call: to get check=0 you MUST provide delay
send(8'hAA, 10, 0);    // ← must spell out delay even if you want default
// ✅ FIX: use named args to jump directly to 'check'
send(.data(8'hAA), .check(0));  // delay stays at default 10 ✅
 
 
// ════ MISTAKE 3: Non-const expression as default ══════════════════
logic [31:0] current_addr;
task automatic bad3 (
    input logic [31:0] addr = current_addr  // ❌ runtime variable as default
);
endtask
// ✅ FIX: use a compile-time constant as default
task automatic good3 (
    input logic [31:0] addr = 32'h0000_0000  // ✅ constant
);
endtask

Quick Reference — Default Arguments Cheat Sheet

SystemVerilog — default arguments quick reference
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── Declaration syntax ────────────────────────────────────────
task automatic my_task (
    input <type> req_arg,             // required — no default
    input <type> opt_arg = value,     // optional — has default
    input <type> another = CONST      // default from localparam/parameter
);
 
// ── Calling: positional (must provide all preceding args) ─────
my_task(req);                 // opt_arg and another use defaults
my_task(req, val1);           // opt_arg=val1, another uses default
my_task(req, val1, val2);     // all provided
 
// ── Calling: named (jump to any argument) ─────────────────────
my_task(.req_arg(req), .another(val2));  // opt_arg uses default
my_task(.another(val2), .req_arg(req));  // order doesn't matter
 
// ── Rules ─────────────────────────────────────────────────────
// 1. Required arguments MUST come before default arguments
// 2. Default value must be a compile-time constant expression
// 3. Named calling: jump to any argument, skip preceding ones
// 4. Cannot mix positional and named in the same call
// 5. output args cannot meaningfully have defaults
// 6. Default can use: literals, localparams, $clog2(), prev args

How the Simulator Evaluates Default Values

Understanding when default values are substituted — and by what mechanism — prevents a class of subtle bugs where engineers expect runtime behavior but get compile-time constants.

Elaboration vs Simulation Time

SystemVerilog simulation has four distinct phases:

  1. Compilation — source is parsed and type-checked. Default expressions are checked for constant-ness here. A runtime variable as a default causes a compile error at this stage.
  2. Elaboration — the design hierarchy is built and parameters are resolved. Default values are substituted into the intermediate representation at every call site that omits that argument. After elaboration, there is no runtime "check if argument was provided" — the constant is simply baked in.
  3. Initialization — initial blocks and variable initial values are applied. Default argument values are not re-evaluated here; they were resolved at elaboration.
  4. Simulation — time advances. When a task is called without an argument, it receives the elaboration-time constant directly, with zero overhead — no branch, no optional check, no performance difference vs explicit supply.
Expression typeLegal as default?Example
Integer literal✅ Yes= 1000
Bit / logic literal✅ Yes= 8'hFF
String literal✅ Yes= "INFO"
localparam / parameter✅ Yes= DEFAULT_TIMEOUT
Constant expression ($clog2, arithmetic)✅ Yes= $clog2(DEPTH)
Previously declared argument in same list⚠ Tool-dependent= prev_arg * 2
Module-level logic / integer variable❌ No= current_addr
Function call return value (non-constant)❌ No= get_base_addr()
Class member access❌ No= cfg.timeout

Advanced API Design Patterns

Default arguments are one tool for building clean testbench APIs. Here are four production-level patterns that combine defaults with other SystemVerilog features for maximum reuse and clarity.

Pattern A: Test Mode Flag — Normal vs Stress Path

SystemVerilog — test mode flag in driver with defaults
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── Normal driver: defaults match the golden-path spec ────────────
task automatic i2c_write (
    input logic [6:0]  dev_addr,
    input logic [7:0]  reg_addr,
    input logic [7:0]  data,
    output logic       ack,
    input int          scl_half_period = 50,   // 100 kHz at 10 ns/cycle
    input bit          inject_nak      = 0,    // normal: device ACKs
    input int          stretch_cycles  = 0,    // no clock stretching
    input bit          corrupt_bit     = 0     // no bit-flip injection
);
    sda = 0; // start
    repeat(scl_half_period) @(posedge clk);
    // ... full I2C sequencing ...
    if (inject_nak)  sda = 1;  // force NAK for error injection tests
    if (corrupt_bit) sda = ~sda;
    ack = !sda;
endtask
 
// Normal regression test: one argument for everything common
i2c_write(7'h48, 8'h01, 8'h3C, ack_bit);
 
// Stress test: inject NAK to test retry logic
i2c_write(.dev_addr(7'h48), .reg_addr(8'h01), .data(8'h3C),
          .ack(ack_bit), .inject_nak(1));
 
// Slow slave: clock stretching
i2c_write(.dev_addr(7'h48), .reg_addr(8'h01), .data(8'h3C),
          .ack(ack_bit), .stretch_cycles(200));
 
// Bit error injection test
i2c_write(.dev_addr(7'h48), .reg_addr(8'h01), .data(8'h3C),
          .ack(ack_bit), .corrupt_bit(1));

Pattern B: Layered Timeout Hierarchy

SystemVerilog — layered timeout defaults from slowest to fastest
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Protocol-level timeouts as single source of truth
localparam int TO_CLK    = 1;        // 1 clock cycle
localparam int TO_FAST   = 100;      // fast peripheral response
localparam int TO_NORM   = 5_000;    // normal bus transaction
localparam int TO_SLOW   = 50_000;   // slow peripherals (flash, ADC)
localparam int TO_BOOT   = 1_000_000;// boot sequence
 
// ── Low-level: uses fast timeout ──────────────────────────────────
task automatic wait_ack (
    input int timeout = TO_FAST
);
    int cnt = 0;
    while (!ack && cnt < timeout) begin @(posedge clk); cnt++; end
    if (!ack) $fatal(1, "ACK timeout after %0d cycles", timeout);
endtask
 
// ── Mid-level: wraps wait_ack, uses normal timeout ─────────────────
task automatic apb_transaction (
    input logic [31:0] addr, data,
    output logic       err,
    input int          timeout = TO_NORM   // inherits from hierarchy
);
    @(posedge clk); psel = 1; paddr = addr; pwdata = data;
    @(posedge clk); penable = 1;
    wait_ack(timeout);          // passes override down if provided
    err = pslverr;
    psel = 0; penable = 0;
endtask
 
// ── High-level: ADC readback, uses slow timeout ───────────────────
task automatic read_adc_result (
    output logic [15:0] result,
    input int           timeout = TO_SLOW
);
    logic err;
    apb_transaction(32'hF000_0010, 32'h0, err, timeout);
    result = prdata[15:0];
endtask
 
read_adc_result(adc_val);              // uses TO_SLOW (50k cycles)
read_adc_result(adc_val, TO_FAST);     // tight timeout for error injection

Pattern C: Overloaded Verbosity — Silence in Regression, Detail in Debug

Without default verbosity flag
Manual verbosity at every site
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Every call site must manage verbosity
apb_write(addr,  data,  e, 1000, 0);
apb_write(addr2, data2, e, 1000, 0);
apb_write(addr3, data3, e, 1000, 0);
// ... 40 more like this in every test
// Miss one → noisy log in regression
// Change one → inconsistent verbosity
With default verbosity flag
Clean by default, explicit when debugging
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// verbose=0 by default: clean by default
apb_write(addr,  data,  e);
apb_write(addr2, data2, e);
apb_write(addr3, data3, e);
// Debug a specific transaction:
apb_write(.addr(addr), .data(data),
          .err(e), .verbose(1));
// All other transactions: silent ✅

Pattern D: Protocol Configuration Struct — When Defaults Become Unwieldy

When a task needs more than 5–6 default arguments, defaults become hard to read and maintain. At that point, replace them with a configuration struct with default field values:

SystemVerilog — config struct replaces many default arguments
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── When you have too many defaults: use a struct ─────────────────
typedef struct {
    int          timeout     = 5000;
    bit          verbose     = 0;
    bit          check_err   = 1;
    int          retries     = 3;
    bit          inject_nak  = 0;
    logic [1:0]  burst_type  = 2'b01;
    int          scl_div     = 50;
    bit          stretch_clk = 0;
} apb_cfg_t;
 
task automatic apb_write_v2 (
    input logic [31:0] addr,
    input logic [31:0] data,
    output logic       err,
    input apb_cfg_t    cfg = {}   // empty braces → all struct defaults
);
    // use cfg.timeout, cfg.verbose, cfg.retries, etc.
endtask
 
// ── All defaults: cleanest possible call ─────────────────────────
apb_write_v2(addr, data, e);
 
// ── Override a few fields cleanly ────────────────────────────────
apb_cfg_t stress_cfg;
stress_cfg.timeout    = 100;
stress_cfg.inject_nak = 1;
apb_write_v2(addr, data, e, stress_cfg);
 
// ── Inline override with struct literal ──────────────────────────
apb_write_v2(addr, data, e,
    '{default:0, timeout:500, check_err:1});

Debugging Academy — Default Argument Bugs

These are the real bugs that appear in SystemVerilog testbenches when default arguments interact with the rest of the language in unexpected ways.

1

Timeout Fires on the First Call, Not When Expected

HIGH
Buggy Code
Variable used instead of localparam-as-default
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Default timeout: 1000 cycles
task auto wait_resp(
    input int timeout = 1000
);
 
// Test file uses localparam...
localparam int LONG_WAIT = 50_000;
 
// ...but accidentally uses variable
int dyn_timeout;
initial begin
    dyn_timeout = 50_000;
    // ❌ tries to use var as default
    wait_resp(dyn_timeout);
    // compiles but won't use LONG_WAIT
    // as a default — it IS being passed
end
Symptom

A call intended to inherit the long wait period instead silently uses the short 1000-cycle default whenever the variable propagation is missed. Tests time out unexpectedly at the wrong point, often in unrelated regressions where the variable was never set.

Root Cause

The bug is actually a misunderstanding — wait_resp(dyn_timeout) works correctly as a positional call passing the variable. The real bug is when you change dyn_timeout in one place but forget to propagate it. Using a localparam as the default eliminates this entirely: every call site without an explicit argument inherits the localparam.

Fix
Localparam as default — single source of truth
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Use localparam as default ✅
localparam int LONG_WAIT = 50_000;
 
task auto wait_resp(
    input int timeout = LONG_WAIT
);
 
// Call with no argument
initial begin
    wait_resp();
    // ✅ uses LONG_WAIT at all sites
    // Change localparam = update all
end
2

Named Argument Typo Silently Uses Default

HIGH
Buggy Code
Capitalised .Verbose ≠ declared .verbose
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
task auto apb_write(
    input logic [31:0] addr, data,
    output logic       err,
    input bit          verbose = 0
);
 
// ❌ Wrong: 'Verbose' ≠ 'verbose'
apb_write(.addr(32'h0),
          .data(32'hFF),
          .err(e),
          .Verbose(1));  // ← typo!
// Tool may warn or error
// verbose stays 0 silently!
Symptom

The intended verbose run produces zero debug output. Some simulators emit an error for an unknown named argument; others silently ignore it and the override is lost. The test report looks identical to a non-verbose run even though the engineer thought they enabled tracing.

Root Cause

Named arguments are case-sensitive in SystemVerilog. .Verbose(1) and .verbose(1) refer to different names. Most simulators emit an error for an unknown named argument, but some older tools quietly ignore it — and the intended override is silently lost. Always enable maximum compiler warnings, and grep your task declarations for exact case before using named args.

Fix
Exact case match required
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ✅ Exact case match required
apb_write(.addr(32'h0),
          .data(32'hFF),
          .err(e),
          .verbose(1));  // ✅
 
// Always compile with -Wall
// to catch named arg mismatches
// Most tools error on unknown
// named argument names
3

Mixing Positional and Named Association in Same Call

MEDIUM
Buggy Code
Positional payload then named flag — illegal
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
task auto send(
    input logic [7:0] data,
    input int         delay = 10,
    input bit         check = 1
);
 
// ❌ Mixing: compile error
send(8'hAB, .check(0));
// positional 8'hAB then named .check
// → illegal in the same call
Symptom

Compile error from the simulator: "cannot mix positional and named argument association." The call site does not compile, but the message can be confusing for engineers who expect the language to "do what I mean."

Root Cause

The LRM prohibits mixing positional and named association in the same task/function call. If you want to use named association to skip a middle default, all arguments in that call must use named association — including the required ones. The compiler catches this; it will not silently accept mixed styles.

Fix
Pick one style per call — all named or all positional
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ✅ Option 1: all named
send(.data(8'hAB), .check(0));
 
// ✅ Option 2: all positional
// (must provide delay too)
send(8'hAB, 10, 0);
 
// Rule: once you use .name(),
// ALL arguments must be named
4

Default Overridden in a Loop Overwrites Preceding Iterations' Defaults

LOW
Misconception
Engineer expects per-iteration default fallback
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Engineer expects: iteration 0
// uses default 1000, iteration 1
// uses 500, etc.
int timeouts[] = '{0:1000, 1:500,
                    2:200};
for (int i=0; i<3; i++) begin
    // ❌ This always passes timeouts[i]
    // Never uses the "default"
    wait_resp(timeouts[i]);
end
Symptom

The "default" branch is never exercised — every iteration explicitly passes a value, so the declared default never applies. This is a conceptual bug rather than a simulator error, but it leads to unexpected test coverage gaps when reviewing waveforms.

Root Cause

Default argument values are per-call-site, not per-task. Each call that omits an argument independently receives the default. There is no mechanism to "reset" a default mid-execution. If you need conditional default-or-override logic, use an if statement at the call site, or pass a sentinel value (like -1) and check it inside the task body.

Fix
Each call is independent — gate explicitly
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ✅ Each call is independent
// Default applies when NO arg given
wait_resp();           // uses default
wait_resp(500);        // overrides
wait_resp(200);        // overrides
 
// In a loop that conditionally
// uses default:
for (int i=0; i<3; i++) begin
    if (i==0) wait_resp();
    else      wait_resp(200*i);
end
5

Default on inout Modifies the Wrong Variable

HIGH
Buggy Code
inout default creates a discarded temporary
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ❌ inout default creates temp var
task auto count_err(
    inout int count = 0
);
    if (pslverr) count++;
endtask
 
// Called without argument
int total_errs = 0;
// ❌ When no arg: count is a
// temporary local = 0, discarded
// at return. total_errs unchanged!
count_err();  // ← increment lost
Symptom

The caller's accumulator never increments. No compile error, no simulation warning — the bug compiles cleanly. Error counters stay at zero even when pslverr fires repeatedly. The mismatch between expected and observed counts often only surfaces during final scoreboard comparisons at end-of-test.

Root Cause

When an inout argument is "omitted" (using its default), the simulator creates an internal temporary variable initialised to the default value. The task increments that temporary — but it is discarded at return. The caller's variable is never updated. This is the most dangerous default-argument bug because it compiles cleanly and produces no warning, yet the inout's write-back is silently lost. Always provide inout arguments explicitly.

Fix
Always pass inout caller-side
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ✅ Always provide inout explicitly
int total_errs = 0;
count_err(total_errs);  // ✅
// total_errs is incremented
// correctly via copy-in/copy-out
 
// Alternatively: use output
// if you don't need copy-in
// Never rely on inout defaults

Interview Q&A — Default Arguments

A default argument is a value declared with = expr after an argument name in a task or function header. If the caller does not provide that argument, the declared value is used. Both tasks and functions support default arguments. The default expression must be a compile-time constant — a literal, localparam, module parameter, or constant expression like $clog2(N). Runtime variables cannot be defaults. There is no restriction on direction for input arguments; output defaults are technically legal but practically useless.