UVM Phasing Mechanism

The backbone of UVM synchronization. Understanding standard phases, the 12 run-time sub-phases, and the objection mechanism.

Mapping the UVM Execution Flow

Why Do We Need Phases?

The Problem: Imagine trying to test a car before the engine is installed. Disaster, right? Without phases, your testbench components would try to work before they're ready.

The Solution: Phases are like a step-by-step instruction manual. They make sure your testbench builds itself in the right order, connects all the pieces, runs the test, and cleans up afterward.

The UVM Phase Schedule

graph TD subgraph Build_Time ["Build Time - Function Phases"] A["build_phase"] -->|Top-Down| B["connect_phase"] B -->|Bottom-Up| C["end_of_elaboration_phase"] C --> D["start_of_simulation_phase"] end subgraph Run_Time ["Run Time - Task Phase"] D --> E{"run_phase"} E -->|"Parallel Threads"| F["reset_phase"] F --> G["configure_phase"] G --> H["main_phase"] H --> I["shutdown_phase"] E -.->|"Primary Task"| J["run_phase (task)"] end subgraph Clean_Up ["Clean Up - Function Phases"] I --> K["extract_phase"] K -->|Bottom-Up| L["check_phase"] L --> M["report_phase"] M -->|Top-Down| N["final_phase"] end style Build_Time fill:#e3f2fd,stroke:#2196f3,stroke-width:2px style Run_Time fill:#fff3e0,stroke:#ff9800,stroke-width:2px style Clean_Up fill:#e8f5e9,stroke:#4caf50,stroke-width:2px

Tip: Function phases execute in zero-time. Run phases consume simulation time.

UVM phases are a timing system. Without them, a Monitor might try to read signals before the interface is connected, or a Driver might send data while your design is still resetting.

The 9 Standard Phases (Detailed)

Phases are executed in a fixed sequence. Most are non-blocking functions (executing in zero simulation time). Only the run_phase (and its sub-phases) is a task that can consume simulation time.

1. build_phase (Function, Top-Down)

  • The Goal: To physically construct the testbench component hierarchy (The Topology).
  • The Action: Components use type_id::create to instantiate child components and query uvm_config_db.
  • Why Top-Down? Precedence flows from parent to child. Parents must exist first to configure children (e.g., Active/Passive).

2. connect_phase (Function, Bottom-Up)

  • The Goal: Wiring components using TLM ports/exports.
  • The Action: Assigning virtual interface handles and connecting monitors to scoreboards.
  • Why Bottom-Up? Children (building blocks) should be fully connected internally before being integrated into parents.

3. end_of_elaboration_phase (Function, Bottom-Up)

  • The Goal: Final structural verification.
  • The Action: Checking connectivity and topology validity before simulation starts.

4. start_of_simulation_phase (Function, Bottom-Up)

  • The Goal: Pre-simulation preparation.
  • The Action: Displaying logging banners, printing factory overrides, or recording initial register states.

5. run_phase (Task, Parallel)

  • The Goal: The primary simulation engine.
  • The Action: The only phase where the "clock" ticks. Drivers drive, Monitors sample.
  • Key Behavior: All run_phase tasks start simultaneously across the hierarchy.

6. extract_phase (Function, Bottom-Up)

  • The Goal: Collecting post-simulation artifacts.
  • The Action: Pulling final stats, coverage counts, or error flags.

7. check_phase (Function, Bottom-Up)

  • The Goal: Validating the test outcome.
  • The Action: Comparing expected vs actual results (e.g. Scoreboard empty check).

8. report_phase (Function, Bottom-Up)

  • The Goal: Summarizing the total result.
  • The Action: Printing "TEST PASSED" or "TEST FAILED".

9. final_phase (Function, Top-Down)

  • The Goal: Resource cleanup.
  • The Action: Closing files or releasing system handles.

The Run-Time Phase Hierarchy

While the run_phase is the most commonly used task-based phase, UVM actually provides a set of 12 run-time sub-phases that execute sequentially.

The 12 Sub-Phase Groups

1.  pre_reset_phase
2.  reset_phase        <-- Assert Reset signals, clear registers
3.  post_reset_phase   <-- Wait for reset-completion acknowledgement
4.  pre_configure_phase
5.  configure_phase    <-- Program LUTs, setup baseline registers
6.  post_configure_phase
7.  pre_main_phase
8.  main_phase         <-- Primary stimulus execution (Most used)
9.  post_main_phase
10. pre_shutdown_phase
11. shutdown_phase     <-- Wait for buffers to drain
12. post_shutdown_phase
                            

Mastering the Objection Mechanism

Since task-based phases consume time, UVM needs a way to know when they are "finished." It uses a simple voting system called the Objection Mechanism.

Simulation moves to the next phase only when there are zero outstanding objections.

Proper Objection Usage

task run_phase(uvm_phase phase);
    // 1. Raise and 'vote' to keep the phase alive
    phase.raise_objection(this, "Starting sequence execution"); 
    // 2. Execute time-consuming activity
    seq.start(m_sequencer);
    // Optional: Drain time allows pending transactions to finish
    phase.phase_done.set_drain_time(this, 100ns);
    // 3. Drop and 'release' the phase
    phase.drop_objection(this, "Sequence finished");
endtask
                                

🚑 Debugging Objection Hangs

The #1 issue in UVM is the simulation never ending because someone forgot to drop an objection.

How to debug: Run your simulation with +UVM_OBJECTION_TRACE. This argument tells UVM to print a log message every time an objection is raised or dropped. You can then scan the log to find the component that raised an objection but never dropped it.

Architect's Secret: Phase Jumping

Did you know you can force the scheduler to jump backward? Using phase.jump(uvm_reset_phase::get()), you can trigger a Warm Reset test where the DUT is reset mid-simulation without restarting the entire job. Use with caution! Components must be coded to handle sudden phase kills/restarts.

Why Call super.build_phase()?

Interview Essential: "Why do we call super.build_phase() in our components?"

The Short Answer:

The parent class (uvm_component) has its own initialization logic (like Factory overrides and Auto-Config) that must run. If you skip it, you break standard UVM features.

Correct Pattern

function void build_phase(uvm_phase phase);
    // ALWAYS call parent first
    super.build_phase(phase);
    // Then add your own logic
    if (!uvm_config_db #(virtual my_if)::get(this, "", "vif", vif))
        `uvm_fatal("BUILD", "No virtual interface found")
    driver = my_driver::type_id::create("driver", this);
endfunction