What is a callback ?
The uvm_callback class serves as the base class for user-defined callback classes. Typically, a component developer creates an application-specific callback class by extending this base class. In the derived class, the developer defines one or more virtual methods, collectively known as the callback interface, which provide the hooks that users can override.
A callback is useful because it allows a flexible and modular way to modify or extend the behavior of a system without altering the original code. Callbacks decouple the code that triggers an action from the code that defines the action itself. It is required in scenarios where customization, or dynamic behavior is necessary.
Callback Macros
`uvm_register_cb
macro is used to register the Callback(CB) with the Object(T) where CB is the user-defined callback class and T is the object in which CB is used.
// Macro definition
`define uvm_register_cb(T,CB) \
static local bit m_register_cb_``CB = uvm_callbacks#(T,CB)::m_register_pair(`"T`",`"CB`");
And a specific callback method is executed by invoking `uvm_do_callbacks
macro.
// 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
UVM Callback Example
Let's create a callback mechanism that allows us to extend the behavior of a monitor by adding custom code to a call_pre_check() and call_post_check() methods.
1. Define the Callback Class
Users can create custom callback classes by extending the uvm_callback
class. In these classes, users define one or more virtual methods. These virtual methods, known as callback methods, are initially empty and can be overridden by the user to implement specific behavior.
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 gives a way to override the check_transaction() method with user-defined logic, such as additional checks.
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
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. Register the Callback in the Test
Test creates an instance of my_monitor and registers the custom_monitor_cb callback.
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 ---
Note in the log above that user-defined methods for call_pre_check and call_post_check are executed instead of the placeholder.