Part 10 · Advanced Topics · Intermediate

Callback vs Virtual vs Config

Decision framework for choosing callbacks, inheritance overrides, or config_db/settings based on variability, timing, and ownership.

Three extension mechanisms, different intent

UVM environments commonly use three mechanisms to vary behavior: callbacks, virtual method overrides via inheritance, and configuration knobs via config_db/fields. They are complementary, not interchangeable.

Choosing incorrectly often creates technical debt: using inheritance for per-test policy leads to subclass explosion; using callbacks for static constants obscures intent; using config values for timing-sensitive logic can become brittle.

diagram
[UVM][ADV] mechanism intent map

  callbacks:
    runtime pluggable behavior at explicit hook points

  virtual override:
    structural behavior change in component implementation

  config:
    static/dynamic parameters consumed by existing logic
diagram
[TEST] quick chooser

  Need optional policy per scenario?        -> callback
  Need fundamentally different algorithm?   -> virtual override
  Need parameter value (depth, timeout)?    -> config
  • Start from ownership: who should control this behavior and when?

  • Use the least powerful mechanism that cleanly solves the need.

  • Avoid mixing mechanisms for one concern unless design explicitly requires it.


Decision table

Use this table when deciding how to implement a variation request.

diagram
Variation request                       Callback        Virtual Override   Config
──────────────────────────────────────────────────────────────────────────────────────────
Inject occasional bad CRC                  YES             Maybe              No
Change driver arbitration algorithm        No              YES                Maybe
Tune timeout cycles                        No              No                 YES
Add per-test transaction audit hook        YES             Maybe              No
Switch monitor decode strategy entirely    No              YES                Maybe
Enable/disable existing feature flag       Maybe           No                 YES
Add temporary delay window in one test     YES             No                 Maybe
Change packet packing format globally      No              YES                Maybe
diagram
[UVM][ADV] decision dimensions

  Axis 1: variability frequency
    - per test? callbacks/config
    - per product line? virtual/config

  Axis 2: behavioral depth
    - small hook-time tweak? callback
    - full core algorithm swap? virtual

  Axis 3: timing interaction
    - needs hook around protocol action? callback
  • Callbacks excel at modular runtime policy insertion.

  • Virtual overrides suit deep component redesign.

  • Config settings should parameterize existing behavior, not replace logic.


Walkthrough comparisons

Case A - Add occasional checksum faults

Best fit: callback . Reason: per-test optional behavior, timed around drive path, no need to fork driver class.

diagram
[TEST] checksum fault case

  Requirement:
    only some tests inject CRC errors at configurable rates

  callback solution:
    crc_error_cb.pre_drive mutates item conditionally

  virtual override cost:
    new derived driver + factory override plumbing per environment

  config-only gap:
    value exists but no execution hook to act on it

Case B - Replace monitor decode algorithm

Best fit: virtual override . Reason: core algorithm changes end-to-end; hook-level tweaks are insufficient.

diagram
[UVM][ADV] monitor decode case

  Requirement:
    new protocol revision needs different frame reconstruction algorithm

  callback limitation:
    hooks cannot safely replace monitor's core state machine

  virtual override:
    derive new monitor class and override decode pipeline methods

Case C - Tune retries and timeout constants

Best fit: config . Reason: values feed existing code paths; no new behavior objects needed.

systemverilog
`uvm_config_db#(int)::set(this, "env.*", "retry_limit", 3);
`uvm_config_db#(int)::set(this, "env.*", "timeout_cycles", 200);
diagram
[TEST] config case

  requirement = numeric policy
  existing host logic already consumes the policy
  therefore:
    set config and keep code unchanged

Hybrid patterns and cautions

Real environments may combine mechanisms: config enables a callback family, callback performs runtime mutation, and a virtual override provides product-line-specific baseline host behavior. Hybrid is valid when boundaries are clear.

diagram
[UVM][ADV] hybrid layering example

  virtual override:
    custom_axi_driver base for project timing model

  config:
    fault_mode = "crc"
    fault_rate = 10

  callback:
    fault_cb reads config-derived policy and injects accordingly
diagram
[TEST] anti-pattern warning

  If one behavior can be changed by:
    - override
    - callback
    - config
  simultaneously without clear priority,
  then debugging ownership becomes unclear.

  Define explicit precedence rules.
  • Document precedence when mechanisms overlap on same behavior path.

  • Keep hook contracts stable even if host internals evolve.

  • Treat callback as policy plug-in, not backdoor for structural rewrites.

Key takeaways

  • Use callbacks for optional runtime behavior around explicit hook points.

  • Use virtual overrides for deep structural algorithm changes.

  • Use config for parameterizing existing logic without new control flow.

  • A small decision table prevents extension-mechanism misuse and code sprawl.

Common pitfalls

  • Using callbacks for static constants - hides simple configuration intent.

  • Using inheritance for every scenario tweak - class tree explodes quickly.

  • Letting config indirectly trigger undocumented hook behavior - opaque side effects.

  • No precedence rules in hybrid designs - nondeterministic debugging.