plusargs in systemverilog

Plusargs in SystemVerilog: $test$plusargs and $value$plusargs (Tested Examples)

In short: Plusargs are switches you add to the simulator’s run command, each starting with +, like +DEBUG or +SEED=42. Your code reads them at run time, so you can change a test’s behaviour without recompiling. $test$plusargs("DEBUG") tells you whether a switch is there. $value$plusargs("SEED=%d", seed) reads the value after it. Always set a default first, because a missing switch leaves the variable alone.

Here’s what it looks like on a real run command:

./simv +TEST=axi_burst_test +COUNT=250 +DEBUG

Same compiled testbench. Different test, different count, debug on.

No rebuild.

Think of it like a café order

The kitchen is built once: that’s your compiled testbench. Every customer hands over an order slip: that’s the command line.

  • **$test$plusargs is the cook asking “is it on the slip?”** “No onions?” Yes or no.
  • **$value$plusargs is the cook asking “what does the slip say?”** “Size?” Large.
  • The default is the house standard. If the slip says nothing about size, you get a regular.

You never rebuild the kitchen to serve a different order.

Plusargs picture: the command line is an order slip; $test$plusargs asks if a switch is on it, $value$plusargs reads its value, and a missing switch keeps the default
Figure 1. One kitchen, a new order slip every run.

Example 1: is the switch there?

// $test$plusargs: is a switch on the command line?
module tb;
  initial begin
    if ($test$plusargs("DEBUG"))
      $display("DEBUG is ON");
    else
      $display("DEBUG is off");
    $finish;
  end
endmodule

Output (tested on Icarus Verilog 12.0 and Verilator 5.020, 11 Nov 2026):

run with nothing          ->  DEBUG is off
run with +DEBUG           ->  DEBUG is ON
run with +DEBUG_LEVEL=3   ->  DEBUG is ON    <- surprise

That third line is the first surprise. More on it below.

Example 2: read a value, keep a default

// $value$plusargs: read a value, keep a default if it's missing
module tb;
  string       test_name = "smoke_test";   // defaults first
  int          count     = 10;
  bit [31:0]   addr      = 32'h1000;

  initial begin
    if ($value$plusargs("TEST=%s", test_name)) $display("TEST  from command line");
    if ($value$plusargs("COUNT=%d", count))    $display("COUNT from command line");
    if ($value$plusargs("ADDR=%h", addr))      $display("ADDR  from command line");
    $display("test_name=%s count=%0d addr=0x%0h", test_name, count, addr);
    $finish;
  end
endmodule

Output, run with nothing (Icarus and Verilator):

test_name=smoke_test count=10 addr=0x1000

Output, run with +TEST=axi_burst_test +COUNT=250 +ADDR=dead_beef:

TEST  from command line
COUNT from command line
ADDR  from command line
test_name=axi_burst_test count=250 addr=0xdeadbeef
  • No switches: every variable keeps its default. Nothing breaks.
  • %h read dead_beef as hex, even with the _ in the middle.
  • The format letter decides how the text is read: %d decimal, %h hex, %b binary, %o octal, %s string, %f real.

Three surprises worth knowing

// Three surprises worth knowing
module tb;
  int n = -1;
  initial begin
    // 1. Prefix match: "DEBUG" also matches +DEBUG_LEVEL=3
    $display("test$plusargs(\"DEBUG\") = %0d", $test$plusargs("DEBUG"));
    // 2. Given twice: which one wins?
    void'($value$plusargs("N=%d", n));
    $display("N = %0d", n);
    // 3. Case matters
    $display("test$plusargs(\"debug_level\") = %0d", $test$plusargs("debug_level"));
    $finish;
  end
endmodule

Output, run with +DEBUG_LEVEL=3 +N=5 +N=9 (Icarus and Verilator):

test$plusargs("DEBUG") = 1
N = 5
test$plusargs("debug_level") = 0
  • Prefix match. $test$plusargs("DEBUG") checks whether any switch starts with DEBUG. So +DEBUG_LEVEL=3 counts. Picture a cook who sees “No onion rings” and skips the onions too. Fix: give switches names that aren’t prefixes of each other, like +DBG_ON and +DBG_LEVEL=3.
  • First one wins. With +N=5 +N=9, you get 5. Handy to know when a regression script adds its own switch after yours.
  • Case matters. debug_level is not DEBUG_LEVEL.
Prefix match: $test$plusargs("DEBUG") is true for +DEBUG and +DEBUG_LEVEL=3, false for +debug and +MY_DEBUG
Figure 2. $test$plusargs matches the start of a switch (tested on Icarus and Verilator).

