Part 7 · Environment & Tests · Intermediate

Raise/Drop Policy: Deterministic Objection Ownership

Define objection ownership so tests neither finish early nor hang forever: top-level raise/drop contract, helper-task safety wrappers, and ownership anti-patterns.

Why ownership policy is mandatory

A run can end too early when nothing raises objections, or never end when something raises and never drops. The fix is a strict ownership contract : the test owns top-level lifetime; helper components may use bounded local objections only when justified.

diagram
[UVM][ENV] ownership model

recommended:
  test.run_phase:
    raise once before scenario
    drop once after scenario + local completion checks

optional:
  component-local transient objections for bounded activity

forbidden:
  background thread raises without guaranteed drop path
diagram
[TEST] policy principle

if a component can raise:
  it must prove every control path drops
  including timeout/error/disable paths

if it cannot prove this:
  it should not raise
  • One owner model is easier to audit than distributed objection ownership.

  • If distributed objections are required, enforce wrappers and metrics.

  • Always log raise/drop reasons to support post-failure triage.


Reference implementation with safety wrappers

systemverilog
class base_test extends uvm_test;
  `uvm_component_utils(base_test)
  my_env env;

  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction

  task automatic with_objection(uvm_phase phase, string reason, task body_t);
    phase.raise_objection(this, reason);
    body_t();
    phase.drop_objection(this, {reason, " done"});
  endtask

  task run_main();
    main_vseq vseq = main_vseq::type_id::create("vseq");
    vseq.start(env.v_sqr);
  endtask

  task run_phase(uvm_phase phase);
    phase.raise_objection(this, "main scenario");
    begin
      run_main();
    end
    phase.drop_objection(this, "main scenario done");
  endtask
endclass
systemverilog
task run_phase(uvm_phase phase);
  phase.raise_objection(this, "error-injection campaign");
  fork
    begin
      run_injector_sequence();
    end
    begin
      watchdog_seq w = watchdog_seq::type_id::create("w");
      w.start(env.v_sqr);
    end
  join
  phase.drop_objection(this, "error-injection campaign done");
endtask
diagram
[UVM][ENV] ownership instrumentation

log at UVM_LOW:
  test objection raised reason + timestamp
  test objection dropped reason + timestamp
  phase.is_ended check in report hooks

this makes objection leaks visible quickly
  • Keep the number of top-level objection scopes small and intentional.

  • Use wrapper tasks to standardize raise/drop and reduce missed drops.

  • Never hide objection control inside deeply nested utility methods.


Anti-patterns and governance checks

Common anti-patterns

  1. Raise in one thread, drop in another with weak synchronization.

  2. Drop only on success path, forgetting timeout/error/disable branches.

  3. Allowing monitors or scoreboards to own long-lived objections by default.

  4. Using objections to mask missing readiness checks or checker deadlocks.

diagram
[TEST] code review gates

Gate 1: exactly where is objection raised?
Gate 2: prove drop executes in all exits
Gate 3: is ownership documented in class header?
Gate 4: does +UVM_OBJECTION_TRACE produce expected timeline?
bash
simv +UVM_TESTNAME=smoke_test +UVM_OBJECTION_TRACE +UVM_VERBOSITY=UVM_LOW

Key takeaways

  • Objection ownership is a first-class API contract of each test class.

  • Centralized top-level ownership prevents the majority of end-of-test leaks.

  • Wrapper patterns and trace logs make ownership auditable.

  • Review gates catch objection leaks before long regressions.

Common pitfalls

  • Copy-paste tests with inconsistent objection style across suite.

  • Allowing utility threads to raise objections without lifecycle ownership.

  • Assuming timeout will clean up objection leaks without root-causing them.

  • Not enabling objection trace when triaging hangs.