Verification

UVM factory registration

By CourseTron Team ·  7 min read ·  Updated Oct 2026 💬 𝕏 in f
Table of Contents
    ? Print / Save as PDF

    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 from uvm_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 from uvm_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.

    Registration, then create() my_driver `uvm_component_utils UVM Factory name to proxy map (singleton) instance returned to env register create() 1. static init at elaboration 2. map holds type name 3. run-time lookup CourseTron · coursetron.com
    Registration populates the factory map; 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 decidedCompile / elaboration time, hard-boundRun time, via factory lookup
    Overridable?NoYes — type or instance override
    Typical useScalar members, non-UVM helpersEvery 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, and
    • req = 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.

    Only create() reaches the factory type_id::create() call in build_phase factory checks overrides Override honoured type chosen at run time new(name, parent) call in build_phase factory never consulted Override silently ignored type hard-bound CourseTron · coursetron.com
    A class built with 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 every my_driver::type_id::create anywhere in the hierarchy returns an err_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.

    Override routes create() to err_driver create("drv") request factory my_driver err_driver Override active: derived type returned. Base path (dashed) is bypassed. CourseTron · coursetron.com
    Same 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.

    Advertisement