Part 7 · Advanced & Integration · Intermediate

$test$plusargs & $value$plusargs

Existence vs value plusargs, format strings, default patterns, return-value checking, and the knob registry idiom.

Two system functions, two jobs

$test$plusargs("NAME") answers one question — was +NAME given on the command line — and returns 1 or 0. $value$plusargs("NAME=%d", var) goes further: it matches the prefix, parses the rest through a format string into your variable, and returns nonzero only when the plusarg was present and the parse succeeded. Existence flags switch features on; value plusargs carry numbers, strings, and hex values into the simulation.

systemverilog
module tb_top;
  int    num_txns = 100;          // default lives with the declaration
  string test_name = "smoke";
  bit [31:0] base_addr = 32'h1000_0000;

  initial begin
    // Existence check — flag style
    if ($test$plusargs("ENABLE_COVERAGE"))
      $display("coverage collection ON");

    // Value parse — %d decimal, %s string, %h hex
    if (!$value$plusargs("NUM_TXNS=%d", num_txns))
      $display("NUM_TXNS not given, using default %0d", num_txns);

    void'($value$plusargs("TESTNAME=%s", test_name));
    void'($value$plusargs("BASE_ADDR=%h", base_addr));

    $display("test=%s txns=%0d base=%h", test_name, num_txns, base_addr);
  end
endmodule

// Run:  simv +ENABLE_COVERAGE +NUM_TXNS=500 +TESTNAME=stress +BASE_ADDR=2000_0000
// Note: $test$plusargs does PREFIX matching — "+ENABLE_COVERAGE_X"
// also satisfies $test$plusargs("ENABLE_COVERAGE"). Name knobs carefully.

The defaults pattern

Initialize the variable to its default, then let $value$plusargs overwrite it only on a successful parse. The return value tells you which happened — check it when the knob is mandatory, and void'() it when the default is acceptable, so lint stays quiet without hiding intent.


Scanning multiple related plusargs

Format strings parse one value per call, but you can loop over a family of numbered plusargs to accept a variable-length list — error injection points, channel enables, per-port delays. The loop stops at the first missing index.

systemverilog
// Accept +ERR_ADDR0=... +ERR_ADDR1=... up to 8 injection points
bit [31:0] err_addr[$];

initial begin
  bit [31:0] val;
  string     key;
  for (int i = 0; i < 8; i++) begin
    key = $sformatf("ERR_ADDR%0d=%%h", i);   // %%h → literal %h in result
    if ($value$plusargs(key, val))
      err_addr.push_back(val);
    else
      break;                                  // first gap ends the list
  end
  $display("%0d error injection points loaded", err_addr.size());
end

// simv +ERR_ADDR0=1000 +ERR_ADDR1=2FF0 +ERR_ADDR2=4000

The knob registry idiom

Mature testbenches do not sprinkle plusarg calls through drivers and monitors. They declare every knob in one registry — name, default, description — parse the command line once at time zero, and print the resolved table into the log. The log of every run then documents its own configuration, and a typo in a plusarg name is visible instead of silently ignored.

systemverilog
class knob_registry;
  int    num_txns   = 100;
  int    verbosity  = 1;
  string test_name  = "smoke";
  bit    enable_cov = 0;

  function void parse();
    void'($value$plusargs("NUM_TXNS=%d",  num_txns));
    void'($value$plusargs("VERBOSITY=%d", verbosity));
    void'($value$plusargs("TESTNAME=%s",  test_name));
    enable_cov = $test$plusargs("ENABLE_COVERAGE");
    print();
  endfunction

  function void print();
    $display("=== KNOBS ===========================");
    $display("  TESTNAME        = %s",  test_name);
    $display("  NUM_TXNS        = %0d", num_txns);
    $display("  VERBOSITY       = %0d", verbosity);
    $display("  ENABLE_COVERAGE = %0b", enable_cov);
    $display("=====================================");
  endfunction
endclass
diagram
KNOB FLOW

  command line     +TESTNAME=stress +NUM_TXNS=500
       │
       ▼  parsed ONCE at time 0
  knob_registry    { test_name="stress", num_txns=500, defaults... }
       │
       ├──► printed to log   (run is self-documenting)
       │
       └──► read by env / driver / sequences via the registry handle
            (no component calls $value$plusargs itself)

Key takeaways

  • $test$plusargs checks existence; $value$plusargs parses a value and reports success via its return.

  • Initialize defaults at declaration and let a successful parse overwrite them — the cleanest default pattern.

  • Plusarg matching is prefix-based — choose knob names that are not prefixes of each other.

  • Centralize parsing in a knob registry, parse once, and print the resolved configuration into every log.

Common pitfalls

  • Ignoring the $value$plusargs return on a mandatory knob — the test silently runs with the default.

  • Prefix collisions like +DUMP and +DUMP_START — $test$plusargs("DUMP") matches both.

  • Parsing a hex command-line value with %d — parse fails or truncates; match the format to how users type it.

  • Calling $value$plusargs inside hot loops or every transaction — wasteful and scatters configuration; parse once at time 0.

Interview angle

Expect: difference between the two plusarg functions and their return values; how to give a knob a default (initialize, then conditional overwrite); the prefix-matching gotcha; and the architecture question — where should plusarg parsing live in a UVM environment (one config/knob class at time zero, not in components).