Callbacks
Extend component behavior without modifying source code using the UVM callback mechanism.
UVM Callbacks
Callbacks let you inject custom behavior into existing components without modifying their source code. They're a lightweight alternative to factory overrides.
The Idea
Think of callbacks as hooks in a component. The component says: "At this point, I'll call any registered callbacks." You provide the callback implementation separately.
Step 1: Define the Callback Interface
systemverilogclass driver_callback extends uvm_callback; `uvm_object_utils(driver_callback) function new(string name = "driver_callback"); super.new(name); endfunction // Hook: called before driving a transaction virtual task pre_drive(my_driver drv, my_txn txn); // Default: do nothing endtask // Hook: called after driving a transaction virtual task post_drive(my_driver drv, my_txn txn); // Default: do nothing endtask endclass
Step 2: Add Hooks to the Component
systemverilogclass my_driver extends uvm_driver #(my_txn); `uvm_component_utils(my_driver) `uvm_register_cb(my_driver, driver_callback) // ... build_phase, etc. ... task run_phase(uvm_phase phase); my_txn req; forever begin seq_item_port.get_next_item(req); // ── Fire pre-drive callbacks ── `uvm_do_callbacks(my_driver, driver_callback, pre_drive(this, req)) drive_item(req); // ── Fire post-drive callbacks ── `uvm_do_callbacks(my_driver, driver_callback, post_drive(this, req)) seq_item_port.item_done(); end endtask endclass
Step 3: Implement and Register a Callback
systemverilogclass error_inject_cb extends driver_callback; `uvm_object_utils(error_inject_cb) int error_count = 0; function new(string name = "error_inject_cb"); super.new(name); endfunction // Flip a random bit with 5% probability task pre_drive(my_driver drv, my_txn txn); if ($urandom_range(100) < 5) begin txn.data[0] = ~txn.data[0]; error_count++; `uvm_info("ERR_CB", $sformatf( "Injected error #%0d on data=0x%h", error_count, txn.data ), UVM_LOW) end endtask endclass // In the test: class error_test extends base_test; `uvm_component_utils(error_test) function new(string name, uvm_component parent); super.new(name, parent); endfunction function void build_phase(uvm_phase phase); super.build_phase(phase); error_inject_cb err_cb; err_cb = error_inject_cb::type_id::create("err_cb"); uvm_callbacks#(my_driver, driver_callback)::add( env.agent.driver, err_cb ); endfunction endclass
Callbacks vs Factory Overrides
| Aspect | Callbacks | Factory Overrides |
|---|---|---|
| Scope | Add behavior at hook points | Replace entire component |
| Stacking | Multiple callbacks can stack | Only one override active |
| Invasiveness | Minimal — just add hooks | Must extend the class |
| Best for | Logging, error injection, coverage | Different driver logic |
Common Use Cases
| Use Case | Example |
|---|---|
| Error injection | Corrupt data, drop transactions |
| Coverage | Sample coverage at specific points |
| Protocol checking | Verify rules inline |
| Logging | Log every transaction without modifying driver |
| Performance | Measure latency between events |
Best Practices
- Define callbacks with virtual empty methods — safe defaults.
- Place hooks at natural boundaries — pre/post drive, pre/post compare.
- Keep callbacks lightweight — no complex logic.
- Multiple callbacks execute in registration order.
- Use
\uvm_register_cbto declare which callbacks a component supports.