UVM Callbacks

Universal Verification Methodology (UVM) provides a robust mechanism for verification engineers to develop reusable and scalable testbenches. One powerful feature of UVM is the callback mechanism, which allows dynamic modification of testbench behavior without altering the original codebase. Please refer systemverilog callback to have a better understanding.

UVM callbacks are a way to insert custom behavior into pre-existing components dynamically. They enable greater flexibility by allowing testbench modifications without directly changing the UVM component code. For example, in a UVM driver, callbacks can be used to alter transaction handling, inject errors, or monitor signals dynamically.

UVM Callback Macros:

UVM provides macros that simplify the definition and use of callbacks:

`uvm_register_cb(TYP, CB_TYP)

`uvm_register_cb(CLASS, CALLBACK_CLASS)
  • CLASS is the UVM component that will invoke the callbacks.
  • CALLBACK_CLASS is the callback class containing user-defined methods.

This macro automatically defines and instantiates the UVM callback mechanism for a given component.

`uvm_do_callbacks(TYP, CB_TYP, METHOD_NAME, ARGUMENTS…)

  • Executes all registered callbacks for TYP, calling the method METHOD_NAME.

These macros reduce the amount of boilerplate code required and ensure consistency in callback implementation.

UVM Callback Classes

A UVM callback class is an extension of uvm_callback. This class defines methods that can be overridden to modify behavior. uvm_callback is inherited by uvm_object.

Example Callback Class Definition:

class my_driver_callback extends uvm_callback;
  
  `uvm_object_utils(my_driver_callback)

  // Custom method for modifying driver behavior
  virtual function void pre_drive(uvm_sequence_item txn);
    `uvm_info("CALLBACK", "pre_drive method in callback", UVM_MEDIUM)
  endfunction

endclass
  • This class extends uvm_callback, making it a valid callback object.
  • The pre_drive method can be implemented in different ways by different callback instances.

UVM Callback Methods

UVM components that support callbacks typically provide methods for managing them:

  • Adding a Callback
uvm_callbacks#(my_driver, my_driver_callback)::add(driver_inst, callback_inst);

Registers a my_driver_callback instance to modify my_driver behavior.

  • Removing a Callback
uvm_callbacks#(my_driver, my_driver_callback)::delete(driver_inst, callback_inst);

Removes a registered callback instance.

  • Executing Callbacks in a Component
`uvm_do_callbacks(my_driver, my_driver_callback, pre_drive, txn);

Calls pre_drive() for all registered callback instances associated with my_driver.

Example:

Let’s take a simple example to see how the callback works in real time scenario. We will define some functions in callback class and these functions will be called by the driver. We can make changes according to our needs in callback functions which can alter the result of the driver without changing the original code of driver.

In this example, we are taking a simple transaction class with ‘data’ field and sending it through the driver. Let’s go step by step.

  • Callback Class (my_callback):
    • Defines pre_send() and post_send() virtual functions.
    • These functions will be called by the driver.
    • Uses uvm_object_utils for UVM object functionalities.
class my_callback extends uvm_callback;

  virtual function void pre_send(int data);
    `uvm_info("MY_CALLBACK", $sformatf("Pre-send callback called with data: %0d", data), UVM_LOW)
  endfunction

  virtual function void post_send(int data);
    `uvm_info("MY_CALLBACK", $sformatf("Post-send callback called with data: %0d", data), UVM_LOW)
  endfunction

  `uvm_object_utils(my_callback)

  function new(string name = "my_callback");
    super.new(name);
  endfunction

endclass
  • Transaction (my_transaction):
    • A simple transaction class with a data field.
    • Uses uvm_object_utils_begin/end and uvm_field_int for UVM object and field automation.
  • Driver (my_driver):
    • Retrieves transactions from the sequencer.
    • Callback Execution:
      • Uses `uvm_do_callbacks#(my_driver, my_callback, functions) to get the registered callback instances.
      • Calls pre_send() before sending data.
      • Calls post_send() after sending data.
    • Simulates the sending of the data with a delay.
