A UVM objection is how a testbench decides when a time-consuming phase, such as run_phase, is allowed to end. Any component or sequence that still has work to do raises an objection to the phase ending, and drops it when the work is finished. When the last objection is dropped, the phase ends and the testbench moves on to the next phase.

A testbench has many components running in parallel, and none of them knows when the others are done. Rather than one central controller deciding when to stop, UVM lets each part of the testbench vote. The phase only ends when nobody is objecting any more.

9 min read | Intermediate Level

What You'll Learn

  • Explain how objections decide when a task phase ends
  • Raise and drop objections correctly from a test or a sequence
  • Use drain time to let in-flight transactions finish
  • Debug a simulation that ends too early or never ends

How Objections Work

Every task phase - run_phase and the run-time phases such as reset_phase and main_phase - has its own built-in uvm_objection object. It behaves like a counter. Raising an objection adds one, dropping it subtracts one, and the phase can end only when the count is back to zero.

The counts are also hierarchical. When m_driver raises an objection, its parents m_agent, m_env and uvm_test_top each add it to their total count, all the way up to uvm_top. That is what makes the trace and display output below show who is holding the phase open.

A task phase ends as soon as nothing is objecting. If no component raises an objection at all, run_phase ends at time 0 and your stimulus never runs.

Syntax

You do not create uvm_objection objects yourself. Use the phase handle passed into every phase method.


// Methods of uvm_phase
virtual function void raise_objection (uvm_object obj, string description = "", int count = 1);
virtual function void drop_objection  (uvm_object obj, string description = "", int count = 1);
function uvm_objection get_objection();                // the phase's objection object

// Method of uvm_objection
function void set_drain_time (uvm_object obj = null, time drain);
  • obj is normally this, so traces show which component or sequence raised the objection.
  • description is optional, but a short message such as "sending reset sequence" makes hang debugging much easier.
  • count lets one call raise or drop several objections at once. It almost always stays at 1.

Raising an Objection in a Test

The usual place to raise an objection is the test, around the stimulus it starts. The test knows when its top-level sequence has finished, so it is in the best position to say the phase may end.


class my_test extends uvm_test;
  `uvm_component_utils(my_test)

  function new(string name = "my_test", uvm_component parent = null);
    super.new(name, parent);
  endfunction

  // Rest of the code (build_phase creates m_env)

  virtual task main_phase(uvm_phase phase);
    my_virtual_seq m_vseq = my_virtual_seq::type_id::create("m_vseq");

    phase.raise_objection(this, "running main stimulus");   // keep main_phase alive
    m_vseq.start(m_env.m_vseqr);                             // blocks until the sequence ends
    phase.drop_objection(this, "main stimulus done");        // main_phase may now end
  endtask
endclass

Because start() blocks until the sequence completes, the objection covers exactly the time the stimulus is running.

Example: Who Keeps the Phase Alive?

This example raises objections in two places to show how they combine. The test objects while a 200 ns sequence runs, and the driver objects for 400 ns. Raising an objection in a driver is unusual - it is done here only for illustration.


`include "uvm_macros.svh"
import uvm_pkg::*;

// Let's create a driver that attempts to raise/drop objection
class my_driver extends uvm_driver;
  `uvm_component_utils(my_driver)

  function new(string name="my_driver", uvm_component parent=null);
    super.new(name, parent);
  endfunction

  virtual task run_phase(uvm_phase phase);
    uvm_objection  l_obj = phase.get_objection();
    `uvm_info(get_type_name(), $sformatf("Driver started"), UVM_MEDIUM)

    // Raise an objection in the driver just for illustration
    // Usually the driver waits until it gets an item from the
    // sequencer and does not have to raise any objections.
    phase.raise_objection(this);
    #400;
    phase.drop_objection(this);
    l_obj.display_objections();

    `uvm_info(get_type_name(), $sformatf("Driver ended"), UVM_MEDIUM)
  endtask
endclass

// Let the driver be instantiated inside an agent
class my_agent extends uvm_agent;
  `uvm_component_utils(my_agent)

  my_driver 	m_driver;

  function new(string name="my_agent", uvm_component parent=null);
    super.new(name, parent);
  endfunction

  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);

    m_driver = my_driver::type_id::create("m_driver", this);
  endfunction
endclass

