Concept of UVM Factory

In short: The UVM factory is a lookup table that decides which class to build when you ask for an object. You register a class once, always build with create() instead of new(), and then you can replace any class with a subclass from the test without editing the code that builds it. That is how one testbench runs error tests, corner cases, and clean tests without changing the environment.

The UVM factory sounds abstract until you see the problem it removes. This guide explains what the factory is, why building objects with create() matters, and the three ways to override a class: by type, by instance, and by name. Every idea comes with runnable code, a comparison table, the exact order the factory searches, and the mistakes that trip people up. We use one running example throughout: a bus_driver that we will later swap for an error-injecting version without touching the environment.

A simple way to picture it

Think of ordering a coffee at a counter. You ask for a latte, and the barista makes it. You do not care which barista is on shift; you just say latte and get a latte. Now suppose the cafe wants to test a new oat-milk recipe for one day. The manager posts a note: whenever someone orders a latte, make the oat-milk version instead. You still order a latte the same way, but what you receive changed, because the manager rerouted the request behind the counter. The factory is that note. Your code keeps asking for a bus_driver, and a single override line tells UVM to hand back an error_driver instead.

The problem the factory solves

Suppose your environment creates a driver with plain SystemVerilog: drv = new(“drv”, this);. That line hardcodes the exact class bus_driver. If next week you want a test that injects protocol errors, you would have to edit the environment to build error_driver instead, then edit it back for normal tests. Multiply that across dozens of tests and components and you get a testbench nobody wants to maintain. The factory breaks that link between asking for an object and choosing its concrete type.

Step one: register the class

Registration tells the factory the class exists. You do it with one macro. Components use uvm_component_utils; objects such as sequence items and sequences use uvm_object_utils.

class bus_driver extends uvm_driver #(bus_item);
  `uvm_component_utils(bus_driver)   // register a component
  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction
endclass

class bus_item extends uvm_sequence_item;
  `uvm_object_utils(bus_item)        // register an object
  function new(string name = "bus_item");
    super.new(name);
  endfunction
endclass

The macro quietly creates a helper type called type_id and a proxy the factory uses to build the class later. You never call those directly; you just need to know the macro is what wires the class into the factory.

Step two: build with create, not new

This is the habit that makes everything else possible. Instead of new(), always ask the factory to build the object through type_id::create(). For components you pass a name and parent; for objects you pass just a name.

// component: name + parent
drv = bus_driver::type_id::create("drv", this);

// object: name only
bus_item req = bus_item::type_id::create("req");

At this point create() returns a bus_driver, exactly as new() would. The payoff comes next: because you went through the factory, a single override line elsewhere can change what create() returns without touching this code at all.

Type overrides: swap a class everywhere

A type override says: everywhere you would have built class A, build class B instead. It is the most common override. Say we write an error_driver that occasionally corrupts data, extending the normal driver:

class error_driver extends bus_driver;
  `uvm_component_utils(error_driver)
  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction
  // override drive() to flip a bit sometimes
  task drive(bus_item req);
    if ($urandom_range(0,9)==0) req.data ^= 32'h1; // 10% corruption
    super.drive(req);
  endtask
endclass

Now, in an error test, one line tells the factory to build error_driver wherever bus_driver was requested. The environment code never changes.

class error_test extends base_test;
  `uvm_component_utils(error_test)
  function new(string name, uvm_component parent); super.new(name,parent); endfunction
  function void build_phase(uvm_phase phase);
    // GLOBAL type override, applied before the env is built
    bus_driver::type_id::set_type_override(error_driver::get_type());
    super.build_phase(phase);   // env builds its driver -> gets error_driver
  endfunction
endclass

Order matters: set the override before super.build_phase(phase) runs, because that is when the environment actually creates the driver. Set it too late and the normal driver is already built.

Instance overrides: swap just one

Sometimes you want to change only one component in the tree, not every instance. An instance override targets a single path. Suppose your chip has two bus agents and you only want errors on the second one:

// only env.agt1.drv becomes an error_driver; env.agt0.drv stays normal
bus_driver::type_id::set_inst_override(
    error_driver::get_type(),
    "env.agt1.drv", this);

The string is the hierarchical path to the exact instance. This is the tool for mixed setups where one interface should misbehave while the rest stay clean.

Override by name versus by type

Every override comes in two flavors: by type, using get_type(), and by name, using a string. The type form is checked by the compiler, so a typo is caught early; the name form is looser but useful when the class is only known as a string, for example from a command-line plusarg. Prefer the type form whenever you can.

// by type (preferred: compile-checked)
bus_driver::type_id::set_type_override(error_driver::get_type());

// by name (string based; handy for plusarg-driven test selection)
factory.set_type_override_by_name("bus_driver", "error_driver");

The three overrides side by side

