Part 7 · Advanced & Integration · Intermediate
Config Architecture: Files vs Plusargs vs Objects
The three-layer configuration model, precedence design, a central config class that parses plusargs once, and the scattered-plusargs anti-pattern.
Three layers of configuration
Every configurable thing in a testbench binds at one of three times. Compile time — parameters and `define — sets what structurally exists: bus widths, number of agents, sim-vs-gate code. Run time — plusargs — selects among compiled behaviors per invocation: test name, seed, iteration counts. Test time — config objects built inside the test — carries rich structured settings (constraints, sub-configs, virtual interface handles) down through the environment. Choosing the wrong layer costs either recompiles or untestable rigidity.
THREE-LAYER CONFIG MODEL
layer mechanism binds at change cost
─────────────────────────────────────────────────────────────────────
STRUCTURAL parameter / `define compile/elab recompile
(widths, agent +define+FLAG ation (minutes-hours)
count)
│
▼
INVOCATION +TESTNAME=x time 0 re-run only
(test select, +NUM_TXNS=500 (seconds)
seeds, knobs) +VERBOSITY=HIGH
│
▼
TEST-INTERNAL config object built build phase edit test +
(constraints, in the test, passed recompile TB
per-agent down the env (or knob-driven)
settings)
rule of thumb: push every decision to the CHEAPEST layer that
can express it — recompiling to change a verbosity is a smell.Precedence and the central config class
When the same setting can arrive from several sources, define precedence once and document it: command-line plusarg beats test override beats config-file value beats hard default . The command line wins because it is what regression scripts and engineers reach for last-minute. A central config class implements this: defaults in declarations, optional file load, then a single plusarg parse as the final word.
class tb_config;
// Layer: hard defaults
int num_txns = 100;
int verbosity = 1;
bit en_scoreboard = 1;
string mem_image = "";
// Called once at time 0; precedence = default < file < plusarg
function void resolve(string cfg_file = "");
if (cfg_file != "") load_file(cfg_file); // middle layer
void'($value$plusargs("NUM_TXNS=%d", num_txns)); // final word
void'($value$plusargs("VERBOSITY=%d", verbosity));
void'($value$plusargs("MEM_IMAGE=%s", mem_image));
if ($test$plusargs("NO_SCOREBOARD")) en_scoreboard = 0;
print();
endfunction
function void load_file(string fname);
int fd; string line, key; int val;
fd = $fopen(fname, "r");
if (fd == 0) return; // file is optional
while ($fgets(line, fd))
if ($sscanf(line, "%s %d", key, val) == 2)
case (key)
"num_txns": num_txns = val;
"verbosity": verbosity = val;
endcase
$fclose(fd);
endfunction
function void print();
$display("CFG: txns=%0d verb=%0d sb=%0b img=%s",
num_txns, verbosity, en_scoreboard, mem_image);
endfunction
endclassDistribution after resolution
Once resolved, the single config object is handed down the environment — in raw SV through constructors or a package-level handle, in UVM through uvm_config_db. Components read their settings from the object; none of them touch the command line themselves.
Anti-pattern: scattered plusargs
The failure mode every legacy testbench exhibits: $value$plusargs calls buried in drivers, monitors, sequences, and scoreboards. Nobody can list the knobs that exist; two components parse the same plusarg with different defaults; a renamed knob breaks three silent call sites; and reproducing a run means archaeology through grep instead of reading one config banner in the log.
Discoverability — one class lists every knob; grep-the-codebase is not a configuration document.
Consistency — one default per knob, not one per call site that drifted apart over two years.
Reproducibility — the printed config banner plus the seed fully describes the run.
Testability — tests can construct and override the config object directly, no command line needed in unit checks.
Key takeaways
Three layers — compile-time structure, run-time plusargs, test-time config objects — each with a different change cost.
Push each decision to the cheapest layer that can express it; recompiling for a knob change is a smell.
Define precedence once: plusarg beats file beats default — and implement it in one resolve() function.
Components read config objects; only the central class reads the command line.
Common pitfalls
Structural settings (bus width, agent count) as plusargs — the compiled image cannot honor them, so they silently do nothing.
Two call sites parsing the same plusarg with different defaults — behavior depends on which component asks.
No config banner in the log — reproducing an old regression run becomes guesswork.
Precedence by accident of call order instead of by design — file load after plusarg parse silently overrides the command line.
Interview angle
A favorite senior-level design question: 'How do you configure your testbench?' Strong answers name the three layers with examples, state an explicit precedence order, and describe one central parse with a printed banner. Mentioning the scattered-plusargs anti-pattern — and that you refactored one — signals real production experience.