Verification

UVM Callbacks

By CourseTron Team ·  7 min read ·  Updated Oct 2026 💬 𝕏 in f
Table of Contents
    ? Print / Save as PDF

    In short: UVM callbacks let you register small hook objects that a driver or monitor invokes at fixed points, so you can inject errors, delays or coverage sampling without editing or subclassing the base component.

    Why callbacks exist

    Every verification team eventually hits the same wall. The driver in your agent works, it is reused across ten tests, and then one test needs to corrupt a CRC, another needs to add random back-pressure, and a third needs to log every transaction to a side file. You could copy the driver and edit it, but now you maintain two drivers. You could use a factory override to swap in a subclass, but that gives you one replacement per type, and combining two behaviors means writing a third subclass.

    Callbacks solve this differently. The base component calls a hook at a defined point, and whatever callback objects are registered at that moment run in order. Behaviors become small, independent, stackable objects. If you are new to the component hierarchy, start with what UVM is and the UVM testbench architecture.

    Key fact: A factory override replaces a component with one alternative. Callbacks add several behaviors to the same component at once, and can be added or removed at runtime.

    The four pieces of the callback mechanism

    The mechanism is built from four classes and macros in the UVM base library (IEEE 1800.2):

    • Callback class: extends uvm_callback and declares virtual hook methods with empty or default bodies.
    • Registration: `uvm_register_cb(my_driver, my_driver_cb) inside the component tells UVM which callback type the component accepts.
    • Call site: `uvm_do_callbacks(my_driver, my_driver_cb, hook(this, item)) loops over every registered callback and invokes the hook.
    • Attachment: uvm_callbacks#(my_driver, my_driver_cb)::add(drv, cb) binds a callback object to one instance, or to all instances of a type.
    Driver with callback hook points my_driver (base) get_next_item(req) drive_to_dut(req) item_done() err_inject_cb delay_cb coverage_cb pre_drive hooks run before the pins move; post_drive hooks run after. CourseTron · coursetron.com
    The driver is untouched; behaviors plug in at pre_drive and post_drive hook points.

    A worked example: error injection into a driver

    Suppose a bus driver sends a packet with a parity bit. We want some tests to flip parity on roughly 2 percent of packets. First, the callback base class and the driver hook point:

    class bus_driver_cb extends uvm_callback;
      `uvm_object_utils(bus_driver_cb)
      function new(string name = "bus_driver_cb"); super.new(name); endfunction
      virtual task pre_drive(bus_driver drv, bus_item item); endtask
      virtual task post_drive(bus_driver drv, bus_item item); endtask
    endclass
    
    class bus_driver extends uvm_driver #(bus_item);
      `uvm_component_utils(bus_driver)
      `uvm_register_cb(bus_driver, bus_driver_cb)
      task run_phase(uvm_phase phase);
        forever begin
          seq_item_port.get_next_item(req);
          `uvm_do_callbacks(bus_driver, bus_driver_cb, pre_drive(this, req))
          drive_to_dut(req);
          `uvm_do_callbacks(bus_driver, bus_driver_cb, post_drive(this, req))
          seq_item_port.item_done();
        end
      endtask
    endclass

    Now the error injector, written once and reusable on any test:

    class parity_err_cb extends bus_driver_cb;
      `uvm_object_utils(parity_err_cb)
      int unsigned pct = 2;
      virtual task pre_drive(bus_driver drv, bus_item item);
        if ($urandom_range(99) < pct) begin
          item.parity = ~item.parity;
          `uvm_info("ERRCB", "parity flipped", UVM_MEDIUM)
        end
      endtask
    endclass
    
    // in the test, after build:
    parity_err_cb cb = new("cb");
    uvm_callbacks#(bus_driver, bus_driver_cb)::add(env.agent.drv, cb);

    Key fact: If the corrupted item is also sent to a scoreboard, the checker must know about the injected error. Otherwise the UVM scoreboard will report false mismatches.

    Callbacks on monitors

    Monitors are the other classic target. A monitor should stay passive and reusable, yet you often want to attach protocol checkers, functional coverage or a debug dumper per project. Place a hook such as item_collected(this, item) immediately before ap.write(item). A coverage callback samples a covergroup, a protocol-check callback asserts ordering rules, and neither requires touching the monitor. Transactions still flow to subscribers through TLM analysis ports as usual; the callback just sees the item first.

    Registration to invocation Test creates callback object cbs::add(inst, cb) queues it Component hits `uvm_do_callbacks Each cb hook runs in order Order is the add() order by default; use add(..., UVM_PREPEND) to run first. Use delete() to detach a callback mid-test. CourseTron · coursetron.com
    Four steps from creating a callback to having it run inside the component.

    Callbacks versus factory overrides versus other options

    TechniqueBest forWeakness
    Factory overrideSwapping an entire component or sequence item typeOne replacement per type; combining behaviors needs new subclasses
    CallbacksAdding stackable, optional behavior at known pointsHook points must be planned in the base component
    Config DB knobsSimple on/off or numeric switchesLogic still lives inside the component
    Virtual interface tricksSignal-level glitchesBypasses transaction abstraction

    A good rule used in many Synopsys VCS, Cadence Xcelium and Siemens Questa projects: use factory overrides when the identity of the component changes, and callbacks when the behavior around an unchanged component changes.

    Animated view: a callback altering a transaction

    Packet passes through the pre_drive hook parity_err_cb flips parity bit P=0 P=1 Green: original item. Red: item after the callback modified it. CourseTron · coursetron.com
    The item enters the hook clean and leaves modified; the driver code never changed.

    Pitfalls interviewers like to probe

    • Forgetting the registration macro. Without `uvm_register_cb the add() call produces a warning and the hook never fires.
    • Hooks in the wrong place. A hook placed after item_done() cannot alter what was driven. Decide hook points at design time.
    • Blocking hooks. A task hook that waits can stall the driver. This is useful for delay injection but dangerous if forgotten.
    • Type versus instance scope. Adding to a type affects every instance, including those in other agents. Pass an instance handle to limit scope.
    • Debuggability. Use verbosity levels inside callbacks so injected faults are traceable in the log.

    A typical regression runs 500 to 2,000 seeds, so keep callbacks lightweight; a callback that does heavy string formatting on every item can double simulation time on a high-throughput bus.

    Key fact: Callbacks are inherited by derived component types, so a callback registered on bus_driver also reaches subclasses of it.

    Where to go next

    Practice by adding a delay callback and a coverage callback to the same driver and confirming they stack. Our structured CourseTron courses include guided labs on this, and if you want instructor-led placement-focused training, see ChipXpert's VLSI training programs.

    Ready to master UVM hands-on? Explore the verification track in our CourseTron courses and build testbenches the way production teams do.

    FAQ

    What is the difference between a UVM callback and a factory override?

    A factory override replaces one type with another at creation time. A callback keeps the original component and lets you attach any number of extra behaviors at predefined hook points.

    Can I use callbacks on sequences?

    Yes, uvm_sequence_base supports callbacks, but most teams use them on drivers, monitors and scoreboards, where hook points are naturally defined.

    Do callbacks work on multiple instances at once?

    Yes. Adding a callback with a null instance handle attaches it to every instance of the type, while passing a specific handle limits it to that one instance.

    Are callbacks still recommended in UVM 1.2 and IEEE 1800.2?

    Yes. They remain part of the standard. Many teams also use configuration objects and analysis components, but callbacks stay the cleanest option for optional, stackable behavior.

    Advertisement