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

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • Start Here6
  • 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 Dive36
  • SystemVerilog Assertions — Interview-Critical Deep Dive40
  • Functional Coverage in SystemVerilog22
  • Testbench Architecture in SystemVerilog23
  • Advanced Language & Integration25
  • Senior & Interview Preparation21
  • Principal-Level Verification28
Loading lesson

‹OOP Patterns for Testbenches
  1. Topics
  2. SystemVerilog
  3. Object-Oriented SystemVerilog for Verification
  4. OOP Patterns for Testbenches
  5. The Factory Pattern from Scratch

Part 2 · OOP for Verification · Intermediate

The Factory Pattern from Scratch

A creator registry with explicit registration and test-time overrides, the UVM factory by hand.

On this page

    Why this matters

    The error test ran clean. The override that should have swapped in an error transaction was installed after the environment was built, so nothing used it.

    After this lesson you can: build objects by name through a registry, override a type for a test, and show the override took effect.

    Ask by name, not by type

    Every t = new() freezes the concrete type at compile time. The factory replaces that call with a registry lookup, so the test picks the type without touching the generator.

    The generator-injection sketch in the polymorphism lesson motivates this pattern. The registry and override below are taught only here.

    Requested name

    Default build

    After the override

    bus_txn

    A fresh bus_txn

    A fresh err_txn

    err_txn

    A fresh err_txn

    A fresh err_txn

    Overrides redirect future creates only. Objects built earlier keep their old type, which is why the override lands before anything is built.

    Registry, creators and explicit registration

    Three pieces cooperate. A parameterized creator builds one instance per type, a static registry maps names to creators, and an override map substitutes names before lookup.

    Registration is an explicit call before the first create. Static-initializer self-registration looks convenient, but cross-class static init order is undefined, so entries can silently vanish.

    design.sv
    1
    2
    3
    4
    5
    // snippet
    txn_factory::register("bus_txn", bus_creator);
    txn_factory::register("err_txn", err_creator);
    txn_factory::set_override("bus_txn", "err_txn");
    bus_txn t = txn_factory::create("bus_txn");   // actually an err_txn

    Overrides redirect future creates

    The error test installs one override before building anything. Every later request for the base name receives the error subclass, with virtual methods dispatching to it.

    Direct new() calls escape this machinery. Any construction outside the factory keeps the old type under every override.

    Hand-built piece

    UVM equivalent

    registry associative array

    uvm_factory type table

    Parameterized creator class

    uvm_object_registry plus the uvm_*_utils macros

    create() by name

    factory.create_object_by_name() / create_component_by_name()

    set_override() by name

    set_type_override_by_name()

    UVM's preferred form is by type, through the type_id proxy: type_id::create() and set_type_override_by_type(). The type handle gives compile-time checking that a string name cannot.

    The spine matches. UVM adds instance-path overrides, reporting and automation around it.

    Common mistakes

    Common pitfall

    Setting the override after the environment is built changes nothing. The old-type objects already exist, so the error test silently runs clean.

    Interview

    Likely questions: “implement a factory without UVM”, “why must the override land before build”, and “why does substitution work through base handles”.

    • Trio: parameterized creator, static registry, override map.

    • Timing: overrides redirect future creates, never existing objects.

    • Dispatch: the factory returns derived objects; virtual methods do the rest.

    Check yourself The override is installed after the environment is built. What breaks?

    Nothing changes for existing objects. Overrides redirect future create() calls only, so the bench still runs the old type.

    Check yourself One generator still calls new() directly. What escapes?

    That construction bypasses the registry. Overrides never affect it, so the error test silently runs clean transactions there.

    Check yourself How does the bench prove the override worked without inspecting the factory?

    Virtual dispatch. kind() and convert2string() on the created handle run the derived methods, which only the substituted type provides.

    Testbench

    Predict first The bench creates t1 as a bus_txn, then installs an override from bus_txn to err_txn, then creates t2. What do t1.kind() and t2.kind() return?

    "bus_txn" and "err_txn". An override changes what later create calls build; t1 already exists and keeps its type.

    Explicit registration, one override, and a testbench that proves substitution through virtual dispatch.

    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
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    class bus_txn;
      bit [15:0] addr;
      bit [31:0] data;
    
      virtual function string kind();
        return "bus_txn";
      endfunction
    
      virtual function string convert2string();
        return $sformatf("bus_txn addr=%04h data=%08h", addr, data);
      endfunction
    endclass
    
    class err_txn extends bus_txn;
      bit corrupt;
    
      virtual function string kind();
        return "err_txn";
      endfunction
    
      virtual function string convert2string();
        if (corrupt != 0) return {super.convert2string(), " [CORRUPT]"};
        return super.convert2string();
      endfunction
    endclass
    
    virtual class creator_base;
      pure virtual function bus_txn build();
    endclass
    
    class creator #(type T = bus_txn) extends creator_base;
      virtual function bus_txn build();
        T obj;
        obj = new();
        return obj;
      endfunction
    endclass
    
    class txn_factory;
      static creator_base registry[string];
      static string override_map[string];
    
      static function void register(string name, creator_base c);
        registry[name] = c;
      endfunction
    
      static function void set_override(string from, string to);
        override_map[from] = to;
      endfunction
    
      static function bus_txn create(string name);
        string actual;
        actual = name;
        if (override_map.exists(name) != 0) actual = override_map[name];
        if (registry.exists(actual) == 0) begin
          $fatal(1, "factory: no creator registered under that name");
          return null;
        end
        return registry[actual].build();
      endfunction
    endclass
    
    Terminal
    PASS: registry creates by name and the override substitutes err_txn

    Remember

    • Create through the factory, never with new(), anywhere an override may matter.

    • An override changes later creates, not objects that already exist.

    • Install overrides before build.

    Rule of thumb: override first, build second.

    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: Singletons and Config Objects

    Previous

    61 / 259

    Next: Singletons and Config Objects

    Up next

    Up nextSingletons and Config Objects

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Callbacks and Hooks
    • Building a Component Hierarchy by Hand