Logo

UVM Component

16 Mar 2024
5 mins

In this article we will see how custom component classes are implemented. We will explore in depth different methods present in base class which can be overridden to achieve the phasing mechanism.

What are UVM Component?

UVM Components, as we have already discussed in an earlier article, are the classes whose objects are created at the start of the simulation and will be available throughout the simulation time. UVM component can be considered as an individual block of the test bench architecture which will perform some specific tasks, while driving the stimulus, monitoring the stimulus, etc.

UVM components are derived from uvm_component class which adds additional functionality related to phasing and reporting to the uvm_object class. All custom components are directly or indirectly child of uvm_component class.

Defining custom UVM component classes

Defining a custom component class consists of four steps:

  • Creating a child class of uvm_component
  • Registering the custom component class with uvm factory
  • Defining constructor
  • Overriding phase methods to synchronize various components.

Defining a custom component class is somewhat like defining a custom object class with some minor differences.

Creating custom component

To create a custom component, we define a child class of uvm_component

class xyz extends uvm_component

The above code creates a custom component xyz which is child of uvm_component. Objects of this class will be created in the build phase and destroyed once the simulation is finished after the finish phase.

Registering with UVM factory

Like UVM objects, custom components also need to be registered with UVM factory. The macro used to register custom components is different from the macro we use for UVM Objects.

`uvm_component_utils(xyz)

Above code snippet registers the component xyz with UVM factory. Macro is different from uvm objects as the constructor for uvm component is different from uvm object.

After registering the components, we can use the create method to create objects of the custom component.

We should always use the create() method to create objects of the custom component. Use of new() is not recommended.

Defining the constructor

To define the constructor, we need to define new() method which will call the new method of the super class, i.e., uvm_component. The constructor for uvm_component has two arguments name and parent. Thus, while defining constructors for our class we need to take these arguments and pass it to super class.

function new(string name, uvm_component parent);
	super.new(name, parent);
endfunction
Name for objects of UVM component should always be unique and thus we do not usually provide a default value for the name argument in component class. If two objects are created with the same name this gives an error. Objects of UVM objects can have same name.
If we pass the parent argument as null, then that instance is considered as the top-level component. In general practice, we should have only one top-level component.

Overriding UVM phase methods

UVM component provides phasing mechanism which helps synchronize different components. To implement this pashing mechanism, uvm component provides various phase methods which we need to override in custom components to perform various actions.

All phase methods have an argument uvm_phase phase. Not all phase methods need to be overridden in the custom classes.

function void build_phase(uvm_phase phase);
	super.build_phase(phase);
	<some actions>
endfunction

The above code will override the build phase. Inside the build phase we first call the build phase of the super class, i.e., uvm_component.

Complete custom component definition will look like

class xyz extends uvm_component;
    `uvm_component_utils(xyz)
    function new(string name, uvm_component parent);
        super.new(name, parent);
    endfunction
    function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        <some actions>
    endfunction

    // other phase methods
    // custom methods
endclass

Different UVM Phase methods

We have discussed different phases and their use in our earlier article. In this article we will discuss various methods provided by UVM component which can be overridden in our custom component to achieve a certain functionality.

PhaseMethod
Buildfunction void build_phase (uvm_phase phase)
Connectfunction void connect_phase (uvm_phase phase)
End of elaborationfunction void end_of_elaboration_phase (uvm_phase phase)
Start of simfunction void start_of_simulation_phase (uvm_phase phase)
Runtask run_phase (uvm_phase phase)
Extractfunction void extract_phase (uvm_phase phase)
Checkfunction void check_phase (uvm_phase phase)
Reportfunction void report_phase (uvm_phase phase)
Finalfunction void final_phase (uvm_phase phase)

Code Example

Let us explore a simple example, where we will create two components comp_A and comp_B. comp_A will be the top component and it will have two instances of comp_B as child.

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

class comp_B extends uvm_component;
    `uvm_component_utils(comp_B)

    rand bit [3:0] delay;
    function new (string name, uvm_component parent);
        super.new(name,parent);
    endfunction

    function void build_phase(uvm_phase phase); 
        super.build_phase(phase);
        `uvm_info(get_full_name(), "In build phase", UVM_LOW)
    endfunction

    function void connect_phase(uvm_phase phase); 
        super.connect_phase(phase);
        `uvm_info(get_full_name(), "In connect phase", UVM_LOW)
    endfunction

    function void start_of_simulation_phase(uvm_phase phase); 
        super.start_of_simulation_phase(phase);
        `uvm_info(get_full_name(), "In start_of_simulation phase", UVM_LOW)
        this.randomize();
    endfunction

    task run_phase(uvm_phase phase);
        super.run_phase(phase);
        phase.raise_objection(this);
        `uvm_info(get_full_name(), "Starting run phase", UVM_LOW)
        #delay;
        `uvm_info(get_full_name(), "Ending run phase", UVM_LOW)
        phase.drop_objection(this);
    endtask
endclass

