Part 6 · Testbench Architecture · Intermediate
Test Knobs via Plusargs
$test$plusargs and $value$plusargs, verbosity/txn-count/error-rate knobs, documentation, and config layering.
Recompile nothing, control everything
A regression cannot recompile per run — the same compiled snapshot must serve smoke tests, stress tests, and debug replays. Plusargs are the runtime control surface: $test$plusargs("NAME") tests presence of a flag, $value$plusargs("NAME=%d", var) parses a value. The TB reads them once at time zero into a config object, and the rest of the environment reads the config — components never call plusarg functions directly.
class tb_config;
int unsigned num_txns = 200; // sane defaults: bare sim runs fine
int unsigned error_rate = 0; // percent of injected protocol errors
string verbosity = "INFO";
bit dump_waves = 0;
function void parse_plusargs();
void'($value$plusargs("NUM_TXNS=%d", num_txns));
void'($value$plusargs("ERROR_RATE=%d", error_rate));
void'($value$plusargs("VERBOSITY=%s", verbosity));
if ($test$plusargs("DUMP_WAVES")) dump_waves = 1;
$display("[CFG] num_txns=%0d error_rate=%0d%% verbosity=%s waves=%0d",
num_txns, error_rate, verbosity, dump_waves);
endfunction
endclass# Same binary, three very different runs:
simv +NUM_TXNS=50 # quick smoke
simv +NUM_TXNS=5000 +ERROR_RATE=10 # stress with error injection
simv +NUM_TXNS=200 +VERBOSITY=DEBUG +DUMP_WAVES # failure replayThe standard knob set
Knobs every class-based TB grows
VERBOSITY — log level (ERROR/WARN/INFO/DEBUG); regression runs at INFO, replays at DEBUG.
NUM_TXNS — transaction count; scales the same test from 30-second smoke to hour-long soak.
ERROR_RATE — percent of stimulus carrying injected protocol errors; 0 for clean runs.
TESTNAME — selects which test/generator variant to run from one compiled image.
DUMP_WAVES — waveform capture off by default; regressions at scale cannot afford waves.
Knobs feed constraints naturally — the generator randomizes around the knob value rather than using it verbatim:
class generator;
tb_config cfg;
task run();
repeat (cfg.num_txns) begin
txn t = new();
// knob shapes the distribution; randomization fills the rest
if (!t.randomize() with {
inject_err dist { 1 := cfg.error_rate,
0 := 100 - cfg.error_rate };
})
tb_status::report_error("GEN", "randomize failed");
out_mb.put(t);
end
endtask
endclassDocumentation and config layering
An undocumented knob is a trap: someone passes +ERR_RATE=10 instead of +ERROR_RATE=10, $value$plusargs silently misses it, and the run quietly tests nothing. Keep a knob table in the TB README and print every effective value at time zero — the [CFG] line above doubles as the runtime documentation.
CONFIG LAYERING (lowest to highest priority)
layer 1: class defaults tb_config fields (always-sane values)
│ overridden by
layer 2: test test code mutates cfg before build
│ overridden by
layer 3: command line plusargs parsed LAST
▼
final cfg object ──► passed down: env → agents → gen/drv/mon
rule: components read cfg fields only.
$value$plusargs appears in exactly one place — tb_config.Interview angle
"How do you control a test without recompiling?" — plusargs parsed once into a config object, layered over defaults.
"Why centralize plusarg parsing?" — one source of truth, printable at time zero, no scattered hidden flags.
"Plusargs vs config object?" — plusargs are the outside interface; the config object is the inside distribution mechanism.
Key takeaways
Plusargs let one compiled image serve smoke, stress, and replay runs.
Parse plusargs in one place into a config object; components read only the config.
Defaults must be sane — a bare sim with no plusargs should pass.
Print every effective knob at time zero; an undocumented knob is a silent no-op.
Common pitfalls
Misspelled plusarg silently ignored — run executes with defaults and nobody notices.
$value$plusargs calls scattered through components — impossible to audit what a run did.
No default values — TB crashes or hangs when a knob is omitted.
Verbosity knob that only gates new code — legacy $display floods DEBUG-level replays.