Part 10 · Advanced Topics · Intermediate

Objection Propagation Tree

How raise/drop operations propagate from leaves to root, how source counts are tracked, and why hierarchy placement matters.

Mental model: one phase, many sources

Every task phase owns an objection object that tracks counts from many sources. A source is typically a component or sequence handle passed as this in raise/drop calls. UVM tracks both who holds objections and how many objections are still active.

A raise at a leaf component increments the count at that node and propagates upward through each parent until the phase root. A drop walks the same path downward in count. The phase ends only when the root aggregate count reaches zero and any configured post-zero logic completes.

diagram
[UVM] propagation tree example

  uvm_test_top
    └── env
        ├── agt_a
        │   ├── drv_a   (raises)
        │   └── mon_a
        ├── agt_b
        │   ├── drv_b   (raises)
        │   └── mon_b
        └── scb

  If drv_a raises:
    drv_a +1
    agt_a  +1
    env    +1
    test   +1
    root   +1

  If drv_b raises too:
    root count is now 2

  Phase cannot end until BOTH drv_a and drv_b drop.

Source object and count ownership

The source argument in raise_objection(source, description, count) is not cosmetic. It is a bookkeeping key. Re-raising from one source and dropping from a different source can leave residual counts that are hard to spot unless you inspect objection traces by source.

systemverilog
task run_phase(uvm_phase phase);
  phase.raise_objection(this, "start packet burst");
  drive_burst();
  phase.drop_objection(this, "burst complete");
endtask

// Multi-count form (less common but legal)
task start_many(uvm_phase phase, int n);
  phase.raise_objection(this, "reserve n units", n);
  // ... perform n independent work items ...
  phase.drop_objection(this, "release n units", n);
endtask
  • Default count is 1; multi-count APIs exist but need strict accounting discipline.

  • Use the same source handle for matching raise/drop unless you intentionally centralize accounting.

  • Descriptions are for humans in logs; source and count are what affect phase semantics.


Walkthrough: two leaves, staggered completion

This timeline shows why phase completion belongs to the entire hierarchy, not one component.

diagram
[CHECK] timeline with staggered drops

  T0  drv_a.raise(this)  -> root count = 1
  T1  drv_b.raise(this)  -> root count = 2
  T2  drv_a.drop(this)   -> root count = 1  (phase still alive)
  T3  scoreboard still comparing...
  T4  drv_b.drop(this)   -> root count = 0  (candidate to end)
  T5  drain_time window  -> monitors may still publish final txn
  T6  no new objections  -> phase ends

The critical point is T2: one source finished, but the phase is still active because another source remains. This is why objection analysis must always be global when debugging hangs.

Hierarchy placement trade-off

  • Leaf-level raises give precise ownership and better traceability.

  • Top-level raises are simpler but can hide which subcomponent actually leaked a drop.

  • For teams, choose one pattern and enforce it consistently across agents.


Introspection APIs and practical logging

During bring-up, instrument objection state with display_objections() and contextual logs at phase transitions. This quickly distinguishes a real DUT stall from a verification-level objection leak.

systemverilog
function void phase_started(uvm_phase phase);
  if (phase.get_name() == "run") begin
    `uvm_info("OBJ", "run phase started", UVM_LOW)
  end
endfunction

function void phase_ended(uvm_phase phase);
  if (phase.get_name() == "run") begin
    `uvm_info("OBJ", "run phase ended", UVM_LOW)
  end
endfunction

task report_objections(uvm_phase phase);
  if (phase.get_name() == "run")
    phase.phase_done.display_objections();
endtask
diagram
[UVM] practical interpretation

  display_objections says:
    source=uvm_test_top.env.agt_a.drv count=1
  then:
    that exact component still holds the phase open.

  Do not "fix" by force-dropping elsewhere.
  Fix the missing drop where ownership began.

Key takeaways

  • Raise/drop propagates through the component ancestry to the phase root.

  • Source identity matters: mismatched sources can strand counts.

  • Root count reaching zero is necessary, not always sufficient (drain/ready_to_end may still apply).

  • Use objection introspection early in bring-up to validate ownership model.

Common pitfalls

  • Mixing source handles in raise/drop pairs and creating phantom residual counts.

  • Assuming one component finishing implies phase completion.

  • Adding force-drops in tests instead of fixing leak at original owner.