A protected constructor blocks outside construction. A static get() builds the single instance on first call and returns the same handle after.
protected beats local here. Subclasses stay possible for project-specific configs, while arbitrary construction stays blocked.
Singleton status rests on access control plus convention. A subclass constructor could still build a second object elsewhere, so the team treats get() as the only source.
The test fills the config up front, then freezes it. Components read settings during build and run, and nobody writes after the freeze.
Wave | Action |
|---|---|
Parse | The test reads plusargs into the config once |
Freeze | The test calls |
Build | Components receive knob values from the config |
Run | Components read the config and never write it |
Report | The log header prints |
Plusargs face outward to regression scripts. The config object faces inward to components, and the test converts between them in one place.
$value$plusargs("N_TXNS=%d", n) reads a value given on the simulator command line as +N_TXNS=50 and returns 0 when it is absent, so the default stays in force. Plusargs and configuration covers it in full.
Any component can reach the singleton, so the dependency hides. Passing config handles down at construction keeps the same values with visible coupling.
One flat global also resists reuse. A block environment tied to it cannot run twice with different settings, which is the gap hierarchical config lookup later fills.
protected rather than local?local blocks subclasses too. protected still prevents outside construction while allowing a derived project config.
Freeze before build. After the test fills the config, components only read it.
The singleton machinery plus a testbench that proves shared identity, plusargs defaults and the freeze flag.
class tb_config;
protected static tb_config m_inst;
protected function new();
endfunction
static function tb_config get();
if (m_inst == null) m_inst = new();
return m_inst;
endfunction
int unsigned n_txns = 100;
bit enable_cov = 1;
bit err_inject = 0;
int unsigned timeout_ns = 100_000;
string test_name = "smoke";
bit frozen = 0;
function void freeze();
frozen = 1;
endfunction
function string convert2string();
return $sformatf("test=%s n=%0d cov=%0b err=%0b to=%0dns frozen=%0b",
test_name, n_txns, enable_cov, err_inject, timeout_ns, frozen);
endfunction
endclass
PASS: one shared config, plusargs defaults hold, freeze is visibleRun with Verilator 5.048 (--binary --timing) on 2026-10-18; every bench printed the PASS line shown, with no warnings. These class benches were run on Verilator only.