UVM Phases

The Universal Verification Methodology (UVM) is widely used in the SystemVerilog verification community to create modular, reusable, and scalable testbenches for complex digital designs. One of UVM’s powerful features is its phasing mechanism, which organizes the simulation process into distinct phases, each designed for a specific part of the testbench operation. Understanding these UVM phases is essential for building efficient and synchronized testbenches.

In this article, we will explore each of the UVM phases, explain their purpose, and discuss the order in which they are executed.

Overview of UVM Phases

In UVM, a phase represents a particular stage in the simulation lifecycle, allowing different components to synchronize their activities and ensuring that simulation tasks are completed in a specific order. UVM organizes these phases into two main categories:

  1. Build Phases: Used for constructing the testbench hierarchy and configuring components.
  2. Run Phases: Used for running simulations, stimulating the Design Under Test (DUT), checking results, and finalizing the environment.

These phases are executed in a predefined sequence, which allows each component of the test bench to perform its assigned tasks in a logical order.

The UVM Phasing Sequence

UVM phases are split into build, connect, run, and cleanup phases, each serving a specific purpose.

1. Build Phases

The build phases are responsible for constructing the testbench hierarchy, configuring each component, and setting up the environment.

  • build_phase: This is the first phase in the UVM simulation and is responsible for constructing the component hierarchy. All components are created and initialized here. Each UVM component (e.g., uvm_env, uvm_agent, uvm_driver) has its build_phase() method, which is executed in this phase.
function void build_phase(uvm_phase phase);
  super.build_phase(phase);
  $display("Building component...");
endfunction
  • connect_phase: This phase establishes connections between different components. For example, drivers are connected to sequencers, and monitors are connected to scoreboards. This phase ensures that all components can communicate properly.
function void connect_phase(uvm_phase phase);
  super.connect_phase(phase);
  $display("Connecting components...");
endfunction
  • end_of_elaboration_phase: After building and connecting the components, this phase performs additional configuration and initialization tasks that need to happen after the hierarchy is established.
function void end_of_elaboration_phase(uvm_phase phase);
  super.end_of_elaboration_phase(phase);
  $display("Finalizing setup...");
endfunction
  • start_of_simulation_phase: This phase executes just before the actual simulation run begins. This is where you can display information about the test configuration or perform any last-minute setup.
function void start_of_simulation_phase(uvm_phase phase);
  super.start_of_simulation_phase(phase);
  $display("Starting simulation...");
endfunction

2. Run Phase

The run phase is the core of the simulation. It is time-consuming and involves sending stimuli to the DUT and collecting responses. Unlike the build phases, the run phase is task-based, meaning it allows delays and waits.

  • run_phase: The run_phase is where the testbench’s core functionality takes place. In this phase, drivers generate stimuli, sequencers provide sequences, monitors capture data, and scoreboards perform checks. Since this phase is task-based, it can include timing delays and other time-consuming operations.
task run_phase(uvm_phase phase);
  $display("Running testbench...");
  // Stimulate DUT and perform checks
  phase.raise_objection(this);  // Raise objection to keep simulation running
  // Perform test actions
  phase.drop_objection(this);   // Drop objection when complete
endtask

Raising and Dropping Objections: During the run_phase, UVM uses a mechanism called objections to control when the simulation should end. By raising an objection at the start of the phase and dropping it at the end, you ensure that the simulation only ends after all tasks have been completed.

3. Cleanup Phases

After the run_phase, the remaining phases are used to finalize and clean up the testbench. They perform checks, report results, and release resources.

  • extract_phase: This phase gathers any data accumulated during the simulation. For example, coverage or statistics data can be extracted from components here.
function void extract_phase(uvm_phase phase);
  super.extract_phase(phase);
  $display("Extracting results...");
endfunction
  • check_phase: In this phase, the testbench performs checks on the gathered data to determine if the test passed or failed. Scoreboards and other checkers use this phase to validate results.
function void check_phase(uvm_phase phase);
  super.check_phase(phase);
  $display("Checking results...");
  // Verify if expected and actual results match
endfunction
  • report_phase: After checking, the report phase is responsible for logging final results, including pass/fail status and any detailed logs of the simulation.
function void report_phase(uvm_phase phase);
  super.report_phase(phase);
  $display("Reporting results...");
