Creating user-defined phases in UVM means writing your own phase class and inserting it into a phase schedule, so that components can run code at a point that none of the standard phases covers. It takes three steps: extend a phase base class, insert the phase into a domain, and implement the matching method in the components that should run it.

Custom phases are a power tool to use sparingly. Every component that should take part needs to know about the new phase, and a reusable verification component that depends on a project-specific phase is no longer reusable. Before adding one, check whether an existing phase, a sequence or an event would do the job.

7 min read | Intermediate Level

What You'll Learn

  • Choose the right base class for a custom function or task phase
  • Write a phase class using the singleton pattern UVM expects
  • Insert the new phase at an exact point in the common or run-time schedule
  • Call the new phase's method in the components that support it

Where a New Phase Can Go

UVM keeps its phases in domains, which are schedules of phases that run in a fixed order. The common domain holds build through final. The UVM domain holds the twelve run-time phases from pre_reset to post_shutdown, which run in parallel with run_phase.

A new phase is inserted between two existing phases of one of those domains. Match the phase type to its neighbours. Between the function phases of the common domain, use a function phase. Among the run-time phases, use a task phase, because that is where simulation time passes and objections apply.

Step 1: Define the Phase Class

Extend one of three base classes, depending on what the phase does.


class my_PHASE_phase extends uvm_topdown_phase;   // function phase, parents before children
class my_PHASE_phase extends uvm_bottomup_phase;  // function phase, children before parents
class my_PHASE_phase extends uvm_task_phase;      // task phase, consumes simulation time

A function phase overrides exec_func() and a task phase overrides exec_task(). UVM calls that method once for every component in the hierarchy, passing the component as an argument.


virtual function void exec_func (uvm_component comp, uvm_phase phase);
virtual task          exec_task (uvm_component comp, uvm_phase phase);

Each phase is a singleton: exactly one instance exists, reached through a static get() method. The constructor is protected so that nobody creates a second copy by accident. Here is a complete function phase named user.


class user_phase extends uvm_topdown_phase;

  local static user_phase m_inst;                // the one and only instance

  // Protected constructor; the name is what phase.get_name() returns
  protected function new(string name = "user");
    super.new(name);
  endfunction

  // Static accessor for the singleton
  static function user_phase get();
    if (m_inst == null)
      m_inst = new();
    return m_inst;
  endfunction

  // Called once per component. Only components that define user_phase() take part.
  virtual function void exec_func(uvm_component comp, uvm_phase phase);
    my_test t;
    if ($cast(t, comp))
      t.user_phase(phase);
  endfunction
endclass

The $cast is needed because uvm_component has no user_phase() method. Only components of a type that declares it can be called. In a larger testbench you would cast to a common base class that all participating components extend.

Step 2: Insert the Phase into a Domain

Get a handle to the domain, find the phases on either side of the insertion point, and call add(). Naming both neighbours places the phase exactly between them.


uvm_domain dm     = uvm_domain::get_common_domain();       // build ... final
uvm_phase  before = dm.find(uvm_connect_phase::get());
uvm_phase  after  = dm.find(uvm_end_of_elaboration_phase::get());

dm.add(user_phase::get(), .after_phase(before), .before_phase(after));

The arguments of add() are the new phase, then optionally with_phase to run it in parallel with an existing phase, after_phase to make it a successor, and before_phase to make it a predecessor. For a run-time phase, start from uvm_domain::get_uvm_domain() instead, and find phases such as uvm_post_main_phase::get().

Quick Check

Q: Why is the phase class written as a singleton with a protected constructor?

Show Answer

The schedule refers to each phase by its single instance. dm.find(uvm_connect_phase::get()) works only because every caller gets the same object. A second instance of the same phase would not match anything in the schedule, so the constructor is protected to prevent it.

Step 3: Use the Phase in a Component

The test below inserts the phase from its constructor, which runs before phasing starts, and defines user_phase() for exec_func() to call. Every phase method prints a message, so the log shows the execution order.


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);
    add_my_phase();                      // schedule must be changed before phasing starts
  endfunction

  virtual function void add_my_phase();
    uvm_domain dm = uvm_domain::get_common_domain();
    dm.add(user_phase::get(),
           .after_phase (dm.find(uvm_connect_phase::get())),
           .before_phase(dm.find(uvm_end_of_elaboration_phase::get())));
  endfunction

  virtual function void build_phase(uvm_phase phase);
    `uvm_info("TEST", "In build_phase", UVM_MEDIUM)
  endfunction

  virtual function void connect_phase(uvm_phase phase);
    `uvm_info("TEST", "In connect_phase", UVM_MEDIUM)
  endfunction

  virtual task run_phase(uvm_phase phase);
    `uvm_info("TEST", "In run_phase", UVM_MEDIUM)
  endtask

  virtual function void extract_phase(uvm_phase phase);
    `uvm_info("TEST", "In extract_phase", UVM_MEDIUM)
  endfunction

  // Called by user_phase::exec_func()
  virtual function void user_phase(uvm_phase phase);
    `uvm_info("TEST", "In user_phase", UVM_MEDIUM)
  endfunction

  virtual function void final_phase(uvm_phase phase);
    `uvm_info("TEST", "In final_phase", UVM_MEDIUM)
  endfunction
