UVM
The Factory Pattern
The creational design pattern UVM applies — registration (uvm_*_utils builds the type_id proxy) and creation (type_id::create invokes it through the factory), and why that indirection enables type substitution.
UVM Factory · Module 6 · Page 6.2
The Engineering Problem
The previous chapter established why the factory exists — substitution without modification. This one covers how: the factory pattern itself, a classic creational design pattern from software engineering that UVM applies almost textbook. Understanding the pattern explains the two macros and one method you've been using (uvm_component_utils, uvm_object_utils, type_id::create) — what each actually does and why both halves are required.
The pattern has a precise structure, and missing half of it is a common bug. There are two halves: registration — every class tells the factory "I exist, here's how to make me" (the uvm_*_utils macro registers the class via a lightweight proxy); and creation — instead of new, you ask the factory "make me one of these" (type_id::create), which the factory fulfils by consulting its registry, applying any override, and instantiating. The indirection between asking for a type and getting an instance — mediated by the proxy and the factory's registry — is exactly what lets an override change what you get. Use create without registering, and the factory has no proxy to make the type; register without using create, and you've bypassed the indirection. This chapter is the pattern: its two halves, the proxy that connects them, and why the indirection is the whole point.
What is the factory pattern, how does UVM implement it with registration (
uvm_*_utils) and creation (type_id::create), what is thetype_idproxy that connects them, and why does that indirection enable type substitution?
Motivation — why the pattern has this structure
The factory pattern's two-half structure is exactly what's needed to make construction substitutable:
- Direct
newis the problem the pattern removes.newbinds the construction site to a concrete type with no indirection — there's nowhere to insert a substitution. The factory pattern adds a layer between "I want a driver" and "here's amy_driver", and that layer is where an override can change the answer. - Registration makes types known to the factory. A factory can only create types it knows about. Registration (
uvm_*_utils) is how a class announces itself — it builds a lightweight proxy the factory stores in a registry, so when someone asks for that type, the factory has a way to construct it. Without registration, the factory has nothing to make. - Creation goes through the indirection.
type_id::createis the request — "factory, give me an instance of this type." The factory looks up the type (and any override) in its registry and instantiates via the proxy. This is the delegation at the heart of the pattern: you don't construct, you ask, and the factory decides what to construct. - The proxy is the lightweight stand-in that makes it efficient. Rather than the factory holding heavy machinery per type, each type registers a small proxy (
type_id) that knows how to create one instance. The factory manipulates proxies — registering them, overriding one with another — which is what makes registration cheap and overrides simple.
The motivation, in one line: substitution needs a layer of indirection between requesting a type and constructing it, so the factory pattern provides exactly that — registration builds a proxy the factory knows, and creation routes construction through the factory, with the proxy as the swappable connector that overrides target.
Mental Model
Hold the factory pattern as a parts counter with a registry of part numbers:
The factory pattern is a parts counter where you order by part number instead of fabricating parts yourself. Two things make it work. First, registration: every part manufacturer files a small catalogue card with the counter — "part number
my_driver, here's how to produce one" (that card is the proxy, thetype_id). The counter keeps a registry of these cards. Second, ordering: instead of fabricating a driver at your workbench (new), you go to the counter and order by part number (type_id::create) — "onemy_driver, please." The clerk (the factory) looks up the card, checks for any substitution note ("for this customer,my_drivermeanserr_driver"), and produces the part the card describes. The magic is the counter in the middle: because you order through it rather than fabricating directly, a substitution note can change what you receive without you changing your order. File a card to be orderable; order through the counter to be substitutable.
So in UVM: register every class (uvm_*_utils) so it has a catalogue card (proxy) at the counter, and order through the counter (type_id::create) rather than fabricating (new). The counter's indirection — and the swappable cards — is the whole pattern.
Visual Explanation — direct new vs the factory pattern
The pattern is the layer of indirection between requesting a type and getting an instance. new has no such layer; the factory pattern inserts the factory (and the proxy) between the request and the construction.
The contrast is the presence of a layer. new is request-equals-construction: the site names a concrete type and gets exactly that, with no intermediary and therefore nowhere to insert a substitution. The factory is request-then-the-factory-decides: the site asks, and the factory consults its registry, applies any override, and constructs through the chosen proxy. That intermediary is the entire pattern.
RTL / Simulation Perspective — the two halves in code
The pattern is two macros and one method: register the class (the uvm_*_utils macro builds the proxy) and create through the factory (type_id::create instead of new). Both halves are required.
// ── HALF 1: REGISTRATION — the class registers with the factory ──
class my_driver extends uvm_driver#(my_item);
`uvm_component_utils(my_driver) // registers my_driver; builds its type_id proxy
function new(string name, uvm_component parent); super.new(name, parent); endfunction
endclass
class my_item extends uvm_sequence_item;
`uvm_object_utils(my_item) // registers my_item; builds its type_id proxy
function new(string name = "my_item"); super.new(name); endfunction
endclass
// ── HALF 2: CREATION — ask the factory, don't new ──
function void my_agent::build_phase(uvm_phase phase);
drv = my_driver::type_id::create("drv", this); // factory request → proxy → instance
endfunction // (vs. drv = my_driver::new(...) — bypasses the factory)The two halves are clearly separable. Registration is the uvm_component_utils(my_driver) / uvm_object_utils(my_item) macro inside each class: it registers the class with the factory and builds its type_id proxy — the lightweight stand-in the factory stores in its registry and uses to construct instances of that type. (Note the matching macros: uvm_component_utils for components, uvm_object_utils for objects, Module 4.3.) Creation is my_driver::type_id::create("drv", this): instead of my_driver::new(...), it asks the factory — via the type's proxy — for an instance, and the factory consults its registry, applies any override, and constructs. The ::type_id::create syntax is the pattern's indirection made concrete: type_id is the proxy, create is the request to the factory. Both halves are mandatory — without the uvm_*_utils registration the factory has no proxy to create from (the next-chapter-bug, and our DebugLab), and without type_id::create you've bypassed the factory and lost substitutability (Module 5.2's bug).
Seeing the Override Actually Happen
Everything above argues that the indirection enables substitution. Here is the substitution itself — the whole point of the pattern, in code you can run.
// ── The base type and a derived replacement ─────────────────────────
class my_driver extends uvm_driver#(my_item);
`uvm_component_utils(my_driver)
function new(string name, uvm_component parent); super.new(name, parent); endfunction
virtual task run_phase(uvm_phase phase);
`uvm_info("DRV", "my_driver is driving (normal behaviour)", UVM_LOW)
endtask
endclass
class err_driver extends my_driver; // MUST derive from what it replaces
`uvm_component_utils(err_driver)
function new(string name, uvm_component parent); super.new(name, parent); endfunction
virtual task run_phase(uvm_phase phase);
`uvm_info("DRV", "err_driver is driving (fault injection)", UVM_LOW)
endtask
endclass
// ── The agent asks for a my_driver. This line NEVER changes. ─────────
class my_agent extends uvm_component;
`uvm_component_utils(my_agent)
my_driver drv;
function new(string name, uvm_component parent); super.new(name, parent); endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
drv = my_driver::type_id::create("drv", this); // a request, not a construction
`uvm_info("AGT", $sformatf("drv was built as: %s", drv.get_type_name()), UVM_LOW)
endfunction
endclass
// ── The test registers the override BEFORE the tree is built ────────
class err_test extends uvm_test;
`uvm_component_utils(err_test)
my_agent agt;
function new(string name, uvm_component parent); super.new(name, parent); endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
// Swap the proxy: every create() request for my_driver now resolves to err_driver.
my_driver::type_id::set_type_override(err_driver::get_type());
agt = my_agent::type_id::create("agt", this); // agent built AFTER the override
endfunction
endclassRun the agent's test without the override line and then with it, and the only difference in output is which type the unchanged create produced:
# without the override
UVM_INFO ... [AGT] drv was built as: my_driver
UVM_INFO ... [DRV] my_driver is driving (normal behaviour)
# with my_driver::type_id::set_type_override(err_driver::get_type())
UVM_INFO ... [AGT] drv was built as: err_driver
UVM_INFO ... [DRV] err_driver is driving (fault injection)Three things this demonstrates that prose cannot. The agent's line never changed — my_driver::type_id::create("drv", this) is byte-for-byte identical in both runs, which is what "substitution without modification" actually means. err_driver derives from my_driver, and that is a requirement, not a style choice: the agent's handle is of type my_driver, so the substituted object must be assignment-compatible with it or the override is rejected at build time. The override precedes the build of the component that uses it — registering it after agt is created is too late, because the resolution already happened.
The instance form differs in one respect only — it targets a hierarchical path instead of every occurrence of the type:
// Type override: EVERY my_driver anywhere becomes an err_driver.
my_driver::type_id::set_type_override(err_driver::get_type());
// Instance override: only the driver at this path is substituted.
my_driver::type_id::set_inst_override(err_driver::get_type(), "uvm_test_top.agt.drv");Use the type form to change a behaviour everywhere, and the instance form to corrupt exactly one agent in a multi-agent environment while the others stay honest — the usual reason a fault-injection test reaches for it. The precedence rules that decide which wins when both apply are the subject of type and instance overrides.
Verification Perspective — the type_id proxy connects the halves
The piece that makes the two halves work together is the proxy — the type_id. Registration builds a proxy; creation uses it; an override swaps one proxy for another. Understanding the proxy is understanding how the pattern's indirection actually functions.
The proxy is the elegant core of the implementation. A proxy (the type_id) is a tiny object — one per registered class — whose only job is to construct one instance of its class. Registration builds this proxy and files it in the factory's registry under the class's name; the class itself doesn't need to be heavyweight at the factory level, just represented by its small proxy. Creation asks the factory for a type, and the factory's job is to find the right proxy — the default one, or an overriding proxy if a test registered an override — and call it to construct. An override, then, is simply the factory replacing one proxy with another in its registry: after set_type_override, a create request for my_driver finds the err_driver proxy instead, and constructs an err_driver. So the proxy is the swappable connector: registration installs it, creation invokes it, and overrides exchange it — all without the construction sites (which only issue create requests) knowing or changing. This is why the pattern enables substitution cleanly: everything routes through small, swappable proxies in a central registry.
Runtime / Execution Flow — creation resolves through the factory at build time
At run time, the pattern's creation half happens during build_phase: each type_id::create is a request the factory resolves — looking up the proxy, applying overrides, constructing — so the tree is built through the factory's indirection.
The phase schedule is what makes this work environment-wide. Registration is static — the uvm_*_utils macros populate the registry before anything runs. Creation happens during build_phase, top-down, so an override registered in the test is already in place when the components beneath it are built. That ordering is why a single line in a test can change a driver five levels down, and why registering an override after the relevant component is built has no effect.
Waveform Perspective — creation through the factory at build time
The pattern's creation half happens at build_phase (zero time), where each create request resolves through the factory. The waveform locates that — registration before the run, creation during build, all before any behaviour.
Creation resolves through the factory during build (zero time), before the run
10 cyclesThe create_req pulses in the build_phase region are the pattern's creation half in action: each is a type_id::create resolving through the factory — looking up the proxy, applying any override, constructing the instance — all at zero time, because construction is a function-phase activity (Module 5.1). There's no pin activity during build because nothing behaves yet; the factory is just constructing the tree through its indirection. Registration happened even earlier, at elaboration (the static uvm_*_utils macros), so the registry is ready when these requests arrive. Then run_phase begins and the constructed tree runs (the valid/data activity). The waveform places the pattern precisely: registration before the run, creation (factory requests) in the zero-time build sliver, behaviour after — so the entire tree is built through the factory's request-resolve-construct indirection before a single cycle of behaviour.
DebugLab — the type that couldn't be created (no registration)
A factory create that failed — the class was never registered with the factory
An engineer wrote a new component and created it correctly with my_comp::type_id::create("c", this) — but it failed: depending on the simulator, a compile error that type_id wasn't a member of my_comp, or a runtime factory error that the type wasn't registered. The creation half looked right (type_id::create, not new), yet the factory couldn't make the type. The component simply couldn't be constructed.
The class was never registered — the uvm_component_utils macro was missing, so there was no type_id proxy for the factory to create from:
my_comp: class my_comp extends uvm_component; // NO `uvm_component_utils(my_comp) ✗
creation: my_comp::type_id::create("c", this); // needs a type_id proxy...
reality: no `uvm_component_utils → no type_id proxy → no factory registry entry
result: type_id doesn't exist (compile) / factory can't find the type (runtime) → create fails
fix: add the registration half:
class my_comp extends uvm_component;
`uvm_component_utils(my_comp) // builds the type_id proxy; registers with factoryThe factory pattern has two halves, and only one was present. type_id::create (the creation half) requires a type_id proxy to construct from — and that proxy is built by the uvm_component_utils macro (the registration half). Without registration, there is no proxy and no registry entry, so the very type_id the creation call needs doesn't exist. The creation half can't function alone; it depends on registration having built the proxy it invokes. The fix is to add the missing uvm_component_utils (or uvm_object_utils for an object) — completing the pattern.
The tell is a type_id::create that fails to find or construct the type — a missing registration. Diagnose pattern-half problems:
- Check for the
uvm_*_utilsregistration. Every class created via the factory must register withuvm_component_utils(components) oruvm_object_utils(objects). Atype_idthat doesn't exist, or a "not registered" factory error, means the macro is missing. - Match the macro to the base class.
uvm_component_utilsfor components,uvm_object_utilsfor objects (Module 4.3). The wrong macro registers the wrong kind, which can also break creation. - Confirm both halves are present. Registration (the utils macro) and creation (
type_id::create) are both required. A failing create with correct syntax usually means the registration half is missing.
Always implement both halves of the pattern:
- Register every class with
uvm_*_utils. The macro builds thetype_idproxy and registers it with the factory — without it, the type can't be created via the factory. Make the registration macro a reflex when defining any component or object. - Create via
type_id::createfor anything you may want to substitute. Components, sequence items and configuration objects should route through the factory so overrides apply. Directnewis not forbidden — it is the right call for internal helper objects that no test would ever swap (a local mailbox, a scoreboard's private bookkeeping struct) — but the moment an object is part of the substitutable structure, constructing it withnewsilently removes it from the factory's reach. - Pair the right macro with the base class.
uvm_component_utilsfor components,uvm_object_utilsfor objects — the registration must match what you're registering.
The one-sentence lesson: the factory pattern needs both halves — registration (uvm_*_utils builds the type_id proxy) and creation (type_id::create invokes it) — so a create that fails to find the type means the uvm_*_utils registration is missing, leaving no proxy to construct from.
Common Mistakes
- Missing the
uvm_*_utilsregistration. Without it, there's notype_idproxy, sotype_id::createcan't construct the type (compile or factory error). Register every class with the matching macro. - Creating with
newinstead oftype_id::create. Bypasses the factory's indirection, so overrides are ignored (Module 5.2). Use the creation half so construction routes through the factory. - Mismatching the registration macro.
uvm_component_utilsfor components,uvm_object_utilsfor objects. The wrong macro registers the wrong kind and can break creation. - Thinking registration alone is enough (or creation alone). Both halves are required: registration builds the proxy, creation invokes it through the factory. One without the other doesn't give you a substitutable construction.
- Treating
type_id::createas just a fancynew. It's the request half of a delegation pattern — the factory decides what to construct (applying overrides). The indirection, not the syntax, is the point.
Senior Design Review Notes
Interview Insights
The factory pattern is a creational design pattern that delegates object construction to a factory instead of constructing directly with new, so the concrete type produced can be substituted without changing the requesting code. UVM implements it in two halves. Registration: each class registers with the factory using uvm_component_utils (for components) or uvm_object_utils (for objects), which builds a lightweight proxy — the type_id — and stores it in the factory's registry, so the factory knows how to make that type. Creation: instead of new, you call type_id::create, which asks the factory for an instance; the factory looks up the type's proxy in its registry, applies any override that's been registered, and constructs the instance via the chosen proxy. The type_id proxy is the connector between the two halves — registration builds it, creation invokes it, and an override swaps one proxy for another. The key idea is the layer of indirection: because construction goes through the factory rather than direct new, there's a place to insert a substitution, which is what enables type overrides. Both halves are mandatory — without registration the factory has no proxy to create from, and without type_id::create you bypass the factory and lose substitutability.
Exercises
- Name the halves. For a new component, write the two halves of the factory pattern — the registration line and the creation line — and state what each produces (proxy, instance).
- Diagnose the failed create. A
my_comp::type_id::create(...)fails with "type_id not a member" or "not registered." State the missing half and the fix. - Trace the proxy. Explain, in order, what registration, creation, and an override each do to the
type_idproxy (build, invoke, swap). - Pattern vs new. Explain why
type_id::createenables a test's override to take effect whilenewdoes not, in terms of the layer of indirection.
Summary
- The factory pattern delegates object construction to a factory instead of direct
new, inserting a layer of indirection between requesting a type and getting an instance — and that layer is where substitution happens. - UVM implements it in two halves: registration (
uvm_component_utils/uvm_object_utilsregisters the class and builds its lightweighttype_idproxy in the factory's registry) and creation (type_id::createasks the factory for an instance, which it resolves via the proxy). Both halves are required. - The
type_idproxy connects the halves: registration builds it, creation invokes it through the factory, and an override swaps one proxy for another in the registry — which is mechanically how a substitution works. - At run time, registration happens at elaboration and creation resolves through the factory during
build_phase, so the entire tree is constructed via the factory's request-resolve-construct indirection — making the whole environment substitutable. - The durable rule of thumb: implement both halves — register every class with
uvm_*_utils(builds the proxy) and create every instance withtype_id::create(invokes it through the factory) — because the indirection between request and construction is the pattern, and it's exactly where overrides take effect.
Where this is specified. The factory, its registration macros, and the override APIs used here are defined in the IEEE 1800.2 UVM standard and its class reference — worth consulting directly for the override precedence rules, which are easy to reconstruct incorrectly from experiment.
The two classes this pattern applies to are uvm_object and uvm_component; the override forms introduced above are developed in type and instance overrides, and the failures they produce in practice are collected in common factory bugs.
Next — The UVM Factory: with the pattern understood, the next chapter looks at the UVM factory as a concrete object — the singleton factory that holds the registry, the API for registration and creation, and how it fits with the rest of the framework you've built up.
Part of UVM Fundamentals·The UVM Factory·Lesson 25 of 33
View programContinue learning
Related tutorials
- Related topic
build_phase
The top-down construction phase — getting configuration, creating children via the factory (type_id::create not new), setting children's config, and why overrides need the factory.
- Related topic
Override Best Practices
The disciplines that keep factory overrides predictable at scale — declare them in one place early, choose the narrowest scope, register by type not string, keep chains shallow, override only at intended seams, and always verify.