OverrideScopeCallUse it when
Type overrideEvery instance of the classset_type_overrideYou want all drivers to misbehave
Instance overrideOne hierarchical pathset_inst_overrideOnly one agent should misbehave
Name overrideBy string nameset_type_override_by_nameClass is chosen from a plusarg or config string

The order the factory checks

When create() runs, the factory does not pick randomly. It follows a fixed search so results are predictable:

  1. Is there an instance override for this exact hierarchical path? If yes, use it.
  2. Is there a type override for the requested class? If yes, use it.
  3. Otherwise build the class that was originally requested.

Instance overrides win over type overrides. That is what lets you set a global type override for all drivers, then carve out one instance with a more specific instance override on top.

A worked end-to-end example

Putting it together, here is how one environment serves three different tests without a single edit. The environment always asks for a bus_driver through the factory. Each test changes what that means.

// ---- shared environment, never edited ----
class bus_env extends uvm_env;
  `uvm_component_utils(bus_env)
  bus_agent agt0, agt1;
  function new(string n, uvm_component p); super.new(n,p); endfunction
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    agt0 = bus_agent::type_id::create("agt0", this);
    agt1 = bus_agent::type_id::create("agt1", this);
  endfunction
endclass

// ---- test A: all normal ----
class clean_test extends base_test; endclass  // no override

// ---- test B: all drivers inject errors ----
class all_error_test extends base_test;
  function void build_phase(uvm_phase phase);
    bus_driver::type_id::set_type_override(error_driver::get_type());
    super.build_phase(phase);
  endfunction
endclass

// ---- test C: only agt1 injects errors ----
class one_error_test extends base_test;
  function void build_phase(uvm_phase phase);
    bus_driver::type_id::set_inst_override(
      error_driver::get_type(), "env.agt1.drv", this);
    super.build_phase(phase);
  endfunction
endclass

Three behaviors, one environment. This is the practical reason the factory exists: it turns a rigid testbench into a library you configure from the top.

Debugging: print the factory

When an override does not behave as expected, ask the factory to print what it knows. This shows every registered type and every active override, which usually reveals a mistyped path or an override set too late.

function void end_of_elaboration_phase(uvm_phase phase);
  factory.print();   // list registrations and overrides
endfunction

Why this is worth the effort

The factory pays off in three ways. You write one environment and reuse it across every test, which cuts maintenance sharply. You add new behavior, like error injection or a smarter scoreboard, by writing a subclass and one override line, without risking the working code. And you keep tests self-contained, because each test declares its own overrides instead of leaving edits scattered through shared files. Together these turn stimulus variety into a configuration choice rather than a code change.

Common mistakes to avoid

  • Using new() instead of create(). If you build with new(), the factory never sees the request and no override can apply.
  • Forgetting the registration macro, so type_id and create() do not exist for that class.
  • Setting a type override after super.build_phase(phase). The component is already built with the original class by then.
  • Mistyping the hierarchical path in an instance override, so it silently matches nothing.
  • Overriding with a class that is not a subclass of the original. The replacement must be type-compatible or the factory rejects it.

Keep learning

The factory makes the most sense alongside the rest of the structure. See where create() is called in the UVM testbench architecture guide, review UVM phasing to understand why override order matters, and explore more on the UVM category page. The language foundation lives on the SystemVerilog page, and factory questions are common in our interview questions.

Frequently asked questions

The factory is one of the main reasons the advantages of UVM over plain SystemVerilog add up, because it lets one test swap a component without touching the shared code.

What is the difference between new() and create()?

new() builds the exact class you name, with no chance to substitute anything. create() asks the factory to build the object, so the factory can return a subclass if an override is active. Using create() everywhere is what makes overrides possible; new() locks you into one type.

Do I register objects and components the same way?

Almost. Components, which extend uvm_component, use the uvm_component_utils macro, while objects such as sequence items and sequences use uvm_object_utils. The difference matters because create() takes a parent for components but not for objects.

When would I use an instance override instead of a type override?

Use an instance override when only one component in the tree should change while the rest stay the same. A common case is a chip with several identical agents where you want just one interface to inject errors. A type override would change all of them at once.

Why did my override not take effect?

The usual causes are building with new() instead of create(), setting the override after super.build_phase() has already built the component, or a typo in the hierarchical path for an instance override. Calling factory.print() in end_of_elaboration_phase shows the active overrides so you can spot the problem.

Can I override a class with any other class?

No. The replacement must be a subclass of the original, or at least type-compatible, so that everything expecting the original type still works. The factory rejects overrides that would break that relationship.

Is the factory slow?

No, in practice it is not a concern. The factory lookup happens when objects are created, which is a tiny fraction of simulation time compared with the run phase. The flexibility it gives far outweighs the negligible lookup cost.

Similar Posts