The UVM factory is the mechanism that makes testbench reuse possible without editing source code. It intercepts every object and component creation request, checks for registered overrides, and returns the right type — all transparently. Tests can swap entire drivers, sequences, or scoreboards without touching the original agent code.
Without the factory, swapping a component requires editing the environment source code — changing new my_driver() to new err_driver() everywhere the original appears. This is fragile and means you cannot run both a clean test and an error-injection test from the same environment without forking the code.
With the factory, the environment always creates my_driver via my_driver::type_id::create(). A test registers an override before build_phase and the factory silently substitutes err_driver at creation time. The environment never needs to know:
// Without factory — must edit environment source driver = new err_driver("driver", this); // ← fragile, need to edit agent // With factory — test registers override, environment unchanged // In the environment (never changes): driver = my_driver::type_id::create("driver", this); // In error-injection test (override registered before build): my_driver::type_id::set_type_override(err_driver::get_type()); // Now every create("driver",...) of my_driver returns an err_driver
Every class that participates in the factory must register itself using one of the following macros. These macros generate the boilerplate type_id class and static registration hooks:
| Macro | Use for | Notes |
|---|---|---|
`uvm_component_utils(T) | Components (driver, monitor, agent, env, scoreboard) | Includes parent arg in create() |
`uvm_object_utils(T) | Objects (seq_item, sequence, transaction) | No parent arg needed |
`uvm_component_utils_begin(T) ... `uvm_component_utils_end | Component with field automation | Add `uvm_field_* macros inside |
`uvm_object_utils_begin(T) ... `uvm_object_utils_end | Object with field automation | Enables copy/compare/print/pack |
`uvm_component_param_utils(T#(P)) | Parameterised components | One registration per parameter set |
// Component registration class my_driver extends uvm_driver #(my_trans); `uvm_component_utils(my_driver) // registers with factory function new(string name, uvm_component parent); super.new(name, parent); endfunction // ... tasks and functions endclass // Object registration with field automation class my_trans extends uvm_sequence_item; rand logic [31:0] addr; rand logic [31:0] data; rand logic write; `uvm_object_utils_begin(my_trans) `uvm_field_int(addr, UVM_ALL_ON) `uvm_field_int(data, UVM_ALL_ON) `uvm_field_int(write, UVM_ALL_ON) `uvm_object_utils_end function new(string name = "my_trans"); super.new(name); endfunction endclass
When you call my_driver::type_id::create("driver", this), the factory:
my_driver in its type registry by string name and/or type handlemy_driver at the requested hierarchical path$cast for objects)// CORRECT — always use type_id::create() my_driver drv = my_driver::type_id::create("drv", this); // WRONG — bypasses factory, overrides never apply my_driver drv = new("drv", this); // ← never do this in UVM // For objects (seq items, sequences) — no parent arg my_trans t = my_trans::type_id::create("t"); // For sequences started inside a task my_seq seq = my_seq::type_id::create("seq"); seq.start(sequencer);
new() is acceptable is inside the constructor itself (super.new(name, parent)). All other instantiation must go through type_id::create(). Even in simple throw-away code, the habit of using new() directly will eventually cause a hard-to-debug override that silently fails.A type override replaces every instance of a base class anywhere in the testbench hierarchy with the override class. The override must be registered before build_phase runs — typically in the test's build_phase before calling super.build_phase(phase):
// Method 1: via the base class type_id (recommended) my_driver::type_id::set_type_override(err_driver::get_type()); // Method 2: via factory singleton (equivalent) uvm_factory::get().set_type_override_by_type( my_driver::get_type(), err_driver::get_type() ); // Method 3: by string name (avoid — error-prone typos) factory.set_type_override_by_name("my_driver", "err_driver"); // In an error-injection test class err_test extends base_test; `uvm_component_utils(err_test) function void build_phase(uvm_phase phase); // Register override BEFORE super.build_phase() triggers environment build my_driver::type_id::set_type_override(err_driver::get_type()); super.build_phase(phase); // now builds err_driver, not my_driver endfunction endclass
An instance override applies only to a component at a specific hierarchical path. This is more targeted than a type override and takes precedence over it:
// Replace only the driver at this specific path my_driver::type_id::set_inst_override( err_driver::get_type(), "uvm_test_top.env.agent.driver" // exact hierarchical path ); // Wildcards are supported — replace all drivers under any agent my_driver::type_id::set_inst_override( err_driver::get_type(), "uvm_test_top.env.*.driver" ); // Via factory singleton uvm_factory::get().set_inst_override_by_type( my_driver::get_type(), err_driver::get_type(), "uvm_test_top.env.agent.driver" );
| Override type | Scope | Precedence | Use when |
|---|---|---|---|
| Type override | Entire testbench | Lower | Globally replacing all instances of a class |
| Instance override | Specific path only | Higher — wins over type override | Replacing one specific component in a multi-agent env |
When overrides don't seem to take effect, use the factory's built-in debug tools:
// Print all registered factory types and active overrides uvm_factory::get().print(1); // 1 = print all; 0 = overrides only // Print the full component topology after build_phase uvm_top.print_topology(); // Plusarg: shows each override application at creation time // Add to simulator command line: // +UVM_FACTORY_PRINT_OVERRIDE_INFO // Check what type the factory will create for a given context begin uvm_object_wrapper w; w = uvm_factory::get().find_override_by_type( my_driver::get_type(), "uvm_test_top.env.agent.driver"); `uvm_info("FAC", $sformatf("Factory will create: %s", w.get_type_name()), UVM_NONE) end
super.build_phase(phase) — by that time all components are already built. Always call overrides as the very first statements in the test's build_phase, before the super call.Factory overrides enable a library of reuse patterns without touching source code:
The factory handles both UVM components and UVM objects, but they differ in how they are created and where they live:
| Feature | uvm_component | uvm_object |
|---|---|---|
| Base class | uvm_component | uvm_object / uvm_sequence_item |
| Lifecycle | Exists for entire simulation | Created and garbage-collected per transaction |
| Parent | Has hierarchical parent | No parent (standalone) |
| Registration | `uvm_component_utils | `uvm_object_utils |
| create() signature | T::type_id::create("name", parent) | T::type_id::create("name") |
| Phase execution | Participates in UVM phases | No phases |
| Examples | driver, monitor, agent, env, test | seq_item, sequence, config_object |
A full example showing base driver, error-injection derived driver, and a test that swaps them via factory:
// --- Base driver (in the agent package) --- class apb_driver extends uvm_driver #(apb_trans); `uvm_component_utils(apb_driver) virtual apb_if vif; function new(string name, uvm_component parent); super.new(name,parent); endfunction function void build_phase(uvm_phase phase); super.build_phase(phase); if(!uvm_config_db#(virtual apb_if)::get(this,"","vif",vif)) `uvm_fatal("VIF","No APB vif") endfunction task run_phase(uvm_phase phase); apb_trans req; forever begin seq_item_port.get_next_item(req); drive_normal(req); // normal APB transfer seq_item_port.item_done(); end endtask virtual task drive_normal(apb_trans req); @(vif.master_cb); vif.master_cb.paddr <= req.addr; vif.master_cb.pwdata <= req.data; vif.master_cb.pwrite <= req.write; vif.master_cb.psel <= 1; @(vif.master_cb); vif.master_cb.penable <= 1; @(vif.master_cb iff vif.master_cb.pready); vif.master_cb.psel <= 0; vif.master_cb.penable <= 0; endtask endclass // --- Error-injection driver (in test package) --- class apb_err_driver extends apb_driver; `uvm_component_utils(apb_err_driver) function new(string name, uvm_component parent); super.new(name,parent); endfunction // Override drive_normal to inject a SLVERR on every other transaction int txn_count = 0; virtual task drive_normal(apb_trans req); txn_count++; if (txn_count % 2 == 0) begin `uvm_info("ERR_DRV", "Injecting SLVERR", UVM_MEDIUM) @(vif.master_cb); vif.master_cb.psel <= 1; vif.master_cb.paddr <= req.addr ^ 32'hDEAD; // corrupted addr vif.master_cb.penable <= 1; @(vif.master_cb); vif.master_cb.psel <= 0; vif.master_cb.penable <= 0; end else super.drive_normal(req); // normal path for odd transactions endtask endclass // --- Error-injection test --- class err_inject_test extends base_test; `uvm_component_utils(err_inject_test) function new(string name, uvm_component parent); super.new(name,parent); endfunction function void build_phase(uvm_phase phase); // Override BEFORE super.build_phase — factory checks at creation time apb_driver::type_id::set_type_override(apb_err_driver::get_type()); `uvm_info("TEST", "Factory override: apb_driver → apb_err_driver", UVM_NONE) super.build_phase(phase); // environment builds — err_driver is created endfunction endclass