UVM Component
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 thecreate()
method to create objects of the custom component. Use ofnew()
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.
Phase | Method |
---|---|
Build | function void build_phase (uvm_phase phase) |
Connect | function void connect_phase (uvm_phase phase) |
End of elaboration | function void end_of_elaboration_phase (uvm_phase phase) |
Start of sim | function void start_of_simulation_phase (uvm_phase phase) |
Run | task run_phase (uvm_phase phase) |
Extract | function void extract_phase (uvm_phase phase) |
Check | function void check_phase (uvm_phase phase) |
Report | function void report_phase (uvm_phase phase) |
Final | function 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 PlaygroundObservations
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 forb1
&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
andb2
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.