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 instancenew — direct, no indirectionnew — direct,no…type_id::create(request)consult registry + overrideconsultregistry +…instantiateConstruction 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 decided12
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. With new (top row), the construction site names a concrete type and gets exactly that — there is no intermediary, so there is nowhere to insert a substitution; the type is fixed at the site. With the factory pattern (bottom row), the site issues a request (type_id::create) to the factory, which consults its registry of proxies (populated by registration), applies any override, and constructs the instance via the chosen proxy. The factory and the proxy form a layer of indirection between "I want this type" and "here's the instance" — and that layer is precisely where an override operates. So the pattern's power comes entirely from inserting that intermediary: new is request-equals-construction (rigid), while the factory is request-then-the-factory-decides-construction (substitutable). The diagram is the whole pattern: add a factory and a proxy between the request and the instance.

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

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 → override swaps itregister builds the proxy → create uses it → override swaps 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 factorybuild_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.

This ties the pattern to the phase schedule. Registration is effectively done before the run — the uvm_*_utils macros register each class's proxy statically, so by the time anything runs, the factory's registry is fully populated. Creation happens during build_phase (Module 5.2): every type_id::create call is a request to the factory, and the factory resolves it — looking up the proxy for the requested type, checking for an override the test registered, and constructing the resulting instance. So the entire component tree is built through the factory's indirection: not one component is constructed by direct new; each is a request the factory fulfils. That's what makes the whole environment substitutable — because every construction is a factory request, an override registered before build (in the test, top-down) is applied to every matching request as the tree builds. The pattern isn't an isolated trick at one site; it's the uniform construction mechanism for the whole environment, resolved through the factory at build time.

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…the factory resolves each: look up proxy, apply override, construct the instancethe factory resolves e…run_phase: the tree — built entirely via the factory — now runsrun_phase: the tree — …clkphaseBLDBLDRUNRUNRUNRUNRUNRUNRUNRUNcreate_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. Use the creation half for every component and object, so construction goes through the factory's indirection (and overrides apply). Never new.
  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.

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.