endfunction
  • final_phase: The last phase in the simulation, final_phase is used for cleaning up resources and finalizing any actions before simulation ends. You can close files, release resources, and reset the environment in this phase.
function void final_phase(uvm_phase phase);
  super.final_phase(phase);
  $display("Finalizing simulation...");
endfunction

Summary of UVM Phases and Execution Flow

The following table summarizes each UVM phase and its purpose:

PhasePurposeExecution Order
Build PhasesConstruct the testbench components.
build_phaseCreates and configures components.top-down
connect_phaseEstablishes connections between components.bottom-up
end_of_elaboration_phaseFinal adjustments before starting simulation.bottom-up
start_of_simulation_phasePre-simulation initialization.bottom-up
Run PhaseCore simulation execution, stimulus generation, and data capture.
run_phaseMain simulation, performs timed actions.Parallel
Cleanup PhasesPost-simulation data processing and reporting.
extract_phaseGathers accumulated data.
check_phaseVerifies results and checks pass/fail status.
report_phaseLogs final results.
final_phaseCleans up resources and ends the simulation.

UVM Phase Synchronization

One of the significant advantages of UVM’s phased approach is that it ensures synchronization across all components. Each phase executes in all components before moving to the next phase, so all components complete build_phase before moving to connect_phase, and so on. This synchronization ensures that all parts of the testbench are prepared and ready at each stage of the simulation.

Why build_phase is top-down while other phases use bottom-up approach?

The build_phase is responsible for the construction and configuration of components in the UVM testbench hierarchy. The main reason it follows a top-down approach because of hierarchical Dependency.

  • Parent components are created before their child components. In UVM, the parent components are responsible for instantiating their child components. For example, an environment (env) creates its subcomponents (e.g., agents, monitors, drivers).
  • A top-down approach ensures that each parent is constructed before attempting to construct its children, preserving the hierarchical structure.
  • Configuration settings (e.g., using uvm_config_db) are often applied at higher levels (e.g., in the test or environment) and passed down the hierarchy.
  • This requires the parent to exist and be configured before its children are constructed.

Reason for Bottom-Up in Other Phases

Most other phases, such as connect_phase, run_phase, and shutdown_phase, use a bottom-up approach. Here’s why:

  1. Dependency on Lower Levels:
    • For example, in the connect_phase, a parent might need to establish connections between subcomponents. This can only happen after the children have completed their individual connection logic.
    • Similarly, in the run_phase, child components generate transactions that flow upward to parent components, so the child behavior must be set up before the parent can react.
  2. Completion Assurance:
    • A bottom-up approach ensures that lower-level components are fully initialized and operational before higher-level components act on their outputs or states.
  3. Interaction Hierarchy:
    • During execution (e.g., in run_phase), child components often perform specific actions (like driving signals or monitoring transactions) that the parent components observe. A bottom-up flow ensures that child components are ready before parents depend on them.

Example of a UVM Phase Implementation in a Test Component

Here’s a simplified example demonstrating how each of these UVM phases might be implemented in a UVM component.

Transaction Class:

typedef enum {false,true} e_bool;
class object extends uvm_object;
  rand e_bool m_bool;
  rand bit[3:0] mode;
  rand byte data[4];
  rand shortint m_queue[$];
  string m_name;
  
  constraint c1{m_queue.size == 3;}
  
  function new(string name = "object");
    super.new(name);
    m_name = name;
  endfunction
  
  `uvm_object_utils_begin(object);
  `uvm_field_enum(e_bool,m_bool,UVM_DEFAULT);
  `uvm_field_int (mode,UVM_DEFAULT);
  `uvm_field_sarray_int(data,UVM_DEFAULT);
  `uvm_field_queue_int(m_queue,UVM_DEFAULT);
  `uvm_field_string(m_name,UVM_DEFAULT);
  `uvm_object_utils_end
  
endclass

Test Class:

class base_test extends uvm_test;
  `uvm_component_utils(base_test);
  function new(string name="base_test", uvm_component parent=null);
    super.new(name,parent);
  endfunction
  
  function void build_phase(uvm_phase phase);
    object obj = object::type_id::create("obj");
    obj.randomize();
    //obj.print();
    `uvm_info(get_type_name(),$sformatf("contents: %s",obj.sprint()), UVM_LOW)
  endfunction
  
  function void connect_phase(uvm_phase phase);
    super.connect_phase(phase);
    `uvm_info(get_full_name(),"connect_phase",UVM_LOW);
  endfunction
  
  function void end_of_elaboration_phase(uvm_phase phase);
    super.end_of_elaboration_phase(phase);
    `uvm_info(get_full_name(),"end_of_elaborate_phase",UVM_LOW);
  endfunction
  
  function void start_of_simulation_phase(uvm_phase phase);
    super.start_of_simulation_phase(phase);
    `uvm_info(get_full_name(),"start_of_simulation_phase",UVM_LOW);
  endfunction
  
  task run_phase(uvm_phase phase);
   // #10ns;
    phase.raise_objection(this);
    `uvm_info(get_full_name(),"raise_objection",UVM_LOW);
    //#10ns;
    phase.drop_objection(this);
    `uvm_info(get_full_name(),"drop_objection",UVM_LOW);
  endtask
  
  function void extract_phase(uvm_phase phase);
    super.extract_phase(phase);
    `uvm_info(get_full_name(),"extract_phase",UVM_LOW);
  endfunction
  
  function void check_phase(uvm_phase phase);
    super.check_phase(phase);
    `uvm_info(get_full_name(),"check_phase",UVM_LOW);
  endfunction
  
  function void report_phase(uvm_phase phase);
    super.report_phase(phase);
    `uvm_info(get_full_name(),"report_phase",UVM_LOW);
  endfunction
  
  function void final_phase(uvm_phase phase);
    super.final_phase(phase);
    `uvm_info(get_full_name(),"final_phase",UVM_LOW);
  endfunction
  
endclass

module tb;
  initial begin
    run_test("base_test");
  end 
endmodule

Output:

UVM_INFO @ 0: reporter [RNTST] Running test base_test...
UVM_INFO testbench.sv(13) @ 0: uvm_test_top [base_test] contents: -------------------------------------
Name       Type          Size  Value 
-------------------------------------
obj        object        -     @349  
  m_bool   e_bool        32    true  
  mode     integral      4     'h3   
  data     sa(integral)  4     -     
    [0]    integral      8     'h9f  
    [1]    integral      8     'h33  
    [2]    integral      8     'h12  
    [3]    integral      8     'h9c  
  m_queue  da(integral)  3     -     
    [0]    integral      16    'h182f
    [1]    integral      16    'h3b39
    [2]    integral      16    'hd7bd
  m_name   string        3     obj   
-------------------------------------

UVM_INFO testbench.sv(18) @ 0: uvm_test_top [uvm_test_top] connect_phase
UVM_INFO testbench.sv(23) @ 0: uvm_test_top [uvm_test_top] end_of_elaborate_phase
UVM_INFO testbench.sv(28) @ 0: uvm_test_top [uvm_test_top] start_of_simulation_phase
UVM_INFO testbench.sv(34) @ 0: uvm_test_top [uvm_test_top] raise_objection
UVM_INFO testbench.sv(37) @ 0: uvm_test_top [uvm_test_top] drop_objection
UVM_INFO /apps/vcsmx/vcs/U-2023.03-SP2//etc/uvm-1.2/src/base/uvm_objection.svh(1276) @ 0: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase
UVM_INFO testbench.sv(42) @ 0: uvm_test_top [uvm_test_top] extract_phase
UVM_INFO testbench.sv(47) @ 0: uvm_test_top [uvm_test_top] check_phase
UVM_INFO testbench.sv(52) @ 0: uvm_test_top [uvm_test_top] report_phase
UVM_INFO testbench.sv(57) @ 0: uvm_test_top [uvm_test_top] final_phase
UVM_INFO /apps/vcsmx/vcs/U-2023.03-SP2//etc/uvm-1.2/src/base/uvm_report_server.svh(904) @ 0: reporter [UVM/REPORT/SERVER] 
--- UVM Report Summary ---

** Report counts by severity
UVM_INFO :   13
UVM_WARNING :    0
UVM_ERROR :    0
UVM_FATAL :    0
** Report counts by id
[RNTST]     1
[TEST_DONE]     1
[UVM/RELNOTES]     1
[base_test]     1
[uvm_test_top]     9