Making The Most of SystemVerilog+UVM PDF
Making The Most of SystemVerilog+UVM PDF
Making The Most of SystemVerilog+UVM PDF
Dr David Long
Doulos
Ringwood, UK
www.doulos.com
ABSTRACT
In the two years since UVM 1.0 was released by Accellera, Doulos has seen a big increase in the
number of customers wanting to learn SystemVerilog: UVM has become the new standard verifi-
cation environment for many companies. However, engineers often find that the size and com-
plexity of the SystemVerilog language and the UVM class library make it hard to learn how to
make effective use of their many features. This paper is intended to give guidance to engineers
about some useful features of both SystemVerilog and UVM that are often overlooked, used in-
correctly or simply avoided because they are perceived as being too hard to understand. The
first part identifies the most common novice-user mistakes and sets out rules to avoid them. The
second part discusses useful enhancements to verification capabilities that can be obtained from
a deeper understanding of several SystemVerilog and UVM features. It provides simple examples
and guidelines to show how each enhancement can be achieved.
Table of Contents
1. Introduction ........................................................................................................................... 4
2. Common Mistakes of SystemVerilog Novices ..................................................................... 4
2.1 FUNDAMENTAL SYSTEMVERILOG ERRORS ............................................................................. 4
2.2 PACKAGES .............................................................................................................................. 5
2.3 INTERFACES, VIRTUAL INTERFACES AND MODPORTS ............................................................. 6
2.4 DIFFERENCES BETWEEN MODULES AND CLASSES................................................................... 6
3. Common Mistakes in UVM and How to Avoid Them ......................................................... 8
3.1 SIMULATION PHASES .............................................................................................................. 8
3.2 USE OF MACROS ..................................................................................................................... 9
3.3 THE CONFIGURATION DATABASE ......................................................................................... 11
3.4 OBJECTIONS .......................................................................................................................... 11
3.5 SYNCHRONISATION AND TIMING .......................................................................................... 12
3.6 RECOMMENDED TEMPLATES ................................................................................................ 15
4. Use of Lesser Known Features to Enhance Verification Capabilities ................................ 17
4.1 IMPROVEMENTS TO RANDOM NUMBER GENERATION ............................................................. 17
4.2 USING UVM REGULAR EXPRESSIONS IN A CHECKER ............................................................. 18
4.3 MAKING USE OF C++ BOOST LIBRARY E.G. ENHANCED REGULAR EXPRESSIONS USING DPI
................................................................................................................................................... 20
4.4 USING STRINGS AND UVM HDL ACCESS TO READ/WRITE SIGNALS IN DUT ......................... 21
4.5 INTERCEPTING MESSAGES WITH UVM REPORT CATCHER .................................................... 23
4.6 USES OF DYNAMIC PROCESSES .............................................................................................. 25
5. Conclusions ......................................................................................................................... 27
6. References ........................................................................................................................... 28
Table of Figures
Figure 1 Illegal SystemVerilog Package Imports...................................................................... 5
Figure 2 SystemVerilog Interface with Modport ...................................................................... 6
Figure 3 Issues when accessing and copying objects ................................................................ 7
Figure 4 Typical UVM Monitor Class run_phase ..................................................................... 9
Figure 5 Use of uvm_info_context within a Sequence ........................................................... 10
Figure 6 Error Due to not Dropping Objection ....................................................................... 12
Figure 7 Template for run_phase with Objection ................................................................... 12
Figure 8 Verilog BFM ............................................................................................................. 13
Figure 9 Verilog Testbench calling BFM................................................................................ 14
Figure 10 UVM Driver .............................................................................................................. 15
Figure 11 Easier UVM Pattern 1 ............................................................................................... 15
Table of Tables
Table 1 UVM Phase Methods ....................................................................................................... 16
Table 2 Posix Regular Expression Classes ................................................................................... 19
Table 3 Posix Regular Expression Quantifiers ............................................................................. 19
Section 2 of this paper identifies some of the most common mistakes in SystemVerilog test-
benches made by novice users. Section 3 explores common mistakes made by UVM novices and
offers some suggestions and guidelines to help avoid them. Section 4 discusses some useful en-
hancements to verification capabilities that can be obtained from a deeper understanding of sev-
eral SystemVerilog and UVM features. It provides simple examples and guidelines to show how
each enhancement might be achieved.
We believe that all of the issues mentioned above can be addressed by following a well-designed
training programme, however, project timescales and budget limitations often result in engineers
having to teach themselves the core elements of SystemVerilog and UVM on-the-job. Mistakes
in the code written by inexperienced and self-taught users may not always be revealed when the
code is compiled, leading to frustration and more often than not, a lot of time wasted debugging
testbenches that do not perform as expected. Examples of mistakes that we commonly see are
described in more detail in the following sections.
2.2 Packages
The use of packages is encouraged in SystemVerilog as a mechanism for code reuse (note that
the UVM base class library is a good example since it is accessed by including a package). How-
ever, if used incorrectly, packages can lead to some unexpected errors.
Unlike VHDL, in SystemVerilog user-defined packages are often created by using `include to
pull in declarations from a list of other source files. Where source files `include other source
files, there is the danger that declarations might end up being included in a single package multi-
ple times, or in two different packages that are both imported into the same scope these would
both generate compile time errors. Careful use of macros (`define) and conditional compila-
tion directives (`ifdef or `ifndef) around declarations is recommended to prevent these er-
rors. However, if packages are compiled as separate "compilation units", any compilation direc-
tives set when one package was compiled would no longer be defined when subsequent packages
were compiled and so would not stop errors due to `include of the same file in multiple pack-
ages. The only solution in this case is to avoid the use of wildcards if multiple packages must be
imported into the same scope.
package pkg1;
typedef struct { int a; int b; } my_struct;
endpackage: pkg1
package pkg2;
typedef struct { int a; int b; } my_struct;
endpackage: pkg2
module top();
import pkg1::*; //pkg1::my_struct potentially locally visible
import pkg2::*; //importing name from 2 packages is illegal
my_struct str;
endmodule: top
The situation gets even more confused if modports are involved! As a matter of principle, we
would recommend the use of modports with virtual interfaces since they impose a set of rules to
control access to the interface members and should therefore help to prevent unexpected situa-
tions from arising accidentally (e.g. writing to a variable that is intended to be read-only). Un-
fortunately, modports are often used in an ad-hoc manner and even when present, they can easily
be bypassed (deliberately or inadvertently). Care is also required when specifying a virtual inter-
face that includes modports older versions of some SystemVerilog simulators had restrictions
on the types of operation that could be performed on interface members via a modport on a vir-
tual interface.
One of their first concerns is often trying to understand when and how objects get created. This
is further compounded by there being two very different types of object in class-based verifica-
tion environments such as UVM: Classes representing components (e.g. drivers and monitors)
and classes representing transactions that are passed between such components. Both types of
object must be created by calling their constructor (new) but at different points in the simulation.
Components are normally created at time zero (just as module instances are created during the
elaboration stage) while transactions are usually created throughout the simulation. Attempting
to access an object that has not yet been created is a very common error that is usually only
picked up when the simulation crashes! Another common error related to object creation occurs
when the constructor is called with the wrong arguments. This can occur if the code writer does
module test();
initial begin
my_class c1,c2,c3; //handles used to point to object
c1.i = 10; //run-time error c1 points to null!!
c2 = new(10,1.2); //creates new object (allocates memory)
c2.i = 20; //OK c2 points to valid object
c1 = c2; //does not create a copy of object
c1.i = 100; //modifies object also pointed to by c2
$display(c2.i); //100
c3 = new c2; //creates new object, values copied from c2
...
Figure 3 Issues when accessing and copying objects
Misunderstandings about how transaction objects are copied are also common. Novices often
(wrongly) believe that passing an object to a task or returning one from a function will automati-
cally create a copy, when in fact, it is simply passing a reference to the original object. In many
cases, the object reference will be used immediately so this does not generate an error. However,
if an object must be stored until some future time step (e.g. in a FIFO buffer that holds multiple,
pending transactions), the results could be very different. In the worst case, all stored object ref-
erences could point to the same transaction object, with each new write to the transaction modi-
fying the content seen through the other references. The correct approach in this case, should be
to make an explicit copy of the transaction (by calling new) either before each call to the task it
is passed to, or before the function returns, or wherever it is received and stored.
The use of classes in a verification environment also exposes users to object-oriented characteris-
tics which they are often unfamiliar with. One of the questions that novices ask most often when
creating their own classes is "how should I use inheritance properly?" For the component classes
in a typical UVM environment, this is usually an easy question to answer since most component
classes will be directly derived from an appropriate UVM base class and it will not be necessary
to create further derived classes. The situation for transaction and sequence classes is not quite
so clear. Tests often require multiple classes to be created that each correspond to slightly dif-
ferent kinds of stimulus. In these cases, a hierarchy of classes with multiple levels of inheritance
can provide an efficient mechanism to create each of variety of stimulus while minimising the
number of lines of code that must be written. However, the improper use of inheritance can lead
to code that runs inefficiently and is hard to understand, maintain and reuse. A base class might
contain code that is not required in, or relevant to, derived classes: In extreme cases, there may
be members of a base class that must be disabled or hidden in a derived class. Both of these in-
dicate a poorly designed class hierarchy.
A source of confusion for many new users of UVM is the operation of components such as
monitors which may behave quite differently from those in traditional RTL testbenches. An
RTL monitor will be a design entity/module that typically contains a single VHDL process or
Verilog always block which samples its input signals on every active clock edge. A monitor in
UVM will most likely be an instance of a class derived from uvm_monitor. Classes do not
contain (static) processes. Instead, a UVM monitor will include one or more member tasks (usu-
ally just run_phase) that define its operation. Unlike processes, tasks are not started auto-
matically at the start of simulation: they must be called explicitly from a running process once
the simulation is running. The UVM phase scheduler ensures that the run_phase task is
called automatically, at the correct point in the simulation, for every class derived from
uvm_component. It is the responsibility of the testbench author to ensure the code they write
within the run_phase task causes it to wake up whenever input signals are required to be sam-
pled, to suspend between sampling events and to continue running until the end of the phase.
Common mistakes include failing to place actions within a suitable loop (results in a monitor that
only reads its signals once) and not waiting for the correct signals (results in values not being
sampled at the correct time).
...
endclass
Figure 4 Typical UVM Monitor Class run_phase
It is common for a monitor in an RTL testbench to perform checks of some kind. These checks
could be for adherence to a particular bus protocol or could test the observed data against ex-
pected values. They might also involve more formal checks that make use of SystemVerilog
Assertions (SVA). It is common for novices to attempt to implement all of these functions in the
run_phase of a UVM monitor but this would be wrong on two accounts. Firstly, UVM moni-
tors are generally written as part of a reusable component for a particular type of interface (e.g. a
standard bus such as APB) and so should not contain any checks that are related to the function-
ality of a particular device under test. Instead, whenever they detect significant activity on their
interface, they should assemble an appropriate transaction object and send it to a separate com-
ponent that is responsible for testing the device functionality. The connections between UVM
components are designed to work at this "transaction level" rather than responding to individual
signal events. Secondly, it is illegal to place SVA (assert property) statements within a
class. SVA properties are a convenient approach for checking bus protocol but are best placed
within a SystemVerilog interface since they are not allowed within the UVM environment.
UVM component and transaction objects are generally created using the UVM "factory". This
mechanism enables the types of object created to be overridden for a particular test without mak-
ing any changes to the source code. UVM provides a macro that adds the code required to create
There are nearly twenty different macros in UVM that may be called as part of a sequence to
create and send sequence items or child sequences. There is considerable overlap between these
macros. We have found that new users are often confused about the differences between the
macros and have doubts about which one(s) to use. These users are generally wanting prescrip-
tive directions to help them write sequences and (unlike Perl programmers) do not appreciate
"There's More Than One Way To Do It"! Since the macros only contain a few simple
task/function calls, we would suggest that it is easier for new users to avoid the sequence macros
completely (for an example, see Figure 14).
UVM provides macros to report information, warning and error messages. Even though these
macros are very simple, it is recommended that they should always be used instead of calling the
corresponding uvm_report functions directly. The rationale for the macros is to test the ver-
bosity settings for the report and only call the report handler when the verbosity is lower than the
current threshold. New users who are not aware of this recommendation and call the report func-
tions directly may find this has a significant detrimental effect on the simulation performance.
Users sometimes complain that using the report macros to generate messages from inside se-
quences are prefixed with an unwanted string that lists the full hierarchical path name of the se-
quencer that is running the sequence plus the name of the sequence object itself. This can be
avoided by using an alternative macro `uvm_info_context and setting the context (the last
argument) to uvm_top.
start_item(req);
void'(req.randomize());
`uvm_info_context("Serial Seq",req.convert2string(),
UVM_LOW,uvm_top)
finish_item(req);
Part of the confusion in using the configuration database is due to the support for wildcards and
regular expressions in the component path name and the rules that determine how this gets
matched. The most common mistake is providing a search expression for an invalid path name,
either due to misunderstandings about how the component hierarchy is created or about the start-
ing point for relative searches.
Another source of confusion lies in the mechanism to override configuration settings as the com-
ponent hierarchy is built. This is explored further in [4].
3.4 Objections
The UVM objection mechanism is used to ensure that the run-time phase tasks of components do
not advance to the next phase until the current phase of every component is ready. Novice users
often fail to understand how objections should be used. Common errors are premature end of
simulation due to a failure to raise objections or simulations that never complete due to raised
objections never getting dropped. This section looks at how these issues occur and how they
might be avoided.
A very common error when users attempt to write their first UVM test is to forget to raise an
objection at the start of the run phase. If no pre-existing components or sequences raise an ob-
jection during the run phase, the simulation will terminate immediately (unlikely to be the de-
sired behaviour). Raising an objection near the start of the test's run_phase method will en-
sure the simulation can run beyond time 0. This brings us on to the second common error: for-
getting to drop the objection once the test is over! If an objection is never dropped, simulation
will continue until the simulation time reaches the "timeout" threshold this could take a very
long time if the test bench includes clock generators that run until the simulation is forcibly
stopped! Forgetting to drop an objection will result in a message at the end of simulation along
the lines of Figure 6.
To ensure at least one objection is raised and dropped correctly, we would recommend the tem-
plate given in Figure 7 for the run_phase method of the test.
The other main error that new users make with objections is raising and dropping them unneces-
sarily. A typical example might be a driver that raises an objection whenever it pulls a new
transaction from a sequencer and drops it once the transaction has been sent to the bus. A better
solution if the transactions are generated within a finite-length sequence, would be to raise a sin-
gle objection at the start of the sequence and drop it at the end (the driver should indicate to the
sequencer once it has finished processing each transaction).
A typical RTL testbench needs to "wiggle" the pins connected to the DUT. If the DUT connec-
tions are part of a standard bus, it has become common to create a "bus functional model" (BFM)
that translates requests from the testbench for read and write operations into the corresponding
pin-level events across multiple bus clock cycles. The BFM could be implemented as a task but
is most likely to be a module with one or more tasks or processes, triggered on the appropriate
clock edge. A SystemVerilog testbench is likely to use a class to represent transactions and may
also replace the stimulus generator and the BFM modules with a class-based components. In a
UVM testbench, the BFM will be an instance of a class derived from uvm_driver and the
stimulus generator will be an instance of uvm_sequencer (or a class derived from
uvm_sequencer). The UVM driver component will contain one or more tasks to talk to both
sequencer and the DUT. An example Verilog BFM is given in Figure 8 and a suitable testbench
in Figure 9. A corresponding UVM driver is given in Figure 10.
task reset;
.... /// plus tasks for other operations
Endmodule
module tb();
...
initial begin
driver.reset;
driver.move_forward(8'hFE);
driver.turn_left;
driver.move_forward(8'b20);
`uvm_component_utils(robot_driver)
endclass: serial_driver
Easier UVM suggests two general templates that should followed when writing code: Pattern1
for components (see Figure 11) and Pattern 2 for sequences (see Figure 12). There is also a simi-
lar template for sequence items.
...
Endclass
Figure 11 Easier UVM Pattern 1
task body;
...
endtask
...
Endclass
Figure 12 Easier UVM Pattern 2
The Easier UVM testbench components should be implemented by overriding the phase methods
given in Table 1.
The UVM factory should always be used to create components as shown in Figure 13.
The behaviour of a UVM sequence is defined by its body task. This should call start_item
or start to execute sequence items and child sequences respectively.
tx = my_tx::type_id::create("tx");
start_item(tx);
assert( tx.randomize() with { cmd == 0; } );
finish_item(tx);
...
endtask
endclass
Figure 14 Easier UVM Sequence Pattern
A user-defined test class overrides the factory (if necessary); creates a configuration object and
writes it to the configuration database; creates the UVM environment and finally starts the top-
level sequence. A typical test is given in Figure 15.
The string matching provided by uvm_is_match is based on Posix regular expressions (it uses
DPI to call a function from the GNU regex.h header). The rules for matching string syntax are
summarised below:
String must begin and end with a forward slash character '/'
Character classes are surrounded by square brackets (e.g. "[A-Z]" matches any upper case
character)
There are several pre-defined character classes as shown in Table 2 (Note that classes
such as Perl's \w, \d, etc are not supported)
{3} exactly 3
{1,3} between 1 and 3
{3, } 3 or more
* 0 or more
+ 1 or more
? 0 or 1
Table 3 Posix Regular Expression Quantifiers
A search starts at the left-hand side of a string and is "greedy" (if there are wildcards it tries to
match as many characters as possible). There are some limitations compared to Perl regular ex-
pressions: The most significant is the lack of support for "back-references" within an expression.
Regular expressions can be useful when searching for a particular pattern within a string. Given
that it is usual for UVM transactions to define the convert2string function (see Figure 17),
this opens up interesting possibilities for checkers as demonstrated in Figure 18.
4.3 Making use of C++ Boost library e.g. enhanced regular expressions using
DPI
The uvm_is_match function calls a C++ function using the SystemVerilog Direct Program-
ming Interface (DPI). It is easy to enhance the capabilities of UVM by importing other useful C
and C++ functions. We will use the Boost regular expression library as an example, This pro-
vides enhanced regular expression and match operations that are similar to Perl and include some
features that are not supported by uvm_is_match.
Calling a C (or C++) library function using DPI may require a C/C++ wrapper function to be
defined. This is the case with the boost::regex_search function that requires arguments
of type std::string and boost::regex. Fortunately, the const char* values
#include <string>
#include <boost/regex.hpp>
extern "C"
bool regex_search(const char* re, const char* str) {
The SystemVerilog code to work with the imported regex_search function is given in Figure
20. In many cases, incorporating existing C/C++ functions simply requires adding their source
code files to the command line when the SystemVerilog code is compiled. However, in the case
of the Boost regular expression library, this required the library to be built separately (as a shared
library) and then added to the VCS command line with the sv_lib option. When building a
C/C++ library, it is important to use the C/C++ compiler that is provided with the SystemVer-
ilog simulator (g++ in the case of VCS in a Linux environment).
4.4 Using strings and UVM HDL access to read/write signals in DUT
It is often convenient in a test, to set or read the value of a variable embedded within a compo-
nent, at some lower level of the component hierarchy. The exact location of the component
might only be known when the test is run, not when the test code is written. UVM provides a
solution to this problem with the find function that searches for a named component and re-
turns a reference to it. The component name can be a string that is read from the command line.
Unfortunately, this mechanism only works for components derived from the uvm_component
base class and within the UVM environment: it does not work for HDL modules within a test
harness or DUT.
if (uvm_hdl_check_path(hdl_path_string) )
`uvm_info("ENV",{"HDL path ",hdl_path_string,
" found"},UVM_HIGH)
else
`uvm_warning("ENV",{"HDL path ",hdl_path_string,
" not found!!"});
assert( uvm_hdl_deposit(hdl_path_string,value_i));
endfunction: end_of_elaboration_phase
There are restrictions on what can be done with the values returned with uvm_hdl_read (such
as not being able to use them to trigger processes). For an in-depth discussion, see [7].
...
endclass: my_test1
Sometimes, there might be a requirement to modify the behaviour of a particular message that is
not known until a simulation is run. In these situations, a regular expression could be used to
check each message as it was generated. The regular expression matching string could be set
dynamically every time a simulation is run, from a command-line argument, as shown in Figure
24.
Figure 24 Report Catcher using Regular Expression and Command Line Argument
The report catcher can also be extended to pause a simulation whenever a particular message is
detected. The example in Figure 25 reads a string as a command line "plusarg" which it tests
against the message string using uvm_is_match.
Figure 26 shows a version of the Verilog BFM from Figure 8 that drives two output channels in
parallel (it is for a controller that has separate channels to drive two motors using a common
clock but separate handshaking). The UVM driver that replaces the Verilog BFM is given in
Figure 27. It spawns a dynamic process do drive each channel. There is a wait fork state-
ment at the end of the loop in the run_phase this waits for all spawned processes to have
completed before getting the next sequence item from the sequencer.
...
endmodule
...
...
endclass: serial_driver
5. Conclusions
We have highlighted some common errors made by new users and suggested some ways to avoid
them. At the end of the day, there really is no substitute for spending time to learn SystemVer-
ilog and UVM properly, but hopefully this paper has provided some guidance to help those who
need to start using SystemVerilog and UVM immediately, until they are able to follow a more
6. References
[1] Chris Spear and Greg Tumbush, "SystemVerilog for Verification: A Guide to Learning the Testbench
Language Features", 3rd Edition, pub. Springer 2012, ISBN: 978-146140714-0
[2] Stuart Sutherland and Don Mills, "Verilog and SystemVerilog Gotchas: 101 Common Coding Error
and How to Avoid Them", pub. Springer 2006, ISBN: 978-038771714-2
[3] Adam Erickson, "Are OVM & UVM Macros Evil? A Cost-Benefit Analysis", DVCon 2011
[4] John Aynsley, " The Finer Points of UVM: Tasting Tips for the Connoisseur", DVCon 2013
[5] John Aynsley, " Easier UVM for Functional Verification by Mainstream Users ", DVCon 2011
[6] Doug Smith, "Random Stability in SystemVerilog", SNUG Austin, 2013
[7] Jonathan Bromley, " I Spy with My VPI: Monitoring signals by name, for the UVM register package
and more ", SNUG Munich, 2012