// Define a virtual sequencer to run the test stimulus/hold other handles
class my_vseqr extends uvm_sequencer;
  `uvm_component_utils(my_vseqr)
  function new(string name="my_vseqr", uvm_component parent=null);
    super.new(name, parent);
  endfunction

endclass

// Define an environment to hold the agent/virtual sequencer
class my_env extends uvm_env;
  `uvm_component_utils(my_env)

  my_vseqr  	 	m_vseqr;
  my_agent 	 		m_agent;


  function new(string name="my_env", uvm_component parent=null);
    super.new(name, parent);
  endfunction

  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);

    m_agent = my_agent::type_id::create("m_agent", this);
    m_vseqr = my_vseqr::type_id::create("m_seqr", this);
  endfunction
endclass

// Define the test stimulus that consumes time
class my_seq extends uvm_sequence;
  `uvm_object_utils(my_seq)
  function new(string name="my_seq");
    super.new(name);
  endfunction

  virtual task body();
    `uvm_info(get_type_name(), $sformatf("Sequence started"), UVM_MEDIUM)
    #200;
    `uvm_info(get_type_name(), $sformatf("Sequence ended"), UVM_MEDIUM)
  endtask
endclass

// Define the test which starts the virtual sequence/test stimulus
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

  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);

    m_env = my_env::type_id::create("m_env", this);
  endfunction

  virtual task run_phase(uvm_phase phase);
    my_seq l_seq = my_seq::type_id::create("my_seq");

    // Lets get a handle to the objection inorder to print stats
    uvm_objection l_obj = phase.get_objection();

    super.run_phase(phase);

    // Let the test raise an objection and then display all objections
    phase.raise_objection(this);
    l_obj.display_objections();

    l_seq.start(m_env.m_vseqr);
    l_obj.display_objections();

    phase.drop_objection(this);
  endtask

endclass

// Let a clock run forever in the testbench
module tb;
  reg clk;

  always #10 clk = ~clk;

  initial begin
    clk = 0;
    run_test("my_test");
  end
endmodule

The simulation was run with +UVM_OBJECTION_TRACE, which prints a line for every raise and drop. display_objections() prints the counts as a hierarchy table.

Output
UVM_INFO @ 0: reporter [RNTST] Running test my_test...
UVM_INFO @ 0: run [OBJTN_TRC] Object uvm_test_top raised 1 objection(s): count=1  total=1
UVM_INFO @ 0: run [OBJTN_TRC] Object uvm_top added 1 objection(s) to its total (raised from source object uvm_test_top): count=0  total=1
UVM_INFO /xcelium23.09/tools/methodology/UVM/CDNS-1.2/sv/src/base/uvm_objection.svh(1072) @ 0: run [UVM/OBJ/DISPLAY] The total objection count is 1
---------------------------------------------------------
Source  Total
Count   Count   Object
---------------------------------------------------------
0       1       uvm_top
1       1         uvm_test_top
---------------------------------------------------------

UVM_INFO testbench.sv(14) @ 0: uvm_test_top.m_env.m_agent.m_driver [my_driver] Driver started
UVM_INFO @ 0: run [OBJTN_TRC] Object uvm_test_top.m_env.m_agent.m_driver raised 1 objection(s): count=1  total=1
UVM_INFO @ 0: run [OBJTN_TRC] Object uvm_test_top.m_env.m_agent added 1 objection(s) to its total (raised from source object ): count=0  total=1
UVM_INFO @ 0: run [OBJTN_TRC] Object uvm_test_top.m_env added 1 objection(s) to its total (raised from source object ): count=0  total=1
UVM_INFO @ 0: run [OBJTN_TRC] Object uvm_test_top added 1 objection(s) to its total (raised from source object uvm_test_top.m_env.m_agent.m_driver): count=1  total=2
UVM_INFO @ 0: run [OBJTN_TRC] Object uvm_top added 1 objection(s) to its total (raised from source object uvm_test_top.m_env.m_agent.m_driver): count=0  total=2
UVM_INFO testbench.sv(82) @ 0: uvm_test_top.m_env.m_seqr@@my_seq [my_seq] Sequence started
UVM_INFO testbench.sv(84) @ 200: uvm_test_top.m_env.m_seqr@@my_seq [my_seq] Sequence ended
UVM_INFO /xcelium23.09/tools/methodology/UVM/CDNS-1.2/sv/src/base/uvm_objection.svh(1072) @ 200: run [UVM/OBJ/DISPLAY] The total objection count is 2
---------------------------------------------------------
Source  Total
Count   Count   Object
---------------------------------------------------------
0       2       uvm_top
1       2         uvm_test_top
0       1           m_env
0       1             m_agent
1       1               m_driver
---------------------------------------------------------

UVM_INFO @ 200: run [OBJTN_TRC] Object uvm_test_top dropped 1 objection(s): count=0  total=1
UVM_INFO @ 200: run [OBJTN_TRC] Object uvm_top subtracted 1 objection(s) from its total (dropped from source object uvm_test_top): count=0  total=1
UVM_INFO @ 400: run [OBJTN_TRC] Object uvm_test_top.m_env.m_agent.m_driver dropped 1 objection(s): count=0  total=0
UVM_INFO /xcelium23.09/tools/methodology/UVM/CDNS-1.2/sv/src/base/uvm_objection.svh(1072) @ 400: run [UVM/OBJ/DISPLAY] The total objection count is 1
---------------------------------------------------------
Source  Total
Count   Count   Object
---------------------------------------------------------
0       1       uvm_top
0       1         uvm_test_top
0       1           m_env
0       1             m_agent
---------------------------------------------------------

UVM_INFO testbench.sv(24) @ 400: uvm_test_top.m_env.m_agent.m_driver [my_driver] Driver ended
UVM_INFO @ 400: run [OBJTN_TRC] Object uvm_test_top.m_env.m_agent.m_driver all_dropped 1 objection(s): count=0  total=0
UVM_INFO @ 400: run [OBJTN_TRC] Object uvm_test_top.m_env.m_agent subtracted 1 objection(s) from its total (dropped from source object ): count=0  total=0
UVM_INFO @ 400: run [OBJTN_TRC] Object uvm_test_top.m_env.m_agent subtracted 1 objection(s) from its total (all_dropped from source object ): count=0  total=0
UVM_INFO @ 400: run [OBJTN_TRC] Object uvm_test_top.m_env subtracted 1 objection(s) from its total (dropped from source object ): count=0  total=0
UVM_INFO @ 400: run [OBJTN_TRC] Object uvm_test_top.m_env subtracted 1 objection(s) from its total (all_dropped from source object ): count=0  total=0
UVM_INFO @ 400: run [OBJTN_TRC] Object uvm_test_top subtracted 1 objection(s) from its total (dropped from source object uvm_test_top.m_env.m_agent.m_driver): count=0  total=0
UVM_INFO @ 400: run [OBJTN_TRC] Object uvm_test_top subtracted 1 objection(s) from its total (all_dropped from source object uvm_test_top.m_env.m_agent.m_driver): count=0  total=0
UVM_INFO @ 400: run [OBJTN_TRC] Object uvm_top subtracted 1 objection(s) from its total (dropped from source object uvm_test_top.m_env.m_agent.m_driver): count=0  total=0
UVM_INFO @ 400: run [OBJTN_TRC] Object uvm_top subtracted 1 objection(s) from its total (all_dropped from source object uvm_test_top.m_env.m_agent.m_driver): count=0  total=0
UVM_INFO /xcelium23.09/tools/methodology/UVM/CDNS-1.2/sv/src/base/uvm_objection.svh(1271) @ 400: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase
UVM_INFO /xcelium23.09/tools/methodology/UVM/CDNS-1.2/sv/src/base/uvm_report_server.svh(847) @ 400: reporter [UVM/REPORT/SERVER]
--- UVM Report Summary ---

Reading the log against the timeline: at 0 ns the test and the driver both raise, so uvm_top holds a total of 2. At 200 ns the sequence finishes and the test drops, leaving a total of 1. The phase still cannot end, because m_driver is objecting. At 400 ns the driver drops, the total reaches 0, and the TEST_DONE message reports that run is moving on to extract.

Quick Check

Q: If the driver in this example did not raise an objection, when would the run phase end?

Show Answer

At 200 ns, when the test drops its objection after the sequence finishes. The driver would simply be stopped in the middle of its #400 delay, because the phase's processes are killed when the phase ends.

Objections in Sequences

A sequence can also hold the phase open, which is useful when sequences are started as default sequences and no test code wraps them. A sequence gets the phase through get_starting_phase(), which returns null unless the phase was set - automatically for a default sequence, or by calling set_starting_phase() before start().

Raising Explicitly in body()


// Rest of the code: my_seq_item is a simple uvm_sequence_item with one rand field

class my_sequence extends uvm_sequence #(my_seq_item);
  `uvm_object_utils (my_sequence)

  rand int num_items = 5;
  constraint c_num_items { num_items > 0; num_items <= 20; }

  function new (string name = "my_sequence");
    super.new(name);
  endfunction

  virtual task body();
    uvm_phase phase = get_starting_phase();   // null unless the starting phase was set

    if (phase != null)
      phase.raise_objection(this, "Sequence generating stimulus");

    repeat (num_items) #10;                    // stand-in for the real stimulus

    if (phase != null)
      phase.drop_objection(this, "Sequence finished stimulus generation");
  endtask
