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.

diagram
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.

systemverilog
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
endclass

Distribution 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.