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.

uvm_callback

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
 Simulation Log
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.