endclass
Output
----------------------------------------------------------------
(C) 2007-2013 Mentor Graphics Corporation
(C) 2007-2013 Cadence Design Systems, Inc.
(C) 2006-2013 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
----------------------------------------------------------------
UVM_INFO @ 0: reporter [RNTST] Running test my_test...
UVM_INFO ./tb_top.sv(42) @ 0: uvm_test_top [TEST] In build_phase
UVM_INFO ./tb_top.sv(47) @ 0: uvm_test_top [TEST] In connect_phase
UVM_INFO ./tb_top.sv(66) @ 0: uvm_test_top [TEST] In user_phase
UVM_INFO ./tb_top.sv(57) @ 0: uvm_test_top [TEST] In run_phase
UVM_INFO ./tb_top.sv(62) @ 0: uvm_test_top [TEST] In extract_phase
UVM_INFO ./tb_top.sv(71) @ 0: uvm_test_top [TEST] In final_phase

--- UVM Report catcher Summary ---


Number of demoted UVM_FATAL reports  :    0
Number of demoted UVM_ERROR reports  :    0
Number of demoted UVM_WARNING reports:    0
Number of caught UVM_FATAL reports   :    0
Number of caught UVM_ERROR reports   :    0
Number of caught UVM_WARNING reports :    0

--- UVM Report Summary ---

The user_phase message appears right after connect_phase, even though user_phase() is declared after extract_phase() in the source. The schedule decides the order, not the position of the methods in the class. end_of_elaboration_phase and start_of_simulation_phase still run between user_phase and run_phase; they print nothing because the test does not implement them.

This log was produced with UVM 1.1d, which is why the banner and the report catcher summary look different from UVM 1.2 output. The phasing behaviour shown is the same in both versions.

A Custom Run-Time Phase

A task phase is written the same way, but extends uvm_task_phase and implements exec_task(). Insert it among the run-time phases, for example between post_main and pre_shutdown.


class my_cleanup_phase extends uvm_task_phase;
  local static my_cleanup_phase m_inst;

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

  static function my_cleanup_phase get();
    if (m_inst == null) m_inst = new();
    return m_inst;
  endfunction

  virtual task exec_task(uvm_component comp, uvm_phase phase);
    my_test t;
    if ($cast(t, comp))
      t.my_cleanup_phase(phase);        // the test raises/drops objections inside
  endtask
endclass

// Insertion, for example in the test's constructor
uvm_domain dm = uvm_domain::get_uvm_domain();
dm.add(my_cleanup_phase::get(),
       .after_phase (dm.find(uvm_post_main_phase::get())),
       .before_phase(dm.find(uvm_pre_shutdown_phase::get())));

Like any task phase, it ends as soon as nothing objects to it, so the component method must raise an objection for as long as it needs. Objections are explained in UVM Objection.

Common Mistakes

Mistake #1: Inserting the phase after phasing has started

The schedule should be complete before UVM starts executing it. Changing it from build_phase or later means editing a schedule that is already running, which makes the result depend on timing. Insert the phase from the test's constructor, which runs inside run_test() before any phase starts.

Mistake #2: Giving only after_phase and assuming series insertion

Name both after_phase and before_phase when you mean "between these two". It documents the intended position, and it guarantees the phase sits in series with its neighbours rather than depending on how add() fills in the missing link.

Mistake #3: Making a task phase among the function phases

Placing a uvm_task_phase between connect and end_of_elaboration mixes time-consuming code into the part of the schedule that is supposed to finish at time 0, before the testbench is even fully set up. Use a function phase there, and keep task phases among the run-time phases.

Mistake #4: Expecting every component to run the new phase

UVM calls exec_func() or exec_task() for every component, but the phase only does something for components your code calls into. A component whose type is not handled by the $cast silently skips the phase.

Summary

In this article, you learned:

  • A custom phase extends uvm_topdown_phase, uvm_bottomup_phase or uvm_task_phase
  • The phase class is a singleton with a protected constructor and a static get()
  • exec_func() or exec_task() calls the phase's method on each component that supports it
  • uvm_domain::add() with after_phase and before_phase places the phase exactly, and it must happen before phasing starts
  • Custom phases reduce reusability, so prefer existing phases where they fit
Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5