UVM-04: The UVM Factory — VLSI Trainers
VLSI Trainers UVM Series · UVM-04
UVM Series · UVM-04

The UVM Factory

How the UVM factory works, why create() must always be used instead of new(), the registration macros for components and objects, type overrides and instance overrides, how polymorphism makes overrides work, and the complete abstract/concrete component pattern.

📋 What the Factory Does

The UVM factory is a global registry that maps class type names to class types. When you ask the factory to create a component by name, it looks up the registered name, constructs the corresponding object, and returns the handle. The critical capability it adds beyond plain new() is override — the factory can be told to substitute one class for another at runtime without any changes to the testbench source code.

Factory — Without Override vs With Override Without Factory Override create(“drv”, this) Factory lookup my_driver → my_driver my_driver created ✓ With Factory Override (in test) create(“drv”, this) Factory lookup my_driver → fast_driver ! fast_driver created ✓Source code unchanged — create() call is identical in both cases Override set in test build_phase — no other file touched vlsitrainers.com
Figure 1 — Factory override mechanism. Without an override, create() returns the exact type requested. With a type override set in the test’s build_phase, the same create() call returns an instance of the substitute type. The source code of the environment and agent is completely unchanged — only the test registers the override.

This is the core value of the factory. It lets you write a generic environment once and specialise it per test by substituting components at the test level. A coverage-only test swaps the driver for a passive BFM. A power test swaps the normal sequence for a low-power sequence variant. An error injection test swaps the standard monitor for one that also injects errors. All without touching the environment code.

📋 Why Never Call new() Directly

Calling new() directly on a UVM class bypasses the factory entirely. The factory never sees the call, so no override can be applied. If you mix new() with create() in the same testbench, overrides will silently not work for the components created with new() — one of the hardest bugs to diagnose in UVM.

✗ Wrong — bypasses factory

// new() skips factory entirely
// No override can affect this
m_driver = new("drv", this);

✓ Correct — goes through factory

// Factory creates — overrides apply
m_driver = my_driver::type_id
             ::create("drv", this);
Always use type_id::create() — never new(). This applies to every uvm_component and uvm_object you create in your testbench. The only exception is the very first object created at the top level — the test itself — which is handled internally by run_test(). Everything else must go through the factory.

📋 Registration Macros

Before the factory can create a class, the class must be registered with the factory. Registration is done with a macro placed immediately after the class declaration opening. There are different macros for components and objects, and parameterised variants of each:

MacroUse forNotes
`uvm_component_utils(T)Non-parameterised uvm_component subclassesMost common — drivers, monitors, agents, envs, tests, scoreboards
`uvm_component_param_utils(T)Parameterised uvm_component subclassese.g. my_driver #(32)
`uvm_object_utils(T)Non-parameterised uvm_object subclassesSequence items, config objects, simple sequences
`uvm_object_param_utils(T)Parameterised uvm_object subclassese.g. my_seq_item #(32)

The registration macros do two things internally: they declare a static type_id member that holds the factory wrapper, and they implement the get_type_name() method that returns the class name as a string. Both are needed for factory lookup and override to work.

// ── Component registration ───────────────────────────────
class my_driver extends uvm_driver#(my_seq_item);
  `uvm_component_utils(my_driver)   // MUST be first inside class body

  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction
  // ... rest of class
endclass

// ── Object registration ──────────────────────────────────
class my_seq_item extends uvm_sequence_item;
  `uvm_object_utils(my_seq_item)

  function new(string name = "my_seq_item");
    super.new(name);               // no parent argument for objects
  endfunction
endclass

