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.
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.
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.
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.
// new() skips factory entirely // No override can affect this m_driver = new("drv", this);
// Factory creates — overrides apply m_driver = my_driver::type_id ::create("drv", this);
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:
| Macro | Use for | Notes |
|---|---|---|
`uvm_component_utils(T) | Non-parameterised uvm_component subclasses | Most common — drivers, monitors, agents, envs, tests, scoreboards |
`uvm_component_param_utils(T) | Parameterised uvm_component subclasses | e.g. my_driver #(32) |
`uvm_object_utils(T) | Non-parameterised uvm_object subclasses | Sequence items, config objects, simple sequences |
`uvm_object_param_utils(T) | Parameterised uvm_object subclasses | e.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 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
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.
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
| Property | Type override | Instance override |
|---|---|---|
| Scope | All instances of the original type | Only the component at the specified path |
| Syntax | Orig::type_id::set_type_override(Sub::get_type()) | Orig::type_id::set_inst_override(Sub::get_type(), "path") |
| Path argument | Not used | Full dot-path: "uvm_test_top.env.agent.drv" — wildcards allowed |
| Typical use | Replace a standard component across the whole environment for a specific test | Inject a specialised component at one specific position while leaving others untouched |
| Priority | Lower — instance overrides take precedence over type overrides | Higher — wins over type override for the specified path |
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
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.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
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.*
+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.| Item | Key fact |
|---|---|
| Factory purpose | Global 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 syntax | Type::type_id::create("name", this) |
| Object create syntax | Type::type_id::create("name") — no parent argument |
| Type override syntax | Orig::type_id::set_type_override(Sub::get_type()) |
| Instance override syntax | Orig::type_id::set_inst_override(Sub::get_type(), "path") |
| Override must be set when | In test's build_phase BEFORE creating the env — before any child uses create() |
| Substitute type constraint | Must extend the original type — directly or through inheritance chain |
| Instance override priority | Instance 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 state | uvm_factory::get().print() from end_of_elaboration_phase |
| Command-line override | +uvm_set_type_override=OldType,NewType — no recompile needed |
| Abstract/concrete pattern | Env built against abstract base; test applies override to activate concrete impl |