Skip to content

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 the type_id proxy 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 new is the problem the pattern removes. new binds 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 a my_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::create is 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, the type_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) — "one my_driver, please." The clerk (the factory) looks up the card, checks for any substitution note ("for this customer, my_driver means err_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.

new binds directly to a type; the factory pattern inserts the factory and proxy between request and instanceConstruction site (new)bound to a concrete typemy_driver instancefixed — no substitution pointConstruction site(create)asks the factoryFactoryregistry of proxies +overridestype_id proxyknows how to make oneinstance (maybeoverridden)what the factory decidednew — direct, noindirectiontype_id::create(request)consult registry +overrideinstantiate12
Figure 1 — direct new vs the factory pattern. With new, the construction site is bound directly to a concrete type — no indirection, no place to substitute. With the factory pattern, the site asks the factory (type_id::create), the factory consults its registry of proxies (built by registration), applies any override, and instantiates via the proxy. The factory and proxy are the layer of indirection between request and instance — which is exactly where substitution happens.

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.

the factory pattern — registration + creation
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── 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.

factory_override_demo.sv — the same create() builds a different type
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── 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
endclass

Run 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:

representative UVM output — exact formatting is simulator- and version-dependent
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
# 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 changedmy_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:

instance override — the same swap, scoped to one path
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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.

Registration builds a type_id proxy, creation uses it via the factory, override swaps the proxyregister builds the proxy → create uses it → overrideswaps it1Register → build proxyuvm_*_utils creates the class's type_id proxy and stores it in thefactory registry.2Create → ask the factorytype_id::create requests an instance; the factory looks up theproxy for the type.3Factory resolves the proxydefault proxy, or an overriding one if registered — then calls itto construct.4Proxy constructs the instancethe chosen proxy makes the object; an override changed which proxywas used.
Figure 2 — the type_id proxy connects registration and creation. Registration (uvm_*_utils) builds a lightweight proxy for the class and stores it in the factory's registry. Creation (type_id::create) asks the factory, which finds the proxy (or an overriding proxy) and calls it to construct the instance. An override replaces one proxy with another in the registry, so the same create request constructs a different type. The proxy is the swappable connector between asking and getting.

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.

During build_phase, type_id::create requests resolve through the factory: lookup proxy, apply override, constructbuild_phase: each create() resolves through the factory1Registration (at elaboration)uvm_*_utils registered every class's proxy before the run — theregistry is populated.2build_phase: create() requestseach component is created with type_id::create — a request to thefactory, not a new.3Factory resolves each requestlook up the proxy, apply any override, construct the (possiblyoverridden) instance.4Tree built via the factorythe whole hierarchy is constructed through the factory'sindirection — substitutable throughout.
Figure 3 — creation resolves through the factory during build_phase. Registration happened earlier (at elaboration, via the static uvm_*_utils macros). During build_phase, each type_id::create is a request to the factory, which looks up the type's proxy, applies any override the test registered, and constructs the instance — so the whole tree is built via the factory's indirection. The construction site issues a request; the factory decides and constructs.

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 cycles
Creation resolves through the factory during build (zero time), before the runbuild_phase (zero time): each type_id::create is a factory request (create_req)build_phase (zero time):each type_id::create is afactory request…the factory resolves each: look up proxy, apply override, construct the instancethe factory resolves each:look up proxy, applyoverride, construct the…run_phase: the tree — built entirely via the factory — now runsrun_phase: the tree — builtentirely via the factory —now runsclkphaseBLDBLDRUNRUNRUNRUNRUNRUNRUNRUNcreate_reqvaliddata0000A0A100B0B1000000t0t1t2t3t4t5t6t7t8t9
Figure 4 — registration happened at elaboration (before the trace). During build_phase (zero time), each type_id::create is a factory request that resolves (look up proxy, apply override, construct) — the create_req pulses mark these requests. No pin activity, because creation is zero-time construction. Then run_phase begins and the built tree runs. The pattern's indirection (request → factory → instance) all happens in the build sliver, before behaviour.

The 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

Symptom

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.

Root cause

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:

why the registered-creation half failed without registration
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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 factory

The 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.

Diagnosis

The tell is a type_id::create that fails to find or construct the type — a missing registration. Diagnose pattern-half problems:

  1. Check for the uvm_*_utils registration. Every class created via the factory must register with uvm_component_utils (components) or uvm_object_utils (objects). A type_id that doesn't exist, or a "not registered" factory error, means the macro is missing.
  2. Match the macro to the base class. uvm_component_utils for components, uvm_object_utils for objects (Module 4.3). The wrong macro registers the wrong kind, which can also break creation.
  3. 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.
Prevention

Always implement both halves of the pattern:

  1. Register every class with uvm_*_utils. The macro builds the type_id proxy 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.
  2. Create via type_id::create for anything you may want to substitute. Components, sequence items and configuration objects should route through the factory so overrides apply. Direct new is 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 with new silently removes it from the factory's reach.
  3. Pair the right macro with the base class. uvm_component_utils for components, uvm_object_utils for 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_*_utils registration. Without it, there's no type_id proxy, so type_id::create can't construct the type (compile or factory error). Register every class with the matching macro.
  • Creating with new instead of type_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_utils for components, uvm_object_utils for 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::create as just a fancy new. 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

  1. 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).
  2. 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.
  3. Trace the proxy. Explain, in order, what registration, creation, and an override each do to the type_id proxy (build, invoke, swap).
  4. Pattern vs new. Explain why type_id::create enables a test's override to take effect while new does 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_utils registers the class and builds its lightweight type_id proxy in the factory's registry) and creation (type_id::create asks the factory for an instance, which it resolves via the proxy). Both halves are required.
  • The type_id proxy 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 with type_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 program

Continue learning