The UVM Factory
Every create() call throughout the FIFO testbench — fifo_driver::type_id::create("driver", this), fifo_monitor::type_id::create("monitor", this), and every other component built via type_id::create() — has looked like a slightly roundabout way of writing new fifo_driver("driver", this). That indirection is the entire point: it's what lets someone reuse the exact same testbench and swap out one class for a specialized variant, without editing or recompiling any of the original code at all.
Registering with the factory
Every uvm_component and uvm_object registers itself with the factory using a utility macro — the same `uvm_component_utils/ `uvm_object_utils pair from Utility & Field Macros, which every class throughout this section has already carried:
class fifo_driver extends uvm_driver #(fifo_txn);
`uvm_component_utils(fifo_driver) // components use this one
// ...
endclass
class fifo_txn extends uvm_sequence_item;
`uvm_object_utils(fifo_txn) // objects use this one
// ...
endclass
These macros register the class's type with a global factory registry (a name-to-type mapping) — without it, the factory has no way to know the class exists at all, and type_id::create() has nothing to look up.
create() vs. new()
new() always constructs exactly the class named. type_id::create() asks the factory, "what type is currently registered for fifo_driver?" — normally fifo_driver itself, but redirectable to something else entirely, as long as it's derived from fifo_driver.
// Always constructs a fifo_driver, no matter what
driver = new("driver", this);
// Asks the factory what to construct — usually fifo_driver,
// but can be redirected without touching this line at all
driver = fifo_driver::type_id::create("driver", this);
Every component built across the previous section used create() for exactly this reason: it costs nothing when no override is active, and it's the only thing that makes overrides possible later.
Overriding: swapping behavior without editing existing code
Say a new test needs a driver that occasionally corrupts write data — without touching fifo_driver itself, or the fifo_agent/fifo_env that build it.
class fifo_driver_error_inject extends fifo_driver;
`uvm_component_utils(fifo_driver_error_inject)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
task drive_transaction(fifo_txn txn);
if (txn.op == fifo_txn::WRITE && $urandom_range(0, 9) == 0)
txn.wr_data = ~txn.wr_data; // corrupt occasionally
super.drive_transaction(txn);
endtask
endclass
From a test, one line redirects every future fifo_driver::type_id::create() call in the tree to build a fifo_driver_error_inject instead:
class error_injection_test extends fifo_base_test;
`uvm_component_utils(error_injection_test)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
// Type override: every fifo_driver becomes a fifo_driver_error_inject
fifo_driver::type_id::set_type_override(fifo_driver_error_inject::get_type());
super.build_phase(phase); // env/agent build here, and get the overridden driver
endfunction
endclass
Neither fifo_driver nor the fifo_agent/fifo_env from the previous section needed a single line changed — the override is entirely test-side, and error_injection_test reuses fifo_base_test's build_phase exactly as The Test & Testbench-Top described extending a base test to add exactly one thing.
Type vs. instance overrides, and their exact signatures
The example above is a type override — it replaces every instance of fifo_driver, everywhere in the tree. An instance override replaces only the instance at one specific hierarchical path, leaving every other instance of the same type untouched — useful when a larger environment has several FIFO agents and only one of them needs the specialized behavior:
// Type override — every fifo_driver in the tree is affected
fifo_driver::type_id::set_type_override_by_type(
fifo_driver::get_type(), fifo_driver_error_inject::get_type());
// Instance override — only the driver at this exact path is affected
fifo_driver::type_id::set_inst_override_by_type(
fifo_driver::get_type(), fifo_driver_error_inject::get_type(),
"uvm_test_top.env.agent.driver");
Both set_type_override/set_inst_override (the simpler form used earlier) and their _by_type/_by_name variants exist — _by_type takes actual class-type handles (get_type()), checked at compile time; _by_name takes plain strings instead, more flexible but with typos that fail silently rather than as a compile error. When both a type override and an instance override could apply to the same component, the instance override wins — it's the more specific match. Every type_id::... call on this page — set_type_override, set_inst_override_by_type, create() itself — is a convenience wrapper: underneath, each one just forwards to the single, global uvm_factory instance's own equivalent method (fifo_driver::type_id::set_type_override(...) and calling set_type_override_by_type(...) directly on the global factory produce an identical result). The type_id-scoped form is what every example above uses because it reads as "override this type," but both forms register the exact same override in the exact same one factory.
An instance override works by matching a full hierarchical path (like "uvm_test_top.env.agent.driver"), and only uvm_component-derived classes occupy a fixed position in that tree. fifo_txn — a uvm_sequence_item, transient data rather than a permanent testbench part — is never built with a path of its own, so it can only be swapped with a type override (fifo_txn::type_id::set_type_override(...)); there's no path for set_inst_override_by_type/set_inst_override_by_name to target. The same limitation applies to any uvm_object-branch class, including sequences.
create() runsAn override only affects create() calls that happen after it's registered — setting it has no retroactive effect on components already built. This is exactly why the override above lives in build_phase, before super.build_phase(phase) hands control down to the environment: the override needs to exist before fifo_env's own build_phase (and everything nested inside it) gets a chance to call create().
factory.print(), callable from anywhere (uvm_default_factory or the global factory handle), dumps every currently-registered override to the log. When a test's behavior doesn't seem to reflect an override that was just added, calling this early in build_phase is usually the fastest way to confirm whether the override actually registered — and against the path expected — rather than guessing.
The payoff of all of this: the same fifo_env gets reused, completely unmodified, across an error-injection test, a protocol-compliance test, a performance test, or anything else — each test supplying only the override it specifically needs.
What's next
Overrides swap which class gets built. The next page covers how already-built components get configuration data from outside — like the virtual interface every driver and monitor in this testbench has been retrieving since The Driver — without hardcoded paths.