Part 10 · Advanced Topics · Intermediate

Registration Per Instance

Manage callback registration scope with add/delete and iterator utilities such as get_first/get_next, including practical ordering patterns.

Instance-scoped callback attachment

Callbacks are usually attached to specific component instances, not globally to every component of a class. This gives precise control: one driver in one agent can run error injection callbacks while other agents remain clean.

The common API is uvm_callbacks#(host_t, cb_t)::add(host_inst, cb_obj) and ::delete(host_inst, cb_obj). Registration can occur in build/end_of_elaboration/start_of_simulation depending on when host handles are available and policy needs to be activated.

systemverilog
function void my_test::end_of_elaboration_phase(uvm_phase phase);
  super.end_of_elaboration_phase(phase);

  delay_cb   dcb = delay_cb::type_id::create("dcb");
  corrupt_cb ccb = corrupt_cb::type_id::create("ccb");

  uvm_callbacks#(eth_driver, eth_driver_cb)::add(env.tx_agt0.drv, dcb);
  uvm_callbacks#(eth_driver, eth_driver_cb)::add(env.tx_agt0.drv, ccb);
endfunction
diagram
[TEST][ADV] instance targeting

  env.tx_agt0.drv  <-- add delay_cb, corrupt_cb
  env.tx_agt1.drv  <-- no callbacks
  env.rx_agt.drv   <-- add audit_cb only

  Same class, different runtime behavior per instance
  • Attach callbacks to the smallest scope needed for scenario precision.

  • Keep callback ownership in test or env policy layer, not inside host constructor.

  • Track callback handles if you plan to remove them later.


Delete and lifecycle control

Dynamic tests may enable callbacks for part of a run then remove them. Deleting callbacks cleanly avoids policy leakage between phases or sequences.

systemverilog
task burst_error_window_seq::body();
  corrupt_cb ccb = corrupt_cb::type_id::create("ccb");
  uvm_callbacks#(axi_driver, axi_driver_cb)::add(p_sequencer.drv, ccb);

  // Inject errors for first window
  repeat (20) send_random_item();

  // Stop injection
  uvm_callbacks#(axi_driver, axi_driver_cb)::delete(p_sequencer.drv, ccb);

  // Continue clean traffic
  repeat (50) send_random_item();
endtask
diagram
[DRV] callback lifecycle window

  t0  add corrupt_cb
  t1..t20   callbacks active -> faults injected
  t21 delete corrupt_cb
  t22..end  base behavior only

  Controlled activation avoids whole-test contamination
  • Delete using the same callback object handle that was added.

  • Pair add/delete calls in one control flow where possible.

  • For long tests, log activation windows for reproducibility.


Iterating callbacks with get_first/get_next

Beyond macro dispatch, UVM exposes iterator-style access to registered callbacks for inspection and advanced host logic. get_first and get_next allow the host or debug utility to walk callback lists in their current order.

This is useful for diagnostics (print active callback chain), selective invocation patterns, or checking if a specific policy callback is currently attached.

systemverilog
function void axi_driver::dump_callbacks();
  axi_driver_cb cb;
  string chain = "";

  cb = uvm_callbacks#(axi_driver, axi_driver_cb)::get_first(this);
  while (cb != null) begin
    chain = {chain, cb.get_name(), " -> "};
    cb = uvm_callbacks#(axi_driver, axi_driver_cb)::get_next(this);
  end

  `uvm_info("CB_DUMP", $sformatf("active callbacks: %s", chain), UVM_LOW)
endfunction
diagram
[UVM][ADV] iterator view

  callback list on drv instance:
    [0] delay_cb
    [1] corrupt_cb
    [2] audit_cb

  get_first(this) -> delay_cb
  get_next(this)  -> corrupt_cb
  get_next(this)  -> audit_cb
  get_next(this)  -> null
  • Iterator order reflects current registration chain for that host instance.

  • Use iterator APIs mostly for visibility and meta-control, not routine dispatch.

  • Keep iterator-based logic side-effect free unless strongly justified.


Ordering strategies and walkthrough

Order can be treated as policy layering: first normalize transaction, then inject disturbance, then audit. A predictable template reduces accidental behavior shifts across tests.

diagram
[TEST][ADV] recommended callback order template

  1) sanitize/normalize callbacks
     - ensure defaults, clip ranges

  2) scenario perturbation callbacks
     - delay, corruption, protocol pressure

  3) observability callbacks
     - audit logs, counters, custom coverage taps
diagram
[DRV] ordering walkthrough

  tr enters host
    ▼
  normalize_cb.pre_txn
    - fills missing sideband defaults
    ▼
  corrupt_cb.pre_txn
    - flips selected fields for target scenario
    ▼
  drive
    ▼
  audit_cb.post_txn
    - records final transmitted form
systemverilog
function void register_policy_chain(axi_driver drv);
  normalize_cb ncb = normalize_cb::type_id::create("ncb");
  corrupt_cb   ccb = corrupt_cb::type_id::create("ccb");
  audit_cb     acb = audit_cb::type_id::create("acb");

  uvm_callbacks#(axi_driver, axi_driver_cb)::add(drv, ncb);
  uvm_callbacks#(axi_driver, axi_driver_cb)::add(drv, ccb);
  uvm_callbacks#(axi_driver, axi_driver_cb)::add(drv, acb);
endfunction

Key takeaways

  • Callback registration is typically per-instance for precise scenario control.

  • Use add/delete to open and close behavior windows during tests.

  • get_first/get_next provide callback chain introspection and debug visibility.

  • Adopt explicit ordering templates to keep stacked callbacks predictable.

Common pitfalls

  • Adding callback objects and losing handles - cannot reliably delete later.

  • Registering in scattered places - effective order becomes accidental.

  • Assuming class-wide behavior when only one instance has callbacks attached.

  • Iterator use for normal dispatch - reimplements macro path unnecessarily.