Uvm 1
Uvm 1
6. Advanced Topics
This chapter discusses UVM topics and capabilities of the UVM Class Library that are beyond the essential
material covered in the previous chapters. Consult this chapter as needed.
All the infrastructure components in an UVM verification environment, including environments and tests,
are derived either directly or indirectly from the uvm_component class. These components become part
of the environment hierarchy that remains in place for the duration of the simulation.1 Typically, you will
derive your classes from the methodology classes, which are themselves extensions of uvm_component.
However, understanding the uvm_component is important because many of the facilities that the
methodology classes offer are derived from this class. User-defined classes derived from this class inherit
built-in automation, although this feature is entirely optional.
The following sections describe some of the capabilities that are provided by the uvm_component base
class and how to use them.The key pieces of functionality provided by the uvm_component base class
include:
— Phasing and execution control
— Configuration methods
— Factory convenience methods
— Hierarchical reporting control.
UVM provides a built-in factory to allow components to create objects without specifying the exact class of
the object being creating. 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 shall be polymorphically compatible.
This includes having all the same TLM interface handles and TLM objects need to be created by the new
replacement component. The factory provides this capability with a static allocation function that you can
use instead of the built-in new function. The function provided by the factory is:
Since the create() method is automatically type-specific, it may be used to create components or objects.
When creating objects, the second argument, parent, is optional.
A component using the factory to create data objects would execute code like the following:
1
Contrast to uvm_sequence, sequence_item, and transaction, which are transient —they are created, used, and then
garbage collected when dereferenced.
In the code above, the component requests an object from the factory that is of type mytype with an
instance name of data.
When the factory creates this object, it will first search for an instance override that matches the full instance
name of the object. If no instance-specific override is found, the factory will search for a type-wide override
for the type mytype. If no type override is found then the type created will be of type mytype.
You must tell the factory how to generate objects of specific types. In UVM, there are a number of ways to
do this allocation.
— The recommended method is to use the `uvm_object_utils(T) or `uvm_compo-
nent_utils(T) macro in a derivative uvm_object or uvm_component class declaration,
respectively. These macros contain code that will register the given type with the factory. If T is a
parameterized type, use `uvm_object_param_utils or
`uvm_component_param_utils, e.g.,
`uvm_object_utils(packet)
‘uvm_component_param_utils(my_driver)
— Use the `uvm_object_registry(T,S) or `uvm_component_registry(T,S) registra-
tion macros. These macros can appear anywhere in the declaration space of the class declaration of
T and will associate the string S to the object type T. These macros are called by the corresponding
uvm_*_utils macros, so you may only use them if you do not use the ‘uvm_*_utils macros.
A global factory allows you to substitute a predefined-component type with some other type that is
specialized for your needs, without having to derive the container type. The factory can replace a component
type within the component hierarchy without changing any other component in the hierarchy. You need to
know how to use the factory, but not how the factory works.
NOTE—All type-override code should be executed in a parent prior to building the child(ren). This means that environ-
ment overrides should be specified in the test.
Two interfaces exist to replace default components. These interfaces allow overriding by type or instance,
and will be examined one at a time.
The first component override replaces all components of the specified type with the new specified type. The
prototype is.
<orig_type>::type_id::set_type_override(<override_type>::get_type(),
bit replace = 1);
The set_type_override() method is a static method of the static type_id member of the
orig_type class. These elements are defined in the uvm_component_utils and
If no overrides are specified, the environment will be constructed using the original types. For example,
suppose the environment creates an ubus_master_driver type component inside
ubus_master_agent.build(). The set_type_override method allows you to override this
behavior in order to have an ubus_new_master_driver for all instances of
ubus_master_driver.
ubus_master_driver::type_id::set_type_override(ubus_new_master_driver::
get_type);
ubus_demo_tb0
(ubus_demo_tb) New type created by type override
driver (ubus_slave_driver)
sequencer (ubus_slave_sequencer)
monitor (ubus_slave_monitor)
bus_monitor (ubus_bus_monitor)
The second component override replaces targeted components of the matching instance path with the new
specified type. The prototype for uvm_component is
<orig_type>::type_id::set_inst_override(<override_type>::get_type(), string
inst_path);
Assume the ubus_new_slave_monitor has already been defined. Once the following code is
executed, the environment will now create the new type, ubus_new_slave_monitor, for all instances
that match the instance path.
ubus_slave_monitor::type_id::set_inst_override(ubus_new_slave_monitor::
get_type(), “slaves[0].monitor”);
In this case, the type is overridden that is created when the environment should create an
ubus_slave_monitor for only the slaves[0].monitor instance that matches the instance path in
the override. The complete hierarchy would now be built as shown in Figure 32. For illustration purposes,
this hierarchy assumes both overrides have been executed.
NOTE—Instance overrides are used in a first-match order. For each component, the first applicable instance override is
used when the environment is constructed. If no instance overrides are found, then the type overrides are searched for
any applicable type overrides. The ordering of the instance overrides in your code affects the application of the instance
overrides. You should execute more-specific instance overrides first. For example,
mytype::type_id::set_inst_override(newtype::get_type(), "a.b.*");
my_type::type_id::set_inst_override(different_type::get_type(), "a.b.c");
will create a.b.c with different_type. All other objects under a.b of mytype are created using newtype. If
you switch the order of the instance override calls then all of the objects under a.b will get newtype and the instance
override a.b.c is ignored.
my_type::type_id::set_inst_override(different_type::get_type(), "a.b.c");
mytype::type_id::set_inst_override(newtype::get_type(), "a.b.*");
ubus_demo_tb0
(ubus_demo_tb) New type created by type override
driver (ubus_slave_driver)
sequencer (ubus_slave_sequencer)
bus_monitor (ubus_bus_monitor)
6.3 Callbacks
Callbacks are an optional facility end users can use to augment component behavior. Callbacks may have a
noticeable impact on performance, so they should only be used when the desired functionality cannot be
achieved in any other way.
d) Define a new callback class extending from the callback base class provided by the developer, over-
riding one or more of the available callback methods.
e) Register one or more instances of the callback with the component(s) you wish to extend.
6.3.2 Example
The example below demonstrates callback usage. The component developer defines a driver component and
a driver-specific callback class. The callback class defines the hooks available for users to override. The
component using the callbacks (that is, calling the callback methods) also defines corresponding virtual
methods for each callback hook. The end-user may then define either a callback or a driver subtype to
extend driver’s behavior.
The developer of the bus_bfm adds the trans_received and trans_executed virtual methods,
with empty default implementations and utilizes some macros that implement the most common algorithms
for executing all registered callbacks. With this in place, end-users can now customize component behavior
in two ways:
— extend bus_driver and override one or more of the virtual methods trans_received or
trans_executed. Then configure the factory to use the new type via a type or instance override.
— extend bus_driver_cb and override one or more of the virtual methods trans_received or
trans_executed. Then register an instance of the new callback type with all or a specific
instance of bus_driver. The latter requires access to the specific instance of the bus_driver.
The driver’s put task, which implements the component’s primary functionality, merely calls the virtual
methods and callbacks at the appropriate times during execution.
initial begin
bd_cb::add(driver,cb);
cbs.display_cbs();
for (int i=1; i<=5; i++) begin
tr.addr = i;
tr.data = 6-i;
driver.in.put(tr);
end
end
endmodule
c) Instance-specific callback registrations can only be performed after the component instance exists.
Therefore, those are typically done in the build() and end_of_elaboration() for exten-
sions that need to apply for the entire duration of the test and in the run() method for extensions
that need to apply for a specific portion of the testcase.
class error_test extends uvm_test;
function new(string name = “error_test”, uvm_component parent = null);
super.new(name, parent);
endfunction
#1000;
bd_cb::add_by_name(“top.bfm”, cbs, this);
#100;
bd_cb::delete(this, cbs);
endtask
endclass
In UVM, it is possible to group similar sequences together into a sequence library. The
uvm_sequence_library is an extension of the uvm_sequence base class.
The uvm_sequence_library is a sequence that contains a list of registered sequence types. It can be
configured to create and execute these sequences any number of times using one of several modes of
operation, including a user-defined mode. When started (as any other sequence) the sequence library will
randomly select and execute a sequence from its sequences queue, depending on the
selection_mode chosen.
— UVM_SEQ_LIB_RAND: Randomly select from the queue.
— UVM_SEQ_LIB_RANDC: Randomly select from the queue without repeating until all sequences
have executed.
— UVM_SEQ_LIB_ITEM: Execute a single item.
— UVM_SEQ_LIB_USER: Call the select_sequence() method, which the user may override, to
generate an index into the queue to select a sequence to execute.
To create a sequence library, declare your own extension of uvm_sequence_library and initialize it as
follows:
Individual sequence types may then be added to the sequence library, by type, using the
‘uvm_add_to_seq_lib macro:
A sequence type may be added to more than one sequence library by having multiple
‘uvm_add_to_seq_lib calls in the sequence definition. The parameterization of the sequences and the
sequence library must be compatible.
Alternatively, sequences can be added to every instance of a particular type of sequence library using the
add_typewide_sequence() and/or add_typewide_sequences() methods:
There are two ways you can create concurrently-executing sequences: the following subsections show an
example of each method.
In this example, the sequences are executed with fork/join. The simulator schedules which sequence
requests interaction with the sequencer. The sequencer schedules which items are provided to the driver,
arbitrating between the sequences that are willing to provide an item for execution and selects them one at a
time. The a and b sequences are subsequences of the fork_join_sequence.
In this example, the concurrent_seq sequence activates two sequences in parallel. It waits for the
sequences to complete. Instead, it immediately finishes after activating the sequences. Also, the a and b
sequences are started as root sequences.