And a fourth, from the bad-input test. Running example 2 with +COUNT=abc:

SimulatorReturnedcount became
Icarus 12.01 (“found it”)x, plus a warning
Verilator 5.0201 (“found it”)0, no warning

Both say “found it” and both throw away your default of 10. $value$plusargs checks that the switch is there, not that the value makes sense. If a value matters, check its range after reading it.

$test$plusargs vs $value$plusargs at a glance

$test$plusargs$value$plusargs
AsksIs the switch there?What value comes after it?
Command line+DEBUG+SEED=42
Code$test$plusargs("DEBUG")$value$plusargs("SEED=%d", seed)
Returns1 if found, else 01 if found, else 0
Writes a variableNoYes, only when found
Café picture“No onions on the slip?”“What size does the slip say?”
Good forOn/off modes, debug printsTest name, seed, counts, addresses, timeouts

Working example 4: a list from the command line

$value$plusargs reads one value into one variable. For a list, number the switches and read them in a loop.

// Read a numbered list: +SLAVE_0=1 +SLAVE_1=3 +SLAVE_2=7
module tb;
  int slaves[$];
  initial begin
    int v, i;
    i = 0;
    // %%d becomes %d after $sformatf, so $value$plusargs sees "SLAVE_0=%d"
    while ($value$plusargs($sformatf("SLAVE_%0d=%%d", i), v)) begin
      slaves.push_back(v);
      i++;
    end
    $display("got %0d slaves", slaves.size());
    foreach (slaves[k]) $display("  slave[%0d] = %0d", k, slaves[k]);
    $finish;
  end
endmodule

Output, run with +SLAVE_0=1 +SLAVE_1=3 +SLAVE_2=7 (Icarus and Verilator):

got 3 slaves
  slave[0] = 1
  slave[1] = 3
  slave[2] = 7

Run with nothing and it prints got 0 slaves. The loop stops at the first missing number, like reading numbered tickets until one is missing.

The %%d is the trick: $sformatf turns %% into a single %, so the format string ends up as SLAVE_0=%d.

Working example 5: pick an enum by name

// Pick an enum by name: +MODE=STRESS
typedef enum {NORMAL, LOW_POWER, STRESS} mode_e;

module tb;
  mode_e mode = NORMAL;          // default
  initial begin
    string s;
    mode_e m;
    bit    found;
    if ($value$plusargs("MODE=%s", s)) begin
      m = m.first();
      repeat (m.num()) begin     // walk every name in the enum
        if (m.name() == s) begin mode = m; found = 1; end
        m = m.next();
      end
      if (!found) $error("Unknown MODE '%s', keeping %s", s, mode.name());
    end
    $display("mode = %s", mode.name());
    $finish;
  end
endmodule

Output (Icarus and Verilator):

+MODE=STRESS  ->  mode = STRESS
+MODE=stress  ->  ERROR: Unknown MODE 'stress', keeping NORMAL
  • Read the name as a string, then walk the enum until a name matches. No casts, no magic numbers on the command line.
  • A typo gets a loud error instead of silently running the wrong mode. (Verilator also stops the run at $error; Icarus prints the error and carries on.)

Working example 6: a parameter can’t change, a variable can

// A parameter can't change at run time. A real variable can.
module tb;
  parameter real BASE_MHZ = 1500.0;   // fixed when the design is built
  real step_mhz = 40.0;               // run-time knob
  initial begin
    void'($value$plusargs("STEP_MHZ=%f", step_mhz));
    $display("BASE_MHZ = %0.1f, step_mhz = %0.1f", BASE_MHZ, step_mhz);
    $finish;
  end
endmodule

Output, run with +STEP_MHZ=50 (Icarus and Verilator):

BASE_MHZ = 1500.0, step_mhz = 50.0

A parameter is printed on the kitchen wall when it’s built. A plusarg is written on today’s order slip. You can’t change the wall with a slip.

Plusargs vs ifdef

ifdef / +define+Plusargs
Decided atCompile timeRun time
Change it meansRecompileJust rerun
Can add or remove code, ports, assignsYesNo
Can change values and choicesYesYes
Café pictureRebuilding the kitchenA different order slip

Rule of thumb: if it changes the structure (what hardware or code exists), use ifdef. If it changes behaviour (which test, how many, how loud), use a plusarg. More in ifdef vs plusargs.

