Uvm e Ug
Uvm e Ug
Cadence Design Systems, Inc. (Cadence), 2655 Seely Ave., San Jose, CA 95134, USA.
Trademarks: Trademarks and service marks of Cadence Design Systems, Inc. (Cadence)
contained in this document are attributed to Cadence with the appropriate symbol. For queries
regarding Cadence’s trademarks, contact the corporate legal department at the address shown
above or call 1-800-862-4522. All other trademarks are the property of their respective holders.
Restricted Print Permission: This publication is protected by copyright and any unauthorized use
of this publication may violate copyright, trademark, and other laws. Except as specified in this
permission statement, this publication may not be copied, reproduced, modified, published,
uploaded, posted, transmitted, or distributed in any way, without prior written permission from
Cadence. This statement grants you permission to print one (1) hard copy of this publication subject
to the following conditions:
1. The publication may be used solely for personal, informational, and noncommercial purposes;
2. The publication may not be modified in any way;
3. Any copy of the publication or portion thereof must include all original copyright, trademark,
and other proprietary notices and this permission statement; and
4. Cadence reserves the right to revoke this authorization at any time, and any such use shall
be discontinued immediately upon written notice from Cadence.
Disclaimer: Information in this publication is subject to change without notice and does not
represent a commitment on the part of Cadence. The information contained herein is the proprietary
and confidential information of Cadence or its licensors, and is supplied subject to, and may be
used only by Cadence’s customer in accordance with, a written agreement between Cadence and
its customer. Except as may be explicitly set forth in such agreement, Cadence does not make, and
expressly disclaims, any representations or warranties as to the completeness, accuracy or
usefulness of the information contained in this document. Cadence does not warrant that use of
such information will not infringe any third party rights, nor does Cadence assume any liability for
damages or costs of any kind that may result from use of such information.
Contents
1 12
Introduction to UVM e 12
About This Book 12
About the Examples Library 13
About e UVCs 14
What Are e UVCs? 14
UVCs versus Regular Verification Environments (VEs) 16
UVCs as Plug-and-Play Components 16
UVC Reuse Requirements 16
2 19
Verification Packages 19
What Is a Verification Component Package 19
Packages As e Concept 20
Kinds of Packages 20
Package-Related Naming Conventions 20
Choosing a Package Name 21
Directory Structure 22
Library Directory 25
Package Directory 25
Self-Verification Directory 28
Accessing Files 29
Using Package-Relative File Names 29
Package Shadowing 29
Importing Files Within the Same e Source Directory 30
sn_which.sh Shell Script 30
Handling Package Versions 30
Where the Version Number Appears 31
Shipping New Versions of a Package 32
Declaring Dependencies on Specman Version and Other Packages 34
Using the Package Compatibility Analyzer 35
PACKAGE_README.txt File 37
PACKAGE_README.txt Location 38
PACKAGE_README.txt Headers 38
Recommendations 42
Shipping and Receiving Whole Libraries 42
Instantiating Non-Env Utilities 43
Adding Suffixes to Types 43
Connecting uvm_env to Package 43
3 44
UVC File Organization 44
UVC Directory Structure 44
uvc/e/ 45
uvc/examples/ 46
uvc/docs/ 46
Partitioning UVC Source into Files 47
File Naming Guidelines 47
Instantiating Entities 49
Importing Files in e UVCs 50
Usage of Package and File Path in Import 51
File Clusters 52
Files: Cyclic Dependencies 52
Example 53
An Alternative Approach to Cyclic Dependencies 53
4 55
Typical UVC Architecture 55
Basic UVC Architecture 55
DUT and UVC 56
Diagram Language 60
BFMs and Monitors 61
DUT Signals 61
Clocks and Events 61
A Look at Agents 62
What Are Agents? 63
Components of Agents 64
Some Important Guidelines for Agents 69
A More Complex UVC Example 69
Combining UVCs 71
Layering UVCs 72
Introduction to UVM e
This chapter presents a general introduction to this manual, to Universal Verification Methodology
(UVM), UVM e, and to e UVCs (UVCs implemented in e).
The Universal Verification Methodology (UVM) is a complete reuse methodology that codifies the
best practices for development of reusable verification components (UVCs) targeted at verifying
large gate-count, IP-based SoCs.
UVM e provides methodology guidelines and a new library with for advanced verification
environments using e, with special focus on enhancements for system verification and multi-
language environments. This includes alignment with UVM SystemVerilog to better support
building verification environments that combine e and SystemVerilog verification components.
The chapter contains the following sections:
About This Book
About the Examples Library
About e UVCs
play. In short, this manual also explains how to develop verification components that will provide
UVC users with consistent and superior verification experiences.
The intended audience for this book includes UVC developers, verification environment
developers, and technical managers responsible for these environments.
Following is a brief description of each chapter in this book.
Chapter 3, “UVC File Organization” How to organize UVC files and directories
Chapter 6, “Using the UVM Registers Describes common advanced use models of the UVM
(vr_ad)” e register package, vr_ad
Chapter 9, “Using the UVM e How to use scoreboards to track and compare data
Scoreboard” sent to and emitted from the DUT
Chapter 12, “e UVC Standardization How to maintain a high standard and uniformity for
Using Compliance Checks” UVCs
About e UVCs
This section contains:
What Are e UVCs?
UVCs versus Regular Verification Environments (VEs)
UVCs as Plug-and-Play Components
UVC Reuse Requirements
UVC implementation is often partially encrypted, especially in commercial UVCs where authors
want to protect their intellectual property. Most commercial UVCs require a specific feature license
to enable them.
This manual takes a fairly liberal approach to the question of what is an e UVC. For the purposes of
this manual, an e UVC is a significant, productized, modular piece of e code that can be used to
verify something and that has a single owner who is responsible for it (supporting it and possibly
selling it).
Notes
An e UVC can depend on another e UVCs. For example, there could be a TCP/IP e UVC that
uses (imports) an Ethernet e UVC. That TCP/IPe UVC could still be developed and sold
separately.
An e UVC must be significant and productized. These are not exact terms, but clearly a 200-
line scoreboard module does not qualify as a UVC. e UVCs are usually 5,000 to 20,000 lines
in size, and they embody significant knowledge and work (so that people would be willing to
pay money for them).
Following is a partial list of possible kinds of UVCs:
Bus-based UVCs (such as PCI and AHB)
Data-communication UVCs (for example, Ethernet, MAC, Datalink)
CPU/DSP UVCs
Higher-level protocol UVCs (TCP/IP, HTTP). These usually sit on top of other UVCs.
Platform UVCs (that is, a UVC for a specific, reusable SoC platform, into which you plug
UVCs of various cores).
Compliance test-suite UVCs. These are tests (and perhaps coverage definitions and more)
that demonstrate compliance to a protocol. For example, there could be a PCI compliance
UVC in addition to the basic PCI e UVC.
HW/SW co-verification UVCs, such as a UVC dedicated to verifying a HW/SW environment
using a particular RTOS/CPU combination.
Note: All of the following recommendations are described in much greater detail throughout the rest
of this manual.
Commonality in implementation
Common data structures
Common UVC testing methodology
Common way to use ports and packages
Verification Packages
This chapter explains how to organize verification components into packages and how to ship
them. For the purposes of this chapter, the verification components can be of any sort—UVC,
shareware component, and so on.
This chapter contains the following sections:
What Is a Verification Component Package
Package-Related Naming Conventions
Directory Structure
Accessing Files
Handling Package Versions
PACKAGE_README.txt File
Recommendations
These package directories are the basic unit for sending verification components from place to
place.
Packages As e Concept
Specman packages are a concept in the e language. This includes:
Package syntax (see package package-name in the Specman e Language Reference).
Ability to hide package private entities (as part of the general e encapsulation solution).
Each package directory contains one or more e packages.
Packages can be encrypted. Encrypted packages can also be licensed.
Kinds of Packages
There are two main kinds of packages:
Environment packages
These are packages that define an env root unit (a child of uvm_env, see below), which can be
instantiated in your verification environment.
Env packages can be divided further into UVC packages and shareware env packages, such
as a small ATM shareware.
Utility packages
These are packages that do not define a unit to be instantiated but rather are helpful utilities
that supply various services, for example, a visualization utility.
Utility packages also divide into shareware utilities and officially supported utilities.
There may be other kinds of packages. ESI adaptors, CE tools, and more may end up being
bundled as packages, thus enjoying the various facilities packages have (such as an easy way to
see their names/titles/versions, standard installation procedure, and so on).
We use the notation package to denote the package name (for example, “xbus”), and PACKAGE to
denote the uppercase version of it (for example, “XBUS”).
By convention, all e file names in the package should start with the package name. For example,
the top file could be:
package_top.e // For example, xbus_top.e
In a verification environment with multiple UVCs, fields can be accessed using the package name
prefix. For example:
transfer : xbus::transfer;
For more information about packages and namespaces, see Packages as Namespaces for
Types in the Creating e Testbenches document.
The company-prefix is a two-to-four letter prefix, such as “cdn” for Cadence or “arm” for ARM.
The proper-name is the name of the corresponding protocol, and so on, for example, “atm”.
We recommend that the package name be all lowercase. It should follow the same convention as a
name, that is, start with a letter and contain only letters, digits, and underscores.
Some prefixes have special meaning. Table 2.1 lists some general reserved prefixes.Table 2.1:
General Reserved Prefixes
shr Shareware that does not use a company name prefix. For example, if a user wants to
donate a register creation package as shareware, s/he can call it “shr_register”.
Directory Structure
package contains not just source code, but also related elements such as documentation,
examples, and so on. Thus, a package is really a directory structure containing all relevant material.
When installed, packages reside in library directories (libraries, for short) and must contain a file
called LIBRARY_README.txt. Libraries are then named in the $SPECMAN_PATH. Thus, a library is any
directory in your $SPECMAN_PATH that contains a file called LIBRARY_README.txt.Figure 2.1:
Packages Grouped Under Libraries
In this example, $SPECMAN_PATH contains three package libraries: a private library, a project-
wide library, and a company-wide library. These libraries will be searched in that order (as is
usually the case with $SPECMAN_PATH).
Contents of some of these example libraries are:
/project/proj_lib
LIBRARY_README.txt
vr_ahb/
PACKAGE_README.txt
demo.sh
e/
sv/
docs/
examples/
misc/
uvc_ve
vr_pci/
PACKAGE_README.txt
demo.sh
e/
sv/
docs/
examples/
misc/
uvc_ve
...
/usr/joe/private_lib/
LIBRARY_README.txt
shr_atm/
PACKAGE_README.txt
demo.sh
e/
sv/
examples/
shr_visualizer/
PACKAGE_README.txt
demo.sh
e/
...
Using the example $SPECMAN_PATH as described above, if you type the following, in Specman:
load shr_atm/e/shr_atm_top.e
Library Directory
Package Directory
Self-Verification Directory
Library Directory
The library directory in the $SPECMAN_PATH usually contains the following:
LIBRARY_README.txt file
This file must exist. By convention, it contains a description of the library on a single line, as
follows:
* Title: This is the project-wide shareware library.
Note that this construction is similar to the title line of the PACKAGE_README.txt file
described below.
Various package directories
The package directories, one per package, are described in Package Directory.
Various package verification environment directories
The package verification environment directories, one per package, are described in Self-
Verification Directory.
A versions directory
By convention, a directory called versions should exist in a library directory. Whenever a new
version of the package arrives, it is first untarred as a subdirectory of the versions directory
and then copied under the package name to the library directory.
Package Directory
The recommended content of the package directory is the following:
PACKAGE_README.txt File
demo.sh File
e Directory
sv Directory
docs Directory
examples Directory
Other Directories
Multi-language verification components should contain a directory for each language’s source files.
For example, you could have sv for SystemVerilog source files, and e for e source files.
PACKAGE_README.txt File
Each package directory must contain a file called PACKAGE_README.txt (uppercase name with a
lowercase txt extension). This applies for every package—UVC, shareware, utility, and so on.
For a detailed description of this file, see PACKAGE_README.txt File.
demo.sh File
This file, which resides directly in the package directory, should run a full demo of the package. It is
important to make the demo as complete as possible.
To have the demo run, you should be able to type the following command from any work directory:
% 'sn_which.sh package/demo.sh'
Usually, there will be several examples in the package/examples directory. demo.sh should be able
to run one of them (perhaps loading an e file called demo.e).
Note: demo.sh can use the predefined sn_which.sh shell script (see sn_which.sh Shell Script) to
avoid setting the $PATH environment variable (assuming $SPECMAN_PATH is set correctly to go
through the desired library directories).
e Directory
This directory contains all essential e code files belonging to the package. These could all reside
flatly in this directory, or be organized in subdirectories within the e directory as needed.
The e directory should contain the package_top.e file, which imports the other necessary files. It can
either import all necessary files, or it can import other top files of the package that, in turn, import all
necessary files. See more about file organization and import methodology Importing Files in e
UVCs.
The package writer must decide whether to have only downward imports (that is, from top files to
other files) or to have each file import all files it depends on.
See Accessing Files for an explanation of how to write the actual import actions.
sv Directory
This directory contains all essential SystemVerilog code files belonging to the package. These
could all reside flatly in this directory, or be organized in subdirectories within the sv directory as
needed.
For more information on implementing SV UVCs, see the UVM SystemVerilog User Guide.
docs Directory
This directory contains the documentation for the package. The documentation can be in any
format; but PDF, ASCII, and HTML are probably the best because they are platform independent.
examples Directory
All examples reside in this directory. Examples can contain e files, HDL files, shell scripts and other
files. The examples directory should also contain sample configuration files that can be modified
and used by clients.
If an example consists of more than one file, it may make sense to create subdirectories for each
example.
Examples should import package files using the package-relative notation to make the package
usable from any location. For example, the file test1.e in the foo package might look like this:
<'
import foo/e/foo_top;
extend foo_packet {
...
};
'>
The demo.sh file described in demo.sh File does not reside in the examples directory, but
normally it imports files from the examples directory.
Other Directories
Other directories can be created as needed, such as verilog/, vhdl/, misc/, and so on.
Note: If the Verilog files are part of a particular example, they should be under that example in
the examples/ directory.
Self-Verification Directory
Each UVC package should have a test suite to verify the main features of the UVC. The test suite
should be in the library, named uvc_ve (UVC verification environment), and include the following:
Coverage plan: Documentation on the coverage goals of the UVC verification
Test descriptions: Description of the tests, including work mode and features activated
Tests: A few random tests providing full coverage
Sequence definitions: Sequences defined and used for UVC verification
Coverage results: From the test suite run by the developer
Script to activate self-verification
Script to display coverage results
Typically, the number of tests achieving full coverage is not large. So the entire self-verification
environment can be maintained in a single directory. However, if the number of tests is large, they
should be divided into subdirectories according to their subject.
Accessing Files
This section contains:
Using Package-Relative File Names
Package Shadowing
Importing Files Within the Same e Source Directory
sn_which.sh Shell Script
Using package-relative names, any file in any library in your $SPECMAN_PATH is equally accessible.
This means that you can use a new UVC by including it in a library. Use the show packages -
on_disk command to see the packages that can be loaded.
Package Shadowing
If you want to try a new version of a package without disrupting other people who are using an older
version of the same package, put the new version in a library that is mentioned earlier in
your $SPECMAN_PATH. In most cases, this will be a private library.
For example, if your $SPECMAN_PATH contains this set of libraries:
…:/usr/joe/private_lib:/project/proj_lib:/cad/uvc_lib
you can put the new version of the package foo under private_lib, and it will be found first. (The
other version of foo later in the $SPECMAN_PATH is “shadowed” by the first one and is not used.)
Assume that foo needs bar, and imports it in a package-relative way:
import bar/e/bar_top;
Then, it will find the old bar, unless you also put a newer version of bar in your private_lib.
cc `sn_which.sh foo/c_files/xx.c`
Note: sn_which.sh is limited to searching for files in your $SPECMAN_PATH. It ignores all other features
like the Specman specman -pre_command.
Tar file/directory
In all three places, the version number should match.
Note: As this is an ASCII name, you can add extra text to describe the release, for example,
“(Experimental)” illustrated above.
define XBUS_VERSION_0_2;
Note: For each major release, use the OR_LATER statement in the define. For example:
define XBUS_VERSION_3_0_OR_LATER;
When you define several major releases, the OR_LATER defines build up. Thus, xbus_top.e might
look as follows:
import xbus_types;
...
'>
Note: The name of the directory is the same as the name of the define except that it is all in
lowercase.
Tar the package directory and gzip it with an appropriate name, for example,
xbus_version_3_0.tar.gz.
To unpack the new version of the package:
Put the tar.gz file in a library directory, and then unzip and untar it to make the new version
available.
Note: The disadvantage of using this method is that there is no easy way for the user to keep
multiple versions of these kinds of packages.
Note: We suggest the convention of putting the versioned directory in the “versions”
subdirectory of the library.
2. Tar the packaged directory from the directory just above it, and gzip the result.
For example, if the package you want to make resides
in somepath/versions/xbus_versions_3_1:
% cd somepath/versions
% tar cvf xbus_versions_3_1.tar xbus
% gzip xbus_version_3_1.tar
* Requires:
specman 4.1
xbus .. 1.1
xserial 0.9 ..
vt_util 0.5 .. 1.2
If the version field of a required package is left empty, any version will be accepted.
For an explanation of the dependency syntax, see Version Numbers and Ranges below.
To specify a list:
Separate the list members with commas and contain them all inside brackets.
For example:
{2.5,3.0 .. 3.12,4.0 ..} //Only the specified versions and ranges
Range lists can include both specific versions and ranges of versions.
All notifications include the required version and the path to the PACKAGE_README.txt file that
specifies the compatibility requirement. In Specview, the path to the PACKAGE_README.txt file is also
a hyperlink.
Notification Examples
*** Warning: WARN_SPECMAN_VER_INCOMP: Package ex_soc is incompatible with
Specman version 4.2
Requirement: Specman 4.3
See: file:/uvc_lib/ex_soc/PACKAGE_README.txt
6.01
Requirement: Specman 6.20..
See: file:/uvc_lib/shr_ram/PACKAGE_README.txt
PACKAGE_README.txt File
This file is mandatory. The format of the PACKAGE_README.txt file should satisfy the following
requirements:
Standardization of information (version, for example)
Easy to read
Easy to search
Easy to process automatically
PACKAGE_README.txt Location
The PACKAGE_README.txt file should be located in the package directory. For example:
vr_ahb_uvc/PACKAGE_README.txt
PACKAGE_README.txt Headers
Header Format
The format of headers should be:
* header-name: text
or:
* header-name:
All header lines start with an asterisk (*). The header name is always followed by a colon (:).
A header line can contain text in addition to the header or it can contain only the header.
Headers are considered to be case- and blank-insensitive, but we recommend following the
examples in these respects.
Mandatory Headers
The following headers just appear in the PACKAGE_README.txt file:
Title
Name
Version
If any of these headers are missing or empty or if the value of Name does not match the package
directory name, the PACKAGE_README.txt is considered invalid. (See Checking Package
Legality.)
Standard Headers
The PACKAGE_README.txt file should contain standard headers. Cadence predefines the following
headers:
Title
Name
Version
Requires
Category
Modified
Support
Comments to
Documentation
Description
Installation
Release Notes
To demo
Other headers can be added as needed.
PACKAGE_README.txt Examples
Following are sample PACKAGE_README.txt files.
Note: Comments starting with “--” are metacomments. They should not appear in the file.
PACKAGE_README.txt for an e UVC
e/ - All e sources
...
...
...
* Installation:
To install it:
....1.
....2.
* To demo: run demo.sh
-- A description of how to run a demo from scratch
---------- End of PACKAGE_README.txt file ----
2. Load vr_hier_unit/e/vr_hier_unit_top.
3. Type "show unit hierarchy".
You should see a nicely-printed hierarchy of your current
units.
--------------- End of PACKAGE_README.txt file ----
specific_version num[.num[.num]]
Each specific version (or range limit) may consist of up to three numeric parts, each separated by a
period. The first and the second numeric parts must be a number between 0 and 999. Leading
zeros are ignored. An all-zeros part is ignored. For example, 6.01 is the same as 6.1, and 6.0 is the
same as 6.
The third numeric part, if present, must be a number between 0 and 99. Alphabetic characters and
anything after an alphabetic character is ignored. Only two digits (preceding any alphabetic
character) will be considered. If there are more than two digits (preceding any alphabetic character),
the third numeric part is invalidated. Any leading zero is ignored; however, a leading zero is
counted as a digit.
Examples
6.1.01 = 6.1.1
6.1.001 = 6.1 (The last numeric part is ignored, because it has more than two digits.)
6.1.1b3 = 6.1.1 (Everything after the alphabetic character is ignored.)
06.11.003-b = 6.11
Recommendations
This section describes recommendations regarding packages:
Shipping and Receiving Whole Libraries
Instantiating Non-Env Utilities
Adding Suffixes to Types
Connecting uvm_env to Package
packages to the same library. For example, Cadence ships the UVM examples library,
and you should not add your own packages there.
This is not to be confused with the situation of receiving packages from multiple
vendors, in which case multiple packages can be placed in a common library. For
examples, you might buy UVCs from multiple vendors and place them all in one library
called “commercial_uvcs”.
extend sn_util {
shr_graph: shr_graph;
init() is also {shr_graph = new};
};
uvc/e/ This directory should contain all source code for the UVC.
uvc/examples/ This directory should contain examples of how to use the UVC.
uvc/docs/ This directory should contain all documentation for the UVC.
Other subdirectories might be appropriate. For example, with a UVC that is designed to facilitate
integration of a design block, a subdirectory named uvc/rtl/ might be used to contain the RTL code
(in VHDL or Verilog) for the design block.
When the verification component is implemented in multiple languages (for example, in both e and
SystemVerilog), the source files of each language are contained within a separate directory (uvc/e
and uvc/sv).
uvc/e/
All e UVC source files should be placed under uvc/e/. As a minimum, an e UVC must have a file
named uvc/e/uvc_top.e. This file should import all other files required for correct loading of the e
UVC. As such, users of an e UVC can load it by placing the following line at an appropriate point in
their code.
import uvc/e/uvc_top;
or:
uvc/e/uvc_top.e
uvc/e/uvc_master_top.e
uvc/e/uvc_slave_top.e
Note: There must still be a file named uvc/e/uvc_top.e to satisfy the requirements for packaging.
Source Subdirectories
For larger e UVCs, Cadence recommends that source files be split into subdirectories under the
uvc/e/ directory. Each subdirectory should contain an appropriately named _top.e file that is
imported by the uvc/e/uvc_top.e.
Subdirectory names should be short and should describe an area of the e UVC (usually a
subcomponent or aspect of the e UVC). For example (showing the top level files for each
subdirectory):
uvc/e/master/uvc_master_top.e
uvc/e/interrupts/uvc_interrupts_top.e
uvc/e/tcp/uvc_tcp_top.e
uvc/e/coverage/uvc_coverage_top.e
Notes
Cadence recommends that the /uvc/e subdirectory names do not include the uvc_ prefix.
All files in a UVC release must have unique names. If there are multiple references to the
same file, the file is loaded only once. If there is an attempt to load a file with the same name
but different location as an already loaded file, Specman issues an error message.
uvc/examples/
Sample config and test files should be placed in uvc/examples/. As a minimum, this should
include:
A template configuration file that users can use as a starting point for building an e UVC
configuration
A fully working demonstration/example of the use of the e UVC
Users should be able to run the full demonstration (including creating/compiling simulator libraries
and so on) by sourcing a script named demo.sh. This script should be placed at the top level of the
e UVC directory structure (for example, uvc/demo.sh).
uvc/docs/
All documentation for the e UVC should be placed under uvc/docs/. This normally includes both
release Notes and a user guide. Where documents change according to the version of the UVC,
Cadence recommends indicating the current version as part of the filename. For example:
uvc/docs/uvc_release_Notes_version_1_2.pdf
uvc/docs/uvc_user_guide_version_1_2.pdf
Where a large number of documents are provided, these should be placed in an appropriate
subdirectory structure under uvc/docs/.
Although a number of different documentation formats are appropriate for UVC documentation,
Cadence recommends providing documentation in TXT or PDF formats, as these are likely to be
readable by the largest number of users.
Guideline Details
Standard names for feature files An appropriate name of the feature, for example,
checker, cover, monitor, sequence
Natural domain names for entities Examples are master, slave, burst
Table 3.2: Files Owned by e UVC Developer (users should not modify)
uvc_monitor.e Definition of central monitor unit, when relevant (for example, buses)
uvc_agent_monitor.e Definition of agent monitor unit, when relevant (for example, serial i/f)
uvc_agent_sequence.e Predefined sequences / API methods
uvc_checker.e Protocol and data checks. Can be divided into agent/topic files
uvc_top.e
Imports all files
Instantiates e UVC entities
Passed to sn_compile
uvc_config_template.e
Shows all e UVC config fields
(uvc_config_*.e)
Shows a sample configuration (may have several such files)
Instantiates the e UVC env in the user’s environment
Imports both uvc_top.e and uvc_setup_example.e
Imported by tests—serves as basis to import the whole e UVC
Note: This template file must be provided in all e UVCs and
should be placed in the uvc/examples/ subdirectory.
Instantiating Entities
Table 3.4: Examples of File Organization
xserial xbus
xserial_types; xbus_types;
xserial_env; xbus_env;
xserial_agent; xbus_agent;
xserial_bfm; xbus_bfm;
xserial_monitor; xbus_bfm_arbiter;
xbus_bfm_master;
xbus_bfm_slave;
xbus_monitor;
xserial_frame; xbus_trans;
xserial_coverage; xbus_protocol_checker
xserial_sequence; xbus_coverage;
xserial_protocol_checker xbus_master_sequence;
xbus_slave_sequence;
xserial_top; xbus_top;
xserial_config_template xbus_config_template
test_1 test_1
An e UVC should never instantiate anything under sys. This is important because an e UVC may
later be instantiated under a larger UVC. Instead, all subcomponents of the UVC should be
instantiated under a unit named uvc_env_u. The user’s uvc_config.e file will then instantiate
uvc_env_u at an appropriate point or points in the user’s verification environment.
To promote reuse of code within an e UVC, structs and units should not be instantiated in the file
that declares them. For example, if the file uvc_master.e declares a unit uvc_master_u, then no
instance of this unit will be created in this file. The instance of the unit uvc_master_u might be
created in the file uvc_env.e.
proj/mode_A_sve/sve_patch.e: This file imports all patches and workarounds used by this
SVE. This includes:
Patches provided by the tool or UVC providers
Workaround to known DUT bugs
Setting check effect (“set check WARNING”, for example)
proj/mode_A_sve/sve_config.e: This file is the file imported by the tests. It should:
Import uvc_top.e
Import sve_patch.e
Import sub-configuration files, if there are. For example, configuration files of sub UVCs.
Declare the sve unit, and instantiate it under sys. This unit contains and constrained all
the _env components of the UVC (Such as vr_ahb_env, vr_enet_env, etc).
Connect the pointers of the sub components, using connect_pointers().
For example, the XCore UVC contains these files:
xcore/e/xcore_compile_base.e: Importing the packages used by the XCore UVC - vr_ad,
xbus and xserial
xcore/e/xcore_top.e: Importing xcore_compile_base.e, and the various files of the UVC
(sequence libraries, configuration of the sub UVCs, checker, etc)
xcore/main_sve/main_sve_patches.e: Containing patches and workaround, updated when a
new version is used, and patches are not required
xcore/main_sve/main_sve_config.e: Importing main_sve_patches.e, instantiation of the three
env units (xcore, xserial, xbus), connecting their pointers, and configuring.
Note: Imports within a package should be local (as much as possible). This is the safest way to
make sure that the package will be loaded from one location, even if you maintain multiple
versions of the package.
Note:“../” should never be used as part of an import statement, because this can lead to
undesired results if there are symbolic links in your directory path. For example:
import ../uvc_another_file.e; // This kind of path specification
// should not be used
e UVC-relative filenames refer to files relative to the top-level of the UVC. For example:
import uvc/e/uvc_top.e; // Imports the top level file of the UVC
import uvc/e/uvc_env.e; // Imports file that declares env unit
Note: Imports of other UVCs or utilities are always performed using UVC-relative filenames.
This is to make sure that the package is found, regardless of the locations of the importing file
and the imported package (SPECMAN_PATH takes care of it all). For example:
import xbus_e/e/xbus_top.e;
File Clusters
When possible, each unit within a design should be treated as a separate object that does not
depend on any objects higher in the unit hierarchy of the UVC. However, in some cases, this is not
possible or practical. For example, in a bus-based UVC, the bus agents might need to know about
the env unit under which they are instantiated. In such cases, a stronger dependency between
some files in the UVC is required.
In such cases, files may be organized into clusters of related files. A cluster should be loaded at one
time, allowing tighter dependencies between the files that make up the cluster. Within a cluster,
some of the guidelines on file organization can be relaxed (out of practicality). For example:
Units can instantiate themselves in other units declared within the cluster where the
relationships between the units in the cluster is fixed by the UVC.
Files can depend cyclically upon each other.
Several clusters can exist within a UVC, and the UVC as a whole can be considered as a cluster.
Where a file cluster is used, Cadence recommends placing it in a subdirectory with a _top.e file.
The uvc_top.e file can then import the file cluster by importing its _top.e file.
Example
units.e
<'
unit env_u like uvm_env {};
unit agent_u like uvm_agent {};
'>
agent.e
<'
import units;
extend agent_u {
env_bp: env_u;
};
'>
env.e
<'
import units;
import agent;
extend env_u {
agent: agent_u is instance;
keep agent.env_bp == me;
};
'>
top.e
<'
import units;
import agent;
import env;
'>
This DUT has two external interfaces: a bus interface and a serial interface. Because each of these
interfaces can interact externally, we can attach a UVC to exercise and interact with each of them.
We start by looking at the serial interface. This interface is composed of a receive port and a
transmit port. The UVC attached to this interface is the xserial UVC. The dual-agent implementation
for the XSerial UVC is shown in Figure 4.2 below. (The actual implementation of the XSerial UVC
is slightly different. It is shown in Figure 4.3.)
The XSerial UVC is encapsulated in the rectangle marked “xserial_env”. This rectangle represents
an instance of a unit of type xserial_env_u, which inherits from uvm_env. For each port of the
interface, the UVC implements an agent. These agents can emulate the behavior of a legal device,
and they have standard construction and functionality. Each env and agent also has a configuration
unit, inheriting from uvm_env_config and uvm_agent_config. This allows for configuration of the
env’s attributes and behavior.
The agents are units, inheriting from uvm_agent, instantiated within the env.
In this representation of the UVC, there are two types of agent:
RX agent A receive agent that can collect data from the DUT’s transmit port
TX agent A transmit agent that can send data to the DUT’s receive port
These agents are constructed in a standard manner. They have the following components:
Signal Map like uvm_signal_map. A unit that contains external ports for each of the HW
signals that the agent must access as it interacts with the DUT.
Synchronizer like uvm_signal_map. A unit that contains external ports for HW signals that are
common to the whole design. Typically, this includes clock and reset signals.
BFM like uvm_bfm. Bus Functional Model—A unit instance that interacts with the DUT
and both drives and samples the DUT signals. There is an option to implement
the BFM in SystemVerilog. In such case, there should be a BFM in e, getting
items from the sequence driver, and passing them to the SystemVerilog BFM.
Collector
like uvm_collector. A unit instance that samples the DUT interface. There is an
option to implement the connector in SystemVerilog.
Monitor like uvm_monitor. A unit instance that passively monitors (samples) the DUT
output and supplies interpretation of the monitored activity to the other
components of the agent. The monitors get the raw data from the collector.
Monitors can emit events when they notice interesting things happening in the
DUT or on the DUT interface. They can also check for correct behavior or collect
coverage.
In Figure 4.2, notice that the BFMs have bidirectional arrows to the DUT. This signifies the fact that
they can both drive and sample DUT signals. Monitors have unidirectional arrows pointing from the
DUT to them. This signifies that they can only sample data from the DUT. Monitors cannot drive
DUT signals.
The actual implementation of the XSerial UVC is slightly different than the architecture shown in
Figure 4.2. Instead of a dual-agent architecture, a single-agent architecture was chosen (see Figure
4.3 below).
Both the dual-agent and the single-agent architectures are valid implementations. Depending on
the specifics of the protocol the UVC deals with, you might prefer one over the other.
In the XSerial protocol, the RX direction is completely passive and involves no driving of signals.
Thus there is no need to have a BFM and a sequence driver in the RX agent. However, the TX
agent behavior also depends on flow control frames received by the RX agent. This means that the
RX agent must communicate frequently with the TX agent to tell it when it has received such
frames.
In the XSerial protocol, the XSerial UVC could have two agents— an RX agent and a TX agent—
where the RX agent is significantly more simple than the TX agent. If the flow control mechanism
involves a high level of interaction between the two directions, implement a single-agent UVC to
model the flow control mechanism efficiently. The single agent covers both the TX and RX
directions. The single agent contains all of the monitors, BFMs, and sequence drivers required for
both directions.
Note: In this example, monitoring is implemented with only one entity, the monitor. As described in
section Components of Agents, you may divide monitoring tasks between two entities — a collector
connected to the DUT and a monitor for high-level tasks.
Diagram Language
What has been described up to now is the typical, generic parts of a UVC environment. Before
looking deeper into the construction of an agent or examining other variants of UVC architecture,
the language used in the architecture diagrams should be understood.
Figure 4.4 gives a brief legend for architecture diagrams.
Figure 4.4: Brief Legend for Architecture Diagrams
Most importantly, notice the difference between structs and units, and the difference between unit
instantiation as opposed to pointers between structs and units. The fact that unit instantiation is
depicted by enclosing one unit rectangle within another serves to express the important relations
between the main entities in a UVC architecture.
Notice also that in Figure 4.2 the DUT was drawn in gray at the bottom of the picture. This
convention is maintained throughout the rest of this manual.
A more complete legend is supplied at the end of this chapter in the figure Full Legend for
Architecture Diagrams. The more complete legend will be helpful when examining the diagrams
in Chapter 5, Sequences: Constructing Test Scenarios.
DUT Signals
Reference to DUT signals should be done using external ports. Cadence recommends that related
signal ports are collected in units called signal maps. Signal map instances can be placed in any
natural location, typically either the env unit (if it is a signal used by all agents) or in an agent (for
agent-specific signals). Where multiple units must access the same signal map, each unit should
have a pointer to the instance of the signal map.
performance overhead.
On the other hand, if the protocol defines that agents can have different clocks (for example,
different speeds), then each agent should have its own clock.
Sequence drivers will always have their own clock. This is necessary to simplify the user interface
by allowing a uniform way to extend the sequence body() TCMs without needing to know the
specific clock name.
Synchronizer
When implementing the interface to the RTL using simple_ports, the synchronizer should contain a
port connected to the clock signal in the RTL and an event based on this port. The agent and env
units in the environment should define their clocks based on the synch.clock. This approach
provides more flexibility over having the events defined in a static position within the UVC.
Clock Agent
When implementing the BFMs and monitors in Verilog, the interface between the two levels of the
UVC—the high level implemented in e and the low level implemented in Verilog—is based on DPI
or method_ports. In such cases, Cadence recommends implementing
a clock_and_reset_agent providing “clock services” to the UVC components, such
as count_cycles() or wait_reset_done().
See, for example, the clock_and_reset Interface implemented in the XBus example
in uvm_examples/xbus_e, in the files /e/clock_agent.e and if/clock_and_reset_interface.sv. The
e part implements a method named count_clock(). This method is a blocking time-consuming
method that ends after the requested number of cycles has passed. It is implemented by calling
Verilog tasks, implemented in the Interface clock_and_reset_interface.
A Look at Agents
This section contains:
What Are Agents?
Components of Agents
Some Important Guidelines for Agents
};
When the agent is not using the predefined base type, the user must define the active_passive field.
For example:
unit xbus_agent_u {
active_passive: uvm_active_passive_t;
};
In addition to the ACTIVE / PASSIVE attribute, agents are considered proactive if they initiate
transactions, and reactive if they only respond to requests. These attributes are not reflected as
fields or field values in code.
Both ACTIVE and PASSIVE agents have a monitor. The monitor can be instantiated directly in the
agent.
A PASSIVE agent does not have a BFM or a sequence driver. Both are instantiated within the
ACTIVE subclass (or when clause) of the agent. For example:
When an agent cannot drive signals (because the protocol does not allow it), it does not have an
active_passive field (because it is always PASSIVE).
Other standard fields include kind fields that allow specifying subtypes of agents and name/ID fields
that allow identification of each agent (as described in Instance Names and IDs).
Any agent in a UVC can be used as a PASSIVE agent. PASSIVE agents represent DUT devices
that are only monitored by the UVC. T2he PASSIVE agent collects information from the DUT and
holds that information as a reference for the verification environment. This enables orderly
monitoring of the DUT agent— collecting information and checking its behavior from a well defined
place in the UVC.
Components of Agents
Figure 4.1: Agent Internals
All agents have a fixed basic architecture with optional additions as required by the specific
environment.
The interface of an agent consists of a configuration unit, a sequence driver, and the signal map
connecting it to the DUT. Agents should be designed to work as independently as possible, not
relying on a specific type of env, because independent agents are more easily adapted to
unforeseen needs that UVC users might have. Therefore, it is important that the agent configuration
and signal map are internal to the agent and are passed to the agent by the enclosing unit.
Note:
In the architecture described in Figure 4.5, the collector is implemented in e. The BFM and collector
can also be implemented in SystemVerilog or Verilog, as shown in Figure 4.3.
An agent can also have a pointer to its enclosing environment. Through the pointer, the agent can
obtain the configuration parameters. In this case, the agent is dependent on a specific environment
and can only be instantiated in that specific type of environment.
For more detail on the agent components, see:
Agent Configuration and Signals
Sequence Drivers
Monitor
BFM
Collector
Checker
Coverage
Sequence Drivers
Sequences are the test writer’s interface to control the verification process. All test-specific control,
including reset and error injection, should be available through sequences.
The sequence driver is a unit instantiated in the active agent. By default, it is connected to the BFM
in pull mode. (Push mode is not recommended.) Items generated in the sequence driver are sent to
the BFM whenever an item is available and the BFM is ready to accept a new item. At any given
time, the BFM and monitor provide the current DUT state to the sequence driver for generating
sequence items.
The sequence driver must support both standalone operation (generation of items independent of
higher-level protocols) and layering of higher-level protocols (generation of items based on higher-
level protocols).
Monitor
The monitor, deriving from uvm_monitor, is responsible for getting data items from the collector and
translating them into meaningful events and status information. This information is available to other
components of the agent and to the test writer. The monitor should never rely on information
collected by other components such as the BFM.
The monitor is a unit that is always instantiated (regardless of subtypes). Its functionality should be
limited to the basic monitoring that is always required. Additional high-level functionality that might
be required should be implemented separately on top of the monitor. This includes protocol
checkers, scoreboards, and coverage, used typically, but not only, in the passive agent.
The events recognized by the monitor depend on the actual protocol. Typically, for the basic data
item the monitor provides an item_started and an item_ended event (for
example, packet_started and packet_ended). The monitor collects the item data from the signals
and creates a current_item that has the complete item data, ready to be used when the item_ended
event occurs. In addition to the raw data, the monitor should collect relevant timing information such
as the duration of the transaction.
There is an option to separate the monitoring tasks between two components. In these cases, the
monitor contains an instance of a collector, which derives from uvm_collector. The collector
performs the low-level monitoring, reading the signals and parsing them. The monitor performs the
higher-level coverage and checking. Such a separation is especially useful when working in mixed-
language environments. The low-level monitoring is performed by a collector implemented in one
language, and the higher-level checks are performed by a monitor implemented in another
language. For best plug and play, the monitor and collector interact via ports.
The low level part of the monitor—implemented either in a collector or in the monitor itself, the part
that communicates with the DUT—can be implemented in e or in Verilog. When implemented in
Verilog, its interface to the higher level part (that is, the checker) could be implemented with DPI or
method ports. In such environments, in which the interface to the DUT is implemented in Verilog,
the UVC is acceleration ready. The Verilog parts of it can be synthesized with the RTL and loaded
to the emulator or acceleration machine.
Global Monitor
Some UVCs require a system-level monitor in addition to the agent-level monitors. This is typically
used in broadcast protocols. In point-to-point protocols, agent monitors are usually sufficient.
BFM
BFMs do the entire signal driving from agents.
The BFM is a unit, deriving uvm_bfm, instantiated only in ACTIVE agents. Changing an ACTIVE
agent to PASSIVE prevents that agent from driving signals.
No generation is done in the BFM. The BFM receives a data item (from the sequence driver) and
performs all operations required to send the data item to the DUT according to the protocol rules.
The item should contain all necessary information to complete the transaction.
To perform its task correctly, the BFM must know the current state of the DUT. The BFM can sense
the DUT signals directly or use signal information extracted by the monitor.
The BFM can be implemented in e or in Verilog. When implemented in Verilog, a small portion of it
is implemented in e, the part that gets items from the sequence-driver and passes them to the
Verilog task which drives the data to the DUT. The interface between the two parts of the BFM—the
one implemented in Verilog and the one implemented in e—can be implemented with DPI or
method ports. In such environments, in which the interface to the DUT is implemented in Verilog,
the UVC is acceleration ready. The Verilog parts of it can be synthesized with the RTL and loaded
to the emulator or acceleration machine.
Collector
The collectors read the signals, create higher level structs, and pass the structs, via ports, to the
monitors for further processing. The collector can be implemented in e, and then it can be
like uvm_collector. To have the environment Acceleration Ready, you can implement the collector
in Verilog or SystemVerilog, using constructs that are synthesizable for the acceleration machine.
See more details in Chapter 11, “Acceleratable UVCs”.
Checker
Typically, a checker is used to verify a DUT, but it can also be used to verify the correctness of the
active UVC agent.
The checker can be implemented either as a separate unit in the agent or in a has_checks subtype
of the monitor. By default, the has_checks flag should be TRUE in passive mode and FALSE in
active mode.
The checker operates based on events and data collected by the monitor. If it is implemented as a
separate unit, it has a pointer to the monitor that is set by the agent when the checker is instantiated.
At a minimum, the checker should check the validity of the basic data item (for example, packet) and
the related timing requirements according to the protocol.
Coverage
Typically, coverage is used to verify a DUT. It can also be used to verify the UVC active agent’s
capabilities by covering sequence types.
Coverage can be implemented either as a separate unit in the agent or in a has_coverage subtype
of the monitor. By default, the has_coverage flag is TRUE in passive mode and FALSE in active
mode. If an end user wants to verify the UVC active agent’s capabilities, the has_coverage flag can
be set to TRUE.
Coverage operates based on events and data collected by the monitor. If it is implemented as a
separate unit, it has a pointer to the monitor that is set by the agent when the coverage unit is
instantiated.
At first glance, you can see the architecture of the XBus UVC is very similar to the XSerial UVC.
There is an env encapsulating configuration fields, signals, and several agents, and the agents are
organized internally in a similar way to the XSerial agents.
However, the XBus protocol is more complex than the XSerial, and this is reflected in the various
types of XBus agents. The agents here can play the roll of a bus master (initiating activity on the
bus), a bus slave (responding to bus master requests but not initiating activity), and an arbiter
(coordinating between the masters so that only one master initiates activity on the bus at a given
time). The exact agents vary for different buses, but this is a very typical arrangement.
The XBus UVC allows for a list of master agents and a list of slave agents. Thus the number of
agents instantiated can vary when the UVC is used in different situations. In the XSerial UVC, there
are usually exactly two agents, but one of them might be ignored.
There are other differences we can notice. The XBus has a bus monitor in addition to the agent
monitors, while the XSerial UVC has only agent monitors. The signals in the XBus are grouped
under the env, while the signals in the XSerial UVC are grouped in the agents.
From these differences, it is clear that a typical UVC architecture can have some variance and yet
still be quite similar.
Combining UVCs
The first examples we looked at were standalone UVCs. We now look at an SoC example that
combines several UVCs. Figure 4.7 shows such a verification environment.
Figure 4.1: XSoC UVC
In this case, the SoC verification environment could be the end user’s full environment. Still, we
choose to represent it as a UVC, because it highlights the fact that any verification environment
today can turn into a verification component in a bigger verification environment tomorrow.
This is the first example we see where one UVC is making use of other UVCs. The XSerial and
XBus UVCs are instantiated within the XSoC UVC. In fact, the XSoC UVC has no agents of its own,
although it probably will have some configuration that is not shown here simply because the
diagram is crowded. In this example, the DUT has two XSerial devices, and thus the XSoC UVC
has a list of xserial_env instances. Not shown in Figure 4.7 are things like the scoreboards that
check the data end-to-end between the two UVCs.
All in all, we can see that the XSoc UVC makes thorough use of the lower-level UVCs. By
integrating components, it creates a new and bigger verification component without adding much
new code.
You can find more information about system UVCs in Chapter 8, “Module-to-System Verification”.
Layering UVCs
Layering is a technique that lets generation and monitoring of traffic be split into distinct logical
layers. This enables independent control over behavior at each layer during generation and
independent observation of behavior at each layer during monitoring.
Layering is normally used in UVCs for protocols that naturally split into layers, for example, ATM
over Utopia (ATM and Utopia each being a layer) or Ethernet (as in Ethernet packets over XGMII
over XSBI).
Layered UVCs provide a flexible, scalable, and reusable approach to implementation of verification
solutions for multi-layer protocols. You can deliver a separate UVC for each layer or a combined
UVC with functionality for all layers.
Single-layer UVCs can be written without the need for prior knowledge of which other layer UVCs
they might be used with. The protocol for interconnection can be separately encapsulated in
connector code, which in turn can be delivered as a separate UVM package.
Any UVM-compatible UVC can be used as a lowest-layer UVC. Higher-layer UVCs require a
method-based API (but can optionally have an additional signal-based API).
This section contains:
Typical Layering Applications
Requirements for Lower-Layer UVCs
Requirements for Higher-Layer UVCs
Partitioning of Layers and Connections between Packages
See Also
Layered Protocols for how to create multi-layered sequences.
Module-to-System Verification for how to use UVC within system UVCs.
Multi-layer UVCs combine two or more protocol layers in a single UVC using a layered
approach. Such UVCs provide a single-package solution for a multi-layer protocol while
letting the user get control and visibility at each protocol layer.
The decision about which approach to take (single-layer or multi-layer) depends on the specific
protocol(s) in question. There may be both commercial and technical aspects to this decision.
In this chapter, we describe the single-layer approach in detail and provide some discussion of how
the same techniques can be applied to multi-layer UVCs.
are placed in the env unit of the higher-layer UVC. However, it is legal to place them either in the
agent unit or the BFM unit (for the higher-to-lower-layer direction) and in the monitor unit (for the
lower-to-higher-layer direction). When deciding where to locate these methods, bear in mind the
likely use model for the higher-layer UVC, and try to choose an interface that is intuitive for users.
Note: These method calls are always called from the lower layer UVC. Hence, both UVCs are
operated in Pull Mode.
When designing the method interfaces, ensure that they are sufficiently flexible to support any
possible lower-level protocol. For example, although it might seem sensible for a method call for an
ATM UVC to work with data partitioned into bytes, this might not be suitably flexible when layered
over a lower-layer UVC that transfers data one bit at a time. Similarly, a lower-layer UVC that can
transfer data 32 bits at a time should not have to call the ATM UVCs method interface four times for
each transfer. As a general rule, method calls should pass a list of bits so that the lower-layer UVC
can decide how much data to request or return at a time.
Independent UVCs
The most general case for layering is as follows:
Higher-layer UVC is supplied by vendor/developer 1.
Lower-layer UVC is supplied by vendor/developer 2.
Connection between layers is supplied by vendor/developer 3.
In this situation, the higher- and lower-layer UVCs provide the relevant hooks to enable layering.
Normally, these UVCs would not contain any code relating to the connecting of the two specific
protocols. As such, it is important that the layer UVCs provide all required hooks as part of their
published APIs. It is also important that the connection code does not use anything other than the
published APIs. In such cases, the inter-layer connection can be part of an end-user verification
environment, or it can be delivered as a separate UVM package.
Dependent UVCs
Sometimes the layer UVCs can be written knowing that layering between them will take place. In
such cases, it might be appropriate to build the layering code into one of the individual layer UVCs
(typically the higher-layer UVC).
Modeling FAQs
Many of the differences between the two sub-UVCs (XSerial and XBus) and the way they are used
raise interesting architecture questions.
Q: If the DUT has N ports of same protocol, should we have 1 env with N agents, or N envs each
with single instance agents? For example, if a DUT has two pairs of serial ports, which of the
following arrangements is better?
A: They should be kept separate (as shown on the left side of the above figure).
It is okay to have a single agent in env. It will probably be more reusable this way.
It is okay to have zero agents in an env. For example, SoC has envs in it but no agents.
Q: Must the Monitor and BFM be units under the env/agent? Might they be TCMs?
A: It is better to have them as units, not as TCMs.
For small environments, having them as TCMs might make sense, but for big environments it
can create problems. Therefore, for uniformity, they should be units.
Terminology
The following sections provide definitions of terminology. These terms are used extensively in this
book, and as many of these terms are used slightly differently by different people, be aware of the
specific connotations we give to each of them.
This section contains:
Main Entities
Sub-Entities
Monitor-BFM Guidelines
Agent Types
Data Item Naming Convention
Main Entities
DUT Device Under Test, the device to be verified (typically an HDL design).
In any given verification environment, there is only one DUT. (Even if the env is
composed of several envs, each with its separate DUT block, still in the full env, the
total of all blocks should be considered one DUT.)
Env The root unit for an independent verification environment, for a well defined protocol,
interface, specification, or HW block.
When combining HW blocks to HW systems, the envs can also be combined by
nesting block envs into the system env.
Each UVC defines an uvc_env.
Multiple bus instances in the DUT imply multiple UVC instances (envs).
Inherits from uvm_env.
Sub-Entities
Monitor A passive entity that samples the DUT signals (but does not change their values at
any time).
Functionality: Extracts events, prints traces, collects transaction and data items, does
checking and coverage.
Inherits from uvm_monitor.
BFM An active entity that emulates an auxiliary device for the DUT.
Functionality: Samples and drives the DUT signals.
Always instantiated inside agents (a unit).
Inherits from uvm_bfm.
Monitor-BFM Relations
BFMs and Monitors are typically built independently (even if they look at the same signals).
Therefore each can work without the other.
Monitor-BFM Guidelines
It is okay for UVC developers to duplicate or reuse some common code.
Within an agent, monitors must not depend on the presence of a corresponding BFM.
Within an agent, BFMs might depend on the presence of a corresponding monitor.
Agent Types
Agents can be of two types (ACTIVE or PASSIVE):
PASSIVE Looks at HW blocks internal to the DUT (often with no external interface). Can also
collect transactions from the DUT, but it does not drive signals. BFM and sequence
drivers are disabled.
Agent Examples
The sequences are a uniform and efficient way of implementing tests scenario, and this chapter
describes using the sequences in UVCs.
Within the uvm_ex_lib/e_ex_lib, Cadence ships several example packages, demonstrating in detail
the various sequence features. Before reading this chapter, we recommend reading the sequences
chapter in the Creating e Testbenches Specman manual.
The sections of this chapter can be divided into the following three main parts:
Introduction
Sequences Overview
How to Use Sequences in Your Environment
Basic Use
Defining Sequences
Collecting Coverage on Sequences
Hooking the Sequence Driver to the Environment
Implementing Sequences
Writing Tests Using Sequences
Sequence File Organization
Advanced Use
Advanced Generation-Related Aspects of Sequences
Implementing Complex Scenarios
Layered Protocols
High Performance Sequences
Miscellaneous Advanced Features
See Also
Creating and using Sequences
Sequences Overview
Sequences let you define streams of data items sent to a DUT (or streams of actions performed on a
DUT interface). You can also use sequences to generate static lists of data items with no
connection to a DUT interface.
For defining sequences, it is also necessary to define standard interfacing entities between the
sequence and the DUT. Therefore, the sequence solution deals with three main entities:
Item A struct that represents the main input to the DUT (for example, packet,
transaction, instruction). Typically, such items already exist in your environment,
and only very small modification is required to use them with sequences.
Sequence A unit that serves as the mediator between the sequences and the verification
Driver environment. The generated items are passed from the sequence to the sequence
driver and the sequence driver acts upon them one by one, typically passing them
to some kind of BFM (Bus Functional Model). Of course, the sequence driver can
be rather empty and, instead of driving items into the DUT, simply place them on a
list.
The execution flow for generation of items and driving them into the DUT is as follows:
The sequence driver launches the main TCM of a sequence (called body()), which in turn
launches the main TCM of any subsequences.
Sequences generate items on the fly (as part of the execution of their body() TCM).
Each generated item is passed to the sequence driver, which in turn passes it to the BFM.
Note: All of the above actions are done automatically by the sequence mechanism. The
subsequence and item actions are encapsulated in the do action.
1. Define the sequence item struct. For more information, see Defining Sequences.
2. Define the sequence and its driver using the sequence statement. For more information,
see Defining Sequences.
3. (Recommended) Define a coverage group for the sequence. For more information,
see Collecting Coverage on Sequences.
4. Hook the sequence driver to the environment. For more information, see Hooking the
Sequence Driver to the Environment.
5. Create your sequence library by implementing various scenarios using the sequence struct.
For more information, see Implementing sequences.
6. Write tests based on the sequence library. For more information, see Writing Tests Using
Sequences.
Note: Throughout these sections, the examples used are from the UVM examples library.
Defining Sequences
This section summarizes how to define sequences in your environment and hook them to your
BFM. The full details are in the Sequences chapter in the Creating e Testbenches Specman
manual.
This section contains:
Defining the Sequence Item
Defining the Sequence and Its Driver Using the sequence Statement
Type ex_atm_sequence_kind
Type soc_sequence_kind
extend xbus_master_sequence {
cover ended is {
item kind using ignore = (kind == RANDOM or
kind == SIMPLE or
kind == MAIN);
};
};
Note: In this example, sampling of the three predefined sequences is ignored. In your environment,
you can specify which of the sequence kinds, if any, to ignore.
You can extend the definition of the coverage group in later files. You can do that with a specific
configuration file or, when reusing the UVC within a larger system, with the addition of project-
specific definitions. You can add items to cover, add ignore rules, and more. For example,
assume SEQ_WRITES_AND_READS xbus_master_sequence cannot be run in the system environment.
Adding this kind to the ignore list is done as follows:
extend xbus_master_sequence {
cover ended is also {
item kind using ignore = (prev or
kind == SEQ_WRITES_AND_READS);
};
};
The prev indicates addition to an existing ignore list rather than replacement of the list.
<‘
struct ex_atm_cell like any_sequence_item {
...
};
‘>
Have a BFM that knows how to drive a cell into the DUT:
<'
unit ex_atm_bfm like uvm_bfm {
// ATM main clock
event a_clock is rise('atm_clk') @sim;
// drives cells to the DUT
drive_cell(cell: ex_atm_cell) @a_clock is {
...
};
};
‘>
<‘
unit ex_atm_agent like uvm_agent {
bfm: ex_atm_bfm is instance;
};
'>
Now define the ATM sequence and sequence driver, and hook the sequence driver into the BFM:
<'
// Define ex_atm_sequence, ex_atm_sequence_kind,
// and ex_atm_driver
sequence ex_atm_sequence using item=ex_atm_cell,
created_driver=ex_atm_driver;
‘>
//Step 5 is optional:
// 5. Extend the base ex_atm_sequence type
extend ex_atm_sequence {
!cell: ex_atm_cell;
};
'>
At this point, your environment is already capable of generating (by default) random sequences.
The sequence driver generates the MAIN sequence and starts its body() method upon run().
The launched MAIN sequence creates count sequences of any kind, randomly selected from
the currently loaded ATM sequences. (count is a field in the predefined MAIN sequence.)
Initially, this is only the SIMPLE sequence, so you will have a random stream of ATM cells.
The execute_items() TCM of the BFM pulls the items created by the sequences and drives
Implementing Sequences
For a basic but detailed description of implementing sequences, refer to the sequenceschapter in
the Creating e Testbenches Specman manual. This section and those that follow describe some
advanced aspects of sequences implementation:
This section contains:
Parameterizing Sequences
Implementing Transactions in Virtual Sequences
Enhancing the User Interface
Creating a Sequence Library
Parameterizing Sequences
Cadence recommends representing any specific behavior of the sequence by a field so that the
value of the field is the parameter for the specific feature in the sequence behavior. For example, if
your sequence creates items in a loop, then the number of iterations of the loop is a typical
parameter. These parameters can be viewed as the public interface of the sequence.
In this way, you can later control the parameters using constraints from the outside without knowing
the actual implementation of body().
For example, consider the predefined RANDOM sequence:
The parameter of this sequence is count, which is the number of random sequences that will be
created by the sequence. You can control the behavior of all RANDOM sequences in the
environment by constraining the count:
Note: Such a constraint can also be applied locally to a RANDOM sequence field in another
sequence or even in a specific do.
Using Methods
In the sequence struct, you can define methods that encapsulate a typical item execution. An
example is a read/write interface.
For example, assume that a bus-based design with the basic item c_bus_op is defined as follows:
When no constraints are involved in creating the data item, you can perform read operations by
using the execute_item() method of the sequence driver, as follows:
Note that creating the item using new rather than do provides better performance, as it does not
involve the constraints solver.
Sometimes, instead of new with or do keeping, it is more convenient to write something like:
write(0x100, j);
k = read(0x104);
To do that, define methods in the corresponding sequence that implement these operations:
<'
extend ex_c_bus_sequence {
!write_op : WRITE ex_c_bus_op;
!read_op : READ ex_c_bus_op;
// Do a c_bus write
write(address : uint,
color : ex_c_bus_color,
data : uint) @driver.clock is {
write_op = new with {
.address = address;
.data = data;
.color = color;
};
driver.execute_item(write_op);
};
// Do a c_bus read
read(address : uint,
color : ex_c_bus_color): uint @driver.clock is {
read_op = new with {
.address = address;
.color = color;
};
driver.execute_item(read_op);
return read_op.data;
};
};
'>
For more information about a read/write interface, see DUT-Independent Read/Write Interface.
Using Macros
Sometimes, you might need the greater syntactical flexibility that only macros can give. In that case,
Cadence recommends creating action macros that implement typical do actions. For example:
Writing the Simplest Test: Using UVC’s Sequence API for Test
Writers
We recommend implementing, in the UVC, API which is specific to test writers. This API creates
and sends sequences, but does not require user’s knowledge of the sequences.
Using the Sequence Driver API, introduced in the Specman8.2s2, this API can be implemented in
any unit in the environment. When using an older version of Specman, this API should be in the
MAIN sequence, since in these versions - sequences can be started or called only within sequence
body.
Cadence recommends implementing sequences API methods within the sequence driver,
consisting three kinds of methods:
Methods driving one item. Per UVC and Verificaiton requirements, decide how many such
methods should be. Few example - sending one random item, sending an item to specified
address, sending a random illegal item.
Methods driving a scenario. For example - write followed by a read to a specified address,
getting the DUT into a specific state.
test_scenario - this TCM is to be extended by the test writer. It should be started at the
appropriate phase (run(), tf_main_test()), hence minimizing the amount of knowledge about
the UVC or e the test writer need to know.
Following example shows creating an API consisting of two methods driving items, and one
scenario method:
<’
extend transfer_driver_u {
!transfer : transfer;
Test example:
<’
extend transfer_driver_u {
test_scenario() @clock is {
send_to_address(0);
send_transfer(1000, 4);
};
};
’>
This is sufficient to create a test, because the MAIN sequence is started automatically as part of the
infrastructure of sequences and so there is no need to handle that in the test.
This approach is useful for creating simple directed tests.
Example
If you plan on using this scenario in several tests, consider adding that sequence to the sequence
library. However, sometimes, it is more convenient to define the new kind directly in the test. This
might be done when:
You do not intend to use the sequence in any other test.
You do not want to expose the environment to this sequence (to avoid random sequences
using this kind occasionally).
For instructions on defining a sequence kind in a test, see Creating a Sequence Library.
This approach can be used in conjunction with a specific unit. In that case, you can add a flag that
determines the behavior and then constrain the flag under the specific unit. For example, to apply
the above (waiting 3 cycles) only to sequences under unit ATM_0, extend the target_sequence within
the e file describing the current test or sequence.
Note: This is very modular, even though an existing sequence is extended inside a file describing
another sequence.
These ID fields are convenient for specifying constraints for sequences and also for constraints on
the unit itself. For example:
extend NORTH ex_atm_env {
keep foo == 4;
};
Once such IDs exist, you can use methods like those mentioned in Propagating Parameters in the
Sequence Tree and Migrating Unit Attributes to the Sequences to apply the IDs to the sequences
themselves.
Note: The name “NORTH ex_atm_env” specifies a role of ex_atm_env, sometimes mistakenly called
an instance.
This section contains:
Unit IDs to Constrain Sequences
Hierarchical Unit IDs
You now have four NORTH ex_atm_envs. If you load your previous test on top of this environment, it
will run a multi_write sequence on each one of them. If, instead, you want to have all but the first be
of type xx, you can do that as follows:
Sequence Define a sequence for a specific item (packet, transaction, and so on) in
definition files any environment
DUT-specific Extends the sequence for use with a specific DUT and hooks the sequence
sequence to the environment
hook file
See Also
Chapter 3, UVC File Organization
For example:
driver.wait_for_grant(me);
};
};
If you use Specman earlier than 8.2, deliver_item method is not supported; then you must use
the do action. In such cases, the way to modify the data item just before it is being sent it using
its pre_do method, which is called right before the item is generated.
Parameters
Note: “it” already refers to the subtype. There is no need for casting to access the subtype attributes.
Example 1
body() @driver.clock is {
do RED seq keeping {
// Access red_level without casting
it.red_level == 10
};
do GREEN seq keeping {
.green_level == 18;
};
do ATOMIC GREEN seq keeping {
it.green_level == 12;
it.atomic_level == PARTIAL;
};
do YELLOW seq ;
};
};
Example 2
body() @driver.clock is {
// seq will be generated to LONG GREEN my_sequence
// (not GREEN LONG - order does matter).
do LONG seq keeping {
it.length > 10; // length is a field of LONG
};
// seq will be generated to ATOMIC GREEN my_sequence
do ATOMIC seq keeping {
it.green_level == 12;
it.atomic_level == PARTIAL;
};
};
};
3. Add an additional constraint for the root sequence in the root-sequence parent.
Example
extend ex_atm_sequence {
port_id: ex_atm_port_id; // Field to be propagated
keep parent_sequence != NULL => // The propagating constraint
soft port_id == parent_sequence.as_a(ex_atm_sequence).port_id;
};
extend ex_atm_driver {
port_id: ex_atm_port_id;
keep sequence.port_id == port_id; // Constrain sequence root in driver
};
extend ex_atm_sequence {
// 1. Define the unit name in the sequence.
name: ex_atm_name;
// 2. Migrate the value from the enclosing unit.
keep name == get_enclosing_unit(ex_atm_agent).name;
};
This is a useful shorthand. Nevertheless, you might still need to reference the IDs of the enclosing
units if you want a more complete hierarchical identification.
// Items/subsequences
!cell: ex_atm_cell;
To return immediately (non-blocking do), implement the method that processes the item to be non-
time-consuming and return immediately.
Interrupt Sequences
DUT-Independent Read/Write Interface
Controlling the Scheduling of Items
Grabbing Without Affecting Scheduling
Locking of Resources
Handling Pipelined Protocols
Handling Back Pressure and Head-of-the-Line Blocking
body() @driver.clock is {
for each (seq) in sl_seq_l {
it.start_sequence();
};
};
};
Note: The number of sequences that PARALLEL_N activates can be controlled by a higher-level
sequence.
Another way to perform initializations before the test is by extending the pre_body() method to
delay the execution of body(). For example:
Interrupt Sequences
Many environments include an interrupt option. Typically, an interrupt occurrence should be
coupled with some reaction from the agent. Once the interrupt is done, you can consider either
aborting any previous activity or continuing it from the point where it stopped. All of this can be
supported using sequences.
To handle interrupts using sequences:
1. Define an interrupt sequence that implements the reaction-upon-interrupt scenario, including:
a. Wait for the interrupt event to occur (that is, serves as the interrupt handler).
b. Grab the sequence driver for exclusive access.
c. (Optional) Stop the activity of the other existing sequences.
d. Execute the interrupt scenario.
e. Ungrab the sequence driver.
2. Instantiate the interrupt sequence under the sequence driver.
3. Start the interrupt sequence in the run() method of the sequence driver.
Example
extend ex_atm_driver {
// 2. Instantiate the interrupt sequence under the sequence driver
iseq: INTERRUPT_ABORT ex_atm_sequence;
keep iseq.driver == me;
Note: You can make the activity-termination option a parameter of the interrupt sequence. For
example, you could have the sequence actually terminate the other activity only if a Boolean flag is
set.
sequence config_sequence;
extend config_sequence_driver {
low_driver: any_sequence_driver;
event clock is only @sys.any;
extend sys {
cbus_env: c_bus_env is instance;
// Instantiate the virtual sequence driver.
config_driver: config_sequence_driver is instance;
// Connect its low-level sequence driver to a specific
// sequence driver.
keep config_driver.low_driver == cbus_env.driver;
};
// Implement the read/write interface methods of the low-level
// sequence driver.
extend ex_c_bus_driver {
write(address: list of bit, data: list of bit) @clock is {
sequence.write(pack(NULL, address), pack(NULL, data));
};
read(address: list of bit) : list of bit @clock is {
result = pack(NULL, sequence.read(pack(NULL, address)));
};
};
// Make the c_bus MAIN silent.extend MAIN ex_c_bus_sequence {
keep count == 0;
};
The test itself has no notion of the actual agent that eventually executes the read/write
transactions.
A similar approach can be used to develop additional generic actions such as reset.
The use of list of bit and packing in the read/write interface of the sequence driver is essential
to prevent dependency on a specific agent or format and a specific bit width.
If no sequence is grabbing the driver, the driver will choose the first do action that satisfies the
following condition:
The is_relevant() method of the sequence do-ing it returns TRUE.
For grabbing without effecting the scheduling, you can use wait_for_grant(), and grab the BFM
only after the grant is received. See Grabbing Without Affecting Scheduling.
driver.wait_for_grant(me);
// ungrab
ungrab(driver);
};
};
};;
Locking of Resources
Sometimes a sequence might need to lock resources for a while so that temporarily other
sequences will not be able to touch them.
For example, a divide-by-zero machine instruction sequence might look like this:
body() @driver.clock is {
do SET_REG inst_sequence keeping {.reg == op1_reg};
...Do any random sequence not writing to op1_reg
do SET_REG inst_sequence keeping {.reg == op2_reg; .value == 0};
...Do any random sequence not writing to op1_reg or op2_reg
do instr keeping {
.op == divide;
.op1 == op1_reg;
.op2 == op2_reg
};
...Release the locking on op1_reg and op2_reg
};
};
The question is what to put in the lines starting with the ellipsis “...”. grab() and ungrab() will not
work here. They are useful to grab sequence drivers, not registers.
Cadence recommends using either Boolean flags or lockers inside the sequence driver (or inside
the unit where the sequence driver resides). Have one flag or locker for every resource that you
want to lock (for example, a register that you do not want people to write to).
For example:
extend my_sequence {
!trans1 : READ transfer;
!trans2 : WRITE transfer;
body()@driver.clock is {
-- List a series of pipelined do actions
do trans1;
do trans2;
};
};
extend my_bfm {
bfm_loop()@clock is {
while TRUE {
trans = driver.get_next_item();
start put_on_bus(trans);
If sampling of data items is required, you need a mechanism for detecting when the data-item fields
are updated and ready for sampling. In the above example, the sequence can start to do the WRITE
burst before the fields of the READ burst are fully updated.
To ensure that data-item fields are ready for sampling:
1. Add a Boolean field to the data item that is set to TRUE when all fields are up to date.
2. Delay sampling of fields with a wait or sync action that is satisfied when the field is TRUE.
For example:
extend transfer {
-- Add Boolean field
!finished : bool;
};
extend my_sequence {
!trans1 : READ transfer;
body()@driver.clock is {
do trans1;
-- Delay sampling
sync true(trans1.finished);
sample(trans1);
};
};
When sampling of a pipelined data item’s fields is required immediately after the item is completely
processed, then the above approach must be refined. The sampling of each item must occur in a
thread distinct from that of the do of the next item. You can achieve this in several ways.
Example 1: Combine do action with associated sampling
extend transfer {
-- Add Boolean field
!finished : bool;
get_data()@driver.clock is {
-- Delay sampling
wait true(finished);
do_something_with(data);
};
};
extend my_sequence {
!trans1 : READ transfer;
!trans2 : WRITE transfer;
body()@driver.clock is {
var num_of_items : uint;
all of {
-- Create separate thread for each do-sample pair
{ // First thread
do trans1;
num_of_items = 1;
trans1.get_data();
};
{ // Second thread
sync true(num_of_items==1);
do trans2;
trans2.get_data();
};
};
};
extend my_bfm {
bfm_loop()@clock is {
while TRUE {
trans = driver.get_next_item();
start put_on_bus(trans);
extend transfer {
-- Add Boolean field
!finished : bool;
get_data()@driver.clock is {
-- Delay sampling
wait true(finished);
do_something_with(data);
};
};
extend my_sequence {
!trans1 : READ transfer;
!trans2 : WRITE transfer;
body()@driver.clock is {
message(NONE,"running ",kind," burst");
do trans1;
start trans1.get_data(); // Sample in separate thread
do trans2;
start trans2.get_data(); // Sample in separate thread
};
};
extend my_bfm {
bfm_loop()@clock is {
while TRUE {
trans = driver.get_next_item();
start put_on_bus(trans);
The solution to head-of-the-line blocking is to use the item post-generation is_relevant() method.
You can find a full description of this method in Creating e Testbenches in the
Specman documentation.
The following code example shows extension of the data item, checking whether the data item
address is currently available. The driver will keep on querying this item every time the BFM
requests the next data item, until the is_relevant() method returns TRUE.
is_relevant(): bool is {
// Check if can currently send to address
result = driver.resource_is_available(address);
Layered Protocols
Many UVCs and verification environments require a layered structure of sequences. The classic
example of layering is where the don’t-care data (a list of byte, usually called “payload”) in the
lower-level protocol is supplied by the higher-level. In some protocols, the relations between the
layers are more complex. Those relations can be classified into four major groups:
One- Each data item of the upper layer is translated into (generally, encapsulated in) one
to- data item of the lower layer.
One
A typical example is IP over Ethernet.
One- One data item of the upper layer is fragmented over several items of the lower layer.
to-
A typical example is IP over ATM.
Many
Many- Multiple data items of the upper layer are concatenated into one data item of the lower
to- layer.
One
A typical example is IP over SONET.
Many- Multiple data items of the upper layer are transformed into multiple data items of the
to- lower layer, where any number of upper-layer data items may correspond with one or
Many more lower-layer data items or vice versa.
A typical example is multi-PPP over Ethernet.
Figure 5.1: Layer Mapping
Sequence layering and virtual sequences are the two main ways in which sequence drivers can be
composed to create a bigger whole.
In the UVM examples directory, there are several example packages that demonstrate layering:
ex_oto_layering This UVM package demonstrates One-to-One layering. Each packet (the
upper-layer data item) is encapsulated within one frame.
ex_otm_layering This UVM package demonstrates One-to-Many layering. Each packet (the
upper-layer data item) is fragmented over several ATM cells.
Notes
For the sake of simplification, the layered examples of the UVC define all UVCs (the two
interface UVCs and the one system UVC) in one package. Normally, each environment would
be in a package of its own.
The examples demonstrate layering of drivers, sequences, and BFMs. In no way do the
examples attempt to implement the full protocol.
This section contains:
Simple Layering (Inside One Sequence Driver
Layered Sequence Drivers
The FRAME_SENDER sequence generates a single frame and sends it in chunks, in one or more
packets, until the data of the frame is exhausted. For example:
body() @driver.clock is {
var full_data: list of byte = pack_the_frame(); // Pack the frame
The FRAME_SENDER sequence can then be used by other sequences. For example, you could define
a SUPER_FRAME_SENDER packet_sequence that takes a super_frame (whatever that is), chops it into
frames, and executes in a loop as follows:
do FRAME_SENDER sequence keeping {
.frame == get_next_frame_in_super_frame();
};
Layering inside one sequence driver is easy to write and easy to understand. However, it only
works well in simple cases. For complex cases, you need a more general approach.
The UVM examples library contains four packages demonstrating each of the layering kinds. This
Interfacing Struct
The UVM example defines a layering struct containing a list of bytes (the data to be passed from the
upper to the lower layer) and a struct of type any_struct. The list of bytes is essential for passing
the minimum required input, the raw data to be sent. The struct can be used by the lower layer to
request more information about the upper-layer data item. This makes the lower layer more aware
of upper-layer details. If the lower layer access the upper_layer_struct, it should cast it,
using as_a().
The struct used in the UVM examples is:
struct layering_data_struct_s {
// Raw data
data : list of byte;
Interfacing Methods
For the lower layer to get items from the upper layer, the two layers’ drivers should be connected via
method ports. The lower layer calls the method. The upper layer implements the method, and its
return value is the next upper-layer data item. The methods can have no parameters at all; or they
can have some parameters, passing information from lower to upper layers. Two examples of such
parameters are
stream_id — Lets several sequences run in parallel when each lower-layer sequence gets
items only from specified upper-layer sequences.
remaining_bytes — Number of bytes the lower layer needs before it reaches the maximum
length of the data item. In some protocols, this parameter is not required.
The lower layer should have an instance of the output method port of this method. The upper layer
should have an instance of the input method port. The ex_mto_layering example does this as
follows:
extend ex_mto_layering_ll_pkt_driver_u {
// Output method port instance in lower layer
get_item_layer_transfer:
out method_port of item_layer_transfer is instance;
};
unit ex_mto_layering_ul_pkt_bfm_u like uvm_bfm {
// Input method port instance in the upper layer
down_item_layer_transfer:
in method_port of item_layer_transfer is instance;
};
extend ex_mto_layering_ll_pkt_driver_u {
// Output method port instance in lower layer
get_item_layer_transfer:
out method_port of item_layer_transfer is instance;
};
extend ex_mto_layering_ll_pkt_sequence {
get_item_from_upper_layer(stream_id : uint, remaining_bytes: uint):
layering_data_struct_s @clock is {
// This method blocks the lower layer while enabling
// other instances to call the upper layer
while result == NULL {
result = get_item_layer_transfer$(stream_id, remaining_bytes);
if result != NULL then {
message(MEDIUM,
"LL sequence got an item from the upper layer");
};
};
};
};
In some protocols, the lower layer must give some indication regarding its current status. In this
example, it passes as a parameter the number of bytes remaining to the end of the current data
item. This lets the upper layer generate a data item that fits this length or create a data item that is
intentionally too long. A data item that is too long makes the lower layer to do one of three things:
fragment the data item (which is legal in One-to-Many or Many-to-Many scenarios, but might be
illegal in some protocols), send an item that is too long, or omit some data.
while continue_work {
driver.wait_for_grant(me);
first of {
{
// 1) Sending item
{
// 2) timeout
// ...
// check out the code within ex_mto_layering for
// details of timeout handling
// ...
continue_work = FALSE;
}; // timeout
}; // first of
}; // body
}; // extend sequence
This sequences uses the API that was added to the sequence driver in Specman 8.2. This API
ex_otm_layering/e/ex_otm_layering_atm_seq_lib.e
ex_mto_layering/e/ex_mto_layering_ll_pkt_seq_lib.e
ex_mtm_layering/e/ex_mtm_layering_ll_pkt_seq_lib.e
Upper-Layer BFM
The upper-layer BFM contains an instance of an input method port, which is bound to the lower-
layer driver’s output method port (see Interfacing Methods, Binding the Layers, and Getting Items
from the Upper Layer). The implementation of this method gets one item from the driver and returns
it to the caller.
The method of the upper layer of the Many-to-Many example operates as follows:
1. Locks the driver (required only in the Many-to-One and Many-to-Many protocols).Assigns
values to the driver’s fields, based on the parameters passed from the lower layer. The
sequence can use these fields when generating its data.
2. Calls the driver’s try_next_item(). This is preferred over calling get_next_item(), because it
does not block the lower layer if the driver has no items.
3. If try_next_item() returned an item, passes it on to the lower layer. Otherwise, returns NULL.
4. If try_next_item() returned an item, emits item_done so that the driver will continue.
5. Unlocks the driver.
In some protocols, a simpler method will suffice. In the One-to-One and One-to-Many protocols,
there is no need to lock the driver. There is also no need for stream_id and remaining_bytes.
The full code of this BFM method is:
down_item_layer_transfer(stream_id: uint,
remaining_bytes: uint): layering_data_struct_s @sys.any is {
driver_locker.lock();
var ul_pkt_item: ex_mtm_layering_ul_pkt;
p_driver.stream_id = stream_id;
p_driver.remaining_bytes = remaining_bytes;
ul_pkt_item = p_driver.try_next_item();
if ul_pkt_item != NULL {
result = new;
result.data = pack(NULL, ul_pkt_item);
result.upper_layer_struct = ul_pkt_item;
emit p_driver.item_done;
} else {
result = NULL;
};
driver_locker.unlock();
};
};
In the Many-to-Many example, there are two drivers in the upper layer. This is for demonstrating a
complicated environment, in which there are multiple upper layers, all activating the same lower
layer.
Using Unique ID
You can use the is_relevant() method of sequence to ensure that the upper layer does items only
when the requesting lower layer sequence is its designated sequence.
In the following example, the upper layer and the lower layer sequences have unique IDs. The
upper layer is relevant only if the driver is requesting items of its ID:
The lower layer sequence gets items from the upper layer driver, specifying the ID:
while continue_work {
first of {
{
var layering_struct: layering_data_struct_s;
var num_of_cells: uint;
var size_of_cells: uint;
layering_struct = get_item_from_upper_layer(atm_id);
}:
{
wait [ATM_TIMEOUT];
continue_work = FALSE;
};
};
};
};
};
The virtual sequence that activates both sequences should assign the matching IDs:
body() @driver.clock is {
do ul_pkt keeping {
.len <= driver.remaining_bytes;
The System
Once there are two layer-ready UVCs, Cadence recommends creating a system UVC to bind the
two interface UVCs. For detailed information and code examples, see System UVCs.
The system should contain:
Virtual Sequence Activating the Layered Sequences
Instantiating and Connecting the Components
Default Configuration of the Environment. For example:
Defining the default behavior of the sequences
In the layered examples in the UVM library, the system of each example is defined in the files:
ex_oto_layering/e/ex_oto_layering_system_env.e
ex_otm_layering/e/ex_otm_layering_system_env.e
ex_mto_layering/e/ex_mto_layering_system_env.e
ex_mtm_layering/e/ex_mtm_layering_system_env.e
extend system_sequence {
// Pointers to BFM sequences
!ul_pkt_sequence : ex_mto_layering_ul_pkt_sequence;
!ll_pkt_sequence : ex_mto_layering_ll_pkt_sequence;
// Driver hookup
keep ul_pkt_sequence.driver == driver.ul_pkt_driver;
keep ll_pkt_sequence.driver == driver.ll_pkt_driver;
};
extend system_driver_u {
// the drivers are instanciated in the sub environments and are
// only reused here
ul_pkt_driver : ex_mto_layering_ul_pkt_driver_u;
ll_pkt_driver : ex_mto_layering_ll_pkt_driver_u;
Note: In the Many-to-Many example, there are four and not two drivers. All of them must be
accessed from the virtual driver.
};
Note: To simplify the layered examples in the UVC, all UVCs (the two interface UVCs and the one
system UVC) are defined in one package. Normally, each environment should be in a package of
its own.
Basic Scenario
The simplest scenario requires running one lower-layer and one upper-layer sequence. If the
sequences in the environment contain an ID number, the two sequences should have the same ID.
The following code is an example of a scenario in which there are two streams running in parallel
— two lower-layer sequences, each getting its item from one upper-layer sequence. The first
sequence, ID 1, gets its items from three upper-layer sequences. The second lower-layer sequence,
ID 2, gets its items from one upper-layer sequence.
all of {
{
do LAYERED frame_sequence keeping {
.ll_id == 1;
.destination_address == 0x110011001100;
.source_address == 0x220022002200;
};
};
{
do LAYERED_LONG packet_sequence keeping {
.upper_layer_id == 1;
};
If the item does not have to be generated right before being sent, and there is no need to wait for its
end (in the example above, the sequence checks the data item after it is done), you can further
improve performance by omitting usage of wait_for_grant, and using queue_item. See info in
The chapter contains descriptions of common advanced use models of the UVM e register
package, vr_ad:
Updating the Shadow Model After READ/WRITE a Register
Multiple BFMs Environment
Using the vr_ad Sequence Driver
Using the vr_ad Pre-Defined Sequences Library
Extensions of vr_ad Driver and vr_ad Sequences
Register Broadcast—Data Propagation
“Twin Registers”
Accessing Unmapped Addresses
read_reg/write_reg from any TCM
Performance Improvement on Demand—Static Info Creation
Accessing Registers by Reference
Configuring Coverage for vr_ad
vr_ad Coverage Per Instance
Configuring vr_ad Messages
Debugging Tips for Code that Uses vr_ad
Using Power Awareness in vr_ad
See the full description of the API of the register file (vr_ad_reg_file) and register (vr_ad_reg) in
the section Register and Memory Data Structures in the UVM e Reference manual.
Cadence recommends defining in the vr_ad_sequence_driver a field per each BFM driver to which
it might drive operations during the run. These fields provide easy access to the various drivers,
thus simplifying sequences implementation. In this example, the register accesses can be
performed on two buses: ZBUS and CBUS. The default bus is CBUS. The code below is taken
from vr_ad/examples/multiple_bfms. Shown here are only the parts demonstrating the BFM
selection.
extend mult_buses_env {
keep soft reg_driver.default_bfm_sd == cbus_env.driver;
};
extend MAIN vr_ad_sequence {
!write_read_seq : WRITE_AND_READ vr_ad_sequence;
!tx_mode_reg : VR_AD_TX_MODE vr_ad_reg;
body() @driver.clock is only {
To define the vr_ad_sequence_driver as participating in the Testflow, define the following, before
loading vr_ad_top:
<'
#define VR_AD_RSD_IS_A_TESTFLOW_SEQER;
'>
tf_nonblocking FALSE
The MAIN sequence is registered as blocking; it will not continue to next phase, before
finishing.
You can override this configuration, just as you can with any sequencer that is a Testflow
sequencer.
To see examples, check vr_ad/examples, and note that the examples with names starting
with vr_ad_tf_* configure the vr_ad_sequecne_driver to be a Testflow sequencer.
For more details on implementing sequences when the sequencer is defined as a Testflow unit, see
the Testflow chapter in the UVM e Reference.
extend vr_ad_sequence_driver {
env_2 : bool;
keep soft env_2 == FALSE;
keep sequence.env_2 == env_2;
The following code is taken from the broadcast example. For the full code, see
vr_ad/examples/vr_ad_broadcast.e. Shown below are two pieces of the code, implementing:
Attaching the target register file to the broadcasted register
Extension of the indirect_access() method of the target register file.
“Twin Registers”
Sometimes two different registers are mapped to the same address, often to simplify time-critical
software or to save space in the address map. Implementing “twin registers” is done using indirect
access.
The twin registers are defined in a register file which is unmapped in the memory.
Per each couple of twin registers, there is one register which is mapped in the memory,
serving as a proxy to the unmapped registers.
The flow in such an environment is:
1. All accesses to one of the unmapped registers are automatically passed for handling by an
INDIRECT sequence (implemented by the user).
2. The INDIRECT sequence assesses the relevant proxy register.
3. After the access, the indirect_access() method of the unmapped register file is automatically
called. The user should extend it to perform required actions.
The following code is copied from the example vr_ad/examples/vr_ad_twin_regs.e, showing these
steps of implementing twin registers:
Instantiation of two register files, one is mapped and the other is not
Implementation of the indirect_access() method of the unmapped file
Implementation of the INDIRECT sequence
extend ex_c_bus_env {
// Add the XCORE and TWIN_REGS register files to the to ex_c_bus_env
main_reg_file : XCORE vr_ad_reg_file;
unmapped_regs : TWIN_REG vr_ad_reg_file;
post_generate() is also {
// Set a notification between the proxy_reg and TWIN_REGS
main_reg_file.ex_proxy_reg.attach(unmapped_regs);
See the Indirect Addressing section of the UVM e Reference manual for full description and more
code examples of defining and implementing unmapped register files and indirect access.
extend ex_c_bus_driver {
vr_ad_execute_op(op : vr_ad_operation) : list of byte @clock is first {
if c_bus_env.addr_map.get_node_by_address(op.address) == NULL then {
// Accessing unmapped address
// Implement required activity. e.g. - start some temporal
// check to check DUT reaction
message(LOW, op.direction == WRITE ? " Writing" :
" Reading",
" an unmapped address, ", op.address);
};
};
};
unmapped_region = driver.addr_map.alloc_from_set(
driver.addr_map.free_addrs,
0, // min address
0x222, // max address
4, // min size
4, // max size
4); // alignement
extend my_bfm {
tf_init_dut() @tf_phase_clock is also {
write_reg {.driver == env.reg_driver} ctrl_reg val 0xabba;
write_reg {.driver == env.reg_driver} ctrl_reg keeping {
.mode == 3;
.address == 0x1234;
};
};
};
For more information, see the section write_reg and read_reg Macros in the UVM e Reference
manual.
Background
Each vr_ad_reg instance contains one instance of static_info, which has valuable information
about the register. These static_info instances are the biggest memory consumer in the vr_ad_
model. Because the static_info is required only when accessing the register, and because most
registers are never accessed, especially in big environments, with thousands of registers, a new
option lets you control the creation of static_info.
Creates static_info structs only when the register is accessed. This is the new behavior.
Backward Compatibility
Existing code should not be a problem. However, you can specify VR_AD_ON_SETUP_STATIC_INFO, as
described above, which specifies the behavior to be that prior to the new option.
Example
<'
define VR_AD_ON_DEMAND_STATIC_INFO;
import vr_ad/e/vr_ad_top;
import my_uvc1;
//… etc
'>
For more information on static info, see the section vr_ad_reg_static_info in the UVM e
Reference manual.
When defining a register, you can define for each field whether it should be covered by adding a
coverage option at the end of the reg_fld definition. The option you add defines whether the
coverage will be collected in READ accesses, WRITE accesses, or both. These options are
described in Coverage of Register Fields in the UVM e Reference)
Recommended usage of the coverage options:
For fields that are readable and not writable, use the cov_r option.
For configuration fields you plan writing in the tests but not reading, use the cov_w option.
For registers that are both written and read, use the cov_rw option.
Do not use the cov option, as the coverage information it will provide is not accurate enough.
You can also control the timing of coverage collection. There are three cover groups in vr_ad, each
of which is sampled at different times.
Two of the coverage groups—reg_access and reg_updated_cover—are default, meaning that
coverage is automatically collected for them These groups are triggered by the vr_ad methods
update/fetch/compare_and_update.
reg_access collects the values as seen on the bus—that is, the values passed by the monitor
<'
#define VR_AD_NO_AUTO_COVER;
import vr_ad/e/vr_ad_top;
'>
There is a third cover group, user_defined_reg_cover. This cover group is based on the
event user_defined_reg_cover. This event is not emitted by vr_ad; it is for the user to emit as
applicable. This cover group is defined to allow you to collect coverage at any time, after applying
changes to the model.
To enable user-defined coverage collection, define VR_AD_USER_TIMED_COVER before loading vr_ad.
For example:
<'
#define VR_AD_USER_TIMED_COVER;
import vr_ad/e/vr_ad_top;
'>
This creates the file “demo_reg_file_cover.e”, which defines the “cover per reg_file”
coverage model.
2. Run the demo to view per-instance information with the following command:
You can then view the collected coverage, per type as well as “per instance”, for each env
unit.
Following is the generated code:
extend vr_ad_reg {
on reg_access {
for each (parent) in get_parents() {
if parent is a vr_ad_reg_file (rf) {
rf.cur_reg = me;
emit rf.reg_access;
};
};
};
on reg_updated {
for each (parent) in get_parents() {
if parent is a vr_ad_reg_file (rf) {
rf.cur_reg = me;
emit rf.reg_updated;
};
};
};
type vr_ad_reg_file_inst_name : [NONE];
extend vr_ad_reg_file {
!inst_name : vr_ad_reg_file_inst_name;
!cur_reg : vr_ad_reg;
event reg_access;
event reg_updated;
event reg_updated_cover;
event user_defined_reg_cover;
on reg_updated {
emit reg_updated_cover;
};
cover reg_access is {
item inst_name using per_instance, ignore = inst_name == NONE;
item direction : vr_ad_rw_t = cur_reg.static_info.direction;
};
....
See Also
For more information about coverage per instance, see Specifying Coverage for Group Per
Unit Instances in Managing Coverage for e Testbenches.
On the other hand, if you want to see only the messages at the register level, set the verbosity
of VR_AD_MSG to MEDIUM. The following example ensures that you will see only register messages for
all access operations on the shadow model.
set message sys -tag=VR_AD_MSG MEDIUM
Output Examples
The following output examples are the result of a call to compare_and_update():
When verbosity is set to HIGH:
[33] C_BUS: (info - Compare) Compare EX_CBUS_MAP vr_ad_map-@1, address:
0x00000100, data: 0x00
[33] C_BUS: (info - Compare) Compare MY_REGS vr_ad_reg_file-@2, address:
0x00000000, data: 0x00
[33] C_BUS: (info - Compare & Update) Compare & Update STATUS vr_ad_reg-@3 in
MY_REGS vr_ad_reg_file-@2, addr 0x00000100, data 0x00000000
See Also
set message command in the Specman Command Reference
Example:
trace vr_ad MEDIUM
For example, to disable all read-related messages (messages reported by the fetch() method):
Specman>vr_ad remove read messages
For example, to set the verbosity of messages tagged with VR_AD_MSG to LOW:
extend my_env {
post_generate() is also {
message_manager.set_screen_messages(sys, VR_AD_MSG, LOW);
};
};
See Also
set_screen_messages | set_screen_messages_off in the Specman e Language Reference
See Also
Probing for Data in Indago Debug Analyzer App User Guide
The test executes, and Indago is launched, reporting a DUT error of a compare mismatch.
See Also
The first part of the section Using the Root Cause Analyzer in Finding the Cause of an Error in
the Indago Debug Analyzer App User Guide describes the basic steps for this process.
If one of them seems suspicious, you can then trace its origin.
Follow these steps to examine the two values:
1. Open the Variables window by clicking on the Variables button.
2. Expand the “me” variable (the register that is being checked).
3. Search for the fields defined by the verification environment. Ignore predefined fields, such
as backdoor_auto_update or in_model.
In this example, the first user-defined field is named “data”.
4. Click the left arrow next to the “data” field to bring up the Source Debugger.
The Source Debugger shows the code in which this field was last assigned.
5. Using the Source Debugger, you can now trace back to the code that called update() to
update the register.
In this example, we can see that update() was called from vr_ad_execute_op().
6. If the current value of the register is correct, but the ref_data value is not as expected, you can
use the Call Stack buttons on the Source Debugger to trace the assignment for this value.
Click the Stack Up icon to move up the stack to the next call and the Stack Down icon to
move down the stack to the previous call.
In this example, we see that the incorrect value for ref_data is a result of incorrect masking.
NOTE: If you are familiar with the flow and know that registers updates are reported with
“Write ...” messages, you can also view the SmartLog and search for Write messages to the
register address. Clicking on the message brings up the source code, showing the relevant
execution of the update() method.
Similarly, you can investigate the read_data value, which is passed from a method call.
For example, you can open the Call Stack window and look for the method listed immediately
below compare_and_update(). This is the method that passes the data it reads from the bus. Now
you can trace how this method got the data. In our example, the method is vr_ad_execute_op, and
the value (named “result”) is assigned by reading the DUT signal.
See Also
For more information about the GUIs described in this section, see:
Tracking the Values of Variables in Indago Debug Analyzer App User Guide
Using the Call Stack in Finding the Cause of an Error in Indago Debug Analyzer App User
Guide
Debugging Source Code in Indago Debug Analyzer App User Guide
Using the Smart Log in Indago Debug Analyzer App User Guide
<'
extend vr_ad_map {
!uvm_lp_domain_monitor : uvm_lp_domain_monitor;
<'
extend vr_ad_map {
on power_down {
reset();
};
};
'>
<'
extend vr_ad_sequence_driver {
send_to_bfm(ad_item: vr_ad_operation) @clock is first {
wait @true(addr_map.power_is_on);
};
};
'>
See Also
vr_ad_reg_file Fields in the UVM e Reference Guide
vr_ad_map Register-Related Fields in the UVM e Reference Guide
Testflow Overview
A test goes through several phases. Some are before the simulation begins (generate, setup_test,
and so on). Some are after the simulation ends (finalize_test, check, and so on). The phases
discussed here are during the simulation (the testflow phases).
Testflow phases partition the simulation into several phases with generally applicable test-related
intention. Using testflow phases for defining the test scenario provides built-in synchronization
between the various components. For example, when defining in the system UVC an activity for
the INIT_LINK phase, you know that this activity will take place only after all of the components in
the environment are done with their previous phases (SETUP, INIT, and RESET). There is no need for
the system environment designer to know the details of the participating UVCs or their events.
Unlike pre-run and post-run phases, testflow phases are time-consuming. Also unlike pre-run and
post-run phases, testflow phases are not global in nature but rather local to the scope of a VE
element like an agent. (This is unlike the run() and check() methods, which are shared by all units
in an environment.)
The following sections give an overview of the testflow phases:
Unit Testflow Phase Activities
Testflow Domains
See Also
The Testflow chapter in the UVM e Reference.
Phase TCMs
There is a different TCM for each testflow phase. Each TCM is called at the beginning of its
corresponding phase. The TCM is initially empty and can be extended. As long as the TCM or any
other TCM that is in its scope is active, the phase will not change, and the domain will not proceed
to the next phase.
Phase TCMs synchronize on the predefined tf_phase_clock event.
When starting an activity in a phase TCM, there can be several cases, defined by these two
attributes:
Blocking/NonBlocking:
Blocking activity means that until the start TCM is done, the domain cannot proceed to next
phase
For example: A TCM performing reset is typically blocking. Until it is done injecting the
defined scenario, the domain must not proceed to the next phase. A monitor is typically non
blocking; it runs passively collecting data.
Phase Dependence/Non-Dependence
A phase-dependant activity should not continue running if the domain phases changes.
For example: The BFM getting items from the sequence driver and sending them to the DUT
should stop immediately when the domain is rerun to a previous phase (when a reset occurs).
After starting a TCM from a phase TCM, you can use the register_thread* methods for specifying
whether the started thread is blocking or not, and whether it should continue running independent of
the phases scheme.
For detailed description of the register_thread* methods, see the Thread Registration section of
the UVM e Reference manual.
In this example, the drive_transfers TCM should run until POST_TEST, as non-blocking (this TCM
does not prevent the domain from proceeding to next phase):
extend MASTERxbus_bfm_u {
tf_main_test() @tf_phase_clock is also {
message(LOW, "Master BFM started");
start drive_transfers();
// Register the thread as running until POST_TEST, non blocking
tf_get_domain_mgr().register_thread_by_name(me, "drive_transfers",
POST_TEST, FALSE);
}; -- tf_main_test()
};
Phase Sequences
If the unit is a sequence driver, it will launch a predefined sequence for each of the phases. Except
for the MAIN_TEST sequence, these sequences are empty by default and must be extended.
The MAIN_TEST sequence behaves like the MAIN sequence (calling sequences COUNT times). For
more information on unit phase methods, see the Specman documentation.
Example of extending a phase sequence:
extend checker {
post_generate() is also {
// Set drain-time of the MAIN_TEST phase to 40 cycles.
// After 40 cycles of no item_seen event - the checker assumes
// that was the last item.
tf_get_domain_mgr().set_objection_drain_time(MAIN_TEST, 40);
};
event item_seen;
on item_seen {
start check_item();
};
check_item() @sys.any is {
// "raise objection" to end of test
tf_get_domain_mgr().register_thread(UNDEF, MAIN_TEST, TRUE);
// Taking my time ... performing the check …
wait [10] * cycle;
// Drop my objection to end of phase.
// Because drain_time is set to 40, then
// if no other phase activity occurs during the next 40 cycles,
// the phase will end.
tf_get_domain_mgr().unregister_thread();
};
};
See Also
End-of-Phase Drain Time in UVM e Reference
#define UVM_OLD_EMPTY_PHASING;
Be sure to set this #define before you load or compile UVM e code.
See Also
Phase TCMS in the UVM e Reference
Testflow Domains
Some units should go through the test phases together. There are no occurrences of one unit
reverting or advancing to another phase while the other units remain in the original phase. An
example of this is the BFM and monitor of the same DUT interface.
But some units in the environment should be independent of each other. For example, a reset might
be performed on one subsystem, but other subsystems should continue the test scenario as usual.
All units that go through the test phases together point to a single shared domain
(testflow_domain). Each unit can have a maximum of one domain. (A unit that does not participate
in the testflow would have no domain.) The domain handles the following tasks:
Holds the current phase.
Holds the current phase status (either executing or waiting). Executing means that the units
associated with the domain are still executing their phase TCMs. Waiting means that the units
associated with the domain terminated, and the domain is waiting to synchronize with other
domains.
At the beginning of each phase, starts the appropriate TCM for each unit associated with the
domain.
Waits for all domain units to finish their current phase TCM before moving to the next phase.
Transitions on the fly to a specified phase.
Holds information about the current phase, for example, the number of times the phase was
visited in the current test.
Supplies visibility and debugging for the domain and its units’ status.
Note: At the beginning of a test, the first task done by the test flow domain manager
(tf_domain_manager) is to raise an objection to TEST_DONE. Then the last phase ends, the
tf_domain_manager drops the objection to TEST_DONE (including if the last phase is a user defined
phase). The objection to TEST_DONE is raised once and dropped once.
See Also
For more information on testflow domains, see Testflow in the UVM e Reference.
For more information on multiple-domain environments, see Multiple-Domain Environments.
Using Testflow
When using the testflow in your verification environment, you must supply basic definitions. Once a
unit is defined as using testflow, you can adopt any of the hooks provided by the testflow utility.
To define a unit as using testflow:
1. Add a tf_testflow_unit member within the unit.
For example:
2. Define the clock on which the unit phase TCMs and sequences will run.
You can define several clocks, meaning that at some phase the testflow should start using
another clock. Cadence recommends connecting the phase clock to the unqualified clock. If
all TCMs run only in appropriate phases, there is no need for a reset-qualified clock. For
example:
event tf_phase_clock is only @synch.unqualified_clock_rise;
For defining different clocks for different phases, you can use the CLOCK_SWITCH_SCHEME. For
example:
CLOCK_SWITCH_SCHEME {ENV_SETUP; MAIN_TEST}
{synch.unqualified_clock_rise; synch.clock_rise};
For more information, see Clocking Issues.
extend sample_bfm_u {
keep soft tf_domain == MY_BUS_TF;
};
extend my_top_env {
connect_pointers() is also {
// MY_SYSTEM_TF domain (and all units in it) should not proceed
// beyond RESET before MY_BUS_TF domain(and all units in it) are
// done with INIT_LINK activities
tf_add_dependency(MY_SYSTEM_TF, RESET, MY_BUS_TF, INIT_LINK);
// All domains (and all the units in them) should synchronize on
// MAIN_TEST and POST_TEST.
// Do not start main test scenario before all are out of reset.
// Do not proceed to post-test activities before all are done
// with their main scenario.
tf_add_mut_dependency({MY_BUS_TF; MY_SYSTEM_TF}, MAIN_TEST);
tf_add_mut_dependency({MY_BUS_TF; MY_SYSTEM_TF}, POST_TEST);
};
};
For example:
extend MAIN RESET sample_sequence {
body() @driver.clock is also {
do INIT_MEMORY sample_sequence;
do READ_REGS sample_sequence;
};
};
extend MAIN MAIN_TEST sample_sequence {
body() @driver.clock is only {
do SEND_FRAMES sample_sequence;
};
};
HARD_RESET Hardware reset / Initial Monitor: Wait for the analog circuitry (for
analog reset / Power-on example, PLLs) in the DUT to lock or settle.
reset
Perform initial reset activities.
RESET Soft reset / Digital reset Driver: Drive the reset logic.
See more in Reset Methodology and Reset
Activities: Causing Reset, Responding to
Reset
INIT_DUT Programming the DUT Driver: Write register values with either
S/W interface or backdoor access.
Initialize the DUT internally.
MAIN_TEST Main test scenario / Traffic Monitor: Monitor the lines, and perform
flow checks.
Driver: Send data according to the protocol
and test definition.
FINISH_TEST Test scenario ending phase Monitor: Wait for injected data to finish its
flow in the system.
Stop initiating traffic, and enter
reactive only mode. Driver: Flush FIFOs.
Example
Sequence Activity
Sequence Groups
For each testflow sequence, there is a tf_phase field, of type tf_phase_t. If the sequence is valid
only for a particular phase, this field’s value should be constrained for that sequence. For example:
keep tf_phase == RESET
When doing sequences, if the sequences must belong to a specific phase group, you can ensure
that only sequences that were constrained to that phase will be chosen as follows:
do seq keeping {.tf_phase == RESET};
cleanup is not performed, the sequence driver assumes that the BFM is still processing the
previous item, and all further calls to get_next_item() will be stuck. The default behavior of
the tf_domain_manager is not to perform the removal of previous get_next_item.
2. Clean up of do requests pending in the sequence driver. These are the do requests passed to
the sequence driver before the rerun, and have not been sent to the BFM. If this cleanup is not
done, then after the BFM is restarted the sequence driver will send it the “old” items, items that
were done before the rerun. The default behavior of the tf_domain_manager is not to perform
the removal of old do requests.
These two cleanups can be controlled by the user, using the two sequence driver API methods:
If the BFM is restarted after the rerun_phase, then the remains of previous get_next_item should be
removed. In such cases, extend the tf_to_clean_previous_bfm_call() so that it will return TRUE.
For example, assume there are several calls to rerun_phase, but only rerun_phase (HARD_RESET)
effects the BFM. So, when rerunning to HARD_RESET or before, BFM is started. Hence, the remains of
get_next_item should be cleaned. When rerunning to later phases, the tf_domain_manager should
not do any special cleanup of the sequence driver.
The following example shows how to request a removal of all pending do’s.
extend ex_c_bus_driver {
// remove items in do queue upon rerun
tf_to_clean_do_queue(next_phase : tf_phase_t) : bool is also {
result = TRUE;
};
};
Multiple-Domain Environments
Complex verification environments can contain clusters that require some independence during the
test. For example, a device’s PCI link might undergo reset while its MAC continues normal
operation. Such cases require partitioning of the testflow into multiple domains. Each domain
handles its own testflow.
To declare a domain:
1. Define the domain name by extending the type tf_domain_t.
2. Constrain the units to use the domain.
Note: By default, the unit’s domain is the domain of its closest enclosing unit. Always constrain the
topmost possible unit in a cluster. This facilitates domain changes. In other words, you must change
only an agent domain. There is no need to change each agent component (BFM, driver, monitor,
and so on).
Typically, all agents connected to the same subsystem must be synchronized in some phases
(like hard_reset), while functioning independently in other phases. For example, when the active
master agent is in the hard_reset phase, the active slave agents must also be in the same phase.
Also, active slave agents must wait in the main_test phase until the active master agent finishes that
phase. Then each agent goes through the finish_test phase independently. To implement this
synchronization, you can define all of these agents in different domains and add synchronization
points to them. As seen in Figure 7.1, each domain may contain agents from more than one
environment, and each environment may have agents in more than one domain.
For more information on domain synchronization, see Domain Dependencies.
Domain Dependencies
Unless directed otherwise, each domain performs independently throughout the test. Naturally,
entirely independent behavior is seldom required or desired. Typically, there is some cross-domain
synchronization, for example, hierarchical synchronization. Such synchronization is application-
specific. It must be declared explicitly in the environment.
extend sys {
connect_pointers() is also {
tf_add_dependency(XSERIAL_TF,RESET,XBUS_TF,RESET);
};
};
extend sys {
connect_pointers() is also {
var mut_domain_list: list of tf_domain_t;
mut_domain_list = {xBUS_TF;xSERIAL_TF;DEFAULT};
tf_add_mut_dependency(mut_domain_list,MAIN_TEST);
tf_add_mut_dependency(mut_domain_list,FINISH_TEST);
tf_add_mut_dependency(mut_domain_list,POST_TEST);
};
};
and other responsive TCMs. For more information, see Responsive TCMs and Responsive
Sequences.
Responsive TCMs
Responsive TCMs usually run in an infinite loop. When implementing testflow, such processes
must be affected by the testflow phases while not blocking the advancement of the phases. To
achieve this goal, these infinite TCMs must be registered as non-blocking threads.
For example:
Responsive Sequences
The responsiveness quality for a sequence is declared per sequence driver. The declaration is
done during the generation phase. It cannot be changed during the test.
The field tf_nonblocking causes the sequences that are run for the specific driver to be terminated
when a phase changes without affecting the testflow progress.
If a specific sequence must be run outside the testflow, it can be started and then depend on the
testflow in the same manner as TCMs.
For example:
extend xbus_slave_driver_u {
keep tf_nonblocking == TRUE;
};
or
extend ethernet_monitor_u {
on dut_clock {
emit tf_phase_clock;
};
};
Clocking Issues
Units that participate in the testflow have a clocking event, tf_phase_clock. This event is defined
empty. You should either override it, or emit it. For example:
extend ethernet_monitor_u {
event tf_phase_clock is only @dut_clock;
};
or
extend ethernet_monitor_u {
on dut_clock {
emit tf_phase_clock;
};
};
This section contains some recommendations and use models for the phase clock:
Clock Scheme — Defining Different Clocks For Different Phases
Testflow driver.clock
Example
The following example, taken from the XBus UVC, specifies that the clock for the first phase is the
unqualified clock. When MAIN_TEST starts, some units start using the qualified clock (called clock).
extend xbus_bus_monitor_u {
CLOCK_SWITCH_SCHEME {ENV_SETUP}
{synch.unqualified_clock_rise};
};
extend xbus_slave_driver_u {
CLOCK_SWITCH_SCHEME {ENV_SETUP;MAIN_TEST}
{synch.unqualified_clock_rise;synch.clock_rise};
};
extend SLAVE xbus_bfm_u {
CLOCK_SWITCH_SCHEME {ENV_SETUP;MAIN_TEST}
{synch.unqualified_clock_rise;synch.clock_rise};
};
Testflow driver.clock
By default, the tf_phase_clock of the driver is bound to driver.clock. When you perform clock
switching on the driver (that is, the driver will have different clocks for different phases), you should
update the driver.clock accordingly. Connect driver.clock to the tf_phase_clock, so that the TCMs
of the driver and its BFM will run on the correct phase clock. Binding the clocks is done using an on
event block. For example:
extend xbus_arbiter_driver_u {
synch : xbus_synchronizer_u;
on tf_phase_clock {
emit clock;
};
};
Reset Methodology
Most verification environments should be able to perform reset several times during a test, verifying
that the DUT performs reset correctly, and go back to normal behavior after reset is done.
Performing reset using the UVM testflow is done by calling the rerun_phase() method of the
testflow domain manager. The UVM testflow defines several phases prior the MAIN_TEST phase,
allowing fine tuning of the reset stage—ENV_SETUP, HARD_RESET, RESET, INIT_DUT, and INIT_LINK.
For example, to go back to the RESET phase, call the method as follows:
tf_get_domain_mgr().rerun_phase(RESET);
Once the rerun_phase() is called, the current phase is terminated, and the target phase starts.
Termination of the phase is done by killing these threads in all domains’ units:
The phase TCMs (tf_xxxx())
Threads that were registered to any phase between the target phase and current phase
Phase sequences
Sequences that were registered to any phase between target phase and current phase
Note: No other threads are killed automatically. All temporal expressions continue running
uninterrupted.
For example, if during MAIN_TEST rerun_phase(RESET) is called, these threads will be killed:
tf_main_test()
extend xbus_env_u {
do_extra_reset() @tf_phase_clock is {
var delay : uint;
gen delay keeping {it in [100..1000]};
wait [delay];
tf_get_domain_mgr.rerun_phase(RESET);
};
tf_main_test() @tf_phase_clock is also {
if tf_get_domain_mgr().get_invocation_count(MAIN_TEST) < 3 then {
// This is the first or second time MAIN_TEST is invoked
// for this domain - rerun RESET
start do_extra_reset();
};
};
};
Note: The rerun() method kills all running threads, including the phase TCMs. This prevents the
testflow mechanism to continue in a correct manner. Avoid calling rerun() of a unit working with
testflow.
Note: The rerun() method kills all running threads, including the phase TCMs. This prevents the
testflow mechanism to continue in correct manner. Avoid calling rerun() of a unit working with
testflow.
For recommendations in respect to typical tasks for the phase sequences, see Typical Activities in
Testflow Sequences.
Replacing run()
Units that do not use testflow start their activities from run(), using run() is also. If some of your
UVC activities require phase-related synchronization (for example, waiting for a reset event),
consider replacing run() is also with an appropriate phase TCM.
For recommendations in respect to typical tasks for each phase, see Typical Roles of Each Phase.
start_my_activity() @sys.any is {
// wait for xbus to complete INIT_LINK
xbus_evc.tf_get_domain_mgr().sync_with_phase(INIT_LINK);
out("XBus ended of INIT_LINK, can continue");
// .. continue with my activity ...
};
To synchronize on a non-testflow-using UVC, you should use the events and other hooks defined in
that UVC. For example, extend the tf_reset phase TCM to wait for reset event coming for another
UVC:
// Pointer to another UVC, one that does not user the Testflow
bus_env : bus_env;
tf_reset() @tf_phase_clock is also {
wait @bus_env.reset;
};
c. If the UVC you depend on does not use the UVM testflow, you can use the events and
other hooks defined in this UVC. For example:
wait @bus_env.reset;
do env1_reset_seq;
wait @env1_env.reset;
do env2_reset_seq;
restarted. If the parameter is NULL or rf_expect is not compatible with its type, an error message is
issued.
Note: If the parameter is NULL, we stop/rerun the expect for all the instances compatible with it.
After rf_expect.stop(inst) is called, the expect of the instance is registered and actually killed at
the end of the tick, right after killing the temporals of instances, for which the quit() method was
called. For the same instance, both rf_expect.stop(inst) and inst.quit() can be called, though it
is redundant. In addition, it should not contradict rf_expect.rerun(inst) or inst.rerun(),
because they are called on the next tick.
After rf_expect.rerun(inst) is called, the expect of the instance is registered to be killed at the end
of the current tick and registered for rerunning on the next tick, on sys.new_time event. If
both rf_expect.rerun(inst) and inst.rerun() are called, expect is started only once.
Syntax
extend rf_expect {
stop(inst: any_struct) is undefined;
rerun(inst: any_struct) is undefined;
};
For example
Module-to-System Verification
This document uses a small example to show the relationship between module and system UVCs
and to explain the methodology of constructing a system verification environment while reusing
module-level components.
The example DUT, XCore, is subcomponent of a larger system, QSoC. The QSoC contains two
instances of the XCore on a single XBus. Each XCore is connected to an XSerial port. The system
is activated by software running on an XBus master.
To demonstrate the module-to-system methodology, we use the XCore as the module level
example with a corresponding module UVC. The QSoC is a system that has a corresponding
QSoC UVC.Aimage
Figure 8.1: The Golden Example QSoC DUT
A system in one verification environment can become a subsystem in a larger environment. The
terms module UVC and system UVC are interchangeable. A module UVC is a system UVC for a
single module.
This small example is actually part of a more complete system named the RSoC.
Figure 8.2: The RSoC DUT
System UVCs
Module and System UVC Architecture
Integrating UVCs into a Testbench
Configuration
Sequences
Coverage
Checking
Scalability Issues in System Verification
See Also
XCore UVC User Guide
System UVCs
A system UVC is a reusable verification environment for a system. It contains module/subsystem
UVCs, interface UVCs, and some additional verification components for the system level.
An interface UVC (for example, AHB, PCI, or Ethernet) is a generic UVC for verifying the protocol
on the interface.
System
Drive system-level scenarios
UVCs
Monitor the behavior of the system
Collect system-level coverage
Interface
Define the interface data item (packet, burst)
UVCs
Drive the items using sequences with the predefined sequence driver and
BFM
Monitor the items (collecting the items and interpreting the related events) for
checking and coverage
Check the protocol
Verifying Systems
The first step in system verification is integrating all of the components into a system verification
environment. This means instantiating the module, subsystem, and interface UVCs and connecting
them. Each component must be configured to create a consistent model of the actual system DUT.
In the RSoC example, the components are:
Interface UVCs (XBus, AHB, XSerial)
Module and subsystem UVCs (QSoC, XCore, XBridge, QSoC_SW)
The next step is adding the system-specific capabilities such as system-level driving (virtual
sequence driver), system-level logic checking (scoreboards), and system-level coverage.
In the RSoC example, the system-specific capabilities are:
Driving multiple interfaces concurrently
Collecting and covering flow-control data (not implemented)
Interface UVCs
Traffic generation
Data items
BFM and sequence driver
Basic sequences
Checks
Protocol checks
Scoreboard
Coverage definitions
The architecture of module/system UVCs is different from interface UVCs. The example used here
is that of the XCore DUT, which has a bus interface connected to the XBus and a serial interface
connected to an XSerial port. The XCore transfers streams of bytes in either direction.
When designing the verification environment for this example, the first considerations that must be
addressed are:
What UVCs will be involved?
What will the module UVC contain?
What will be the UVC interactions?
This section contains:
Module UVC Architecture
System UVC Architecture
System UVCs are similar to module UVCs. In general a system UVC includes:
Module UVCs representing the modules that comprise the system
An address map representing the address space of the memory blocks and register files in the
various subcomponents
A VSD and an RSD instantiated in the testbench or a higher-level system UVC that uses the
system UVC as a component
The VSD is defined in the UVC but is used on a higher level
The RSD is defined in the register and memory package
The system UVC is connected (using pointers or ports) to other UVCs, in particular to the related
interface UVCs’ monitors and BFMs.
};
};
Stand-In Mode
Module UVCs should be designed to support stand-in (active) mode. In stand-in mode the system
UVC drives the simulation by playing the role of the DUT. A reference model is used to emulate the
device behavior.
When the module UVC becomes active, its corresponding agents in the interface UVCs become
active as well. The sequence driver in the active part of the module UVC is layered on top of the
sequence drivers in the interface UVCs. The built-in driving capabilities of the interface UVCs are
used by the module UVC to drive the simulation.
Reference Mode
Reference models can be implemented at three levels of precision:
Rough Justification
Given some input and some output, check the validity of DUT
behavior
Might use DUT’s internals for justification
Transaction-Level
Given an input, provide a list of valid outputs
Functionality
Used for scoreboards (comparing actual to predicted)
Used for functional stand-in mode
Cycle-Accurate
Given an input, perform cycle-accurate emulation and provide
the expected result
Used for cycle-accurate stand-in mode
An overkill for scoreboards
All UVCs and the connections between them are created during generation. This section deals with
the best known practices of generating and connecting UVCs, considering current limitations and
future improvements:
environment:
Packet to burst flow checker
Figure 8.1: System-Level Environment Containing Flow Hubs and Flow Checkers
See Also
uvm_flow_hub and uvm_flow_hub_item in the UVM e Reference
uvm_flow_checker in the UVM e Reference
<'
uvm_create_flow_hub env1_hub using flow_data = trans, packet;
uvm_create_flow_checker packet_trans_checker using flow_data = trans, packet;
unit env1 {
hub : env1_hub is instance;
trans_monitor : trans_monitor is instance;
packet_monitor : packet_monitor is instance;
reset_and_clock_monitor : reset_and_clock_monitor is instance;
checker : packet_trans_checker is instance;
connect_ports() is also {
hub.connect_producer_monitor(trans_monitor);
hub.connect_producer_monitor(packet_monitor);
// connect the reset event
hub.connect_producer_monitor(reset_and_clock_monitor, TRUE);
hub.connect_consumer_checker(checker);
};
};
'>
UVC2 Example
UVC2 defines the “burst” type plus a hub for the burst type, and it implements a burst data checker.
<'
uvm_create_flow_hub burst_hub using flow_data = burst;
uvm_create_flow_checker burst_checker using flow_data = burst;
unit env2:
burst_monitor : burst_monitor is instance;
burst_hub : burst_hub is instance;
burst_checker : burst_checker is instance;
connect_ports() is also {
burst_hub.connect_producer_monitor(burst_monitor);
burst_hub.connect_consumer_checker(burst_checker);
};
};
'>
UVC3 Example
UVC3 reuses UVC1 and UVC2, adding a checker that checks the system flow, comparing packets
and bursts.
Note that the system checker does not contain a port for trans, as it does not check the trans data
item. When connecting it to the env1.hub, only matching ports are connected. Ports with no match
are ignored.
<'
uvm_create_flow_hub system_hub using sub_hubs = env1_hub, burst_hub;
uvm_create_flow_checker packet_to_burst_checker using flow_data = packet, burst;
unit system_env {
env1 : env1 is instance;
env2 : env2 is instance;
hub : system_hub is instance;
checker : packet_to_burst_checker is instance;
connect_ports() is also {
hub.connect_producer_hub(env1.hub);
hub.connect_producer_hub(env2.hub);
hub.connect_consumer_checker(scbd);
};
};
'>
In the system UVC, define the pointers from the system UVC to the interface UVCs.
In the testbench, instantiate the UVCs.
In both the UVCs and the testbench, connect the pointers procedurally.
Step 1: Define the pointers from the system UVC to the interface UVCs
p 1: Define the pointers from the system UVC to the interface UVCs
unit vr_qsoc_env_u like uvm_env {
-- A pointer to the XBus UVC
!xbus_uvc : xbus_env_u;
-- Point to the two XSerial interfaces
!xserial_0_uvc : xserial_env_u;
!xserial_1_uvc : xserial_env_u;
};
Each system UVC connection (for example, the QSoC connections to the XBus) has a
corresponding interface UVC agent. You might need additional pointers to the agents and their
components (for example, the monitor).
Avoid pointers in the opposite direction. The interface UVC should be independent of the system
UVCs associated with it.
Note: These pointers can be used only during run time, not during generation itself.
Step 2: Instantiate the UVCs in the verification environment
extend vr_qsoc_sve_u {
connect_pointers() is also {
qsoc_uvc.xbus_uvc = xbus_uvc;
qsoc_uvc.xserial_0_uvc = xserial_0_uvc;
qsoc_uvc.xserial_1_uvc = xserial_1_uvc;
};
};
See Also
connect_pointers() documentation in the Specman e Language Reference
connect_ports() documentation in the Specman e Language Reference
check_generation() documentation in the Specman e Language Reference
Configuration
The UVC configuration is a reusable aspect that must be encapsulated with the UVC. When a UVC
becomes part of a system, some of its configuration is fixed according to the specific use in the
system. For example, when the system integrator assigns an XCore to be slave S0 on the XBus,
that part of the configuration should be packaged into the system UVC.
The interface UVC developer creates a general purpose verification environment for a given
protocol. The configuration of the UVC is left to the integrator.
The module UVC developer creates a special purpose verification environment for a specific
module. The module UVC uses interface UVCs and imposes certain constraints on their
configuration.
The system UVC developer creates a verification environment for a system. The system UVC uses
module and interface UVCs and imposes further constraints on the configuration.
Finally, the system integrator creates the simulation and verification environment for a project. The
testbench uses system, module, and interface UVCs and sets the final configuration.
Who knows what?
The interface UVC developer knows how to configure the interface UVC.
The module UVC developer knows how to configure the module UVC and might know
something about configuring the interface UVC.
The module UVC user (developing a module DUT or developing a system UVC) knows
something about the module, but probably knows very little about the interfaces and the
interface UVCs.
Configuration is always projected top down. At the beginning of the run, when the environment is
generated, configuration is propagated using constraints from the parent. When performing
reconfiguration during the run, configuration is propagated using a configure method.
The configuration of a component depends only on the configuration of the parent
environment.
If the configuration of siblings is mutually dependent, the dependency must be generated in
the parent environment and propagated down to the siblings.
One challenge for the system UVC user is to configure correctly all of the sub-UVCs. An even
greater challenge arises when reconfiguration of the whole system is needed.
The challenge for the UVC developer is to pre-configure the UVC to ease the process of integration
while maintaining the necessary flexibility.
UVC Configuration includes:
The configuration struct
Fields affecting the verification process (knobs)
Configuration constraints
Assigning signal names
Specifying address spaces
Specifying subtypes
Agent type, operation mode
Controlling the operation mode using type definitions
Bus width
A configuration method
This section contains:
Configuration Unit, and Configuration Parameters Struct
Initial Configuration
Reconfiguration and Configuration Method
The configuration unit is generated when the run begins, with all of the environment. The fields of
the unit get their initial value by constraints see Configuration with Constraints. All fields should
have default values using soft constraints so that the values can be overridden by other constraints
driven from above.
The configuration fields can also be changed procedurally, when the unit is reconfigured during the
run, by its configure() method.
Cadence recommends encapsulating the fields that users must change during the run in a struct
that is like uvm_config_params. This struct serves as the API to the configure() method. Hence, it
contains only fields that are hooks that enable configuration modification during the run. For
example, a configuration parameters struct for the configuration unit shown above might contain
three fields to enable modification of three configuration parameters:
You can define the configuration unit and struct manually. You can also use the uvm_build_config.
This defines a config unit and struct, instantiates the configuration unit within the agent.env unit,
and instantiates a params struct within the config unit. For example:
uvm_build_config env xcore_env_u xcore_env_config_u
xcore_env_config_params_s;
In this example, the utility defines xcore_env_config_u unit and xcore_env_config_params_s struct.
A field called params of type xcore_env_config_params_s is defined in the xcore_env_config_u. It
extend xcore_env_u {
show_config() is {
print config.params;
};
};
Building the configuration hierarchy using this macro guarantees that all agents and envs in the
environment have:
configure() — Configuration method
config — Configuration unit. The configuration unit has:
params — Configuration parameters struct
See Also
Reconfiguration Utilities in the UVM e Reference
Reconfiguration and Configuration Method
Initial Configuration
Initial configuration is the process of configuring the environment when the test begins. It includes:
Topology definition (for example, the number of agents)
Binding of ports
Work mode of the DUT
Work mode of the verification environment (for example, whether to perform checks and
whether to inject errors)
Definition of dependencies among testflow domains (if participating components use the
testflow). For more information, see Multiple-Domain Environments.
This section describes the features used when defining initial configuration:
Configuration with Constraints
Extending Subtypes
Defining Types
extend vr_qsoc_env_u {
keep soft xcore_uvc0.sig_reset == "xbus_reset";
keep soft xcore_uvc0.monitor.sig_base_addr == "base_addr";
keep soft xcore_uvc0.hdl_path() == "xcore_a_inst";
keep soft xcore_uvc1.sig_reset == "xbus_reset";
keep soft xcore_uvc1.monitor.sig_base_addr == "base_addr";
keep soft xcore_uvc1.hdl_path() == "xcore_b_inst";
}; // extend vr_qsoc_env_u
Extending Subtypes
Each agent and env unit has a name field that can be assigned in the UVC. Unique names are
used to constrain the individual units as follows:
Each unique name serves one aspect only, but sometimes this is not sufficient for your purpose. For
example, the XCore is used to build the QSoC UVC. In that case:
You might want to use the name to identify the XSerial port of XCore.
You might also want to identify the XSerial port of QSoC.
Each unit has only one name field. Therefore, you must choose a consistent naming convention.
Naming should be deferred to the instantiation. In the following example, the two XSerial UVCs are
named in the testbench where they are instantiated rather than in the QSoC UVC.
Defining Types
It is best to define some operation modes using types, for example the bus width
vr_ahb_data_width. While the type is defined in the UVC, occasionally it must be overridden in the
testbench.
The actual bus width is determined only when the bus is instantiated. To allow overriding from the
testbench, the definition of the bus width in the UVC is done using define under #ifndef. For
example:
#ifndef VR_AHB_DATA_WIDTH {
define VR_AHB_DATA_WIDTH 32;
};
It can be overridden in the testbench configuration (before importing the UVC) as follows:
define VR_AHB_DATA_WIDTH 64;
coverage.
The configuration method should have two parameters:
Number of the configuration
This is a hook for test writers. In a test, the method can be extended by defining specific
activities depending on the configuration number.
This can also serve as a debugging aid, either in messages or when viewing the
number from the source debugger.
Configuration parameters struct
This is the main input to the method. The method should modify the configuration unit
(and maybe other units as well) based on the values of this struct.
The configuration method’s main activities are:
Performing required changes according to the modified configuration struct (including
changing fields of the configuration struct, changing fields of the unit itself, clearing
scoreboards, and so on)
Propagation of configuration information to subcomponents
Example of a Configuration Parameters Struct
The configuration parameters struct can be defined manually:
The configuration parameters struct can also be defined using uvm_build_config, which also
defines the configuration unit. In the XCore example. you would extend the config parameters struct,
adding UVC-specific fields:
extend xcore_env_u {
configure(ctr : uint,
new_params : xcore_env_config_params_s) is {
-- Propagate config info to subcomponents.
-- Note how each field is not necessarily mapped to a
-- single value of the lower layer. NORMAL mode in XCore,
-- for example, can cause either SLOW or NORMAL in the XSerial.
if new_params.mode == NORMAL then {
if new_params.max_speed < 500 then {
uvm_configure ctr xserial_uvc {mode}
{xserial_mode_t’SLOW};
} else {
uvm_configure ctr xserial_uvc {mode}
{xserial_mode_t’NORMAL};
};
} else {
uvm_configure ctr xserial_uvc {mode}
{xserial_mode_t’FAST};
};
// Update local parameters
config.params = new_params.copy();
}; -- configure
}; -- extend xcore_env_u
Configuration Reuse
The bigger the system is, the more configuration it requires. All UVCs used in the verification
environment must be configured. If the UVC integrator must configure all sub-UVCs, it is a lot of
work. The integrator might not even know all of the details of every subsystem. To make the work of
UVC users easier, reusability must be kept in mind in respect to configuration. The key is
determining what elements of the configuration are reusable when the system becomes part of a
bigger system. The elements that are reusable should be part of the UVC itself. The non-reusable
configuration elements go into the testbench configuration file.
Golden Configuration
UVC
xcore As it uses xserial and xbus, it has a configuration file for each of those sub-UVCs. It
also has a reusable configuration file of the xcore itself for the default configuration
and a non-reusable configuration file in the testbench directory. The reusable
configuration files are xcore_xbus_config.e, xcore_xserial_config.e,
xcore_config.e, xcore_port_config.e (for port binding), and
xcore_tlm_port_config.e (for TLM mode).
qsoc As it uses xserial, xbus, and xcore, it has a configuration file for each of those sub-
UVCs. It also has a reusable configuration file of the xcore itself for the default
configuration and a non-reusable configuration file in the testbench directory. The
reusable configuration files are vr_qsoc_xbus_config.e, vr_qsoc_xserial_config.e,
vr_qsoc_xcore_config.e, and vr_qsoc_port_config.e (for port binding).
See Also
Integrating UVCs into a Testbench for more information about where to instantiate UVCs.
Sequences
Driving the verification environment is done using sequences. Reusing sequences from the various
components of the environment saves effort when building the verification environment.
This section contains:
Virtual Sequences for SoC
Reusing Sequence Drivers
Reusing Sequences
Sequences: Module to System
Reuse of Register Sequences
Organizing and Using Sequence Libraries
SoC environments typically require synchronization of the input of several agents, as illustrated in
Figure 8.10.
To control a multiple-agent environment:
1. Ensure that each of the agents has its own sequence (and sequence driver).
2. Define a new (virtual) sequence (and sequence driver) using the sequence statement and
omitting the item parameter.
3. Add the existing sequence drivers as fields of the new sequence driver.
4. Pass the existing sequence drivers using constraints to the BFM sequences done by the
virtual sequence.
This section contains:
SoC Sequence Example
Advanced Issues in Virtual Sequences
Writing Tests Using Virtual Sequences
See Also
Virtual sequences in the Specman e Language Reference
xcore_combined_sequence_h.e xcore_combined_seq_lib.eand in the xcore/e directory
Cadence recommends using do on action. Using do on defines the created sequence as a thread of
its related driver (eth_driver and atm_driver, in this example), thus if these drivers are quit or rerun,
the sequences will stop. Using regular do, as in the example above, these sequences are
considered as threads of the virtual driver.
The main difference between these two syntaxes is that using do, the created sub sequence is run
under the virtual sequence-driver. Using do on, the created sub sequence is run under its relevant
sequence-driver. This is especially meaningful when the sequence-driver undergoes reset. When
using do, the sub-sequence will continue running even if the sub-sequence-driver rerun() method
or rerun_phase() was called. Using do on, if the sub-sequence-driver rerun() or sub-sequence-
driver rerun_phase() method is called, the sub-sequence stops.
Consider, for example, the virtual sequence shown above:
If, after the eth_config sequence started, eth_driver.rerun() is called - eth_config sequence will
quit.
tx_frames_before : uint;
keep soft tx_frames_before in [4..10];
tx_frames_after : uint;
keep soft tx_frames_after in [4..10];
wait_before_resume : uint;
keep soft wait_before_resume in [400..1000];
inter_tx_delay : uint;
keep soft inter_tx_delay in [10..100];
keep prevent_test_done;
body() @driver.clock is only {
wait [100] * cycle;
-- Write TX frames, for the XCore to transmit
for i from 0 to tx_frames_before - 1 {
do program_xcore_to_tx;
};
-- Send a HALT frame. There should be some TX frames in the fifo
do send_halt_frame;
-- Continue to write TX frames, and release the XCore by
-- sending a RESUME frame
all of {
{
for i from 0 to tx_frames_after - 1 {
gen inter_tx_delay;
wait [inter_tx_delay] * cycle;
do program_xcore_to_tx;
}; -- for i from 0 to...
};
{
wait [wait_before_resume] * cycle;
do send_resume_frame;
};
}; -- all of
wait [1000] * cycle;
stop_run();
}; -- body() @driver.clock
}; -- extend MAIN MAIN_TEST xcore_combined_sequence
Reusing Sequences
Module-level sequences are not always reusable on the system level. In general, mixing random
sequences can break a design. For example, inputs that are accessible at module level might not
be accessible at system level.
Parameters should be used whenever appropriate. Parameterized sequences are more reusable.
The XCore sequence library constrains static_item to the register file of the XCore.
The QSoC sequence library constrains static_item to be one of the two register files.
To make the sequence reusable, have “xcore” as a parameter.
Noe: For documentation about vr_ad, a register and memory modeling package, see Chapter 6,
“Using the UVM Registers (vr_ad)" and Register and Memory Modeling Package for e (vr_ad) in
the UVM e Reference.
Sequence libraries can reside either in the UVC or in the testbench/scenarios directory in the
testbench.
Sequence library files are imported by setup files. The role of setup files is to:
Specify which sequences are valid
Define other environment parameters
Setup files are imported by the test files.
Coverage
Coverage definitions can be reused from module to system, while adding system-specific coverage
definitions.
Interface UVCs collect coverage of traffic on the specific protocol they monitor. Module UVC
coverage is collected in its monitor, based on
information from interface UVCs and from the DUT/reference model.
System-level coverage is based on information in all module and interface UVCs in the system.
Coverage results can be crossed to show interactions and combined states.
This section contains:
Module UVC Coverage
System-Level Coverage
Reusing Coverage
Adding Coverage Definitions to System UVCs
System-Level Coverage
System coverage is very similar to module-level coverage with some exceptions:
Connectivity coverage is very important.
Crossing of all valid configurations of the modules in the system is possible.
There is no internal state on system level.
Lower-level coverage definitions should be reused where applicable (fine-tuned if necessary).
Irrelevant coverage goals should be filtered out on the system level.
Reusing Coverage
To facilitate reuse of existing coverage groups defined in the interface UVC, some modifications
might be needed. You should:
Define meaningful ranges for the system (might be different from the original ranges).
Ignore irrelevant groups or items.
Control coverage collection using the has_coverage flag, assuming it was implemented in the
sub-UVC.
When the XCore is reused on the system level, its coverage definition (as well as the coverage of
its registers) must be modified as follows:
extend vr_ad_reg {
cover reg_access (kind == XCORE_RX_MODE) is also {
item direction using also ignore = (direction == WRITE);
};
};
extend xcore_monitor_u {
post_generate() is also {
covers.set_cover(
“xbus_agent_monitor_u.agent_trans_end(name == NO_AGENT)”,
FALSE);
};
};
By default there is no coverage collection on active slaves. However, if a module UVC operating in
stand-in mode needs the coverage information from the corresponding agent in the interface UVC, it
can turn coverage collection on. For example:
extend S3 vr_ahb_slave {
keep has_coverage == TRUE;
};
Checking
Checking includes both protocol and data checking.
Protocol checks are typically handled by interface UVCs.
Data checks are typically done using scoreboards. If using the UVM scoreboard, connect
ports from the monitor to the scoreboard ports. See more in Chapter 9, Using the UVM e
Scoreboard.
Stimulus Generation
Sequences are a key component of the UVC, but loading large numbers of sequences can have a
considerable performance penalty when large verification environments are created using many
UVCs. The most effective guidelines in this area are:
Use sequence libraries, and load only what is needed for the simulation.
Use generation judiciously.
Sequence Libraries
Sequences should be implemented in sequence libraries. Each library is a separate file containing
one or more sequences and optionally some related code. Sequence libraries should be small and
modular, one or a few sequences in each library. This enables careful selection of just the right
sequences for each simulation.
Some basic sequences are provided as part of the UVC. Additional libraries might be offered as
examples. Finally, most sequence libraries are developed and maintained by the end user during
the verification process.
Sequence libraries may reside in various places.
Basic Library
One or more basic sequence libraries are provided with each UVC. A basic library contains the
most common sequences used in almost every test, such as:
Basic read and write operations for master agents
Basic reactive sequences for slave agents
Basic sequence libraries should reside in the /e directory of the UVC. They should be loaded by the
top file so that the basic sequences are always available. A basic library name should reflect its
purpose. For example, the vr_axi_master_seq_lib contains sequences
like SIMPLE_READ and SIMPLE_WRITE.
extend vr_axi_master_seq_kind: [SIMPLE_WRITE, SIMPLE_READ];
User-Defined Libraries
Additional sequence libraries are developed and maintained end users in their verification
environment under their /seq_lib directory.
Generation Tips
Random generation is a very powerful tool for productivity (reducing the manual work of configuring
the environment or DUT and creating stimulus) and improved quality (hunting for bugs in areas that
are hard to foresee). However, excess use of generation might result in scalability problems. When
building a large verification environment, consider carefully how to avoid unnecessarily complex
generation that might slow down the entire simulation.
The following tips show how to avoid some common pitfalls related to large verification
environments. The overall effect depends on the size and complexity of the DUT.
extend any_sequence_item {
auto_quit() : bool is only {
return TRUE;
};
};
Note: If a sequence item is used by the verification environment after item_done, then automatic
quitting is not applicable. The program must issue quit() when done.
implementation is as follows:
A more efficient way of implementing the write() method is to create the burst (using new) and then
constrain the do action to use the pregenerated struct:
The second solution is clearly more complicated. Only apply it in cases where the profiler indicates
that the simple implementation is too expensive.
Assume a device with four ports and multiple registers controlling each port. The simple way to
initialize the device would be as follows (where write_reg is a macro hiding a do statement):
A more efficient way to write the above sequence is to use a for loop, having a single role instead
of the four roles in the previous implementation:
Note: gen has a similar effect to do. This suggestion applies to gen as well.
Checking
Most UVCs have a large set of checks prepared for various verification tasks. Depending on the
nature of the DUT, some of the checks might be irrelevant for certain tasks. For example, when the
DUT is a slave, the master checks in the UVC are not useful.
has_checks: bool;
keep active_passive == ACTIVE => soft has_checks == FALSE;
keep active_passive == PASSIVE => soft has_checks == TRUE;
keep monitor.has_checks == read_only(has_checks);
All checks in a UVC should be implemented under the has_checks subtype of the monitor.
extend has_checks MASTER vr_axi_agent_monitor {
...
Grouping Checks
The UVC developer should identify the checks needed for each verification goal. If the checks can
be grouped in a logical way, each group should have its own has_*_checks flag so that each group
can be turned off separately. For example, you could implement signal checks in a separate group
under has_signal_checks. Other groups could include data checks, latency checks, power checks,
and so on.
In such cases, all subgroups are soft constrained to the main has_checks flag.
keep soft has_signal_checks == read_only(has_checks);
Checks can also be turned off individually using the set checks command:
set check @module IGNORE
Coverage
Coverage is handled similar to checking. While it serves an important role during module
verification, some coverage may be traded off for performance during system-level verification.
All coverage definitions and related code should be implemented under a has_coverage subtype of
the monitor. The value is inherited from a similar has_coverage flag in the agent.
In addition, when checking is turned off, coverage should be turned off as well. It makes no sense to
collect coverage when checking is off.
Grouping Coverage
As in checking, coverage definitions can also be structured in logical groups, under
various has_*_coverage flags, like has_whitebox_coverage or has_error_coverage. Error coverage
and whitebox coverage are important at the module level. On the other hand, for system-level
verification, they could create holes in the coverage. There might be no simple way to create all
error cases or activate all internal states in a specific system. Therefore, this kind of coverage
should be implemented under special subtypes so that it can be disabled at the system level.
All subgroups should be soft-constrained to the main has_coverage flag. For example:
keep soft has_whitebox_coverage == read_only(has_coverage);
As a general rule, coverage subgroups should match the checking subgroups. Whenever a
checking subgroup is turned off, the corresponding coverage subgroup should be turned off as well.
keep soft has_whitebox_coverage == has_whitebox_checks;
Messages
UVCs typically produce a lot of useful information for module-level verification. Some of that
information might be redundant in system verification.
Messages and message loggers that are not activated require minimal or no resources. UVC
developers should leave all message loggers in their default state to avoid an unnecessary
performance penalty. Loggers and messages in a large SoC environment should be activated only
for debugging purposes. Even then, they should be activated selectively, focusing on the area of
interest.
The message facility in UVM lets you improve performance by turning off unnecessary messages.
You can lower message verbosity either globally (by controlling all messages from the system
logger) or selectively (by controlling the loggers in the env unit of each UVC). In addition, the
message facility provides more selective control over messages, based on the verification
environment architecture, message tags, and so on.
UVC-Specific Solutions
UVC performance can typically be improved by profiling it for a specific DUT and tweaking the UVC
capabilities. Often, there is a trade-off between high performance versus full flexibility and
debuggability. Performance can be improved by giving up some of the capabilities. This section
describes some performance improvements that UVC developers might provide.
Sharing Monitors
In some environments, performance can be improved by having multiple components share a
monitor. In particular, in point-to-point interfaces, both agents representing an endpoint can use the
same monitor. In addition, when there are multiple agents on a bus, you can use a single bus
monitor for all of the bus agents rather than a separate monitor for each bus agent.
pre_do(is_item:bool)@sys.any is {
emit driver.upper_driver.clock; // Emit the clock for the
// high-level driver
upper_data = driver.upper_driver.get_next_item().data;
emit driver.upper_driver.item_done;
};
Add Flow
When an item is passed to an add port, these steps are performed by the scoreboard:
1. An uvm_scbd_item is created, containing information about the added item. Some of them are:
The item itself, the “originator”.
The item’s key. By default, the key is the result of running CRC function (crc_32())
performed on all the item’s physical fields.
The item’s stream ID. By default, the ID is defined to be UNDEF.
Out-of-order (OOO) attributes, defining DUT expected ordering, such as FIFO.
The port on which the matching item is expected to be. The default is NULL, meaning
the item can come out on any of the out ports.
2. The new uvm_scbd_item is added to the scoreboard database.
All these fields and their values affect the match algorithm. Users can override or control all of these
tasks, thus affecting the matching algorithm. The next sections of this chapter demonstrate several
usage examples for overriding the scoreboard default implementation.
Match Flow
When an item is passed to a match port, these steps are performed by the scoreboard:
1. Calculating a key for the item. By default, the key is CRC function (crc_32()) performed on all
the item’s physical fields.
2. Searching for a matching item in the stored items – an item with the same key, same stream
ID, and on expected match port.
3. If a legal match is found, and if the scoreboard logger verbosity is LOW or above, an “item
matched” message is printed.
If a legal match is not found, a relevant DUT error is reported:
UVM_SCBD_ERR_MISMATCH:
Did not find an exact match, so compares and reports on the first item on expected port.
UVM_SCBD_ERR_ITEM_MISPLACED
A matching item was found, but not in the expected place. This can happen if previous
items in the database were not matched.
UVM_SCBD_ERR_WRONG_PORT
A matching item was found, but not on the expected port. This means the item was
expected to be sent form one of the DUT’s ports, but it was sent from another port. This
can be an addressing problem in interconnect, for example.
UVM_SCBD_ERR_WRONG_STREAM_ID
A matching item was found, but it is from a different stream than expected. (Refer to
Ordering streams interleaving in one port for more information on streams.)
UVM_SCBD_ERR_DUPLICATE_MATCH
A matching item was found, but this matching item has been matched before.
Users can override or control all of these tasks, and thus affect the matching algorithm. The next
sections of this chapter demonstrates several usage examples.
Assume the data item of the DUT interface is packet_s, containing these fields:
};
By setting the match port, we define some virtual channels between the ports, thus creating a flow
within the scoreboard similar to the flow the DUT supports.
Figure 9.3: Data-Mover Scoreboard - “Virtual Channel”
When instantiating the scoreboard in the environment, the integrator must bind the ports to the
relevant tlm_analysis ports in the relevant monitors. In the code example below, we assume that
each agent has a tx_monitor, collecting the items transmitted by the agent to the DUT, and
an rx_monitor collecting the items sent from the DUT to the agent.
Binding the monitors to the scoreboard ports is illustrated in Figure 9.4. Note how the scoreboard
imitates the DUT expected behavior.
Figure 9.4: D ata-Mover Scoreboard - After Binding
Assuming all packets that are sent to the DUT are expected to be transmitted by the DUT in the
exact same order they were received by the DUT, the scoreboard is ready to use. When a packet is
added to an add port, a key is calculated for it based on its physical fields: addr and data. When an
item is passed to a match port, a key is calculated the same way, and a search is performed for an
item with the same key on the expected port. Items with identical keys are considered matching.
Error messages that can result from this search are described in Match Flow.
Usually, the scoreboard default algorithm should be overridden, such as those described in the
following sections:
In this example, each item sent to the DUT is expected to be sent by the DUT to one of its outputs.
You can also broadcast capabilities, some of the items sent to more than one port. See the code
example in The DUT Broadcasts Items.
See Also:
UVM e Reference manual for the detailed description of the scoreboard port-group
capabilities.
extend packet_to_packet_scbd {
// extend the predict method of packet_add_1
// Packets to address 0 are considered looback
packet_add_1_predict(item : packet_s) is also {
if item.addr != 0 then {
// Default:
// Items of add port #1 are to be matched
// against items of match port #1
set_match_port(item, packet_match_1);
} else then {
// Loopback mode:
// Packets with address 0 should be matched
// against match port #2.
set_match_port(item, packet_match_2);
};
};
};
extend packet_to_packet_scbd {
packet_add_predict(item : packet_s) is only {
// Add to the scoreboard only legal data packet
if (item.legal and item.kind == data_packet) then {
add_to_scbd(item);
};
};
extend packet_to_packet_scbd {
if (item.addr == 0) then {
set_match_port(item, match_port_0);
add_to_scbd(item);
set_match_port(item, match_port_1);
add_to_scbd(item);
set_match_port(item, match_port_2);
};
};
};
extend packet_to_packet_scbd {
compute_key(item : any_struct) : uint is only {
// The matching should be performed only on the data field.
// Calculate the key only on the data field.
// Note the type check, as the method’s argument is of any_struct
if item is a packet_s (p) then {
var bit_list : list of bit;
bit_list = pack(NULL, p.data);
result = bit_list.crc_32, bit_list.size()/8);
};
};
};
After defining this method, each two packets with same data fields will be considered as matching,
regardless the value of all other fields.
extend packet_to_packet_scbd {
packet_add_predict(item : packet_s) is only {
// Add to the scoreboard only legal data packet
if (item.legal and item.kind == data_packet) then {
// Perform data transformation - modify addr field
// and recalculate the packet check_sum field
var expected_p : packet_s = item.copy();
expected_p.addr = p_agent.base_addr;
expected_p.calculate_check_sum();
add_to_scbd(expected_p);
};
};
Assume the two data items the DUT handles are packets and frames, containing these fields:
connect_ports() is also {
// Connect the #1 to #2 flow
// Packet TX connected to the packet_add port
// Frame RX connected to the frame_match port
packet_agent.tx_monitor.item_a_ended.connect(scbd.packet_add);
frame_agent.rx_monitor.item_b_ended.connect(scbd.frame_match);
After defining the scoreboard and connecting the ports, we get the two required flows illustrated
in Figure 9.6:
Packet to Frame:
Packets that are sent to the DUT are collected by the packets monitor, agent #1, and
passed to the packet_add port.
The packets are added to the scoreboard; their expected-match-port is set to be
the frame_match port.
Frames that are transmitted by the DUT are collected by the frames monitor, agent #2,
and passed to the frame_match port.
The scoreboard searches for a matching item: the packet passed to the packet_add port
is matched with the frame passed to the frame_match port.
Frame to Packet:
Frames that are sent to the DUT are collected by the frames monitor, agent #2, and
passed to the frame_add port.
The frames are added to the scoreboard; their expected-match-port is set to be
extend packet_frame_scbd {
compute_key(item : any_struct) : uint is only {
var bit_list : list of bit;
After this implementation of the compute_key() method, a frame will be considered as matching a
packet, when their payload and data fields respectably are identical.
match_port_ooo_depth == 1: Indicating that each port for itself works in FIFO ordering.
};
match_port_ooo_depth == 1: Indicating that each port for itself works in FIFO ordering.
delay_match_period == 200: It might happen that the DUT starts sending the frames, before
the originating burst is fully received. This results with the scoreboard getting frames on the
frame_match port, before the burst is seen on the burst_add port. Without setting the match
delay field, this would result with a DUT error of “frame sent when not expected”. The match
delay field, by default, is calculated in sys.any cycles.
if collected_frames.size() == 5 then {
// Got 5 frames - create a burst
var expected_b : burst_s = new;
var data_list := pack(NULL, collected_frames.data);
unpack(NULL, data_list, expected_b.payload);
add_to_scbd(expected_b);
set_match_port(expected_b, burst_match);
collected_frames.clear();
};
};
var f : frame_s;
for i from 0 to 4 {
f.payload = data_list[i];
add_scbd(f.copy());
set_match_port(f, frame_match);
};
};
};
Network to Bus: Check that all received frames are read correctly from the DUT
Figure 9.2: Scoreboard for a Peripheral
Assume the network data item of the DUT interface is frame_s and the bus data item is transfer_s,
containing these fields:
The following code implements the scoreboard shown above for a peripheral DUT:
We have to define which transfers are to be checked by the scoreboard. In the bus-to-net flow only
write-to-TX-FIFO transfers should be added to the scoreboard; and in the net-to-bus flow only read-
from-RX-FIFO transfers should be matched. To add this to the scoreboard:
Define base_addr field in the scoreboard, constrained from an upper level, so that the
scoreboard can recognize access to the FIFOs.
Extend the transfer_add_predict() method; add only specific transfers.
The predict method is called whenever an item is passed to the add port. The default
implementation of this method is adding the item to the scoreboard.
The exact name of the predict method is port-name_predict, and it has one argument,
same type of the port item type. Note that if you override this method, you must add the
item to the scoreboard, by calling add_to_scbd().
Extend the transfer_match reconstruct method; match only specific transfers.
The reconstruct method is called whenever an item is passed to the match port. The
default implementation of this method is calling the match method, and searching for a
matching item in the scoreboard.
The exact name of the reconstruct method is port-name_reconstruct, and it has one
argument, the same type as the port item type. Note that if you override this method, you
must call match_in_scbd().
Assume the four data items of the two buses are defined as followed – transfer and response per
each bus:
Usually, you should implement some code overriding the scoreboard default algorithm. Two typical
examples are:
extend bridge_scbd {
transfer_add_predict(item : bus_b_transfer_s) is only {
add_to_scbd(item);
set_match_port(item, transfer_match);
;
};
};
Data compare of the two different items requires more than comparing just few fields.
Performing a One-to-Many or Many-to-One Transformation
When a data item of one interface is a combination of several items of the other interface
We recommend verifying memory controllers using the registers and memory package and not with
the scoreboard. This package has an effective implementation of memory, supporting all kinds and
combination of accesses.
Refer to Register and Memory Package for e (vr_ad) in the UVM e Reference for more information.
Ordering Models
The default checking of the scoreboard assumes First In first Out (FIFO) ordering; items are
expected to come out of the DUT in the same order they entered it. In many devices this is not the
case, and some reordering is required.
This section describes several ordering models.
Full FIFO
The first item sent to the DUT, is expected to be sent out of the DUT.
For defining such a scoreboard:
Nothing has to be done, this is the default behavior of the UVM scoreboard.
Sometimes each output port of the DUT has to keep FIFO ordering on the items it sends, but only
on items coming from same source. That is, items sent to the DUT via input #1 can be sent out even
when there are pending items that came via input #2.
To define this ordering rule, constrain the scoreboard fields this way:
To maintain order within each match port, nothing has to be done; this is the default behavior
of the UVM scoreboard.
To allow non-ordering between the ports, constrain global_ooo_depth.
The value of this field controls the depth or allowed reordering between the ports. You can set
it to be UNDEF, meaning no ordering is required at all. Or, for example, you can set it to the
value of the DUT FIFO.
To allow non-ordering on the add ports, constrain add_ooo_depth
The value of this field controls the depth or allowed reordering of items in the add ports. You
can set it to be UNDEF, meaning no ordering is required at all. Or, for example, you can set it
to the value of the DUT FIFO.
No ordering requirements
When no ordering is necessary, constrain the global OOO control, global_ooo_depth, to UNDEF.
Constrain global_ooo_depth.
Constrain match_port_ooo_depth.
Constrain add_port_ooo_depth
set_item_match_port_ooo_depth()
set_item_add_port_ooo_depth()
Note: We recommend reading the “Out Of Order Matching for UVM Scoreboards” and OCVM
Scoreboards Streams sections in the UVM Scoreboard Reference chapter of
the UVM e Reference manual for detailed description and usage examples.
extend sample_scbd {
// This domain is of more importance, in this example
// power up/down in this domain effect scoreboard behavior
const !PDmod1_lp_domain_monitor : uvm_lp_domain_monitor;
on PDmod1_lp_domain_monitor.lp_power_down {emit power_down;
unstable_state = TRUE};
on PDmod1_lp_domain_monitor.lp_power_up {emit power_up;
unstable_state = FALSE};
on PDmod1_lp_domain_monitor.lp_power_standby {emit power_down;
unstable_state = TRUE};
// Item added when power is down mark as UNCERTAIN
add0_p_predict(originator: packet) is also {
if unstable_state {
set_scbd_item_status (get_scbd_item(originator), UNCERTAIN);
message(NONE, "Item marked as UNCERTAIN");
};
};
};
e code example:
message_manager.set_screen_messages(sys, UVM_SCBD, NONE);
In addition to the messages implemented in the uvm_scoreboard base unit, you can, of course,
extend the scoreboard methods and add your own messages.
<Xcelium-install-dir>/specman/uvm/uvm_lib/uvm_scbd/utils
/debug/uvm_scbd_indago_probe_opts
See Also
Probing for Data in Indago Debug Analyzer App User Guide
The test executes, and Indago is being launched, reporting a “misplaced item” error:
DUT error - UVM_SCBD_ERR_ITEM_MISPLACED.
This error indicates that a matching item was found, but not in the expected place. This can happen
if previous items in the database were not matched.
Now you see how all_items changed throughout the run. Clicking the arrow next to the value
brings up the source code where the change was made.
10
Introduction
The Low Power Universal Verification Component (LP UVC) is connected to the power signals in
the device under test, and provides:.
Monitoring of power changes during the run
Analysis or power states, and computation of legal transitions
Performing power transition
Most of the functionality of the LP UVC is defined in the UVM LP package in UVM Library
(uvm_lib/uvm_lp). DUT specifics are added by the UVM LP Creator, based on the DUT CPF file(s).
For example, the unit uvm_lp_agent contains an empty list of domains. Adding LP to
the apb_subsystem DUT, requires defining an apb_subsystem subtype of uvm_lp_agent, and defining
this subtype as containing three domains, named PDcore, PDurt and PDsmc.
Adding power awareness to the verification environment is done in two steps:
1. Creating the LP UVC, using the Automatic UVC Creator
set_instance i_apb_subsystem
extend uvm_lp_env {
keep soft name == tb_apb_subsystem;
};
extend apb_subsystem_sve_u {
lp_env : tb_apb_subsystem uvm_lp_env is instance;
};
uvm_lp_monitor
If a components uses the LP UVC frequently, Cadence recommends adding in it a pointer to the
relevant uvm_lp_agent instance.
The following sections contain these code examples:
Example: Adding a reference to the LP UVC from other VE components
Example: Defining events based on power events
Example: Getting power current status
Example: Defining events based on power events
extend my_scbd {
!lp_monitor : my_subsystem uvm_lp_monitor;
extend apb_subsystem_sequence_driver_u {
!lp_agent : my_subsystem uvm_lp_agent;
};
wait @driver.lp_agent.monitor.lp_power_mode_changed;
extend system_monitor {
event ransfer_when_PM0 is
lp_agent.get_cur_mode() == PM0
@transfer_started;
};
};
The following code, taken from the apb_subsystem example in the uvm_lp, connects the LP clock to
the pclk signal.
extend uvm_lp_synchronizer {
As with all sequences, you can extend MAIN uvm_lp_pcm_seq defining its body using the sequence
library, and also do uvm_lp_pcm_seq sequences from other sequences, for example, from a system-
level sequence.
For example:
extend uvm_lp_synchronizer {
event clock is only rise(sig_clock$) @sim;
The following code, taken from the apb_subsystem example in the uvm_lp, connects the LP virtual
seq-er to the AHB seq-er, thus allow driving power transition requests using AHB transfers:
!ahb_driver : ahb_master_seq_driver;
connect_pointers() is also {
driver.lp_seqer = lp_env.agents[0].virtual_driver;
lp_env.agents[0].virtual_driver.
as_a(apb_subsystem_uvm_lp_sequence_driver).
ahb_driver =
ahb_if.masters[0].ACTIVE'driver;
};
};
Define Sequences
To implement LP virtual sequences:
Either define a new sequence kind and implement it
Or
Extend one of the existing types—VIA_BUS CHANGE_MODE or SOFTWARE CHANGE_MODE—adding
the implementation of how to change the power mode from i/f bus or software.
The following code example implements a sequence that initiates a power change, by driving a
transfer via the AHB bus.
11
Acceleratable UVCs
An acceleratable Universal Verification Component (UVC) is a UVC that can run both on
simulation, and also with a hardware accelerator, such as the Cadence® Palladium® series.
Simulation-acceleration is performed with the combination of a software simulator executing on a
workstation, and a dedicated hardware acceleration machine. The complete verification
environment is partitioned to have some parts executed by the simulator and others by the
hardware accelerator. The parts that are executed by the simulator are said to reside in the software
partition. The parts that are executed by the hardware accelerator are said to reside in the hardware
partition. Figure 11.1 shows the basic structure of an accelerated verification environment: the HVL
side, the HDL side, and the interface.There are multiple ways to implement interface between the
HW and SW components. You can use ports, DPI for function calls, or SCE-MI.
Figure 11.1: Accelerated Verification Environment - Data Injection Flow
The low-level components of the verification environment run on the accelerator, thus with
maximum speed. This includes the BFMs and Collectors, and the clock generator. The high-level
components of the verification environment run on the HVL side, for example, on Specman, running
on the workstation. The components implemented on the HVL side include the abstracted
testbench components implementing the verification high-level tasks. The result is a fast, efficient,
and reusable verification environment.
Speed
Palladium is used for acceleration.
Performance of the higher levels of the verification environment is improved (see Optimizing
Performance).
Efficiency
The three fundamentals of verification—generation, checking, and coverage—are
can be synthesized and run on the acceleration machine. If you do not have IXE or UXE
installed, contact your local AE to get the documentation.
BFM Bus Functional Model. The BFM translates back and forth between the
abstract and concrete cycle-accurate representations of the transactions. It
drives and responds to the actual signal-level interface on the DUT bus
interface.
Data Item An abstracted implementation of a transaction. Its name and content depends
on the protocol, typical example: packet, transfer, burst.
HDL Hardware Description Language (In this chapter and related code example,
Verilog)
HVL Hardware Verification Language (In this chapter and related code example,
e)
HVL Side Partition containing abstracted testbench components and proxy models
Proxy Model HVL part of a transactor. The Proxy BFM receives the generated data from
the sequence driver and sends it to the HDL BFM, and vise versa.
Synchronization Event where the HDL side stops and grants control to the HVL side
Transaction A level of abstraction for signal-level protocol interchange that may have
different representations.
Optimizing Performance
High performance, allowing to apply long tests to large systems, is the basic objective of
accelerated verification environments.
This section focuses on performance considerations when creating an acceleratable verification
environment.
Optimizing performance of the regression environment is accomplished by:
Reducing the time spent in the testbench on the host (HVL).
Avoid low-level coverage and checks. The has_checks and flags of the interface UVCs
can be set to FALSE. Only system-level checks are required at this stage, and these are
implemented in the system UVC.
Do not generate structs that do not require constraint solving. Create them using new
and assign the field procedurally. For structs that are generated, remember to add the !
sign for fields that are not to be generated (for example, they are filled in
post_generate(), or during the run.
Employ High Performance Sequences.
Avoid having two TCMs implementing same functionality. For example, if all monitors
are connected to the same interface, they can have one bus monitor rather than several
agent monitors.
Many times, when running accelerated verification, there is no need to instantiate
PASSIVE agents. The role of PASSIVE agents is performing low-level checks and
coverage collection, which are typically disabled in acceleration mode. The data which
is required for system-level checks and coverage is collected by the ACTIVE agents.
Maximizing utilization of the hardware accelerator (HDL side).
The methodology for creating the HDL (synthesizable) BFM and Collector are described
in the chapter “Considerations for Building BFMs” of the TBA Native Function-Based
Interface Methodology Guide.
Minimizing synchronization time overhead (HVL-HDL callbacks).
Remove clock dependencies from the HVL side. See Working with Unclocked Models.
Define in the BFM a default behavior for idle cycles. There is not much added value for
random generation during idle cycles. By transferring this capability to the HDL side, you
greatly improve performance without losing any verification capability.
In acceleration mode, remove events that are not essential to system verification.
Removing events can be done by redefining the event (in e, using is only) to be @never,
where never is defined as an event that is never emitted. The never event is not part of
the language; we recommend adding such an event to your environment, as follows:
event never;
event reset_change is only cycle @never;
event unqualified_clk is only cycle @never;
Acceleration-Ready UVCs
Figure 11.3 shows the acceleration-ready UVC architecture for a basic UVC.
The e BFM and Collector serve as a proxy between the high levels of the verification environment
to the HDL parts. The Proxy BFM, implemented in e, gets the data items from the driver and passes
them to the HDL BFM. For a detailed description of the proxy model and its code, see Low Level of
the UVC.
When the UVC must support a layered protocol, the environment contains more than one agent and
more than one BFM. Higher-layer BFMs activate lower-layer BFMs. Partitioning in layered UVCs
should take into account the performance guideline for minimizing interactions between HDL and
HVL. Figure 11.4 shows the acceleration-ready UVC architecture for a layered UVC.
Basic rules for good separation between low-level and high-level components of the UVC are:
Only designated methods can access the DUT. In accelerated mode, most of those methods
should be overridden so that they are either disabled or get their input from another resource,
such as a Collector, not directly from the DUT.
Avoid direct access to the DUT from within on blocks. Code in on blocks should refer to
methods that can be easily overridden.
No generation, checking, or coverage should be based directly on probing of signals. This
prevents reusability when going to a simulation-acceleration environment. Instead, use the
higher-abstraction-level information gathered by the monitor.
while true {
next_item = driver.get_next_item();
drive_item$(next_item);
emit driver.item_done;
};
The monitor should get items from the collector. For example:
get_transactions() @sys.any is {
cur_transfer = new;
while TRUE {
wait @transfer_started;
get_transaction_from_if$(cur_transfer);
};
};
BFM as Proxies to HW
When working with an accelerator, the main parts of the BFM are implemented on the HDL side.
Only a small part of it, a proxy, remains on the HVL side.
Stimulus generation and transmission is implemented according to these roles:
The sequence driver and the sequences handle data creation.
The Proxy BFM gets the data items from the driver, and passes them to the HDL BFM.
The HDL BFMs gets the data items from the Proxy model, and injects them into the DUT.
How you implement the Proxy BFM is determined by whether the desired style of interaction
between the HVL and HDL is Push or a Pull (see Table 11.2).Table 11.1: Proxy BFM
Implementation
Reactivity Description
Reactive / The BFM reacts to activities on the HW (particularly, DUT activities). In such cases,
Blocking the HVL BFM should wait for information from the HDL before generating next data
item. In this mode, the connection is Blocking: the initiator does not proceed, before
the task is complete.
Batching / The BFM activities are independent of activities on the HW. In such cases, the
Non HVL BFM can prepare a batch of input and send it to the HDL. In this mode, the
Blocking connection is Non Blocking: the initiator passes several items without waiting for
any response.
Both styles of reactivity, Reactive and Batching, can be implemented with either Push or Pull
mode:
Push — Proxy BFM initiates the interactions, pushing items to the HDL.
Pull — HDL BFM initiates the interactions, pulling items from the HVL when it can send the
next item (previous item finished).
In the Batching mode, the Proxy BFM fills the buffer in the HDL BFM without waiting for responses,
as long as the buffer capacity can accept the messages. This naturally improves performance.
Cadence recommends this mode of reactivity whenever it meets your verification requirements. For
example, this is usually the case for an interface on which data is transferred without an
acknowledgement from the receiver (a typical example is Ethernet). The channel reactivity mode
can be modified during the run to satisfy different verification requirements in different phases of the
test. For example, during initialization, data generation might depend on responses of the device to
previous items and therefore Reactive mode is required. But when the main scenario of the test
begins, and the proxy generates bulks of data, the environment can switch to Batching mode.
On the other hand, Push and Pull types are hard coded in the transactor architecture. As such, they
are static throughout the run. As part of your verification planning, you must decide which of these
architecture types you will work in: Push or Pull. The advantage of Pull Reactive mode is in the
freshness of the items. They are generated just before being sent to the device.
There can be three kinds of interfaces between the code running on HW and code running on SW:
Passing Transactions Between HVL & HDL
Signal Access From HVL
Function Calls Between HDL and HVL
Accelerated BFM
The following code is extracted from the XBus sample UVC:
Accelerated Collector
The e monitor gets events and data form the SystemVerilog collector.
The following code is extracted from the XBus sample UVC:
extend xbus_bus_monitor_u {
// The monitored transfer is built up in this field.
package !transfer : MONITOR xbus_trans_s;
event transfer_started is @transfer_started_iep$;
event transfer_ended is @transfer_ended_iep$;
get_transactions() @sys.any is {
cur_transfer = new;
while TRUE {
// When transaction starts - get it
wait @transfer_started;
get_transaction_from_if$(cur_transfer);
//…
};
};
};
sys.call_to_sv_set_scope("hw_top.xi0");
sys.wait_dpi_hw_delay(100);
driver.drop_objection(TEST_DONE);
For more details and more examples of the sequence driver API, refer to the documentation
for any_sequence_driver in the Specman e Language Reference.
import snSvDPI::*;
module bfm();
//…
always @(posedge clk) begin
// do things…
// call Specman GC
snSvDoGcIfNeeded();
end // always @ (posedge clk)
end // module
3. To enhance performance, verify that all checks are defined under a has_checks flag.
4. To enhance performance, verify that all checks are defined under a has_cover flag.
See Also
Acceleration-Ready UVCs
12
Multiple resets— RQ 3
Does the package manage multiple resets
during the test?
Programmable resets— RC 3
Does the package provide a mechanism for
generating programmable reset(s) and can
this feature be disabled?
Error messages— ST 3
Do all error messages provide sufficient detail
for the user to identify the area and instance of
the package/DUT that produced the error?
Protocol checking— RQ 3
Does the package provide sufficient DUT
checking (for example, protocol checkers) to
cover all possible DUT errors?
Error injection— RQ 3
Do the sequence items provide sufficient ability
to inject errors into the generated data
stream(s)?
Logger constraints— RC 3
Are all loggers constrained using only soft
constraints?
ST 3
Error extraction—
For sequence items that collect output from the
DUT, is there a sufficient number of virtual fields
provided to indicate formatting errors detected in
the data structure?
BFM documentation— RQ
Are all BFMs documented (API and behavior)?
Constrainable fields— RQ
Does the documentation describe all user-
constrainable fields and indicate when they are
generated and what default constraints are
applied to them?
Examples— RQ
Does the documentation give sufficient
examples to cover the most likely user
scenarios?
Documentation format— RC
If the documentation is to be distributed
electronically, does it clearly print both on color
and B&W printers and on both European A4 and
US Letter paper sizes?
Package architecture— RQ
Does the documentation describe the
architecture of the package and give an
overview of its intended use?
Proper documentation— RC
Are concepts introduced before being referred
to?
Recommended practice— RQ
Does the documentation clearly differentiate
between what is good and bad practice when
using the package (for example, which structs
should and should not be extended)?
Reset— RQ
Does the documentation explain whether the
package manages multiple resets during the
test?
SD documentation— RQ
Are all sequence-driver test interfaces
sufficiently documented?
Support policies— RQ
Does the documentation clearly define the
support polices for the package and indicate
contact information for obtaining support?
UVC structure— RQ
Are diagrams provided to explain the structure of
the UVC? Typical environments and
configurations; how to use scoreboarding; the
class diagram of the main units and structs.
Usable fields— RQ
Does the documentation describe all non-user-
constrainable fields that users may use to
control their constraints?
Customer feedback— RQ
Number of customer engagements this UVC has
been involved in. Provide customer quotes or
feedback indicating satisfaction level, likes and
dislikes if available.
Support model— RQ
Description of the post sales support model for
this UVC. (for example, on-site support for 7
days, then telephone support).
Test plan— RQ
Test plan for the UVC and/or a description of
how this UVC was tested (for example, 5 tests
were written and feedback gathered from 3 beta
sites).
Code Structure
Coding Style
Test Interface
13
Reference
Primary Requirements
For multiple resets of the DUT in tests:
Secondary Requirements
Allow reset initiated by DUT or by UVC
Easy/flexible hooks for users to do what they want upon reset (for example, drive chosen reset
values into signals during reset)
Reset Methodology
Figure 13.1: Reset Methodology
Reset Examples
extend xyz_env { 1. Define clocks.
event unqualified_clk is
change('(sig_clock)') @sim;
event clk is
true('(sig_reset)'==DEASSERTED)
@unqualified_clk;
};
rerun() is also {
-- If my_sub_structs is a list of structs
under xyz_agent...
for each (s) in my_sub_structs {
s.rerun();
};
};
};
Users can of course achieve this with the absolute path from sys to the instance. For example:
extend sys {
keep my_environment.my_uvc_instance_a.masters[3].speed == 5;
};
However, this is not a particularly user-friendly interface. Giving each instance a logical name or ID
results in a far more powerful interface. The logical name or ID can be used in constraints within the
struct or unit to achieve differentiated behavior between different instances.
There are two methods of providing this functionality: Enumerated Logical Names and Scalar IDs.
Where practical, we recommend the Enumerated Logical Names approach.
With this approach, users can then subtype the struct or unit using the logical name to apply
constraints to a specific instance. For example:
Note: It is possible to have multiple instances of a struct or unit with the same logical instance
name. Constraints applied to the subtype of the struct or unit will then apply to all instances with that
logical instance name. This lets users create a group of instances that have identical behavior.
Scalar IDs
In some cases, it does not make sense for users to have to create a logical name in order to create
an instance— for example, where the UVC automatically creates a large number of instances. In
such cases, it may be appropriate for instances to be identified by an ID field of an appropriate
scalar type. Cadence recommends calling the ID field “id”. For example:
e UVC Developer File
Note: Use of a scalar ID means that the struct or unit cannot be subtyped. However, the ID can still
be used to control constraints in extensions of the base type. For example:
extend my_uvc_agent_u {
keep id == 5 => port_name == “PORT_A”;
};
struct my_uvc_packet_s {
agent_name : my_uvc_agent_name_t;
packet_delay : uint;
...
};
Note: While it is possible to constrain the data struct’s agent_name field with get_enclosing_unit(),
we recommend that this field be constrained by the unit that generates the struct at the point of
generation. This results in code that is less susceptible to problems arising from unforeseen reuse.
xbus_agent.e
xbus_env.e
xbus_trans.e
xbus_sequence.e
xbus_coverage.e
extend xbus_trans_s {
event trans_done;
cover trans_done is {
item name : xbus_agent_name_t = agent_name
};
Test_1.e
debugging purposes).
End-Of-Test Mechanism
The end-of-test mechanism is implemented on top of the general status coordination mechanism.
The rest of this section explains the process.
Basic End-Of-Test Mechanism
End-Of-Test Handling Examples
Notes
The end-of-test objection is a generic solution for status coordination. There is a special kind
of status—progressing through test phases (such as init, reset, and so forth.). For this kind of
status coordination we recommend using Testflow. See Chapter 7, Using Testflow Phases.
In a UVM-ML (multi-language) environment, always manage the end of test with
the TEST_DONE objection mechanism—that is, do not call stop_run()—as described in Ending
(Stopping) the Test in the UVM-ML Open Architecture User Guide.
extend sys {
all_objections_dropped(kind: objection_kind) is {
if kind == TEST_DONE then {
start uvc_util_check_objection_and_stop_run();
};
};
};
When the last objection is dropped, Specman waits until the end of the Specman tick and then
stops the run.
Notes
If no objection is ever raised, then the objection mechanism assumes that there is another
process for stopping the test.
You can always call stop_run() directly and stop the test immediately. This bypasses the
whole objection mechanism.
Note: It is okay to raise and drop this objection several times during a test.
End-Of-Test Handling via Sequences
Typically, you want your proactive sequence drivers to influence end of test. To make that happen,
you would extend the MAIN sequence of the relevant proactive sequences with code like the
following:
Note: If you do not use UVM Testflow, call the raise/drop_objection from
the pre_body and post_body of the MAIN sequence, as in this example:
You might also want to allow for drain time after the MAIN sequence is done. You could add a wait
action to the first example above as follows:
extend bridge_unit {
!stopper_started: bool;
all_objections_dropped(kind: objection_kind) is also {
if kind == TEST_DONE and not stopper_started {
raise_objection(TEST_DONE);
start stopper();
stopper_started = TRUE;
};
};
stopper() @clk is {
wait [NUM_OF_DRAIN_CYLCLES];
drop_objection(TEST_DONE);
};
};
Encrypting Packages
The purpose of encryption is to protect IP by restricting access to implementation details. The
problem with encryption is that it tends to introduce hardships for both developers and end users.
An encrypted package is harder to debug, and users who want to extend the package’s capabilities
might not have some required information (for example, names of fields). Our goal is to protect IP
rights while minimizing the burden of integrating, using, and supporting encrypted packages.
This section contains:
How Encryption Works
How Much to Encrypt
Stability If code is stable, you can encrypt it. (There is less chance that debugging will
be required.)
Completeness If code is self-sufficient, you can encrypt it. (If users do not need to enhance the
code, they do not need to know implementation details.)
Profitability If code is valuable (and debugging and user support are likely to be minimal),
you can encrypt it.
Checks Users never need to enhance checks. Checks are valuable, and a package has little
value without them.
BFMs BFMs are important code, but their implementation is low-level and hence unlikely to
be enhanced by users. Once BFMs are working, they are unlikely to require
debugging.
Architecture Even though the architecture is documented, most users find it helpful to see the
basic structure— the relations among the main units and structs.
Sequences Users might want to know how sequences are defined to use them in their tests.
Constraints When adding constraints, users might need to see what constraints already exist.
Users also might need to debug generation.