Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutRoadmapCareersInternshipsFAQContactPrivacyTerms
Skip to lesson

Course contents

  • Start Here5
  • SystemVerilog Language Foundations28
  • Object-Oriented SystemVerilog for Verification30
    • Classes, Objects & Handles
    • Inheritance and Polymorphism
    • Static, Encapsulation & Parameterized Classes
    • Virtual Interfaces: the Class-to-Pins Bridge
    • Events, Semaphores, Mailboxes and Processes
    • OOP Patterns for Testbenches
      • The Transaction Base Class
      • The Factory Pattern from Scratch
      • Singletons and Config Objects
      • Callbacks and Hooks
      • Building a Component Hierarchy by Hand
  • Constraint Randomization — Interview-Critical Deep Dive39
  • SystemVerilog Assertions — Interview-Critical Deep Dive41
  • Functional Coverage in SystemVerilog25
  • Testbench Architecture in SystemVerilog24
  • Advanced Language & Integration26
  • Senior & Interview Preparation23
  • Principal-Level Verification34
Loading lesson

←OOP Patterns for Testbenches
  1. Topics
  2. SystemVerilog
  3. Object-Oriented SystemVerilog for Verification
  4. OOP Patterns for Testbenches
  5. Singletons and Config Objects
On this page

    Part 2 of 9, lesson 28 of 30Part 2 of 9/OOP for Verification/Lesson 28 of 30Intermediate

    Singletons and Config Objects

    One shared config object with a protected constructor, plusargs parsing and freeze before build.

    Two components read different values for the same setting, because each built its own copy of the config. A singleton makes there be exactly one.

    After this lesson you can: write a singleton with a protected constructor, fill it from plusargs once, and freeze it before the bench is built.

    Optional lesson. Read it when a bench needs shared settings, or when a UVM config_db question comes up. The first pass through this part works without it.

    Overuse warning. A singleton is a global in disguise. Anything can read or change it from anywhere, tests start to depend on hidden state, and two environments in one simulation cannot hold different settings. Use it only for truly read-only, one-per-simulation data such as parsed plusargs, and pass the config handle explicitly into components wherever you can.

    One instance by construction

    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.

    Parse once, freeze before build

    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 freeze() before building anything

    Build

    Components receive knob values from the config

    Run

    Components read the config and never write it

    Report

    The log header prints convert2string() with the exact knobs

    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.

    Global access has a cost

    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.

    Components writing the config mid-run create shared mutable state. Freeze before build, then treat the object as read-only for the rest of the run.

    Likely questions: “write a singleton in SystemVerilog”, “why protected and not local”, and “how UVM distributes config without a flat global”.

    • Sketch: protected new(), static handle, lazy static get().

    • Rationale: protected admits a derived project config.

    • UVM map: uvm_config_db keeps the idea with hierarchical scoping.

    Check yourself Why is the constructor protected rather than local?

    local blocks subclasses too. protected still prevents outside construction while allowing a derived project config.

    Check yourself A component writes the config mid-run and failures turn irreproducible. Which rule broke?

    Freeze before build. After the test fills the config, components only read it.

    Testbench

    The singleton machinery plus a testbench that proves shared identity, plusargs defaults and the freeze flag.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    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
    
    Terminal
    PASS: one shared config, plusargs defaults hold, freeze is visible
    • A protected new() plus a static get() gives one shared object.

    • Parse plusargs once, then freeze before build.

    • After the freeze, components only read.

    Rule of thumb: one config, filled once, read everywhere.

    How this was checked

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

    Related topics

    • Next: Callbacks and Hooks

    Previous

    61 / 275

    Next: Callbacks and Hooks

    Up next

    Up nextCallbacks and Hooks

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • The Transaction Base Class
    • Building a Component Hierarchy by Hand