Uvm 3
Uvm 3
The upper-layer monitor observes lower-layer items on an analysis export and translates them to upper-layer
items, then publishes them on its higher-layer analysis port. Any lower-layer item observed by the monitor
that is not relevant to the upper-layer protocol is simply ignored.
The higher- and lower-layer sequencers and monitors are instantiated in the protocol agent, and are
connected during that agent’s connect phase.
function void
connect();drv.seq_item_port.connect(l_sqr.seq_item_export);
u2lseq.upper_sequencer = u_sqr;
l_mon.ap.connect(u_mon.a_export);
ap.connect(u_mon.ap);
endfunction : connect
Arbitrarily-layered protocols are protocols with a user-defined layer structure. The nature and topology of
the various layers is not defined by the protocol themselves and can be combined as required by the design
under verification. Each protocol exists independently from each other and is modeled using its own agent,
often written independently. The protocols should be layered within an environment, with an agent instance
for each protocol layer instance.
The layering of a higher-layer protocol onto a lower-layer protocol is performed in a layering driver, that
replaces the default driver in the higher-layer agent (see Figure 38).
Higher‐Level Agent
Delayering
Sequencer
Monitor
High‐Level
Sequence
Layering
Driver
Lower‐Level Agent
Sequencer Monitor
Passthru
Sequence
Driver
A layering driver is implemented using the same familiar techniques used to implement a “regular” driver,
except that a higher-layer sequence item is executed in terms of lower-level sequence items instead of pin
wiggling through a virtual interface. Similarly, the default monitor in the higher-layer agent is replaced with
a de-layering monitor.
The layering driver and monitor must supersede the default driver and monitor using the class factory. The
layering driver is connected to the lower-layer sequencer using a pass thru sequence. A pass thru sequence is
a simple sequence that executes a single sequence item in a directed fashion, without randomizing it. A pass
thru sequence is used in preference to using the uvm_sequencer_base::send_request() or
uvm_sequencer_base::execute_item() methods so it can be configured with state information,
such as priority, to shape the lower-level protocol traffic.
The pass thru sequence is functionally equivalent to the virtual interface of a “regular” driver and thus
should similarly be passed by the containing environment the configuration database. The connection
between the agent’s monitoring paths is performed at the time the containing environment’s connect()
method is invoked, using the same technique as when connecting a subscriber to an analysis port.
When creating complex protocol hierarchies (see Figure 39), multiple pass thru sequences can be
concurrently executed on the lower-layer agent, one for each higher-layer protocol.
Higher‐layer
Agent B
Higher‐layer Agent
Higher‐layer Sequencer C
Agent A
Sequencer
Sequencer
Layering
Driver
Layering
Layering Driver
Driver
Sequencer
Lower Layer Agent
Passthru
Sequence
Passthru
Sequence
Passthru
Sequence Driver
The following example assumes the existence of a lower-layer agent and higher-layer agent named
lower_agent and upper_agent respectively, with the usual item, driver and monitor classes named
lower_item, lower_driver, lower_monitor, upper_item, upper_driver, and
upper_monitor. The example further assumes a simple one-to-one mapping between the upper and
lower-layer protocol. A complete example can be found in the UVM distribution under the directory
examples/simple/layering/agents.
Not all code required for creating UVM-compliant components (such as utility macro calls and
configuration of virtual interfaces) is shown for brevity and to focus on the implementation of the layering.
First, a lower-layer pass thru sequence must be defined. It is a trivial sequence that will be used to execute
individual lower-level items and optionally get their response. The sequence has no body() method. The
items will be executed by explicitly calling its start_item() and finish_item() methods from the
layering driver.
endclass: lower_passthru_seq
The layering driver is an upper-layer driver that pulls upper-layer items and translates them to lower-layer
items in a forever loop. This is accomplished in the driver’s run_phase() method. It is important NOT to
call the run_phase() method of the base class as it implements the default driver functionality the
layering driver is designed to replace. The lower-level items are executed by directly calling the
start_item() and finish_item() methods of the pass thru sequence and specifying the lower-level
sequencer where the item is executed. The reference to the lower-level sequencer is obtained from the
configuration database at the beginning of the run_phase. Notice it is not necessary to explicitly start the
pass thru sequence as it is only a context placeholder for executing the lower-level items. Should it be
necessary to deal with the response from the execution of the lower-level item, it can be obtained by
similarly directly calling the get_response() method of the pass thru sequence. The layering driver
should not raise objections: it is the responsibility of the higher-layer sequence to object as necessary.
forever begin
upper_item u_item;
lower_item l_item;
seq_item_port.get_next_item(u_item);
l_item = upper_to_lower(u_item)
l_seq.start_item(l_item, -1, sqr);
l_seq.finish_item(l_item, -1, sqr);
// Optional: l_seq.get_response(rsp);
seq_item_port.item_done();
end
endtask
endclass: upper_layering_driver
The (de)layering monitor is an upper-layer monitor that observes lower-layer items on an analysis export
and translates them to upper-layer items, then publishes them on its higher-layer analysis port. Any lower-
layer item observed by the monitor that is not relevant to the upper-layer protocol is simply ignored. The
run_phase() method is left empty and does not call super.run_phase() to disable the normal
higher-layer monitor functionality.
If the protocol layering requires handshaking or feedback from the lower-layer protocol, first declare an
uvm_analysis_imp and a corresponding write() method in the layering driver that is connected to
the delayering monitor. The driver’s write() method will have to notify the layering thread of the relevant
protocol state changes.
The higher-layer and lower-layer agents are instantiated in a layered environment. The layering driver and
monitor are instantiated in the higher-layer agent using the class factory. The lower-level sequencer instance
is configured as the lower-level sequencer for the layering driver using the configuration DB. Note this must
be done in the connect phase as the sequencer instance does not yet exists at the completion of the build
phase. The analysis port of the lower-layer agent is connected to the analysis export of the higher-layer
layering monitor using the usual connection mechanism
In most networking application, it is necessary to verify the dynamic reprovisioning of components and the
ability of components to adapt to changes in the protocol topology. For example, adding and removing
devices and hubs from a USB network is a normal operation of that protocol which modifies the structure of
the protocol layers. Therefore, it should be possible for the layered protocol structure to adapt to dynamic
changes, and be able to add and remove additional protocol layers and sibling protocol streams.
Unfortunately, due to the static nature of UVM components, the protocol layer hierarchy cannot be
dynamically modified at run-time; all alternative protocol hierarchies must be created entirely at build time.
Different protocol layer topologies can then be created by starting and stopping the corresponding layering
or pass thru sequence at the root of the protocol stack that must be introduced or removed.
The various `uvm_do* macros perform several steps sequentially, including the allocation of an object
(sequence or sequence item), synchronization with the driver (if needed), randomization, sending to the
driver, and so on. The UVM Class Library provides additional macros that enable finer control of these
various steps. This section describes these macro variations, which represent various combinations of calling
the standard methods on sequence items.
6.5.3.1 `uvm_create
This macro allocates an object using the common factory and initializes its properties. Its argument is a
variable of type uvm_sequence_item or uvm_sequence. You can use the macro with
SystemVerilog’s constraint_mode() and rand_mode() functions to control subsequent
randomization of the sequence or sequence item.
In the following example, my_seq is similar to previous sequences that have been discussed. The main
differences involve the use of the `uvm_create(item0) call. After the macro call, the rand_mode()
and constraint_mode() functions are used and some direct assignments to properties of item0 occur.
The manipulation of the item0 object is possible since memory has been allocated for it, but randomization
has not yet taken place. Subsequent sections will review the possible options for sending this pre-generated
item to the driver.
6.5.3.2 `uvm_send
This macro processes the uvm_sequence_item or uvm_sequence class handle argument as shown in
Figure 15 and Figure 16, without any allocation or randomization. Sequence items are placed in the
sequencer’s queue to await processing while subsequences are processed immediately. The parent
pre_do(), mid_do(), and post_do() callbacks still occur as shown.
In the following example, we show the use of uvm_create() to pre-allocate a sequence item along with
`uvm_send, which processes it as shown in Figure 15, without allocation or randomization.
Similarly, a sequence variable could be provided to the `uvm_create and `uvm_send calls above, in
which case the sequence would be processed in the manner shown in Figure 16, without allocation or
randomization.
These macros are identical to `uvm_send (see Section 6.5.3.2), with the single difference of randomizing
the given class handle before processing it. This enables you to adjust an object as required while still using
class constraints with late randomization, that is, randomization on the cycle that the driver is requesting the
item. `uvm_rand_send() takes just the object handle. `uvm_rand_send_with() takes an extra
argument, which can be any valid inline constraints to be used for the randomization.
The following example shows the use of `uvm_create to pre-allocate a sequence item along with the
`uvm_rand_send* macros, which process it as shown in Figure 15, without allocation. The
rand_mode() and constraint_mode() constructs are used to show fine-grain control on the
randomization of an object.
In the preceding sections, all uvm_do macros (and their variants) execute the specified item or sequence on
the current p_sequencer. To allow sequences to execute items or other sequences on specific sequencers,
additional macro variants are included that allow specification of the desired sequencer.
All of these macros are exactly the same as their root versions, except they all take an additional argument
(always the second argument) that is a reference to a specific sequencer.
‘uvm_do_on(s_seq, that_sequencer);
‘uvm_do_on_with(s_seq, that_sequencer, {s_seq.foo == 32’h3;})
6.6.1 Introduction
The Command Line Processor class provides a general interface to the command line arguments that are
provided for the given simulation. Not only can users retrieve the complete arguments using methods such
as ~get_args()~ and ~get_arg_matches()~, but they can also retrieve the suffixes of arguments
using ~get_arg_values()~.
The uvm_cmdline_processor class also provides support for setting various UVM variables from the
command line, such as components’ verbosities and configuration settings for integral types and strings.
Command line arguments that are in UPPERCASE should only have one setting to invocation. Command
line arguments in lowercase can have multiple settings per invocation. All of this is further described in
uvm_cmdline_processor in the UVM 1.2 Class Reference.
To start using the uvm_cmdline_processor, the user needs to first get access to the singleton instance of the
uvm_cmdline_processor.
A common use case involves using the get_arg_value() function to get the value of a specific argument,
which is returned through an output argument. The total number of matches returned from this function
usually is of interest when there are no matches and no default value. In this case, the user may generate an
error. Similar to $test$plusargs, if the command line contains multiple matching arguments, the first
value is returned.
If the user knows the value is an integer, this string value may be further turned into an integer by calling the
SystemVerilog function atoi() as follows.
If processing multiple values makes sense for a particular option (as opposed to just the first one found), use
the get_arg_values() function instead, which returns a queue of all the matches.
string my_value_list[$];
int rc = cmdline_process.get_values("+abc=", my_value_list);
The uvm_cmdline_processor provides comprehensive access to the command line processing; see
Section 6.6.3 and the UVM 1.2 Class Reference for more details.
This section highlights how to select tests, set verbosity, and control other UVM facilities using the CLI.
The uvm_cmdline_processor is used to pass the +UVM_TESTNAME option to the run_test() routine to
select which class will get constructed as the top-level testcase.
The uvm_cmdline_processor looks for the +UVM_VERBOSITY option to change the verbosity for all
UVM components. It is also possible to control the verbosity in a much more granular way by using the
+uvm_set_verbosity option. The +uvm_set_verbosity option has a specific format that allows
control over the phases where the verbosity change applies, and in the case of time-consuming phases,
exactly what time it applies. Typically, verbosity is only turned up during time-consuming phases as the test
approaches the time where an error occurs to help in debugging that error. The simulation will run faster if it
is not burdened by generating debug messages earlier on where they are not required.
sim_cmd
+uvm_set_verbosity=component_name,id,verbosity,phase_name,optional_time
In a similar fashion, the severity, and also the action taken, can be modified as follows.
sim_cmd +uvm_set_action=component_name,id,severity,action
sim_cmd +uvm_set_severity=component_name,id,current_severity,new_severity
6.6.3.3 Other UVM facilities that can be Controlled from the Command Line
Table 14 shows other UVM options the user can set from the CLI.
Facility Setting
Timeout +UVM_TIMEOUT
Please see the UVM 1.2 Class Reference for more examples of using the uvm_cmdline_processor
class facilities.
To reduce coding overhead, the UVM library provides a set of macro declarations. These macros can be
used to combine the definition of multiple things into one step (e.g., declare a field and a task, when both are
required to exist at the same time). No other SystemVerilog code structure can express such concerns in a
concise manner. However, you are not required to use these macros and may instead choose to build the
expanded (required) code yourself.
f) It is also important to understand that different macro types are used in different contexts.
1) Registration macros (uvm*utils*, *callbacks, constants, etc.) are typically used once
and do not incur a performance penalty.
2) Sequence macros (uvm_do*) typically expand in just a few lines and do not create any over-
head, but for really fine grained sequence and item control, the underlying sequence functions
may be used directly.
3) Field utils macros (uvm_field_type) provide for a number of field types (int, enum,
string, object, *aa*, *sarray*, *queue*, etc.), which are generic implementations
for that particular field of a print(), compare(), pack(), unpack(), or record()
function. Handcrafted implementations for these functions can be supplied instead of the
generic code provided by the uvm_field_* macros by implementing the do_print(),
do_compare(), do_pack(), do_unpack(), or do_record() functions respectively.
4) The uvm_field_* macro implementations have a non-programmable execution order within
the compare/copy/etc. methods, whereas the handcrafted implementations provide full con-
trol over this order via do_compare/do_copy/etc. methods.
The UVM distribution comes with two examples of small environments illustrating the usage with and
without macro usage. The examples are located under examples/simple/basic_examples/pkg
and examples/simple/sequence/basic_read_write_sequence.
Example