UVM ESE PH Day-25
UVM ESE PH Day-25
Database
At the end of this module
you will be able to:
Session Plan:
Introduction to UVM Factory
Need of UVM Factory
Factory Registration
Overriding Components / Objects
Factory Override - Precedence
UVM Configuration
Configuration Object
Work Sheet.
The purpose of the UVM factory is to allow an object of one type to be substituted with an object
of a derived type without having to change the structure of the testbench or edit the testbench
code.
The mechanism used is referred to as an override and the override can be by instance or type.
This functionality is useful for changing sequence functionality or for changing one version of a
component for another.
Any components which are to be swapped must be polymorphically compatible. This includes
having all the same TLM interface handles and TLM objects must be created by the new
replacement component. Additionally, in order to take advantage of the factory certain coding
conventions need to be followed.
Factory Registration:-
UVM methodology dictates that engineers should simply invoke the appropriate factory
registration macro when defining <uvm_object> and <uvm_component>-based classes. Use of
macros is required to ensure portability across different vendors' simulators.
`uvm_object_utils(packet)
endclass
`uvm_object_utils(packetD)
endclass
/* Program 1.2 Sample parameterized object factory registration */
`uvm_object_param_utils(packet #(T,WIDTH))
endclass
/* Program 1.3 Sample non parameterized component factory registration
*/
`uvm_component_utils(comp)
endclass
/* Program 1.4 Sample parameterized component factory registration */
`uvm_component_param_utils(comp #(T,WIDTH))
endclass
The `uvm_*_utils macros for simple, non-parameterized classes will register the type with the
factory and define the get_type, get_type_name, and create virtual methods inherited from
<uvm_object>. It will also define a static type_name variable in the class, which will allow you to
determine the type without having to allocate an instance.
The `uvm_*_param_utils macros for parameterized classes differ from `uvm_*_utils classes in the
following ways:
• The get_type_name method and static type_name variable are not defined. You will need
to implement these manually.
• A type name is not associated with the type when registeriing with the factory, so the
factory's *_by_name operations will not work with parameterized classes.
• The factory's <print>, <debug_create_by_type>, and <debug_create_by_name> methods,
which depend on type names to convey information, will list parameterized types as
<unknown>.
Having registered your objects and components with the factory, you can now make requests for
new objects and components via the factory. Using the factory instead of allocating them directly
(via new) allows different objects to be substituted for the original without modifying the
requesting class.
super.new(name);
endfunction
The following code defines a driver class that is parameterized and how to create instance of it.
/* Program 1.5 Parameterized driver class */
class driverB #(type T=uvm_object) extends uvm_driver;
`uvm_component_param_utils(driverB #(T))
function new(string name, uvm_component parent=null);
super.new(name,parent);
endfunction
// get_type_name not implemented by macro for parameterized
classes
const static string type_name = {"driverB
#(",T::type_name,")"};
virtual function string get_type_name();
return type_name;
endfunction
endclass
// using the factory allows pkt overrides from outside the class
virtual function void build_phase(uvm_phase phase);
pkt = packet::type_id::create("pkt",this);
endfunction
The UVM factory allows a class to be substituted with another class of a derived type when it is
constructed. This can be useful for changing the behavior of a testbench by substituting one class
for another without having to edit or re-compile the testbench code. In order for factory override
process to work there are a number of coding convention pre-requisites that need to be followed,
these are explained in earlier sections.
The UVM factory can be thought of as a lookup table. When "normal" component construction
takes place using the <type>::type_id::create("<name>", <parent>) approach, what
happens is that the type_id is used to pick the factory component wrapper for the class, construct
its contents and pass the resultant handle back again. The factory override changes the way in
which the lookup happens so that looking up the original type_id results in a different type_id
being used. Consequently, a handle to a different type of constructed object is returned. This
technique relies on polymorphism which is the ability to be able to refer to derived types using a
base type handle. In practice, an override will only work when a parent class is overridden by one
of its descendents in the class extension hierarchy.
Component Overrides:
There are two types of component overrides in the UVM - type overrides and instance overrides.
A type override means that every time a component class type is created in a testbench hierarchy,
a substitute type is created in its place. This applies to all instances of that component type i.e.,
type overrides apply for whole testbench.
Component type override can be implemented in two ways, one is by overriding by type and
other one is overriding by name. These are best explained in following examples.
• set_type_override_by_type(arg1,arg2,arg3);
– Arg1 is original type
• Handle.get_type()
• Class_name::get_type()
– Arg2 is overriding type
– Arg3 is replace
• If 1, replace any previous overrides on original type with current override
• If 0, checks for previous overrides. If exists, ignores current override, else
current override takes effect.
– Eg: set_type_override_by_type( env1::get_type(),
env2::get_type(),
1);
– Arg1
• String, name of the original type
– Arg2
//etc
endclass: red
class dark_red extends color;
`uvm_component_utils(dark_red)
//etc
endclass: dark_red
red::get_type(),
1);
// This means that the following creation line returns a red,
rather than a color.
pixel = color::type_id::create("pixel", this);
dark_red,
1);
// This means that the following creation line returns a dark_red,
rather than a red.
pixel = color::type_id::create("pixel", this);
• Handle.get_type()
• Class_name::get_type()
– Arg3 is overriding type
– set_inst_override_by_type( "agent1.driver1“,
B_driver::get_type(),
D2_driver::get_type());
// etc;
endclass
`uvm_component_param_utils(driverD2 #(T))
endclass
endclass
`uvm_component_utils(env)
agent agent0;
agent agent1;
endclass
initial begin
endmodule
When the above example is run, the resulting topology (displayed via a call to
<uvm_root::print_topology> method) is similar to the following:
# UVM_INFO @ 0 [RNTST] Running test ...
# UVM_INFO @ 0 [UVMTOP] UVM testbench topology:
# ----------------------------------------------------------------
# Name Type Size
Value
# ----------------------------------------------------------------
# env env - env0@2
# agent0 agent - agent0@4
# driver0 driverB #(packet) - driver0@8
# pkt packet - pkt@21
# driver1 driverD #(packet) - driver1@14
# pkt packet - pkt@23
# agent1 agent - agent1@6
# driver0 driverD #(packet) - driver0@24
# pkt packet - pkt@37
# driver1 driverD2 #(packet)- driver1@30
# pkt packet - pkt@39
# ----------------------------------------------------------------
Objects can also be overridden by type or instance. But objects or sequence related objects are
generally only used with type overrides since the instance override approach relates to a position
in the UVM testbench component hierarchy which objects do not take part in.
However there is a coding trick which can be used to override specific "instances" of an object
and this is explained in the session on uvm sequences. The code for an object override follows
the same form as the component override.
• Type-based
– The type-based interface is far less prone to errors in usage.
– When errors do occur, they are caught at compile-time.
• Name-based
– The name-based interface is dominated by string arguments that can be
misspelled and provided in the wrong order.
– Errors in name-based requests might only be caught at the time of the call, if at
all.
– Further, the name-based interface is not portable across simulators when used
with parameterized classes.
In real time scenarios, multiple factory overrides at various hierarchy levels are used. To keep
factory override deterministic, few precedence rules are defined.
UVM Configuration
One of the key tenets of designing reusable testbenches is to make testbenches as configurable
as possible. Doing this means that the testbench and its constituent parts can easily be reused
and quickly modified.
In a testbench, there are any number of values that you might normally write as literals - values
such as for-loop limits, string names, randomization weights and other constraint expression
values, coverage bin values. These values can be represented by System Verilog variables, which
can be set (and changed) at runtime, or System Verilog parameters, which must be set at compile
time. Because of the flexibility they offer, variables organized into configuration objects and
accessed using the uvm_config_db API should always be used where possible.
However, bus widths have to be fixed at compile time, so cannot be implemented as
configuration objects. There are a number of articles on handling parameters in the UVM:
• Parameterized Tests shows how to use parameterized tests with the UVM factory
• The test parameter package article shows how to centralize the parameters shared
between DUT and testbench
The uvm_config_db class is the recommended way to access the resource database. A resource is
any piece of information that is shared between more than one component or object. We use
uvm_config_db::set to put something into the database and uvm_config_db::get to retrieve
information from the database. The uvm_config_db class is parameterized, so the database
behaves as if it is partitioned into many type-specific "mini databases." There are no limitations on
the type - it could be a class, a uvm_object, a built in type such as a bit, byte, or a virtual interface.
There are two typical uses for uvm_config_db. The first is to pass virtual interfaces from the DUT
to the test, and the second is to pass configuration classes down through the testbench.
This code puts two AHB interfaces into the configuration database at the hierarchical location
"uvm_test_top", which is the default location for the top level test component created by
run_test(). The two different interfaces are put into the database using two distinct names,
"data_port" and "control_port". Two things to note about this code are:
• We use "uvm_test_top" because it is more precise and more efficient than "*". This will
always work unless your top level test does something other than super.new( name ,
parent ) in the constructor of your test component. If you do something different in your
test component, you will have to adjust the instance name accordingly.
The code attempts to get the virtual interface for the AHB dataport and put it into the correct
agent's configuration class. If this attempt does not succeed, we provide a meaningful error
message.
/* Program 1.11 Sample code fragments illustrate getting a configuration
class in a transactor */
class ahb_monitor extends uvm_monitor;
ahb_agent_config m_cfg;
function void build_phase( uvm_phase phase );
...
if( !uvm_config_db #( ahb_agent_config )::get( this ,
"" ,
"ahb_agent_config”,
m_cfg)) begin
`uvm_error("Config Error" ,
"#( ahb_get_config )::get can’t find resource"" )
end
...
endfunction
endclass
This code gets the configuration for the ahb agent and all its children. Two things to note about
this code are:
The UVM configuration database takes care of the scope and storage of the object. Below is the
code for a typical configuration object for an agent. It has a virtual interface, which is used to
point to the interface that the agent is connected to, and a number of variables used to describe
and control that interface.
/* Program 1.12 Sample code fragments illustrate configuration object */
class env_config extends uvm_object;
`uvm_object_utils(env_config)
// Configuration Parameters
iic_slave_agent_config m_iic_slave_agent_config;
wb_agent_config m_wb_agent_config;
int m_wb_verbosity; // verbosity level for wishbone messages
int m_iic_verbosity; // verbosity level for wishbone messages
endclass
Using a Configuration Object:
Any component that requires configuration should perform the following steps:
• get its own configuration
• create its own internal structure and behavior based on its configuration
• configure its children
// Methods
function void build_phase(uvm_phase phase);
super.build_phase(phase);
`uvm_info(get_type_name(),"START Build_Phase",UVM_DEBUG)
// get virtual interface from config_cb set by top
if(!uvm_config_db#(virtual iicIf)::get(this, "", "iicIf", m_iicIf))
endclass
env1_h env2
agnt1_h agnt1
agnt2_h agnt2
env2_h env2
agnt1_h agnt1
agnt2_h agnt2
Experiment various ways of overriding agnt1 with agent2. Print topology for every
attempt and observe results.
2. Configure some data from Test case and retrieve the configuration information at various
sub-component levels
Worksheet