class my_driver extends uvm_driver#(my_transaction);

  `uvm_component_utils(my_driver)
  `uvm_register_cb(my_driver,my_callback)
  
  my_callback cb;

  function new(string name = "my_driver", uvm_component parent = null);
    super.new(name, parent);
  endfunction

  virtual task run_phase(uvm_phase phase);
    my_transaction req;
    forever begin
      seq_item_port.get_next_item(req);

      // Pre-send callback
      `uvm_do_callbacks(my_driver,my_callback,pre_send(req.data));
      `uvm_info("MY_DRIVER", $sformatf("Driver sending data: %0d", req.data), UVM_LOW);
      
      //post-send callback
      `uvm_do_callbacks(my_driver,my_callback,post_send(req.data));

      seq_item_port.item_done();
    end
  endtask

endclass
  • Sequencer (my_sequencer):
    • Standard UVM sequencer.
  • Sequence (my_sequence):
    • Generates random transactions and sends them to the sequencer.
  • Agent (my_agent):
    • Instantiates the driver and sequencer.
    • Connects the driver and sequencer ports.
  • Environment (my_env):
    • Instantiates the agent.
  • Test (my_test):
    • Instantiates the environment and sequence.
    • Creates an instance of the callback class.
    • Callback Registration:
      • Uses uvm_callbacks#(my_driver, my_callback)::add(env.agt.drv, my_cb_instance); to register the callback instance with the driver.
    • Starts the sequence.
class my_test extends uvm_test;

  my_env env;
  my_sequence seq;
  my_callback my_cb_instance;

  `uvm_component_utils(my_test)

  function new(string name = "my_test", uvm_component parent = null);
    super.new(name, parent);
  endfunction

  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    env = my_env::type_id::create("env", this);
    seq = my_sequence::type_id::create("seq");
    my_cb_instance = my_callback::type_id::create("my_cb_instance",this);
  //  uvm_callbacks#(my_driver, my_callback)::add(env.agt.drv, my_cb_instance);
  endfunction
  
  virtual function void end_of_elaboration_phase(uvm_phase phase);
    super.end_of_elaboration_phase(phase);
    uvm_callbacks#(my_driver, my_callback)::add(env.agt.drv, my_cb_instance);
  endfunction 

  virtual task run_phase(uvm_phase phase);
    phase.raise_objection(this);
    seq.start(env.agt.seqr);
    phase.drop_objection(this);
  endtask

endclass
  • Top Module (top):
    • Runs the test.

Output:

UVM_INFO @ 0: reporter [RNTST] Running test my_test...
UVM_INFO sequence.sv(16) @ 0: uvm_test_top.env.agt.seqr@@seq [MY_SEQUENCE] Sequence sending data: -517620205
UVM_INFO driver_callback.sv(4) @ 0: reporter [MY_CALLBACK] Pre-send callback called with data: -517620205
UVM_INFO driver.sv(19) @ 0: uvm_test_top.env.agt.drv [MY_DRIVER] Driver sending data: -517620205
UVM_INFO driver_callback.sv(8) @ 0: reporter [MY_CALLBACK] Post-send callback called with data: -517620205
UVM_INFO sequence.sv(16) @ 0: uvm_test_top.env.agt.seqr@@seq [MY_SEQUENCE] Sequence sending data: 774544662
UVM_INFO driver_callback.sv(4) @ 0: reporter [MY_CALLBACK] Pre-send callback called with data: 774544662
UVM_INFO driver.sv(19) @ 0: uvm_test_top.env.agt.drv [MY_DRIVER] Driver sending data: 774544662
UVM_INFO driver_callback.sv(8) @ 0: reporter [MY_CALLBACK] Post-send callback called with data: 774544662
UVM_INFO sequence.sv(16) @ 0: uvm_test_top.env.agt.seqr@@seq [MY_SEQUENCE] Sequence sending data: 1471791918
UVM_INFO driver_callback.sv(4) @ 0: reporter [MY_CALLBACK] Pre-send callback called with data: 1471791918
UVM_INFO driver.sv(19) @ 0: uvm_test_top.env.agt.drv [MY_DRIVER] Driver sending data: 1471791918
UVM_INFO driver_callback.sv(8) @ 0: reporter [MY_CALLBACK] Post-send callback called with data: 1471791918
UVM_INFO sequence.sv(16) @ 0: uvm_test_top.env.agt.seqr@@seq [MY_SEQUENCE] Sequence sending data: 459431653
UVM_INFO driver_callback.sv(4) @ 0: reporter [MY_CALLBACK] Pre-send callback called with data: 459431653
UVM_INFO driver.sv(19) @ 0: uvm_test_top.env.agt.drv [MY_DRIVER] Driver sending data: 459431653
UVM_INFO driver_callback.sv(8) @ 0: reporter [MY_CALLBACK] Post-send callback called with data: 459431653
UVM_INFO sequence.sv(16) @ 0: uvm_test_top.env.agt.seqr@@seq [MY_SEQUENCE] Sequence sending data: -377759977
UVM_INFO driver_callback.sv(4) @ 0: reporter [MY_CALLBACK] Pre-send callback called with data: -377759977
UVM_INFO driver.sv(19) @ 0: uvm_test_top.env.agt.drv [MY_DRIVER] Driver sending data: -377759977
UVM_INFO driver_callback.sv(8) @ 0: reporter [MY_CALLBACK] Post-send callback called with data: -377759977
UVM_INFO /apps/vcsmx/vcs/U-2023.03-SP2//etc/uvm-1.2/src/base/uvm_objection.svh(1276) @ 0: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase
UVM_INFO /apps/vcsmx/vcs/U-2023.03-SP2//etc/uvm-1.2/src/base/uvm_report_server.svh(904) @ 0: reporter [UVM/REPORT/SERVER] 
--- UVM Report Summary ---

** Report counts by severity
UVM_INFO :   23
UVM_WARNING :    0
UVM_ERROR :    0
UVM_FATAL :    0
** Report counts by id
[MY_CALLBACK]    10
[MY_DRIVER]     5
[MY_SEQUENCE]     5
[RNTST]     1
[TEST_DONE]     1
[UVM/RELNOTES]     1

$finish called from file "/apps/vcsmx/vcs/U-2023.03-SP2//etc/uvm-1.2/src/base/uvm_root.svh", line 527.
$finish at simulation time