endclass

class my_test extends uvm_test;
  `uvm_component_utils(my_test)

  function new(string name="my_test", uvm_component parent=null);
    super.new(name, parent);
  endfunction

  virtual task run_phase(uvm_phase phase);
    my_sequence l_seq = my_sequence::type_id::create("l_seq");
    `uvm_info(get_type_name(), $sformatf("my_test started"), UVM_MEDIUM)

    l_seq.set_starting_phase(phase);   // give the sequence the phase to object to
    l_seq.start(null);

    `uvm_info(get_type_name(), $sformatf("my_test ended"), UVM_MEDIUM)
  endtask
endclass
Output
UVM_INFO @ 0: reporter [RNTST] Running test my_test...
UVM_INFO testbench.sv(64) @ 0: uvm_test_top [my_test] my_test started
UVM_INFO @ 0: run [OBJTN_TRC] Object l_seq raised 1 objection(s) (Sequence generating stimulus): count=1  total=1
UVM_INFO @ 0: run [OBJTN_TRC] Object uvm_top added 1 objection(s) to its total (raised from source object l_seq, Sequence generating stimulus): count=0  total=1
UVM_INFO @ 50: run [OBJTN_TRC] Object l_seq dropped 1 objection(s) (Sequence finished stimulus generation): count=0  total=0
UVM_INFO @ 50: run [OBJTN_TRC] Object l_seq all_dropped 1 objection(s) (Sequence finished stimulus generation): count=0  total=0
UVM_INFO @ 50: run [OBJTN_TRC] Object uvm_top subtracted 1 objection(s) from its total (dropped from source object l_seq, Sequence finished stimulus generation): count=0  total=0
UVM_INFO @ 50: run [OBJTN_TRC] Object uvm_top subtracted 1 objection(s) from its total (all_dropped from source object l_seq, Sequence finished stimulus generation): count=0  total=0
UVM_INFO /xcelium23.09/tools/methodology/UVM/CDNS-1.2/sv/src/base/uvm_objection.svh(1271) @ 50: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase
UVM_INFO testbench.sv(69) @ 50: uvm_test_top [my_test] my_test ended
UVM_INFO /xcelium23.09/tools/methodology/UVM/CDNS-1.2/sv/src/base/uvm_report_server.svh(847) @ 50: reporter [UVM/REPORT/SERVER]
--- UVM Report Summary ---

Five items of 10 ns each give the drop at 50 ns. The description strings appear in the trace, which is exactly what you want when hunting for the objection that never dropped.

Automatic Phase Objection

For the common case where a sequence should object for its whole lifetime, call set_automatic_phase_objection(1) in its constructor. If the starting phase is set, UVM raises the objection just before pre_start() and drops it just after post_start().


class my_auto_sequence extends uvm_sequence #(my_seq_item);
  `uvm_object_utils (my_auto_sequence)

  rand int num_items = 5;
  constraint c_num_items { num_items > 0; num_items <= 20; }

  function new (string name = "my_auto_sequence");
    super.new(name);
    set_automatic_phase_objection(1);   // raise before pre_start(), drop after post_start()
  endfunction

  virtual task body();
    `uvm_info (get_type_name(), $sformatf("sequence started"), UVM_MEDIUM)

    // No explicit raise_objection or drop_objection calls are needed here,
    // as they are handled automatically by the base class
    repeat (num_items) begin
      #10; // Simulate some time passing between transactions
    end

    `uvm_info (get_type_name(), $sformatf("sequence ended"), UVM_MEDIUM)
  endtask
