A UVM callback is a hook that a component developer places in their code, so that users can add or change behavior at that point without editing the component itself. The hook calls methods of callback objects that a test can add or remove at run time, which makes callbacks a clean way to inject errors, add checks or collect extra information.
8 min read | Intermediate Level
What You'll Learn
- What
uvm_callbackis and how the callback mechanism is put together - Register and call callbacks with
`uvm_register_cband`uvm_do_callbacks - Add a callback to a component instance from a test
- Decide when to use a callback instead of a factory override
What is a callback ?
The uvm_callback class is the base class for user-defined callback classes. A component developer creates an application-specific callback class by extending it. In that derived class, the developer declares one or more virtual methods, together called the callback interface, which are the hooks that users can override.
Callbacks let you modify or extend the behavior of a component without changing its original code. They separate the code that triggers an action, the hook inside the component, from the code that defines the action, the callback object. This is useful whenever a reusable component needs to support test-specific behavior.
A callback mechanism always has four parts:
- Callback class: extends
uvm_callbackand declares empty virtual hook methods - Registration:
`uvm_register_cbin the component declares which callback type it supports - Hooks:
`uvm_do_callbackscalls in the component, placed where users may want to act - Implementation: a class derived from the callback class, created and added by the test
Syntax
// In the component class (T) that offers the hook
`uvm_register_cb (T, CB) // T supports callbacks of type CB
`uvm_do_callbacks (T, CB, METHOD(ARGS)) // Call METHOD on every CB added to this object
// In the test
uvm_callbacks #(T, CB)::add (OBJ, CB_INST); // Add CB_INST to component instance OBJ
uvm_callbacks #(T, CB)::delete (OBJ, CB_INST); // Remove it again
T- the class that contains the hooks, such as a driver or monitor (required)CB- the callback base class whose methods the hooks call (required)OBJ- the component instance to attach to. Passingnullattaches the callback to every instance ofT
Callback Macros
The `uvm_register_cb macro registers the callback class CB with the class T in which it is used. Internally, it declares a static variable whose initialization records the T/CB pair.
// Macro definition
`define uvm_register_cb(T,CB) \
static local bit m_register_cb_``CB = uvm_callbacks#(T,CB)::m_register_pair(`"T`",`"CB`");
A specific callback method is executed by invoking the `uvm_do_callbacks macro. It creates an iterator over all callbacks added to the current object and calls the requested method on each one, in the order they were added.
// Macro definition
`define uvm_do_callbacks(T,CB,METHOD) \
`uvm_do_obj_callbacks(T,CB,this,METHOD)
// Basically iterates through and executes the requested method
`define uvm_do_obj_callbacks(T,CB,OBJ,METHOD) \
begin \
uvm_callback_iter#(T,CB) iter = new(OBJ); \
CB cb = iter.first(); \
while(cb != null) begin \
`uvm_cb_trace_noobj(cb,$sformatf(`"Executing callback method 'METHOD' for callback %s (CB) from %s (T)`",cb.get_name(), OBJ.get_full_name())) \
cb.METHOD; \
cb = iter.next(); \
end \
end
If no callback has been added, the loop in `uvm_do_callbacks does nothing. Hooks are therefore safe to leave in a component permanently, at almost no cost when they are unused.
Where are Callbacks Used?
Reusable verification IP, such as a vendor's PCIe or Ethernet agent, is usually shipped as compiled or read-only code. Callbacks are the official way for users to customize it. The driver offers hooks such as "before sending a packet", and a test adds a callback there to corrupt a CRC or delay a response.
UVM Callback Example
This example creates a callback mechanism that extends the behavior of a monitor by adding custom code to its call_pre_check() and call_post_check() hooks.
1. Define the Callback Class
Create the callback class by extending uvm_callback and declaring the virtual hook methods. These methods are empty placeholders, so a monitor with no callbacks attached behaves exactly as before.
class my_monitor_cb extends uvm_callback;
`uvm_object_utils(my_monitor_cb)
function new(string name="my_monitor_cb");
super.new(name);
endfunction
virtual function void call_pre_check();
// Placeholder
endfunction
virtual function void call_post_check();
// Placeholder
endfunction
endclass
2. Define a Custom Callback
custom_monitor_cb extends my_monitor_cb and overrides its hook methods with user-defined logic. Here they just print a message, but they could perform extra checks or collect coverage.
class custom_monitor_cb extends my_monitor_cb;
`uvm_object_utils(custom_monitor_cb)
function new(string name="custom_monitor_cb");
super.new(name);
endfunction
// Override the callback method with custom behavior
virtual function void call_pre_check();
`uvm_info(get_type_name(), $sformatf("[call_pre_check] start pre_check"), UVM_LOW)
endfunction
virtual function void call_post_check();
`uvm_info(get_type_name(), $sformatf("[call_post_check] start post_check"), UVM_LOW)
endfunction
endclass
3. Add Callback Hooks and Register the Callback
The monitor registers my_monitor_cb as a callback type it supports, and places a `uvm_do_callbacks hook before and after its normal checking logic.
class my_monitor extends uvm_monitor;
`uvm_component_utils(my_monitor)
function new(string name="my_monitor", uvm_component parent=null);
super.new(name, parent);
endfunction
// Register the callback class for this component
`uvm_register_cb(my_monitor, my_monitor_cb)
// Method that processes a transaction and uses the callback
function void check_transaction();
// Call the registered callback(s)
`uvm_do_callbacks(my_monitor, my_monitor_cb, call_pre_check());
// Normal checking logic
`uvm_info("MONITOR", "Checking transaction", UVM_MEDIUM)
// Or use a callback macro
`uvm_do_callbacks(my_monitor, my_monitor_cb, call_post_check());
endfunction
// Run phase where the transaction check happens
virtual task run_phase(uvm_phase phase);
super.run_phase(phase);
// Call the checking function, which triggers callbacks
check_transaction();
endtask
endclass
4. Add the Callback in the Test
The test creates an instance of my_monitor, creates a custom_monitor_cb, and adds it to that monitor instance with uvm_callbacks#(my_monitor)::add().
class my_test extends uvm_test;
`uvm_component_utils(my_test)
function new(string name="my_test", uvm_component parent=null);
super.new(name, parent);
endfunction
my_monitor mon;
custom_monitor_cb my_cb;
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
// Create the monitor
mon = my_monitor::type_id::create("mon", null);
// Create and register the custom callback
my_cb = custom_monitor_cb::type_id::create("my_cb");
uvm_callbacks#(my_monitor)::add(mon, my_cb);
endfunction
endclass
Finally, create a module to run the test.
module tb;
initial
run_test("my_test");
endmodule
UVM_INFO @ 0: reporter [RNTST] Running test my_test... UVM_INFO testbench.sv(60) @ 0: reporter [custom_monitor_cb] [call_pre_check] start pre_check UVM_INFO testbench.sv(37) @ 0: mon [MONITOR] Checking transaction UVM_INFO testbench.sv(64) @ 0: reporter [custom_monitor_cb] [call_post_check] start post_check UVM_INFO /xcelium23.09/tools/methodology/UVM/CDNS-1.2/sv/src/base/uvm_report_server.svh(847) @ 0: reporter [UVM/REPORT/SERVER] --- UVM Report Summary ---
The log shows that the user-defined call_pre_check() and call_post_check() methods ran in place of the empty placeholders, before and after the monitor's own "Checking transaction" message. The callback messages are reported by reporter rather than mon, because a callback is an object, not a component.
Quick Check
Q: The test adds two different callbacks to mon. In what order do their call_pre_check() methods run?
Show Answer
In the order they were added. add() appends by default. Passing UVM_PREPEND as its ordering argument places a callback at the front of the list instead.
Callback vs Factory Override
Both mechanisms change behavior without editing the original component, but they suit different needs.
| Aspect | Callback | Factory override |
|---|---|---|
| What changes | Code at specific hook points | The whole class is replaced by a derived class |
| Requires hooks in the original code | Yes | No |
| Can be added or removed during the run | Yes | No, it takes effect when objects are created |
| Several behaviors at once | Yes, any number of callbacks can be added | One override per type or instance |
| Typical use | Error injection, extra checks, coverage hooks | Replacing a sequence, driver or transaction type |
Where is Each Approach Used?
Error-injection tests often combine both: a factory override swaps in an error-capable transaction class, while a callback in the driver decides on each packet whether to corrupt it. Because callbacks can be added and deleted at any time, one test can also inject errors only during a specific phase of traffic.
Common Mistakes
Mistake #1: Forgetting `uvm_register_cb in the component
Adding a callback to a component that never registered that callback type causes UVM to issue a CBUNREG warning. Always place `uvm_register_cb(T, CB) inside the class that contains the `uvm_do_callbacks hooks, with the same T and CB.
Mistake #2: Adding the callback before the component exists
Wrong: calling uvm_callbacks#(my_monitor)::add(mon, my_cb) before mon is created
Right: create the component first, as the test above does, or add the callback in connect_phase or later
Why? If mon is still null, add() treats it as a request to attach the callback to every instance of my_monitor, which may not be what you intended.
Mistake #3: Declaring a hook as a task but calling it from a function
The hook `uvm_do_callbacks(...) expands into a direct call of the callback method. A function such as check_transaction() cannot call a task, so hook methods called from functions must be functions. If a callback needs to consume time, for example to delay a response, declare it as a task and call the hook from a task such as run_phase.
Practice Exercise
Challenge: An Error-Injection Callback for a Driver
A driver my_driver sends items of type my_item, which has a field bit [7:0] crc. Add a hook called pre_drive that runs just before each item is driven and receives the item. Then write a callback that inverts the CRC of every 10th item, and add it from a test.
Hint
Give the hook method arguments, for example pre_drive(my_driver drv, my_item item). Keep a counter inside the callback object, since the same object is called for every item.
Solution
typedef class my_driver; // Forward declaration: used before it is defined
// 1. Callback class with an empty hook
class driver_cb extends uvm_callback;
`uvm_object_utils (driver_cb)
function new (string name = "driver_cb");
super.new (name);
endfunction
virtual task pre_drive (my_driver drv, my_item item);
endtask
endclass
// 2. Driver registers the callback type and calls the hook
class my_driver extends uvm_driver #(my_item);
`uvm_component_utils (my_driver)
`uvm_register_cb (my_driver, driver_cb)
// Rest of the code (constructor, virtual interface, etc.)
virtual task run_phase (uvm_phase phase);
forever begin
seq_item_port.get_next_item (req);
`uvm_do_callbacks (my_driver, driver_cb, pre_drive (this, req)) // Hook runs before driving
// Drive req to the DUT here
seq_item_port.item_done ();
end
endtask
endclass
// 3. Error-injection callback
class crc_err_cb extends driver_cb;
`uvm_object_utils (crc_err_cb)
int unsigned count;
function new (string name = "crc_err_cb");
super.new (name);
endfunction
virtual task pre_drive (my_driver drv, my_item item);
count++;
if (count % 10 == 0) begin
item.crc = ~item.crc; // Corrupt every 10th item
`uvm_info ("CRC_ERR", $sformatf ("Corrupted CRC of item %0d", count), UVM_LOW)
end
endtask
endclass
// 4. In the test, after the env is built (for example in connect_phase)
// crc_err_cb m_err_cb = crc_err_cb::type_id::create ("m_err_cb");
// uvm_callbacks #(my_driver, driver_cb)::add (m_env.m_agent.m_drv, m_err_cb);
The hook is a task called from run_phase, so the callback could also add delays. Tests that do not add crc_err_cb run the driver unchanged.
Summary
In this tutorial, you learned:
- A callback lets users add behavior at predefined hooks in a component without editing its code
- The callback class extends
uvm_callbackand declares empty virtual hook methods `uvm_register_cbdeclares which callback type a component supports, and`uvm_do_callbackscalls a hook on every added callback- Tests attach callbacks with
uvm_callbacks#(T, CB)::add(), and anullobject attaches them to all instances - Callbacks run in the order added and can be removed with
delete() - Use callbacks for targeted, removable changes at hook points, and factory overrides to replace a whole class