// ── Parameterised component registration ─────────────────
class bus_driver #(int BUS_W = 32) extends uvm_driver;
  `uvm_component_param_utils(bus_driver #(BUS_W))

  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction
endclass

📋 The create() Pattern

The factory create() call has different signatures for components and objects. Both use the type_id::create() pattern on the class being requested — not the class you actually want to get (the factory handles that substitution internally).

// ── Creating a component ─────────────────────────────────
// Syntax: ClassName::type_id::create("instance_name", parent)
m_driver = my_driver::type_id::create("m_drv", this);
m_agent  = my_agent::type_id::create("agent", this);

// ── Creating an object (no parent) ───────────────────────
// Syntax: ClassName::type_id::create("name")
seq_item = my_seq_item::type_id::create("item");
cfg      = my_cfg::type_id::create("cfg");

// ── Alternative: uvm_object::create() utility ────────────
// Less common but valid for cloning / generic creation
my_seq_item clone_item;
$cast(clone_item, item.clone());  // clone goes through factory
create() Call Flow — What Happens Inside the Factory 1. create() called my_driver::type_id ::create(“drv”, this) 2. Factory lookup Is there an override for my_driver? 3. Apply override Yes: fast_driver No: my_driver itself 4. Construct + return Calls new() internally Returns correct handleThe caller receives the correct type via a base-class handle. Polymorphism allows the substitute type to be used transparently. The factory calls new() internally — which is why you must never call new() yourself. vlsitrainers.com
Figure 2 — create() call flow inside the factory. The factory checks its override table, applies any registered substitution, calls new() on the resulting type, and returns the handle. The factory itself calls new() internally — this is why you must never call new() directly in your code.

📋 Type Overrides

A type override tells the factory: “Whenever anyone asks to create type A, give them type B instead.” It applies to every instance of A created anywhere in the testbench hierarchy.

Type overrides must be applied before the component tree is built — which means in the test’s build_phase before the env is created.

// ── Type override syntax ─────────────────────────────────
// OriginalType::type_id::set_type_override(SubstituteType::get_type());

function void build_phase(uvm_phase phase);
  // Set override BEFORE creating the env (before any child is built)
  my_driver::type_id::set_type_override(fast_driver::get_type());

  // Now create the env — every my_driver creation returns fast_driver
  m_env = my_env::type_id::create("m_env", this);
endfunction

// ── Optional second argument: replace=1 forces override of existing override
my_driver::type_id::set_type_override(fast_driver::get_type(), 1);

The substitute type must be a subclass of the original type. The factory relies on SystemVerilog polymorphism — the returned handle is typed as the original class, so the substitute must be compatible via inheritance. If fast_driver does not extend my_driver (directly or indirectly), the override will fail at runtime with a type cast error.

📋 Instance Overrides

An instance override targets a specific position in the component hierarchy. Instead of replacing all instances of a type, it replaces only the instance at a given path. This is used when most agents should use the standard driver, but one specific agent needs a specialised driver.

// ── Instance override syntax ─────────────────────────────
// OriginalType::type_id::set_inst_override(
//   SubstituteType::get_type(), "path_string");

function void build_phase(uvm_phase phase);
  // Only override the driver in apb_agent — not in spi_agent
  my_driver::type_id::set_inst_override(
    error_driver::get_type(),
    "uvm_test_top.env.apb_agent.m_drv");

  m_env = my_env::type_id::create("m_env", this);
endfunction
Type Override vs Instance Override Type Override — ALL instances replaced env fast_driver ★ fast_driver ★ fast_driver ★Every my_driver → fast_driver. All three agents affected. Instance Override — ONE instance replaced env error_driver ★ my_driver my_driverapb_agent.m_drv spi_agent.m_drv i2c_agent.m_drvOnly apb_agent’s driver is replaced. Others unchanged. vlsitrainers.com
Figure 3 — Type override vs instance override. A type override replaces every instance of the original type across the entire hierarchy. An instance override replaces only the component at the specified hierarchy path, leaving all other instances of that type unchanged.
PropertyType overrideInstance override
ScopeAll instances of the original typeOnly the component at the specified path
SyntaxOrig::type_id::set_type_override(Sub::get_type())Orig::type_id::set_inst_override(Sub::get_type(), "path")
Path argumentNot usedFull dot-path: "uvm_test_top.env.agent.drv" — wildcards allowed
Typical useReplace a standard component across the whole environment for a specific testInject a specialised component at one specific position while leaving others untouched
PriorityLower — instance overrides take precedence over type overridesHigher — wins over type override for the specified path

📋 How Polymorphism Makes It Work

Factory overrides rely entirely on SystemVerilog polymorphism. The handle type in the parent component's member declaration is the original (base) type. The factory returns an instance of the substitute (derived) type. Since the derived type extends the base type, this assignment is valid and the virtual method dispatch takes care of calling the correct implementation.

// In my_env: handle declared as base type
my_driver m_drv;  // base type handle

// In build_phase: factory creates a fast_driver and assigns it
// Works because fast_driver extends my_driver
m_drv = my_driver::type_id::create("m_drv", this);
// m_drv now points to a fast_driver object
// All virtual method calls dispatch to fast_driver implementation

// ── The override class MUST extend the original ───────────
class fast_driver extends my_driver;  // ← must be a child
  `uvm_component_utils(fast_driver)

  // Override run_phase with faster implementation
  task run_phase(uvm_phase phase);
    // ... optimised drive logic
  endtask