endclass

// Rest of the code: my_test is the same as above, starting my_auto_sequence instead
Output
UVM_INFO @ 0: reporter [RNTST] Running test my_test...
UVM_INFO testbench.sv(57) @ 0: uvm_test_top [my_test] my_test started
UVM_INFO @ 0: run [OBJTN_TRC] Object l_seq raised 1 objection(s) (automatic phase objection): count=1  total=1
UVM_INFO @ 0: run [OBJTN_TRC] Object uvm_top added 1 objection(s) to its total (raised from source object l_seq, automatic phase objection): count=0  total=1
UVM_INFO testbench.sv(34) @ 0: reporter@@l_seq [my_auto_sequence] sequence started
UVM_INFO testbench.sv(42) @ 50: reporter@@l_seq [my_auto_sequence] sequence ended
UVM_INFO @ 50: run [OBJTN_TRC] Object l_seq dropped 1 objection(s) (automatic phase objection): count=0  total=0
UVM_INFO @ 50: run [OBJTN_TRC] Object l_seq all_dropped 1 objection(s) (automatic phase objection): count=0  total=0
UVM_INFO @ 50: run [OBJTN_TRC] Object uvm_top subtracted 1 objection(s) from its total (dropped from source object l_seq, automatic phase objection): count=0  total=0
UVM_INFO @ 50: run [OBJTN_TRC] Object uvm_top subtracted 1 objection(s) from its total (all_dropped from source object l_seq, automatic phase objection): count=0  total=0
UVM_INFO /xcelium23.09/tools/methodology/UVM/CDNS-1.2/sv/src/base/uvm_objection.svh(1271) @ 50: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase
UVM_INFO testbench.sv(62) @ 50: uvm_test_top [my_test] my_test ended
UVM_INFO /xcelium23.09/tools/methodology/UVM/CDNS-1.2/sv/src/base/uvm_report_server.svh(847) @ 50: reporter [UVM/REPORT/SERVER]
--- UVM Report Summary ---

