UVM Phase Objection Mechanism – Complete Guide

UVM Phase Objection Mechanism

Intermediate UVM SystemVerilog 22 min read

The UVM phase objection mechanism is the traffic-light system controlling when simulation phases start, run, and terminate. Misunderstanding it causes the two most common verification bugs: simulations ending at time 0 and simulations hanging forever in CI regressions.

Core Concept: The Airport Gate Analogy

Think of each simulation phase as a departure gate. The flight cannot leave while any passenger holds an “I am not ready” card. When the last card is dropped, the gate closes and simulation advances.

  • phase.raise_objection(this) – Picks up the card. Do not end this phase.
  • phase.drop_objection(this) – Returns the card. You may proceed.

The uvm_objection class inside every uvm_phase maintains a reference counter. The phase ends only when this counter transitions from 1 to 0, followed by an optional drain time window.

Component Hierarchy and Objection Lifecycle
  uvm_top
  |
  +-- my_test  <---- raise_objection() HERE (count: 0 to 1)
      |
      +-- my_env
          +-- my_agent
          |   +-- m_sequencer  <-- sequences run here
          |   +-- m_driver
          |   +-- m_monitor
          +-- my_scoreboard

Phase Timeline:
  [connect_phase] -- [ run_phase: raise to drop + 100ns drain ] -- [extract_phase]

// File: my_test.sv - Production UVM objection pattern
class my_test extends uvm_test;
    `uvm_component_utils(my_test)
    my_env m_env;
    function new(string name = "my_test", uvm_component parent = null);
        super.new(name, parent);
    endfunction
    function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        m_env = my_env::type_id::create("m_env", this);
    endfunction
    // Set drain_time BEFORE run_phase in end_of_elaboration_phase
    function void end_of_elaboration_phase(uvm_phase phase);
        uvm_phase run_ph = uvm_domain::get_uvm_schedule().find_by_name("run");
        run_ph.get_objection().set_drain_time(this, 100ns);
    endfunction
    task run_phase(uvm_phase phase);
        my_directed_sequence seq = my_directed_sequence::type_id::create("seq");
        // STEP 1: Raise BEFORE any fork or time-consuming operation
        phase.raise_objection(this, "Launching stimulus");
        fork
            begin : STIMULUS_ARM
                seq.start(m_env.m_agent.m_sequencer);
                `uvm_info(get_type_name(), "Sequence done.", UVM_MEDIUM)
            end
            begin : WATCHDOG_ARM
                #1_000_000ns; // 1ms timeout
                `uvm_error(get_type_name(), "TIMEOUT")
            end
        join_any
        disable fork; // Kill zombie threads
        phase.drop_objection(this, "Drain window begins");
    endtask
    function void report_phase(uvm_phase phase);
        uvm_report_server svr = uvm_report_server::get_server();
        if (svr.get_severity_count(UVM_ERROR) == 0)
            `uvm_info(get_type_name(), "** TEST PASSED **", UVM_NONE)
        else
            `uvm_error(get_type_name(), "** TEST FAILED **")
    endfunction
endclass : my_test
Simulator Log – Nominal Passing Run (+UVM_OBJECTION_TRACE)
# UVM_INFO @ 0ns: [RNTST] Running test my_test...
# UVM_INFO @ 1ns: [OBJTN_TRC] my_test raised run_phase objection. count=1 total=1
# ... simulation runs, DUT receives transactions ...
# UVM_INFO @ 2000ns: Sequence done.
# UVM_INFO @ 2000ns: [OBJTN_TRC] my_test dropped run_phase objection. count=0 total=0
# UVM_INFO @ 2100ns: [PH_END] run_phase drain complete (100ns). Moving to extract_phase.
# UVM_INFO @ 2100ns: ** TEST PASSED **
# UVM_INFO: 7  UVM_WARNING: 0  UVM_ERROR: 0  UVM_FATAL: 0  RESULT: PASS
Top Objection Pitfalls

Pitfall 1: Raising Inside a Fork – A delta-cycle window exists between fork and raise where count=0 and phase drains. Always raise synchronously first.

Pitfall 2: uvm_fatal Before drop_objection – uvm_fatal calls $finish immediately, drop never executes. Use uvm_error and return cleanly.

Pitfall 3: Mismatched this Reference – Always pair raise_objection(this) with drop_objection(this). Mismatched refs corrupt the debug trace tree.

Interview Focus: Phase Objection Mechanism
Entry Level Synopsys Cadence What happens if no component calls raise_objection during run_phase?

The objection counter stays at 0. The UVM scheduler sees count=0 and immediately terminates run_phase. The simulation advances through all remaining phases in near-zero time. Your DUT receives zero stimulus. Fix: always call phase.raise_objection(this) as the first statement in your test run_phase.

Mid Level Intel AMD What is drain_time and when should it be non-zero?

drain_time is a post-drop window where simulation continues after objection count hits 0. Use it when pipeline latency exists between stimulus completion and scoreboard checking. For example, after the last write, the DUT pipeline may need 5-10 cycles before data appears at the output. Without drain_time, check_phase runs before the monitor captures the last response, producing false UVM_ERRORs. Configure in end_of_elaboration_phase, not run_phase.

Lead / Principal Apple Google Describe centralized vs distributed objection strategy for a 4-agent environment.

Centralized: Test raises once, forks all 4 sequences, waits with join_all, drops once. Simple trace, clear ownership, but inflexible when agents need different completion times.

Distributed: Each agent independently raises and drops its own objection. Highly composable, agents are self-contained. More complex trace but scales to large environments without test modifications.

Best practice: Use distributed with a base class enforcing the raise-before-fork pattern. Centralize drain_time in environment end_of_elaboration_phase.

Scroll to Top