class comp_A extends uvm_component;
    `uvm_component_utils(comp_A)

    // Member variable
    comp_B b1, b2;

    function new (string name, uvm_component parent);
        super.new(name,parent);
    endfunction

    function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        `uvm_info(get_full_name(), "In build phase", UVM_LOW)
        b1 = comp_B::type_id::create("b1", this); 
        b2 = comp_B::type_id::create("b2", this); 
    endfunction

    function void connect_phase(uvm_phase phase);
        super.connect_phase(phase);
        `uvm_info(get_full_name(), "In 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(), "In end_of_elaboration phase", UVM_LOW)
        uvm_top.print_topology(); 
    endfunction

    task reset_phase(uvm_phase phase);
        super.reset_phase(phase);
        `uvm_info(get_full_name(), "Starting reset phase", UVM_LOW)
        #40;
        `uvm_info(get_full_name(), "Ending reset phase", UVM_LOW)
    endtask

    function void report_phase(uvm_phase phase);
        super.report_phase(phase);
        `uvm_info(get_full_name(), "In report phase", UVM_LOW)
    endfunction

    function void final_phase(uvm_phase phase);
        super.final_phase(phase);
        `uvm_info(get_full_name(), "In final phase", UVM_LOW)
    endfunction

endclass

module top;
    initial begin
        run_test("comp_A"); 
    end
    /* alternate way
    comp_A a;
    initial begin
        a = comp_A::type_id::create("a", null);
        run_test();
    end */
endmodule
Output
 UVM_INFO @ 0: reporter [RNTST] Running test comp_A...
# UVM_INFO uvm_component_intro.sv(50) @ 0: uvm_test_top [uvm_test_top] In build phase
# UVM_INFO uvm_component_intro.sv(14) @ 0: uvm_test_top.b1 [uvm_test_top.b1] In build phase
# UVM_INFO uvm_component_intro.sv(14) @ 0: uvm_test_top.b2 [uvm_test_top.b2] In build phase
# UVM_INFO uvm_component_intro.sv(19) @ 0: uvm_test_top.b1 [uvm_test_top.b1] In connect phase
# UVM_INFO uvm_component_intro.sv(19) @ 0: uvm_test_top.b2 [uvm_test_top.b2] In connect phase
# UVM_INFO uvm_component_intro.sv(57) @ 0: uvm_test_top [uvm_test_top] In connect phase
# UVM_INFO uvm_component_intro.sv(62) @ 0: uvm_test_top [uvm_test_top] In end_of_elaboration phase
# UVM_INFO @ 0: reporter [UVMTOP] UVM testbench topology:
# ---------------------------------
# Name          Type    Size  Value
# ---------------------------------
# uvm_test_top  comp_A  -     @457 
#   b1          comp_B  -     @465 
#   b2          comp_B  -     @473 
# ---------------------------------
# 
# UVM_INFO uvm_component_intro.sv(24) @ 0: uvm_test_top.b1 [uvm_test_top.b1] In start_of_simulation phase
# UVM_INFO uvm_component_intro.sv(24) @ 0: uvm_test_top.b2 [uvm_test_top.b2] In start_of_simulation phase
# UVM_INFO uvm_component_intro.sv(31) @ 0: uvm_test_top.b2 [uvm_test_top.b2] Starting run phase
# UVM_INFO uvm_component_intro.sv(31) @ 0: uvm_test_top.b1 [uvm_test_top.b1] Starting run phase
# UVM_INFO uvm_component_intro.sv(68) @ 0: uvm_test_top [uvm_test_top] Starting reset phase
# UVM_INFO uvm_component_intro.sv(33) @ 5: uvm_test_top.b2 [uvm_test_top.b2] Ending run phase
# UVM_INFO uvm_component_intro.sv(33) @ 14: uvm_test_top.b1 [uvm_test_top.b1] Ending run phase
# UVM_INFO verilog_src/uvm-1.1d/src/base/uvm_objection.svh(1268) @ 14: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase
# UVM_INFO uvm_component_intro.sv(75) @ 14: uvm_test_top [uvm_test_top] In report phase
# UVM_INFO uvm_component_intro.sv(80) @ 14: uvm_test_top [uvm_test_top] In final phase
Try this code in EDA Playground

Observations

The following are the key points that need to be observed in the above example.

  • Object of comp_A is automatically created when we call the run_test() method.

    If we do not pass any component class name in run_test(), then the component which has parent assigned as null will be used as a top-level component.

  • Object created by run_test has name uvm_test_top which can be seen in the topology print.
  • The build phase for the top-level component (uvm_test_top) is executed first and then the build phase for b1 & b2 is executed.
  • Connect phase for b1 & b2 (child component) is executed first and then the connect phase of uvm_test_top is executed. This is because the connect phase follows bottom-up approach.
  • In run phase of comp_B, objection is raised and then after a random delay the objection is dropped. Once the objection is dropped from both the instances of comp_B, i.e., b1 and b2 the simulation moves to the extract phase.
  • In the reset phase of comp_A, there is no objection raised. We can see that the simulation is moving to extract phase without waiting for the reset phase to be completed.
  • Not all phase methods need to be overridden in the custom component as seen in the example.
  • When overriding the phase methods, we should call the phase method of the super class. This is important as super class might also have some activities in respective phase which will be missed otherwise.

Conclusion

In the present article, we explore the process of constructing a custom component derived from uvm_component. Additionally, we delve into the steps for registering this custom component with the UVM factory and demonstrate the utilization of the create method to instantiate new instances of the component. Furthermore, we examine the creation of a top-level component through the run_test method.