Skip to content

The Factory

The problem it solves

Say a test needs to inject an error: one specific driver, in one specific test, should occasionally corrupt a field before driving it. The naive fix — editing the driver class to add error-injection logic, gated by some flag — pollutes every test (including the 99% that don't want error injection) with logic that only matters to one test. The UVM factory lets you substitute a derived class in place of the original, from the test level, without touching the original class at all.

Registration

Every class that should be substitutable registers itself:

class my_driver extends uvm_driver #(my_seq_item);
  `uvm_component_utils(my_driver)   // components
  // ...
endclass

class my_seq_item extends uvm_sequence_item;
  `uvm_object_utils(my_seq_item)    // objects (sequence items, sequences)
  // ...
endclass

These macros register the class with a global factory registry under its type name (as a string) — this string-keyed registry is exactly what makes override-by-name possible later.

create() instead of new()

Anywhere a component or object might need to be substituted, it must be constructed via the factory, not new() directly:

// in build_phase:
drv = my_driver::type_id::create("drv", this);   // factory-aware — CAN be overridden
// NOT:
drv = new("drv", this);                           // bypasses the factory entirely — can never be overridden

This is the single most common reason a factory override "doesn't work": somewhere in the hierarchy, a component was constructed with plain new() instead of ::type_id::create(), so the factory never got a chance to substitute anything for it.

Type overrides

class error_driver extends my_driver;
  `uvm_component_utils(error_driver)
  // ... overridden behavior, e.g. occasional field corruption ...
endclass
// in the test's build_phase, BEFORE the env (and its driver) is built:
my_driver::type_id::set_type_override(error_driver::get_type());
super.build_phase(phase);   // env/agent/driver get built here — now gets error_driver instead

Every my_driver::type_id::create(...) call anywhere in the hierarchy now instantiates error_driver instead — with zero changes to the env, agent, or driver class itself. The substitution is entirely test-side.

Instance overrides: substituting just one specific instance

A type override is global — every instance of my_driver anywhere gets replaced. If only one driver (say, in a multi-agent env, only agent #2's driver) needs the override, use an instance override with a hierarchical path:

my_driver::type_id::set_inst_override(error_driver::get_type(),
                                        "env.agent2.drv");

Config-driven overrides without recompiling

The same effect can be triggered from the command line, without editing any test code:

+uvm_set_type_override=my_driver,error_driver

This is what makes the factory genuinely powerful in a regression environment: a nightly regression can inject error scenarios into arbitrary tests via command-line plusargs, without a single line of test code being written for that specific run.

Why this depends on virtual methods

Everything here works because of ordinary SystemVerilog polymorphism (see OOP & Classes) — a factory override just means "when asked to create a my_driver, actually construct an error_driver object and return a handle typed as my_driver." Every existing call site that only ever knew about the my_driver type keeps compiling and running unchanged, because error_driver extends my_driver and virtual dispatch handles the rest. The factory isn't magic — it's a registry plus a disciplined use of the same OOP substitution principle covered earlier.

Overrides must be set before the target is built

set_type_override/set_inst_override only affects create() calls that happen after the override is registered. Setting an override in build_phase works because UVM's function-phases run top-down (test's build_phase runs before env's, which runs before agent's, which runs before driver's) — but setting an override any later than that (e.g. in connect_phase) is too late; the components are already built.