UVM phase jumping lets a running phase end early and send the simulation to a different phase, usually back to an earlier one, so a test can react to a mid-test reset by running the reset sequence again. Phase domains let different parts of the testbench follow independent phase schedules, which are then synchronized only where needed. Both are advanced features of the run-time phasing mechanism, and both are easy to misuse.

Most testbenches never need them. A single schedule with a clean build, a run phase and some run-time sub-phases covers the great majority of tests. This article explains the two features, what they are meant for, and why many teams leave them alone until a specific problem demands them.

8 min read | Intermediate

What You'll Learn

  • List the run-time phases that jumps and domains operate on
  • Jump to an earlier phase with phase.jump(), for example to re-run reset
  • Put part of a testbench into its own domain and synchronize it with the rest
  • Recognise the situations where these features are not worth the risk

The Run-Time Phases

The run_phase task runs for the whole simulation. In parallel with it, UVM runs twelve run-time sub-phases in a fixed order, and every component takes part in them unless it is moved to another domain:


pre_reset      -> reset      -> post_reset
pre_configure  -> configure  -> post_configure
pre_main       -> main       -> post_main
pre_shutdown   -> shutdown   -> post_shutdown

Their names suggest a use: reset the DUT, configure it through its registers, run the main stimulus, and shut down cleanly. UVM Phases describes the whole set. Phase jumping and domains only apply to these run-time phases. The build-time phases, such as build_phase and connect_phase, are functions that run once, in a fixed order.

Phase Jumping

Phase jump domains

function void jump(uvm_phase phase);                  // end this phase, go to another
function void set_jump_phase(uvm_phase phase);        // set the target, without ending now
function void end_prematurely();                      // end the phase, without setting a target
function uvm_phase get_jump_target();                 // valid in phase_ended()

jump() does two things: it marks the current phase to end early, and it sets the phase to move to afterwards. The jump applies to the phase as a whole, not only to the component that called it, so every component's run of that phase ends.

Example: A Reset in the Middle of a Test

The standard use is a mid-test reset. A test wants to check that the DUT recovers from a reset asserted while it is busy. The main phase waits for the reset to be asserted and jumps back, so that all components run their reset phases again:


class reset_recovery_test extends base_test;
  `uvm_component_utils(reset_recovery_test)
  bit did_reset;
  // Rest of the code (constructor)

  virtual task main_phase(uvm_phase phase);
    phase.raise_objection(this);
    if (!did_reset) begin
      fork
        run_traffic();                            // normal stimulus
        begin
          #5us;                                   // let some traffic go through
          vif.rstn <= 0;                          // assert the reset on purpose
          did_reset = 1;
        end
      join_any
      phase.jump(uvm_pre_reset_phase::get());     // everyone goes back to reset
    end else begin
      run_traffic();                              // second pass: no more resets
    end
    phase.drop_objection(this);
  endtask
endclass

The jump ends main_phase for every component and runs pre_reset, reset and the later phases again from the top. Anything still running from the first pass, such as the traffic process, must be stopped by the test, so it cannot interfere with the second pass. The flag did_reset makes sure the second pass doesn't reset again forever.

For this to work, every component must be ready to start a phase a second time. The driver's reset phase must clear its internal state, the scoreboard must forget transactions in flight, and sequences that were running must have been killed. A component that initializes things only in build_phase carries stale state into the second pass. Designing every component for restart is the real cost of phase jumping.

Quick Check

Q: A scoreboard holds 12 expected transactions when the test jumps back to reset. If the scoreboard clears its queue only in build_phase, what happens in the second pass?

Show Answer

The 12 stale expectations are still queued, so the first transactions of the second pass are compared against the wrong expected values, and the test reports mismatches that the DUT didn't cause. The queue must be cleared in the scoreboard's reset_phase.

Phase Domains

By default every component belongs to a single domain called uvm, and its phases advance together: no component starts configure until all have finished reset. That's a problem when part of the design has its own reset or its own timing. A subsystem that stays in reset for hundreds of microseconds longer than the rest would hold up everything else.

A uvm_domain is an independent copy of the run-time schedule. Components assigned to it move through the phases at their own pace.


class domain_test extends base_test;
  `uvm_component_utils(domain_test)
  uvm_domain m_slow_dom;
  // Rest of the code (constructor)

  virtual function void connect_phase(uvm_phase phase);
    super.connect_phase(phase);
    m_slow_dom = new("slow_dom");
    m_env.m_slow_agent.set_domain(m_slow_dom);       // this agent and all below it
    // Keep the two domains together for the main phase only
    m_slow_dom.sync(uvm_domain::get_uvm_domain(), uvm_main_phase::get());
  endfunction
endclass

set_domain() moves a component, and with its default argument everything below it, into the new domain. sync() then ties selected phases of two domains together, here so that both domains start and end the main phase at the same time, while reset and configure stay independent. unsync() removes such a tie. Use +UVM_PHASE_TRACE the first time to confirm that the schedule behaves the way you intended.

Should You Use Them?

Widely used guideline sets say to avoid both features unless there is no alternative, and the reasons are practical:

  • Components from different sources are rarely written to restart cleanly, so jumps expose hidden state bugs.
  • Domains and jumps make the order of events hard to follow in the log and harder to reuse across projects.
  • There are simpler ways to do the same thing.

A mid-test reset can often be done without phases. The test applies the reset from a sequence or the run phase, and a reset_event or a flag tells components to clear their state. An independent subsystem can have its own agents and sequences, started from the test when that subsystem is ready, without a separate domain.

Phase jumping and domains are worth the effort when the reset sequence is long and shared by many components, when restart behaviour is itself the subject of the verification, or when the design truly has parts that come out of reset at very different times.

Common Mistakes

Mistake #1: Jumping without restart-safe components

Stale queues, counters and half-finished transactions survive the jump unless every component clears them in the phases that are re-run. Check each component before using a jump.

Mistake #2: Jumping forever

A jump back to reset that is triggered by a condition that is still true when the test returns to main phase loops endlessly. Use a flag, as the example does, or a counter.

Mistake #3: Leaving an objection raised on the jump path

Every raise_objection() needs its drop, including on the path that jumps. An objection that is never dropped can hold the phase open and stall the jump.

Mistake #4: Using domains to solve an ordering problem

A second domain is not a tool for starting things in a different order. If one component needs to wait for another, use an event or a sequence, and keep the standard schedule.

Summary

In this article, you learned:

  • Jumps and domains act on the twelve run-time phases, from pre_reset to post_shutdown
  • phase.jump() ends the current phase for every component and continues from the target phase
  • A jump back to reset only works if every component clears its state in the phases that are re-run
  • A uvm_domain gives part of the testbench its own schedule, tied to the rest with sync()
  • Both features carry real risk, and a flag or event often does the same job more simply
Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5