In short: Registering a class with `uvm_component_utils or `uvm_object_utils hands its construction to the UVM factory, so that building every object through create() instead of new() lets you swap in a derived type later with a one-line override and zero edits to the testbench.
Why the factory exists at all
A SystemVerilog constructor is final: once you write my_driver d = new("d", this); the simulator binds that exact type at elaboration. If a bug reproduces only when the driver injects a specific protocol error, you would normally have to edit the environment, recompile the whole 2-million-gate testbench, and relink against Synopsys VCS or Cadence Xcelium — minutes to hours of turnaround for a one-class change. The UVM factory breaks that rigidity. It is a global singleton (uvm_factory) that maps a type or string name to a construction request, so the decision of which class to instantiate is deferred from compile time to run time.
The factory is a classic design pattern (the "factory method") adapted to a language with no run-time reflection. The `uvm_*_utils macros generate the boilerplate that makes a type discoverable by the factory — without them, the factory simply does not know your class exists.
Registration: the two macros
Every UVM class you want the factory to build must register itself. The rule is mechanical:
`uvm_component_utils(classname)— for anything derived fromuvm_component(drivers, monitors, agents, environments, tests). Components are quasi-static: they are built once during the build phase and persist for the whole simulation.`uvm_object_utils(classname)— for anything derived fromuvm_object(sequence items / transactions, sequences, configuration objects). Objects are transient data that flow through the testbench.
Placed just inside the class body, each macro expands into a static type proxy, a get_type() accessor, a create() helper, and automatic field registration when you pair it with `uvm_field_* macros. Crucially, the proxy's static initializer runs at elaboration and registers the type name string with the factory singleton. That string — by convention the class name — is the key you later use for string-based overrides.
create() consults that map at run time.create() versus new() — the one rule that matters
This is the single most common interview question on the topic, and the single most common rookie mistake. Both allocate memory and return a handle, but they differ fundamentally:
new() (direct) | create() (factory) | |
|---|---|---|
| Type decided | Compile / elaboration time, hard-bound | Run time, via factory lookup |
| Overridable? | No | Yes — type or instance override |
| Typical use | Scalar members, non-UVM helpers | Every component and transaction |
| Signature (component) | new(name, parent) | type_id::create(name, parent) |
In practice you write, inside a parent component's build phase:
drv = my_driver::type_id::create("drv", this);for a component, andreq = my_item::type_id::create("req");for a transaction (objects take no parent).
If a class is built with new(), the factory never sees the request and every override you set is silently ignored — the testbench runs, nothing errors, and you spend an afternoon wondering why your error-injection driver never activates. Always build UVM objects with create().
A useful mental check: if the handle you are allocating is itself a registered UVM class, use create(). If it is an int, a string, a mailbox, or a virtual interface wrapper, plain new() is correct.
new() bypasses the factory, so overrides never apply; create() goes through it.Enabling overrides
With registration and create() in place, overrides become trivial. There are two axes:
- Type override — replace every instance of a base type:
set_type_override_by_type(my_driver::get_type(), err_driver::get_type());Now everymy_driver::type_id::createanywhere in the hierarchy returns anerr_driver. - Instance override — replace only one path:
set_inst_override_by_type("env.agent0.drv", my_driver::get_type(), err_driver::get_type());Agent 0 gets the error driver; agent 1 keeps the baseline. Instance overrides win over type overrides when both match.
The override must execute before the target's build phase — place it in the test's build_phase before super.build_phase(), or pass it on the command line with +uvm_set_type_override=my_driver,err_driver, which VCS, Xcelium and Siemens Questa all honour. That command-line hook is why factory-based testbenches let a regression farm flip behaviour across thousands of jobs without a single recompile — the economic payoff of the whole mechanism.
create() call; the factory routes it to err_driver once an override is registered.Parameterised classes: the gotcha
Parameterised components need the `uvm_component_param_utils(classname#(P)) variant — the non-param macro will not compile. Because each parameter specialisation is a distinct type, each needs its own registration, and overrides must reference the exact specialisation. Forgetting this produces the infamous "type not registered with factory" run-time warning while everything still elaborates cleanly — a trap worth memorising for both regressions and interviews. Our deep dive on factory overrides and the companion config_db vs factory article walk through the parameterised cases end to end.
Want to build a full UVM environment and watch overrides flip behaviour live in Questa and VCS? The CourseTron Verification track takes you from uvm_object to a self-checking, factory-driven testbench with graded labs.
Teams that need a production methodology audit rather than training can engage Avecas design-services for testbench architecture reviews, while structured classroom cohorts are also offered through ChipXpert training.
Frequently asked questions
Do I still call super.new() in a factory-registered class?
Yes. Registration does not replace the constructor; the factory internally calls your constructor. You must still define function new(string name, uvm_component parent); super.new(name, parent); endfunction so the base-class chain initialises correctly.
What happens if I register a class but still build it with new()?
It compiles and runs, but that instance bypasses the factory entirely, so no type or instance override will ever affect it. This silent failure is the most common reason an override "does nothing."
Can I override a sequence as well as a component?
Yes — sequences derive from uvm_object, so once registered with `uvm_object_utils and created with type_id::create, they accept the same type and instance overrides as components.
Is the factory name always the class name?
By default yes, because the macro uses `"classname`". You can register under a custom string with the lower-level uvm_factory API, but keeping name == type is the convention that keeps command-line overrides readable.
References & Further Reading
- Accellera — Universal Verification Methodology (UVM) standard & reference
- IEEE 1800.2 — Standard for UVM Language Reference Manual
- Siemens Verification Academy — UVM cookbook and factory sessions
- Wikipedia — Universal Verification Methodology
- Synopsys VCS — functional verification simulator documentation