Timeline: ifdef is decided at compile time, parameters at elaboration, plusargs at run time, so plusargs cannot change parameters or structure
Figure 3. ifdef at compile time, parameters at build time, plusargs at run time.

Plusargs you already use in UVM

  • +UVM_TESTNAME=my_test picks the test that run_test() builds.
  • +UVM_VERBOSITY=UVM_HIGH turns up the messages.
  • UVM also has uvm_cmdline_processor, which can return every match of a repeated switch (+SLAVE=1 +SLAVE=3), something plain $value$plusargs can’t do.
  • The random seed is usually a simulator switch, not your own plusarg. On VCS, for example, it’s +ntb_random_seed=42. Check your simulator’s manual for the exact name.

Common mistakes (and the fix for each)

These come from real Verification Academy threads, retold in my own words with links.

1. A match string that’s too short, or too long

  • What happened: someone checked $test$plusargs("ARG1") and found it was also true for +ARG1232 (Verification Academy). In 2016 another engineer was surprised that $test$plusargs("CPU0_LOAD="), with the =, only matched +CPU0_LOAD=... and not +CPU0_LOAD (Verification Academy, 2016).
  • Why: the string is matched against the start of each switch, character for character, = included.
  • Fix: pick switch names that aren’t prefixes of each other, and write the match string exactly as it starts on the command line.

2. Trying to set a parameter at run time

  • What happened: in 2020 an engineer wanted +base_freq=1.7 to change a PLL model’s parameter (Verification Academy, 2020).
  • Why: a parameter is fixed when the design is built. Plusargs arrive after that.
  • Fix: use a real variable and %f, like example 6.

3. Reading a whole list in one go

  • What happened: someone tried $value$plusargs("target_slaves=%p", list) with +target_slaves={1,3} and got “Second argument must be a modifiable variable” (Verification Academy).
  • Why: %p isn’t a plusarg format, and $value$plusargs writes one variable, not an array.
  • Fix: numbered switches in a loop (example 4), the same approach Dave Rich suggested in a 2020 thread. Or uvm_cmdline_processor in UVM.

4. Setting an enum from the command line

  • What happened: in 2024 an engineer wanted both +COLOR=yellow and +COLOR=3 to work for an enum (Verification Academy, 2024). In another thread, every enum config came out as its first value no matter what was passed (Verification Academy).
  • Why: there’s no enum format letter. You read either a string or a number, then convert it yourself.
  • Fix: read a string and match it against the enum’s names (example 5), or read an int and cast it.

5. Using a plusarg to add or remove hardware

  • What happened: in 2020 an engineer put assign statements inside if (!$test$plusargs("HB_CONN_DISABLE")) to drop a connection for one test, and got a genvar error (Verification Academy, 2020).
  • Why: an if around assigns is a generate block, decided when the design is built. Plusargs aren’t known yet.
  • Fix: structure goes in ifdef. Plusargs choose behaviour inside code that already exists.

Every idea on this page, in one picture

IdeaPicture it as
Compiled testbenchA café kitchen, built once
PlusargsThe order slip each customer hands over
$test$plusargs“Is ‘no onions’ on the slip?”
$value$plusargs“What size does the slip say?”
Default valueThe house standard when the slip says nothing
Prefix matchSeeing “no onion rings” and skipping the onions too
First one winsThe cook reads the slip top to bottom and stops at the first match
Bad number still “found”The slip says “size: banana”; the cook still thinks a size was given
Numbered listReading numbered tickets until one is missing
Parameter vs plusargThe menu on the wall vs today’s slip
ifdefRebuilding the kitchen

FAQ

What is the difference between $test$plusargs and $value$plusargs?

$test$plusargs only checks whether a switch is there. $value$plusargs also reads the value after it into a variable.

Do I need to recompile to change a plusarg?

No. That’s the point. Just rerun with different switches.

What happens if the plusarg is missing?

$value$plusargs returns 0 and leaves your variable untouched, so set a default before calling it.

How do I pass a string with a plusarg?

+TEST=axi_burst_test, read with %s into a string variable.

Can I read a hex value?

Yes, use %h: +ADDR=dead_beef reads as 0xdeadbeef.

Can a plusarg change a parameter?

No. Parameters are fixed at build time. Use a variable instead.

References

  • IEEE 1800-2017, SystemVerilog standard: section 21.6 (command line input: $test$plusargs and $value$plusargs).
  • Verification Academy forum threads linked in the mistakes section (accessed 11 Nov 2026).
  • All examples tested on Icarus Verilog 12.0 and Verilator 5.020 on 11 Nov 2026.

What to read next