Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • Start Here4
  • UVM Foundations14
  • UVM Phases & Simulation Lifecycle26
    • The Phase System
    • UVM Phases: Build, Connect, Elaborate and Run
    • Objections Deep Dive: Drain Time and Propagation
      • Objection Hierarchy Propagation: Up the Component Tree
      • Objection Counting Rules: Raises, Drops, and Edge Cases
      • set_drain_time Deep Dive: Grace Period After Objections Clear
      • phase_ready_to_end: Last-Chance Phase Extension
      • End-of-Test Strategy: Choosing a Mechanism
      • Objections at Scale
    • Phase Debugging and End-of-Test Signoff
  • Factory & Configuration15
  • Transaction-Level Modeling (TLM)13
  • UVM Sequences & Stimulus21
  • UVM Agents & Protocol VIP21
  • Environment & Test Classes11
  • Checking & Coverage in UVM21
  • UVM Register Abstraction Layer (RAL)22
  • Advanced UVM in Practice13
  • Debug & Regression10
  • Coding Practice47
  • Verification in Industry Practice10
  • Senior Prep: Architecture, Leadership and Interviews32
Loading lesson

‹Objections Deep Dive: Drain Time and Propagation
  1. Topics
  2. UVM Verification
  3. UVM Phases & Simulation Lifecycle
  4. Objections Deep Dive: Drain Time and Propagation
  5. Objections at Scale

Part 2 · Phases & Lifecycle · Intermediate

Objections at Scale

Per-transaction raise and drop cost, set_propagate_mode(0), objection callbacks, sequences versus components as raisers, count versus total, and +UVM_OBJECTION_TRACE.

