Part 2 · Phases & Lifecycle · Intermediate

Multi-Component Objections: Coordinating Many Raisers

Patterns for testbenches with multiple simultaneous objectors — ownership conventions, trace readability, env-level coordination, and the objector registry pattern.

Many components, one phase counter

Real testbenches often have multiple simultaneous objectors: the test, background sequences, config sequences, reset monitors, and VIP agents. All contribute to the same phase root count.

diagram
[PHASE][RUN][UVM] typical multi-raiser testbench

  test.run_phase          raise "main vseq"        ─┐
  bg_traffic_seq          raise in pre_body         ├─ root count = 3
  env.configure (earlier) raise "reg config"       ─┘
                                                    (if still in same phase)

  phase ends when ALL THREE drop
  • Document every objector and its owner in testbench architecture docs.

  • Use distinctive reason strings — 'main vseq' not 'running'.

  • Each objector is independent — one dropping doesn't affect others.


Ownership conventions

diagram
[PHASE][UVM] objection ownership matrix

  Component          Owns objection for          Pattern
  ─────────────────  ──────────────────────────  ─────────────────────
  test               main virtual sequence       test raise/drop
  sequence           its own duration            pre_body/post_body
  env                register config (sub-phase) configure_phase raise/drop
  agent              reset activity (sub-phase)  reset_phase raise/drop
  driver             NEVER (forever loop)        no objection
  monitor            NEVER (forever loop)        no objection
  scoreboard         pending drain (optional)    phase_ready_to_end
  watchdog           NEVER (diagnostic only)     no raise/drop
systemverilog
// GOOD: clear ownership, distinctive reasons
task run_phase(uvm_phase phase);
  fork super.run_phase(phase); join_none

  phase.raise_objection(this, "TEST:main_vseq");
  main_vseq.start(env.v_sqr);
  phase.drop_objection(this, "TEST:main_vseq_done");

  // bg seq self-manages: "SEQ:bg_traffic" in pre_body/post_body
  bg_traffic_seq::type_id::create("bg").start(env.bg_sqr);
endtask
  • Prefix reason strings with component role: TEST:, SEQ:, ENV:, AGT:.

  • One owner per objection — don't split ownership across components.

  • Passive components (driver/monitor) should not raise.


Env-level coordination pattern

systemverilog
class soc_env extends uvm_env;
  // Env owns configure_phase objection — not the test
  task configure_phase(uvm_phase phase);
    reg_cfg_seq cfg;
    phase.raise_objection(this, "ENV:ral_configure");
    cfg = reg_cfg_seq::type_id::create("cfg");
    cfg.regmodel = regmodel;
    cfg.start(reg_sqr);
    phase.drop_objection(this, "ENV:ral_configure_done");
  endtask

  // Env owns reset_phase for all agents
  task reset_phase(uvm_phase phase);
    phase.raise_objection(this, "ENV:wait_all_reset");
    reset_ev.trigger();  // signal all agents
    wait (all_agents_reset_done);
    phase.drop_objection(this, "ENV:all_reset_done");
  endtask
endclass

class soc_test extends uvm_test;
  // Test owns ONLY main_phase duration
  task main_phase(uvm_phase phase);
    fork super.main_phase(phase); join_none
    phase.raise_objection(this, "TEST:main_vseq");
    soc_vseq.start(env.v_sqr);
    phase.drop_objection(this, "TEST:main_vseq_done");
  endtask
endclass
diagram
[PHASE][RUN] layered ownership

  reset_phase:     ENV owns (coordinates all agents)
  configure_phase: ENV owns (RAL programming)
  main_phase:      TEST owns (virtual sequence)
  shutdown_phase:  ENV owns (drain all agents)

  test stays thin — env handles structural phases
  • Env owns structural phases (reset, configure, shutdown).

  • Test owns stimulus duration (main sequence).

  • Clear separation makes traces readable and ownership obvious.


Objector registry pattern (advanced)

For complex testbenches, maintain a registry of active objectors for debug visibility:

systemverilog
class objection_registry extends uvm_component;
  `uvm_component_utils(objection_registry)
  static objection_registry inst;
  string active_objectors[$];

  function void register(string id, string reason);
    active_objectors.push_back({id, ":", reason});
    `uvm_info("OBJ_REG", $sformatf("+%s (%0d active)", id, active_objectors.size()), UVM_HIGH)
  endfunction

  function void unregister(string id);
    // remove matching entry
    `uvm_info("OBJ_REG", $sformatf("-%s (%0d active)", id, active_objectors.size()), UVM_HIGH)
  endfunction

  function void report_phase(uvm_phase phase);
    if (active_objectors.size() > 0)
      `uvm_error("OBJ_REG", $sformatf("leaked objectors: %p", active_objectors))
  endfunction
endclass

// Wrap raise/drop with registry calls in a macro or helper
`define RAISE(phase, comp, reason) \
  phase.raise_objection(comp, reason); \
  objection_registry::inst.register(comp.get_full_name(), reason);

`define DROP(phase, comp, reason) \
  phase.drop_objection(comp, reason); \
  objection_registry::inst.unregister(comp.get_full_name());

Key takeaways

  • Multiple simultaneous objectors are normal — phase ends when ALL drop.

  • Establish ownership conventions: test=main, env=structural, seq=self.

  • Use prefixed reason strings (TEST:, ENV:, SEQ:) for trace readability.

  • Objector registry pattern catches leaks at report_phase.

Common pitfalls

  • No ownership convention — six components raise with generic 'running' reason.

  • Test and env both raise for configure — double ownership confusion.

  • Background sequence without pre_body raise — sim ends when test drops.

  • Registry not updated on error path — false leak report at end.