Callbacks Vs Factory
In short: Callbacks and the factory both help you reuse UVM code, but they solve different problems. A callback adds a small piece of behaviour at a fixed point inside a component you keep as-is, which is ideal for error injection or a locked third-party VIP. A factory override swaps a whole component or object for a different version before the testbench is built, which is better when the change is large or affects many places. Pick the callback for a light tweak and the factory for a full replacement.
Both callbacks and the factory answer the same underlying question: how do I change what my testbench does in one test without breaking every other test? They just take different routes. A callback leaves the original component in place and injects extra code at a chosen spot. The factory replaces the class itself with your version. Knowing which to reach for saves a lot of rework.
A simple way to picture it
Imagine a coffee machine at your office. A callback is like a sticky note on the machine that says “add extra sugar to the next cup”. The machine is unchanged; you just added one small instruction at one point. The factory is like wheeling in a completely different coffee machine that looks the same from the outside and fits the same spot, but brews differently inside. Use the sticky note for a tiny change, and swap the machine when you need a genuinely different one.
What each one is good at
Callbacks are best for narrow, occasional changes:
- Add behaviour to existing logic without editing it.
- Good when only a rare or minor feature needs a change.
- A popular way to inject errors or corrupt a sequence coming from a VIP.
- Easy to add and remove during a single test.
The factory is best for broad replacements:
- Substitute a component or transaction with a different version before build, keeping the rest of the environment the same.
- Good when many features of the component need to change together.
- Works cleanly when you want several tests to each use their own variant.
- Less suited to changing a locked VIP component you cannot subclass easily.
Side by side comparison
| Aspect | Callback | Factory |
|---|---|---|
| What it changes | Adds code at a fixed hook inside a component | Replaces the whole class with a different one |
| Original component | Stays exactly as written | Is swapped out before build |
| Best for | Small, rare tweaks; error injection | Large changes; many variants |
| Third-party VIP | Works well when hooks are published | Harder if you cannot subclass the sealed class |
| When applied | During run time at the hook point | At build time, before components are created |
| Turn on or off | add / delete during a test | set_type_override / set_inst_override at build |
| Typical use | Corrupt one field, add a check, sample coverage | Use a special driver or extended transaction |
The same goal, two ways
Say you want one test to send a corrupted address. With a callback, you attach a small class that flips a bit at the driver hook, and the base driver is untouched:
// Callback approach: tiny change, base driver unchanged
class addr_corrupt_cb extends bus_driver_cb;
`uvm_object_utils(addr_corrupt_cb)
function new(string name = "addr_corrupt_cb"); super.new(name); endfunction
task pre_send(bus_driver drv, bus_item tr);
tr.addr ^= 32'h1; // flip one address bit
endtask
endclass
// In the test:
addr_corrupt_cb cb = addr_corrupt_cb::type_id::create("cb");
uvm_callbacks#(bus_driver, bus_driver_cb)::add(env.agt.drv, cb);With the factory, you write a whole extended driver and tell UVM to build it instead of the base driver:
// Factory approach: a different driver replaces the base one
class err_bus_driver extends bus_driver;
`uvm_component_utils(err_bus_driver)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
task drive_item(bus_item tr);
tr.addr ^= 32'h1; // same effect, but this is a new class
super.drive_item(tr);
endtask
endclass
// In the test build_phase, before components are created:
function void build_phase(uvm_phase phase);
set_type_override_by_type(bus_driver::get_type(),
err_bus_driver::get_type());
super.build_phase(phase);
endfunctionBoth reach the same result: one test sends a corrupted address. The callback did it with a few lines and no new component. The factory did it by defining a full replacement driver. For a one-line change the callback is lighter; if the new driver needed to change many methods, the factory would be cleaner.
How to choose
- Is the change small and occasional? Lean toward a callback.
- Are you changing a locked or encrypted VIP? Use its published callback hooks.
- Does the change touch many methods or the whole component? Use a factory override.
- Do you need several distinct variants across tests? The factory handles that well with per-instance overrides.
- Do you need to turn the change on and off mid-test? Callbacks add and delete on the fly; factory overrides are set at build.
Common mistakes to avoid
- Using the factory for a one-line tweak. Writing a whole new class to flip one bit is more code to maintain than a callback.
- Using a callback for a large rewrite. If you are overriding many methods, a factory override reads more clearly.
- Setting a factory override too late. Call set_type_override before super.build_phase so the new type is used when components are created.
- Forgetting to register components with the factory. The `uvm_component_utils macro is what makes overrides possible.
- Trying to factory-override a sealed VIP class. If you cannot subclass it cleanly, its callback hooks are usually the intended path.
Keep learning
For the mechanics behind each tool, read how a UVM callback works and the concept of the UVM factory. To see where drivers and monitors sit in the testbench, review the UVM testbench architecture. More guides live in our UVM section, and you can practice with our interview questions.
Frequently asked questions
What is the main difference between callbacks and the factory in UVM?
A callback adds a small piece of behaviour at a fixed point inside a component that stays unchanged. A factory override replaces the whole component or object with a different class before the testbench is built. Callbacks suit small tweaks; the factory suits large replacements.
When should I prefer a callback over a factory override?
Prefer a callback when the change is small or rare, such as injecting an error, or when you work with a locked third-party VIP that publishes hook points. Callbacks are also easy to add and remove during a single test.
When is the factory the better choice?
Use the factory when the change is large, touches many methods, or when several tests each need their own variant of a component or transaction. The factory swaps the class itself, keeping the rest of the environment the same.
Can callbacks and the factory solve the same problem?
Yes, they can often reach the same result, such as sending a corrupted transaction. The right choice depends on how big the change is and whether you can subclass the target component.
Why are callbacks preferred for third-party VIP?
Encrypted VIP usually cannot be subclassed freely, so a factory override may not be possible. The VIP vendor instead publishes callback hooks at set points, letting you add behaviour without editing or replacing the sealed component.
Is a factory override applied at run time or build time?
A factory override is applied at build time. You call set_type_override or set_inst_override before super.build_phase so UVM creates your version when components are constructed. Callbacks, by contrast, act at run time at the hook point.