On this page

    An SoC team profiles a slow regression. The top of the profile is not the DUT, not the scoreboards, not constraint solving: it is uvm_objection::m_raise and m_drop, called millions of times. Every bus agent's monitor raises and drops an objection for every transaction it sees, "so the test cannot end mid-transfer". Turning on +UVM_OBJECTION_TRACE to debug a hang produces a 14 GB log.

    Objections are cheap once and expensive a million times. This lesson gives you the cost model and the tools that keep end of test correct without paying for it on every transaction.

    The idea: a raise is a walk up the tree

    Think of the tallies from Objection Hierarchy Propagation as a chain of people passing a message upstairs. Each raise is a message carried from the raiser to uvm_top, stopping at every floor to update a tally and to tell that floor's manager (raised() callback). A drop carries another message the same way, and when a total reaches zero it also schedules a drain and an all_dropped callback at that level. In a bench ten levels deep, one raise-and-drop pair is twenty tally updates, twenty callback calls, and up to ten scheduled drains. Done once per test, that is nothing. Done once per transaction across 40 agents, it is a measurable share of the run.

    The picture: what one raise costs

    Work per raise or drop

    Propagate mode 1 (default)

    Propagate mode 0

    Tally updates

    Every level from source to uvm_top

    Source and uvm_top only

    raised() / dropped() calls

    On every component on the path

    On the source and uvm_top only

    Drain and all_dropped processing when a total hits 0

    At every level on the path

    At the source and uvm_top

    Trace lines with tracing on

    Two or more per level

    About two per raise or drop

    get_objection_total(env) meaningful?

    Yes

    No: middle levels are not updated

    Tool 1: don't raise per transaction

    The cheapest objection is the one you do not raise. Monitors never raise objections at all; end of test belongs to the test and to checkers. Drivers do not either: they live in forever loops that end with the phase. The per-transaction raise usually comes from a good intention ("don't end in the middle of a burst") that is better served by a single hold: the scoreboard's owed-work count from phase_ready_to_end costs one raise per test, not one per item.

    If a component truly must hold the phase while work is in flight, raise when its outstanding count goes from 0 to 1 and drop when it returns to 0. The count lives in your component; the objection changes only at the edges.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // snippet
    // Raise on the 0 -> 1 edge, drop on the 1 -> 0 edge: two objection calls per busy period.
    function void item_started(uvm_phase phase);
      if (outstanding++ == 0) phase.raise_objection(this, "SB: items in flight");
    endfunction
    
    function void item_finished(uvm_phase phase);
      if (--outstanding == 0) phase.drop_objection(this, "SB: idle");
    endfunction

    Tool 2: set_propagate_mode(0)

    set_propagate_mode(0) on an objection tells UVM to skip the middle levels: a raise or drop updates the source and uvm_top only. The phase still ends correctly, because the decision uses uvm_top's total. What you lose is everything in between: get_objection_total(env) no longer reflects the env's subtree, and raised()/dropped() callbacks on intermediate components stop firing. The library refuses to change the mode while the objection is raised or draining (it reports UVM/BASE/OBJTN/PROP_MODE), so set it before any raise, for example in start_of_simulation_phase on the run phase's objection.

    design.sv
    1
    2
    3
    4
    5
    // snippet
    function void start_of_simulation_phase(uvm_phase phase);
      uvm_phase run_ph = phase.find_by_name("run");     // the run phase node in this schedule
      if (run_ph != null) run_ph.get_objection().set_propagate_mode(0);
    endfunction

    Use it when profiling shows objection traffic in the top entries and nothing in the bench relies on intermediate totals or callbacks. Do not use it as a reflex: a hang dump that shows only the source and uvm_top is harder to read in a deep hierarchy.

    Tool 3: callbacks instead of polling

    Components get three hooks on every objection that passes through them: raised() and dropped() (functions) and all_dropped() (a task, called after that level's drain when its total reaches zero). They are the right place for bookkeeping that would otherwise poll the counts. For objection-wide hooks that are not tied to one component, register a uvm_objection_callback with uvm_objection_cbs_t::add.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // snippet
    class dma_env_dbg extends dma_env;
      `uvm_component_utils(dma_env_dbg)
      function new(string name, uvm_component parent);
        super.new(name, parent);
      endfunction
    
      // Called whenever this env or anything below it raises.
      virtual function void raised(uvm_objection objection, uvm_object source_obj,
                                   string description, int count);
        `uvm_info("ENV_OBJ", $sformatf("%s raised %0d (%s)",
          source_obj.get_full_name(), count, description), UVM_HIGH)
      endfunction
    
      // Called after the env's drain time, when its whole subtree has dropped.
      virtual task all_dropped(uvm_objection objection, uvm_object source_obj,
                               string description, int count);
        `uvm_info("ENV_OBJ", "env subtree idle", UVM_MEDIUM)
      endtask
    endclass

    Keep callback bodies cheap: they run on every raise and drop that passes through. With propagate mode 0, intermediate components do not see these calls at all.

    Sequences versus components as raisers

    Component raiser

    Sequence raiser

    Path to uvm_top

    Component parents

    Its sequencer, then the sequencer's parents

    Lifetime

    Whole simulation

    One start() call; a new object per start

    Typical owner of

    The scenario (test) or the tail (scoreboard)

    Its own duration, via set_automatic_phase_objection(1)

    Cost if overused

    Low: one per component

    High if every short sequence raises: one pair per start

    A sequence library that sets set_automatic_phase_objection(1) on every small sequence produces one raise-drop pair per sequence start, which at thousands of starts adds up. Use it on top-level sequences started from the test, not on the sub-sequences they start.

    Count versus total, once more

    At scale, the query you choose decides whether your tools work. get_objection_count(obj) is the object's own raises; get_objection_total(obj) is its subtree; with no argument, both ask about uvm_top. A watchdog or dashboard should read get_objection_total(), the number that decides the phase. A hang dump should call display_objections(), which lists every object with a non-zero total, sequences included.

    Watch it run: what tracing costs

    +UVM_OBJECTION_TRACE prints every raise and drop at every level. On the DMA bench, the drain test produces 18 trace lines for two raises, because each event is reported at the scoreboard, the env, the test and uvm_top, for dropped and all_dropped separately. Two of them:

    Diagram
    UVM_INFO @ 365000: run_objection [OBJTN_TRC] Object uvm_test_top.env.sb raised 1 run_objection objection(s) (1 word(s) still owed by the DMA): count=1  total=1
    UVM_INFO @ 365000: run_objection [OBJTN_TRC] Object uvm_top added 1 run_objection objection(s) to its total (raised from source object uvm_test_top.env.sb, 1 word(s) still owed by the DMA): count=0  total=1

    Multiply by a per-transaction raiser in a deep SoC tree and the trace becomes unusable. Use it on a short reproducer, or switch it on at a chosen time with phase.get_objection().trace_mode(1) just before the window you care about.

    What breaks: the monitors that slowed the chip

    The opening SoC team had 40 AXI and APB monitors, each raising and dropping per transaction, eight levels deep. Profiling attributed about a fifth of the run time to objection bookkeeping and callbacks. Worse, the raises did not even help: a monitor sees a transaction after it completes on the bus, so its raise covered nothing still in flight.

    They removed every monitor objection, gave each subsystem scoreboard an owed-work hold in phase_ready_to_end, and kept the test's scenario objection. The regression got faster, the end-of-test became exact, and +UVM_OBJECTION_TRACE went back to a few dozen lines per test, short enough to read during a hang.

    Interview

    "Your SoC regression is slow and the profile shows uvm_objection near the top. What do you do?"

    A weak answer: "Turn on set_propagate_mode(0)." It may help, but it treats the symptom and does not ask why there are millions of objection calls.

    A strong answer: "First find who raises per transaction. Usually it is monitors or drivers, which should not raise at all; end of test belongs to the test and the scoreboards. I replace per-item raises with edge-triggered holds or a scoreboard owed-work count in phase_ready_to_end. Then, if objection traffic is still significant and nothing relies on intermediate totals or raised() callbacks, I set propagate mode 0 on the run objection before any raise. For watchdogs I read get_objection_total(), not get_objection_count()."

    Follow-ups: What do you lose with propagate mode 0? (intermediate totals and callbacks; the phase still ends correctly). When does all_dropped run? (after that level's drain, when its total reaches zero).

    Check yourself With propagate mode 0 on the run objection, a scoreboard in env.sb raises. What do get_objection_total(env) and get_objection_total() return?

    get_objection_total(env) returns 0, because the middle levels are skipped. get_objection_total() (uvm_top) returns 1, because the source and uvm_top are always updated. The phase is held correctly; only the intermediate view is gone.

    Check yourself Why does a raise in a monitor's write path rarely protect the end of a test?

    A monitor publishes a transaction after it has completed on the pins, so its raise and drop bracket nothing that is still in flight inside the DUT. It adds cost without covering the real tail. Outstanding work is better counted by the checker, from what the DUT was asked to do.

    Check yourself A virtual sequence starts 5,000 short sequences, each with set_automatic_phase_objection(1). What does that cost, and what would you change?

    5,000 raise-drop pairs, each walking from the sequence through its sequencer to uvm_top with callbacks at every level. Set automatic objection only on the top-level virtual sequence started by the test; the sub-sequences finish inside its duration and need no objection of their own.

    Next: the counts are only half of debugging. Objection triage and hang forensics turns these tools into a playbook for the test that will not end.

    Related topics

    • Objection triage and hang forensics

    • Objection hierarchy propagation

    • End-of-test strategy

    Previous

    38 / 280

    Next: Phase Debugging and End-of-Test Signoff

    Up next

    Up nextPhase Debugging and End-of-Test Signoff

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Objection Hierarchy Propagation: Up the Component Tree
    • Objection Counting Rules: Raises, Drops, and Edge Cases
    • set_drain_time Deep Dive: Grace Period After Objections Clear