endclass
The substitute type must extend the original type — directly or through a chain of inheritance. The factory uses a dynamic cast internally to verify compatibility. If fast_driver does not extend my_driver, the factory emits a fatal error at runtime. This constraint also means you cannot use a factory override to swap between completely unrelated component types.

📋 Abstract / Concrete Pattern

One of the most powerful factory patterns is the abstract/concrete component pattern. The environment is written against an abstract base class (which does nothing). Tests override the abstract base with a concrete implementation. This allows an environment to be written before any concrete implementation exists.

// ── Abstract base — does nothing, overridden by tests ────
class base_driver extends uvm_driver#(my_seq_item);
  `uvm_component_utils(base_driver)
  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction
  task run_phase(uvm_phase phase);
    phase.raise_objection(this);
    `uvm_info("DRV", "Abstract driver — no stimulus", UVM_LOW)
    phase.drop_objection(this);
  endtask
endclass

// ── Concrete APB driver ──────────────────────────────────
class apb_driver extends base_driver;
  `uvm_component_utils(apb_driver)
  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction
  task run_phase(uvm_phase phase);
    // ... full APB drive implementation
  endtask
endclass

// ── Test: activate the concrete driver ───────────────────
class apb_test extends uvm_test;
  `uvm_component_utils(apb_test)
  function void build_phase(uvm_phase phase);
    // Replace abstract base with concrete APB driver
    base_driver::type_id::set_type_override(apb_driver::get_type());
    m_env = my_env::type_id::create("m_env", this);
  endfunction
endclass
The abstract/concrete pattern separates environment design from implementation. The environment team writes against the abstract base class immediately. Protocol-specific teams provide concrete implementations. Tests wire the correct implementation via factory override. This enables parallel development and clean reuse — the environment never has protocol-specific code in it.

📋 Factory Debug

When factory overrides are not working as expected, the factory provides debug methods to inspect its state:

// ── Print the factory registration table ─────────────────
// Call from end_of_elaboration_phase to see all registered types
function void end_of_elaboration_phase(uvm_phase phase);
  uvm_factory::get().print();           // print all registrations + overrides
  uvm_top.print_topology();             // print full component tree
endfunction

// ── Debug a specific type ─────────────────────────────────
// Shows what type would be created for a given original type
uvm_factory::get().debug_create_by_type(
  my_driver::get_type(), "uvm_test_top.env.agent");

// ── Command-line override (no recompile) ──────────────────
// +uvm_set_type_override=my_driver,fast_driver
// +uvm_set_inst_override=my_driver,fast_driver,uvm_test_top.env.agent.*
Factory overrides can also be set from the command line using +uvm_set_type_override and +uvm_set_inst_override plusargs — no recompile or testbench change required. This is the most powerful use of the factory: switching between DUT configurations purely from the simulator command line.

📋 Quick Reference

ItemKey fact
Factory purposeGlobal registry enabling runtime component substitution without recompile
Never use new()Always use ClassName::type_id::create("name", parent) for all UVM classes
Component registration macro`uvm_component_utils(ClassName) — mandatory, first line in class body
Object registration macro`uvm_object_utils(ClassName) — mandatory, first line in class body
Parameterised registration`uvm_component_param_utils(ClassName #(P))
Component create syntaxType::type_id::create("name", this)
Object create syntaxType::type_id::create("name") — no parent argument
Type override syntaxOrig::type_id::set_type_override(Sub::get_type())
Instance override syntaxOrig::type_id::set_inst_override(Sub::get_type(), "path")
Override must be set whenIn test's build_phase BEFORE creating the env — before any child uses create()
Substitute type constraintMust extend the original type — directly or through inheritance chain
Instance override priorityInstance overrides take precedence over type overrides for the matching path
Wildcards in path"uvm_test_top.env.*.m_drv" matches m_drv in any agent under env
Debug: print factory stateuvm_factory::get().print() from end_of_elaboration_phase
Command-line override+uvm_set_type_override=OldType,NewType — no recompile needed
Abstract/concrete patternEnv built against abstract base; test applies override to activate concrete impl
Scroll to Top