Part 10 · Advanced Topics · Intermediate

Raise/Drop Discipline

Matched objection patterns for tests and sequences, safe handling of asynchronous branches, and anti-patterns that create hangs.

The invariant: every raise must have one drop

Objection correctness is fundamentally an accounting problem. The invariant is strict: each raise contributes a unit of outstanding work and must be balanced by exactly one drop from the same ownership path.

Most hangs are not simulator issues. They are plain accounting leaks: an early return path skipped the drop, a forked branch never completed, or control flow threw an error between raise and drop.

diagram
[CHECK] matching discipline

  GOOD:
    raise -> do work -> drop
    raise -> fork ... join -> drop

  BAD:
    raise -> return (no drop)
    raise in parent, drop in child with different source ownership
    drop called twice for one raise (underflow / warnings / undefined intent)

Canonical test-level pattern

At test level, objection ownership is usually centralized: run-phase test code raises once, starts one or more top sequences, waits for completion, then drops.

systemverilog
task run_phase(uvm_phase phase);
  main_vseq vseq;
  phase.raise_objection(this, "main scenario start");

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

  phase.drop_objection(this, "main scenario done");
endtask

When to prefer test-level ownership

  • Top-level scenario orchestration with one entry/exit point.

  • Predictable runtime envelope in CI where test controls all sequence launches.

  • Teams with strict convention to avoid mixed ownership across env layers.

When to avoid test-level-only ownership

  • Deep asynchronous workers may continue after main sequence returns.

  • Passive scoreboards need conditional hold near phase end.

  • Per-agent independent background traffic requires local ownership.


Sequence-level objections and async hazards

Sequence-level objections can be valid, but only when ownership is deliberate. The biggest hazard is spawning async work inside sequence body and returning before that work drains.

systemverilog
class traffic_seq extends uvm_sequence #(bus_txn);
  `uvm_object_utils(traffic_seq)

  task body();
    if (starting_phase != null)
      starting_phase.raise_objection(this, "traffic_seq running");

    repeat (50) begin
      req = bus_txn::type_id::create("req");
      start_item(req);
      assert(req.randomize());
      finish_item(req);
    end

    if (starting_phase != null)
      starting_phase.drop_objection(this, "traffic_seq done");
  endtask
endclass
diagram
[UVM] async leak pattern

  body():
    raise
    fork
      send_stream_A();
      send_stream_B();  // may block on handshake
    join_none
    drop   <-- too early, background still running

  Correct:
    either join all branches before drop,
    or move ownership to manager component that tracks branch completion.
  • If you use join_none, you need explicit completion tracking before dropping.

  • Do not rely on simulation end timeout to hide missing drops.

  • Sequence ownership should be documented because it is easy to mix with test ownership accidentally.


Defensive templates for robust accounting

SystemVerilog lacks exception-safe finally semantics, so defensive coding means structuring control flow so drop is on every path.

systemverilog
task run_phase(uvm_phase phase);
  bit raised = 0;
  phase.raise_objection(this, "defensive pattern");
  raised = 1;

  // Use guarded blocks; keep returns centralized.
  begin
    if (!cfg.enable_feature) begin
      `uvm_warning("CFG", "feature disabled")
    end else begin
      run_feature_flow();
    end
  end

  if (raised)
    phase.drop_objection(this, "defensive pattern complete");
endtask
diagram
[CHECK] review checklist before commit

  1. Is every raise paired with one drop?
  2. Can any return/disable/fork path bypass drop?
  3. Are source handles consistent?
  4. Is ownership location (test vs sequence vs component) documented?
  5. Do logs include reason strings for trace readability?

Key takeaways

  • Objection bugs are accounting bugs; design with explicit ownership.

  • Choose one ownership style per flow: test-level, sequence-level, or component-level.

  • Asynchronous forks require explicit completion tracking before drop.

  • Defensive run_phase templates prevent silent control-flow leaks.

Common pitfalls

  • join_none followed by immediate drop while work is still live.

  • Mixed ownership where test raises and nested sequence drops unpredictably.

  • Early returns and error branches that bypass drop logic.