SystemVerilog and UVM Callbacks - Complete Guide
Callbacks provide hooks to inject custom behavior into existing code without modification. They're essential for error injection, coverage collection, and extending testbench functionality in reusable VIPs.
What are Callbacks?
Callbacks are predefined "hooks" that allow you to:
- Extend functionality without modifying original code
- Inject errors or modify data in specific tests
- Add debug/coverage at strategic points
- Keep VIP code clean while allowing customization
Key Benefit: Callbacks let test writers customize VIP behavior without touching the VIP source code.
Callback Flow
sequenceDiagram
participant Test
participant Driver
participant Callback
Test->>Callback: Register custom callback
Test->>Driver: Start test
Driver->>Driver: Get transaction
Driver->>Callback: pre_drive() hook
Callback->>Callback: Modify/inject error
Callback-->>Driver: Return
Driver->>Driver: Drive to DUT
Driver->>Callback: post_drive() hook
Callback->>Callback: Collect coverage
Callback-->>Driver: Return
SystemVerilog Callbacks (OOP Approach)
Using polymorphism to implement callbacks:
Step 1: Define Callback Base Class
// Callback base class with virtual methods (hooks)
class driver_callback;
// Pre-drive hook - called before driving transaction
virtual task pre_drive(ref transaction tr);
// Default: do nothing
endtask
// Post-drive hook - called after driving transaction
virtual task post_drive(ref transaction tr);
// Default: do nothing
endtask
endclass
Step 2: Driver with Callback Support
class driver;
// Queue of registered callbacks
driver_callback callbacks[$];
// Register a callback
function void add_callback(driver_callback cb);
callbacks.push_back(cb);
endfunction
// Main drive task
task drive(transaction tr);
// Call all pre_drive callbacks
foreach (callbacks[i]) begin
callbacks[i].pre_drive(tr);
end
// Actual driving logic
$display("[DRV] Driving: addr=0x%0h data=0x%0h", tr.addr, tr.data);
#10;
// Call all post_drive callbacks
foreach (callbacks[i]) begin
callbacks[i].post_drive(tr);
end
endtask
endclass
Step 3: Custom Callback Implementation
// Error injection callback
class error_inject_callback extends driver_callback;
int error_rate = 10; // 10% error rate
virtual task pre_drive(ref transaction tr);
if ($urandom_range(100) < error_rate) begin
$display("[CB] Injecting error: corrupting data");
tr.data = tr.data ^ 32'hDEAD_BEEF;
end
endtask
endclass
// Coverage callback
class coverage_callback extends driver_callback;
covergroup cg with function sample(transaction tr);
addr_cp: coverpoint tr.addr[31:28] {
bins low = {[0:7]};
bins high = {[8:15]};
}
endgroup
function new();
cg = new();
endfunction
virtual task post_drive(ref transaction tr);
cg.sample(tr);
$display("[CB] Coverage sampled: addr=0x%0h", tr.addr);
endtask
endclass
Step 4: Test Using Callbacks
module tb;
initial begin
driver drv;
transaction tr;
error_inject_callback err_cb;
coverage_callback cov_cb;
drv = new();
err_cb = new();
cov_cb = new();
// Register callbacks
drv.add_callback(err_cb);
drv.add_callback(cov_cb);
// Run transactions
repeat (5) begin
tr = new();
assert(tr.randomize());
drv.drive(tr);
end
end
endmodule
UVM Callback Mechanism
UVM provides a structured callback framework with macros:
flowchart TD
A["uvm_callback"] --> B["Define callback class"]
B --> C["Register with `uvm_register_cb"]
C --> D["Add callbacks in test"]
D --> E["Execute via `uvm_do_callbacks"]
style A fill:#dbeafe,stroke:#3b82f6
style E fill:#d1fae5,stroke:#10b981
Step 1: Define UVM Callback Class
class my_driver_callback extends uvm_callback;
`uvm_object_utils(my_driver_callback)
function new(string name = "my_driver_callback");
super.new(name);
endfunction
// Virtual callback methods
virtual task pre_drive(my_driver drv, ref my_seq_item tr);
// Default: empty
endtask
virtual task post_drive(my_driver drv, ref my_seq_item tr);
// Default: empty
endtask
endclass
Step 2: Driver with UVM Callbacks
class my_driver extends uvm_driver #(my_seq_item);
`uvm_component_utils(my_driver)
`uvm_register_cb(my_driver, my_driver_callback)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
virtual task run_phase(uvm_phase phase);
my_seq_item tr;
forever begin
seq_item_port.get_next_item(tr);
// Execute pre_drive callbacks
`uvm_do_callbacks(my_driver, my_driver_callback, pre_drive(this, tr))
// Drive transaction
drive_item(tr);
// Execute post_drive callbacks
`uvm_do_callbacks(my_driver, my_driver_callback, post_drive(this, tr))
seq_item_port.item_done();
end
endtask
virtual task drive_item(my_seq_item tr);
`uvm_info(get_name(), $sformatf("Driving: %s", tr.convert2string()), UVM_MEDIUM)
// Drive to interface...
#10;
endtask
endclass
Step 3: Custom UVM Callback
// Error injection callback
class error_inject_cb extends my_driver_callback;
`uvm_object_utils(error_inject_cb)
int unsigned error_rate = 20; // 20% error rate
function new(string name = "error_inject_cb");
super.new(name);
endfunction
virtual task pre_drive(my_driver drv, ref my_seq_item tr);
if ($urandom_range(100) < error_rate) begin
`uvm_info("ERR_CB", "Injecting CRC error", UVM_LOW)
tr.crc_error = 1;
end
endtask
endclass
// Scoreboard bypass callback
class scoreboard_bypass_cb extends my_driver_callback;
`uvm_object_utils(scoreboard_bypass_cb)
function new(string name = "scoreboard_bypass_cb");
super.new(name);
endfunction
virtual task post_drive(my_driver drv, ref my_seq_item tr);
if (tr.is_config_write) begin
`uvm_info("SB_CB", "Config write - bypassing scoreboard", UVM_MEDIUM)
tr.skip_scoreboard = 1;
end
endtask
endclass
Step 4: Register Callbacks in Test
class error_injection_test extends base_test;
`uvm_component_utils(error_injection_test)
error_inject_cb err_cb;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
// Create callback instance
err_cb = error_inject_cb::type_id::create("err_cb");
err_cb.error_rate = 50; // 50% for this test
// Register callback with driver
uvm_callbacks #(my_driver, my_driver_callback)::add(env.agent.driver, err_cb);
endfunction
endclass
UVM Callback Macros
| Macro | Usage | Description |
|---|---|---|
`uvm_register_cb | In component class | Registers callback type with component |
`uvm_do_callbacks | In component methods | Executes all registered callbacks |
`uvm_do_callbacks_exit_on | In component methods | Stops if callback returns specified value |
uvm_callbacks#()::add | In test/env | Adds callback instance to component |
uvm_callbacks#()::delete | In test/env | Removes callback from component |
Callbacks vs Factory Overrides
| Aspect | Callbacks | Factory Override |
|---|---|---|
| Purpose | Add behavior at specific points | Replace entire component/object |
| Scope | Targeted hooks | Complete replacement |
| Multiple | Can have multiple callbacks | One override at a time |
| Use case | Error injection, coverage | Different driver/monitor behavior |
| VIP modification | None required | None required |
// Callback: Add error injection at hook point
uvm_callbacks#(my_driver, my_driver_callback)::add(drv, err_cb);
// Factory: Replace entire driver class
my_driver::type_id::set_type_override(custom_driver::get_type());
Common Use Cases
1. Error Injection
virtual task pre_drive(my_driver drv, ref my_seq_item tr);
// Corrupt data randomly
if ($urandom_range(100) < error_rate) begin
tr.data[7:0] = ~tr.data[7:0]; // Bit flip
tr.inject_error = 1;
end
endtask
2. Protocol Violation
virtual task pre_drive(my_driver drv, ref my_seq_item tr);
// Violate timing for negative testing
if (inject_timing_error) begin
tr.setup_time = 0; // Zero setup time
end
endtask
3. Debug Logging
virtual task post_drive(my_driver drv, ref my_seq_item tr);
`uvm_info("DEBUG", $sformatf("Transaction driven: %s", tr.convert2string()), UVM_HIGH)
endtask
4. Functional Coverage
virtual task post_drive(my_driver drv, ref my_seq_item tr);
cg.sample(tr.addr, tr.cmd, tr.size);
endtask
Best Practices
- Plan hooks early: Add callback hooks during VIP development
- Keep hooks strategic: pre/post for major operations
- Use ref parameters: Allow callbacks to modify transactions
- Document hooks: List available callbacks in VIP documentation
- Test callbacks: Verify callback mechanism works correctly
Interview Questions
Q1: What are callbacks in UVM?
Answer: Callbacks are hooks that allow users to inject custom code at predefined points without modifying the original component. They enable error injection, coverage collection, and protocol modifications in tests.
Q2: What macros are used for UVM callbacks?
Answer: `uvm_register_cb registers a callback type with a component, `uvm_do_callbacks executes registered callbacks, and uvm_callbacks#()::add registers a callback instance.
Q3: When would you use callbacks vs factory override?
Answer: Use callbacks for targeted modifications at specific points (error injection, coverage). Use factory override to completely replace a component or object with a different implementation.
Key Takeaways
- Callbacks provide hooks for customization without code modification
- SystemVerilog callbacks use polymorphism (virtual methods)
- UVM provides structured macros:
`uvm_register_cb,`uvm_do_callbacks - Common uses: error injection, coverage, debug logging
- Multiple callbacks can be registered and executed in sequence
- Plan callback hooks during VIP development
Comments (0)
Leave a Comment