Never enable automatic phase objection on a sequence whose body() contains a forever loop. The objection is dropped only when the sequence ends, so the phase never ends and the simulation hangs.

Drain Time

When the last objection drops, transactions may still be in flight: a response the DUT has not produced yet, or a monitor that has not finished sampling. Drain time makes the objection wait a little longer before it lets the phase end.


virtual task run_phase(uvm_phase phase);
  phase.get_objection().set_drain_time(this, 100ns);   // wait 100ns after the count hits 0
  phase.raise_objection(this);
  // Rest of the code (start stimulus)
  phase.drop_objection(this);
endtask

If any component raises a new objection during the drain time, the countdown is cancelled and the phase stays open. Set drain time once, in the test or environment, rather than scattering delays at the end of individual sequences.

Debugging Objections

  • +UVM_OBJECTION_TRACE on the command line prints every raise and drop with its description, as in the logs above.
  • +UVM_PHASE_TRACE traces phase starts and ends, which helps when a phase seems to end too early.
  • display_objections() on the objection object prints the current hierarchy of counts at any point in your code.
  • For finer control, extend uvm_objection_callback to react to raises and drops, and use clear() on an objection to release all counts at once.

Common Mistakes

Mistake #1: Raising an objection that is never dropped

A raise inside a branch that returns early, or a sequence that blocks forever, leaves the count above zero, and the simulation hangs until a timeout. Make every raise have exactly one matching drop on every path, and use descriptive strings so +UVM_OBJECTION_TRACE shows the culprit.

Mistake #2: Raising no objection at all

Starting a sequence in run_phase without an objection lets the phase end at time 0. The log shows the test starting and the report summary straight after, with no stimulus in between.

Mistake #3: Calling get_starting_phase() on a manually started sequence

A sequence started with start() has no starting phase unless the caller set one with set_starting_phase(). Without a null check, phase.raise_objection() dereferences a null handle.

Mistake #4: Raising objections in every component

Monitors, drivers and scoreboards that each raise and drop objections for every transaction add overhead and make the end of test hard to reason about. Keep objections in the test or top-level sequence, and in the scoreboard only if it must wait for outstanding checks.

Summary

In this article, you learned:

  • Each task phase has a built-in objection, and the phase ends when its count reaches zero
  • Objections are raised and dropped through the phase handle, ideally with a description
  • Counts propagate up the hierarchy, so traces show which component is holding a phase open
  • Sequences can object through get_starting_phase() or automatically with set_automatic_phase_objection(1)
  • Drain time delays the end of a phase so in-flight transactions can finish
Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5