Part 10 · Advanced Topics · Intermediate
uvm_do_callbacks Flow
How callback macros execute, callback ordering rules, and behavior when multiple callback objects are registered.
Macro-driven callback dispatch
The `uvm_do_callbacks macro iterates over all registered callbacks for a host instance and executes a specified hook expression. It is the standard dispatch path because it keeps callback iteration consistent and aligned with UVM callback utility behavior.
Although it looks like one call site, macro expansion effectively performs: fetch callback iterator, cast each callback to the registered type, invoke the requested method, continue through list. This is why hook code should be robust and deterministic.
task axi_driver::send_one(axi_item tr);
`uvm_do_callbacks(axi_driver, axi_driver_cb, pre_send(this, tr))
drive_axi_channels(tr);
`uvm_do_callbacks(axi_driver, axi_driver_cb, post_send(this, tr))
endtask[DRV][ADV] dispatch sequence
send_one(tr)
├─ macro: iterate callbacks on this driver instance
│ ├─ cb0.pre_send(this,tr)
│ ├─ cb1.pre_send(this,tr)
│ └─ cb2.pre_send(this,tr)
├─ base drive_axi_channels(tr)
└─ macro: cb0/cb1/cb2 post_send(this,tr)Use one macro call per hook point to keep flow readable.
Place macros at stable semantic boundaries in host code.
Treat callback dispatch as part of the host state machine contract.
Ordering and multiple callback objects
When multiple callbacks are registered, execution order matters. By default, callbacks run in registration order for a given host instance. If callback A modifies a transaction and callback B validates it, swapping order can change behavior.
Design callbacks to be composable: avoid implicit dependence on being first unless explicitly documented. Where order dependence is unavoidable, centralize registration in one place and comment the intended chain.
[UVM][ADV] multi-callback order example
Registered on drv:
1) delay_cb
2) corrupt_cb
3) audit_cb
pre_send order:
delay_cb.pre_send()
corrupt_cb.pre_send()
audit_cb.pre_send()
post_send order:
delay_cb.post_send()
corrupt_cb.post_send()
audit_cb.post_send()[TEST] order sensitivity illustration
Case A: delay then corrupt
- delay_cb shifts drive timing
- corrupt_cb flips bits after timing decision
Case B: corrupt then delay
- corrupt_cb alters size/flags
- delay_cb computes delay from changed fields
Different order => different scenarioOrder is a behavior dimension - document it like any other API rule.
Prefer orthogonal callbacks to minimize order coupling.
Avoid hidden shared state between callback instances unless required.
Walkthrough: three callbacks on one transaction
Consider a driver transaction with three callbacks: one inserts controlled delay, one perturbs payload for error injection, and one logs metadata for debug correlation.
class delay_cb extends eth_driver_cb;
virtual task pre_drive(eth_driver drv, eth_item tr);
if (tr.class_of_service == LOW) #(20ns);
endtask
endclass
class corrupt_cb extends eth_driver_cb;
virtual function void pre_drive(eth_driver drv, ref eth_item tr);
if (tr.inject_crc_error) tr.force_bad_crc = 1;
endfunction
endclass
class audit_cb extends eth_driver_cb;
virtual function void post_drive(eth_driver drv, eth_item tr);
`uvm_info("AUDIT", $sformatf("seq=%0d crc_bad=%0d", tr.seq_id, tr.force_bad_crc), UVM_MEDIUM)
endfunction
endclass[DRV] one-item walkthrough
incoming tr(seq=42, cos=LOW, inject_crc_error=1)
▼
pre_drive callbacks:
1. delay_cb -> waits 20ns
2. corrupt_cb -> sets force_bad_crc=1
3. audit_cb -> no pre action
▼
base drive frame on interface
▼
post_drive callbacks:
1. delay_cb -> no post action
2. corrupt_cb -> no post action
3. audit_cb -> logs final metadataMix of task/function hooks is valid if host hook signature supports it.
Callback chain can combine timing and data perturbation policies.
Post hooks are ideal for audit trails and scenario accounting.
Macro variants and guard patterns
Some hooks should run conditionally. Instead of burying conditions inside every callback, hosts can guard callback invocation at the hook site. This keeps callback dispatch explicit and reduces unnecessary calls.
task pcie_driver::drive_pkt(pcie_item tr);
if (callbacks_enabled)
`uvm_do_callbacks(pcie_driver, pcie_driver_cb, pre_pkt(this, tr))
drive_core(tr);
if (callbacks_enabled)
`uvm_do_callbacks(pcie_driver, pcie_driver_cb, post_pkt(this, tr))
endtask[UVM][ADV] guarded dispatch
callbacks_enabled = 0
-> base driver behavior only
callbacks_enabled = 1
-> callback chain wraps host behavior
Useful for:
- A/B debug
- performance sensitivity
- deterministic replayGuarding at host call site is clearer than ad-hoc checks in every callback.
Do not skip callbacks silently in some phases unless contract states that.
Add logging when callback guards are toggled to aid debug reproducibility.
Key takeaways
uvm_do_callbacks is the canonical dispatch mechanism for registered callback hooks.
Multiple callbacks run in registration order; order can materially change behavior.
Hook placement in host code defines semantic meaning of callback execution.
Document and test callback chains with realistic stacked configurations.
Common pitfalls
Assuming callback order does not matter - it often does.
Placing hook macros inside unstable internal code paths - API contract becomes fuzzy.
Calling callback methods directly instead of macro iteration - skips registered set.
No regression with multiple callbacks enabled - interactions remain untested.