0% found this document useful (0 votes)
19 views343 pages

Uvm e Ug

Uploaded by

samanthasmr305
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
19 views343 pages

Uvm e Ug

Uploaded by

samanthasmr305
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 343

UVM e User Guide

Product Version 21.09


September 2021
© 2020 Cadence Design Systems, Inc. All rights reserved.

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.

Restricted Rights: Use, duplication, or disclosure by the Government is subject to restrictions as


set forth in FAR52.227-14 and DFAR252.227-7013 et seq. or its successor.
UVM e User Guide
Table of Contents

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

September 2021 3 Product Version 21.09


UVM e User Guide
Table of Contents

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

September 2021 4 Product Version 21.09


UVM e User Guide
Table of Contents

Typical Layering Applications 72


Requirements for Lower-Layer UVCs 73
Requirements for Higher-Layer UVCs 73
Partitioning of Layers and Connections between Packages 74
Modeling FAQs 75
Terminology 76
Main Entities 77
Sub-Entities 78
Monitor-BFM Guidelines 78
Agent Types 78
Data Item Naming Convention 79
Full Legend for Architecture Diagrams 80
5 81
Sequences: Constructing Test Scenarios 81
Sequences Overview 82
How to Use Sequences in Your Environment 83
Defining Sequences 84
Defining the Sequence Item 84
Defining the Sequence and Its Driver Using the sequence Statement 85
Collecting Coverage on Sequences 85
Hooking the Sequence Driver to the Environment 86
Implementing Sequences 89
Parameterizing Sequences 89
Implementing Transactions in Virtual Sequences 90
Enhancing the User Interface 90
Creating a Sequence Library 93
Writing Tests Using Sequences 93
Writing the Simplest Test: Using UVC’s Sequence API for Test Writers 94
Redefining MAIN Sequence body() 95
Writing a Typical Test: Using the Sequence Library 96
Writing a Unit-Related Test Using Unit ID 99
Writing System Tests - Virtual Sequences 100
Sequence File Organization 100
Advanced Generation-Related Aspects of Sequences 101
Freshness of Items - Generate Right Before Send 101
Specifying Subtype in do Actions 102

September 2021 5 Product Version 21.09


UVM e User Guide
Table of Contents

Propagating Parameters in the Sequence Tree 104


Migrating Unit Attributes to the Sequences 105
Generating the Item/Sequence in Advance 105
Constraining Complex Sequence Items 107
Implementing Complex Scenarios 107
Defining Concurrent Sequences 108
Initializations and Configurations before Starting Sequences 109
Interrupt Sequences 110
DUT-Independent Read/Write Interface 111
Controlling the Scheduling of Items 114
Grabbing Without Affecting Scheduling 114
Locking of Resources 115
Handling Pipelined Protocols 116
Handling Back Pressure and Head-of-the-Line Blocking 121
Layered Protocols 123
Simple Layering (Inside One Sequence Driver) 125
Layered Sequence Drivers 126
High Performance Sequences 143
Reducing The Number of Generations 143
Reducing Number of Context Switching - Queuing the Items 144
Miscellaneous Advanced Features 145
6 146
Using the UVM Registers (vr_ad) 146
Updating the Shadow Model After READ/WRITE a Register 147
Multiple BFMs Environment 148
Using the vr_ad Sequence Driver 149
Using the vr_ad Pre-Defined Sequences Library 150
Extensions of vr_ad Driver and vr_ad Sequences 151
Register Broadcast—Data Propagation 152
Multiple Register Instances 153
“Twin Registers” 154
Accessing Unmapped Addresses 156
read_reg/write_reg from any TCM 157
Performance Improvement on Demand—Static Info Creation 158
Background 158
Option to Reduce Memory 158

September 2021 6 Product Version 21.09


UVM e User Guide
Table of Contents

Backward Compatibility 159


Accessing Registers by Reference 159
Configuring Coverage for vr_ad 160
vr_ad Coverage Per Instance 161
Configuring vr_ad Messages 163
Configuring vr_ad Message Verbosity with Message Tags 164
Configuring Message Verbosity with the vr_ad trace Command 165
Enabling/Disabling Register Access Messages with the vr_ad add/remove Command 166
Configuring vr_ad Messages from Within e Code 166
Debugging Tips for Code that Uses vr_ad 166
Optimizing Probing with the vr_ad_indago_probe_opts File 167
Running the vr_ad Debugging Example 167
Using Power Awareness in vr_ad 169
7 171
Using Testflow Phases 171
Testflow Overview 171
Unit Testflow Phase Activities 172
Testflow Domains 176
Using Testflow 177
Using Multiple Domains 177
Defining Test Scenarios Using Testflow 178
Typical Roles of Each Phase 179
Sequences Using Testflow 180
Typical Activities in Testflow Sequences 181
Sequence Groups 182
Rerun Phase in Sequences 182
Multiple-Domain Environments 183
Multiple Domains in SoC 184
Multiple Domains in Multi-Layered Protocols 186
Domain Dependencies 186
Using Testflow in Reactive Agents 187
Responsive TCMs 188
Responsive Sequences 188
Clocking Issues 189
Clock Scheme — Defining Different Clocks For Different Phases 189
Testflow driver.clock 190

September 2021 7 Product Version 21.09


UVM e User Guide
Table of Contents

Reset Methodology 190


Rerun and Objections 192
Reset Activities: Causing Reset, Responding to Reset 192
rerun_phase() and rerun() 192
Reset Synchronization among UVCs 193
Adding Testflow to an Existing UVC 194
Defining Phase Clocks 194
Synchronization with Reset 194
Using the Testflow Sequences 196
Replacing run() 196
Mixing Testflow & Non-Testflow UVCs 197
Writing Tests Using Testflow 197
Using Reflection API to start and stop expects 198
8 200
Module-to-System Verification 200
System UVCs 201
Verifying Systems 202
System UVC Packages 204
Module and System UVC Architecture 205
Module UVC Architecture 206
System UVC Architecture 207
System UVC Monitor 208
Stand-In Mode 211
Reference Mode 213
Integrating UVCs into a Testbench 214
Connecting UVCs with Flow Hubs and Creating Flow Checkers 215
Connecting UVCs with Pointers 218
Configuration 220
Configuration Unit, and Configuration Parameters Struct 222
Initial Configuration 223
Reconfiguration and Configuration Method 226
Sequences 230
Virtual Sequences for SoC 230
Reusing Sequence Drivers 238
Reusing Sequences 238
Sequences: Module to System 238

September 2021 8 Product Version 21.09


UVM e User Guide
Table of Contents

Reuse of Register Sequences 239


Organizing and Using Sequence Libraries 239
Coverage 240
Module UVC Coverage 240
System-Level Coverage 241
Reusing Coverage 241
Adding Coverage Definitions to System UVCs 242
Checking 243
Scalability Issues in System Verification 243
Stimulus Generation 244
Checking 248
Coverage 249
Messages 251
UVC-Specific Solutions 251
9 253
Using the UVM e Scoreboard 253
UVM Scoreboard Overview 253
Add Flow 254
Match Flow 255
Basic Steps For Defining a Scoreboard 256
Scoreboard For a Data-Mover 256
Defining the Scoreboard 256
The DUT Has Multiple Interfaces of Same Protocol 260
The DUT Can Perform Loopback 261
The DUT Does Not Forward All of the Items 261
The DUT Broadcasts Items 262
The DUT Performs Transformation on the Items 263
Scoreboard of Data-Mover Interfacing Two Different Protocols 264
Scoreboard For a Peripheral 273
Defining the Scoreboard 273
Matching Network Item to Bus Item 276
Scoreboard for a Bridge 277
Defining the Scoreboard 278
The DUT Does Not Forward All of the Items 280
Matching Items of Different Protocols 280
Scoreboard for an Interconnect 281

September 2021 9 Product Version 21.09


UVM e User Guide
Table of Contents

Scoreboard for a Memory Controller 281


Ordering Models 282
Scoreboard Power Aware and Reset 284
Debugging Aids for the UVM Scoreboard 286
Viewing Debug Messages 286
Querying the Scoreboard Database 287
Browsing the Scoreboard Database 287
Optimizing Indago Probing with the uvm_scbd_indago_probe_opts File 287
Running the Scoreboard Debugging Example 288
10 290
Using the Low Power Universal Verification Component (LP UVC) 290
Introduction 290
Integrating the LP UVC in the Verification Environment 291
Modify the Ports Configuration 292
Using LP UVC Monitoring 292
Example: Adding a reference to the LP UVC from other VE components 293
Example: Scoreboard flushing when power goes down 293
Example: Getting power current status 294
Example: Defining events based on power events 295
Using the LP UVC Sequencers 295
Using the PCM Sequence Driver 296
Using the LP Virtual Sequence Driver 297
11 300
Acceleratable UVCs 300
Terminology Used in this Chapter 302
When to Apply Acceleration 303
Optimizing Performance 304
Acceleration-Ready UVCs 305
Accelerated UVC Architecture 306
High Level of the UVC 309
Low Level of the UVC 310
High Performance Sequences 314
Working with Unclocked Models 315
Acceleration Customized Data Item 316
Modifying a UVC to be Acceleration-Ready 316
12 318

September 2021 10 Product Version 21.09


UVM e User Guide
Table of Contents

e UVC Standardization Using Compliance Checks 318


Packaging and Name Space Compliance Checks 319
Architecture Compliance Checks 319
Reset Compliance Checks 320
Checking Compliance Checks 321
Coverage Compliance Checks 321
Sequences Compliance Checks 322
Messaging Compliance Checks 322
Monitor Compliance Checks 322
Documentation Compliance Checks 323
General Deliverables Compliance Checks 325
End of Test Compliance Checks 325
Other Possible Compliance Checks 326
13 327
Reference 327
Supporting Multiple Resets 327
Reset Assumptions and Requirements 327
Reset Methodology 328
Reset Examples 330
Instance Names and IDs 331
About Instance Names and IDs 331
Enumerated Logical Names 332
Scalar IDs 333
Use of Instance Names and IDs 333
Instance Names: XBus Example 334
Status Coordination and End of Test 337
Introduction to Status Coordination 338
End-Of-Test Mechanism 339
Encrypting Packages 341
How Encryption Works 342
How Much to Encrypt 342
Adding Extra Debugging Aids to Encrypted Packages 343
Protecting Non-Encrypted Code 343
Publishing the Interface of Encrypted Files 343

September 2021 11 Product Version 21.09


UVM e User Guide
Introduction to UVM e

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

About This Book


This book describes the Universal Verification Methodology (UVM) using the e Language
and e UVCs (UVCs implemented in e)
The UVM e methodology is all about maximizing reusability of verification code written in e. UVM
ensures code reusability by discussing the best-known methods for designing, coding, and
packaging e code as reusable components.
Most of the guidelines herein apply to any scale or type of verification environment. Since e-based
UVM Verification Components (e UVCs) are an ultimate form of reusable verification environment,
the majority of this book relates to creating UVCs.
A key factor for reusing code is arranging it as an independent and easy-to-use code package.
When developing verification code in e, the reusable package is typically organized as an e UVC.
For this reason, UVM speaks a lot about e UVCs: standardizing e UVC development practices,
defining a common e UVC user model, and ensuring that UVM-compliant e UVCs are plug-and-

September 2021 12 Product Version 21.09


UVM e User Guide
Introduction to UVM e

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 1, “Introduction to UVM e” This chapter

Chapter 2, “Verification Packages” How to organize verification components/utilities and


how to ship them

Chapter 3, “UVC File Organization” How to organize UVC files and directories

Chapter 4, “Typical UVC Architecture” How typical UVCs should be constructed

Chapter 5, “Sequences: Constructing How to use Specman sequences in a uniform and


Test Scenarios” efficient way

Chapter 6, “Using the UVM Registers Describes common advanced use models of the UVM
(vr_ad)” e register package, vr_ad

Chapter 7, “Using Testflow Phases” Using the Testflow e UVM utility

Chapter 8, “Module-to-System How to construct a system verification environment


Verification” that reuses module-level components

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 create standard messages


Using Compliance Checks”

Chapter 12, “e UVC Standardization How to maintain a high standard and uniformity for
Using Compliance Checks” UVCs

Chapter 13, “Reference” Detailed discussion of specific topics

About the Examples Library


The UVM e examples library contains a few example packages, constructed according to UVM
guidelines. They are designed to teach UVM methodology and concepts and to provide a starting
point for UVC development. The examples in the library are:

September 2021 13 Product Version 21.09


UVM e User Guide
Introduction to UVM e

xbus_e Golden e UVC representing a bus-based environment.

xserial Golden e UVC representing a serial data stream environment.


xcore Golden e UVC representing a module-level environment

ex_oto_layering Small example demonstrating one-to-one sequence layering


ex_otm_layering Small example demonstrating one-to-many sequence layering
ex_mto_layering Small example demonstrating many-to-one sequence layering

ex_mtm_layering Small example demonstrating many-to-many sequence layering

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

What Are e UVCs?


An e UVC is an e-based UVM Verification Component — a verification component implemented in
e. It is a ready-to-use, configurable verification environment, typically focusing on a specific protocol
or architecture (such as PCI Express, Ethernet, AHB, or USB).
Each e UVC consists of a complete set of elements for stimulating, checking, and collecting
coverage information for a specific protocol or architecture. You can apply the UVC to your device
under test (DUT) to verify your implementation of the UVC protocol or architecture. UVCs expedite
creation of a more efficient testbench for your DUT. The e UVCs work with all UVM languages and
with Xcelium™ Simulator and all third-party simulators supported by Cadence® Specman® Elite.
e UVCs can be integrated with UVCs implemented in other languages.
You can use an e UVC as a full verification environment or add it to a larger environment. The UVC
interface is viewable and thus can be the basis for user extensions. We recommend doing such
extensions in a separate file. Maintaining the UVC in its original form facilitates possible upgrades.

September 2021 14 Product Version 21.09


UVM e User Guide
Introduction to UVM e

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.

September 2021 15 Product Version 21.09


UVM e User Guide
Introduction to UVM e

UVCs versus Regular Verification Environments


(VEs)
The distinction between a UVC and a modular, well written verification environment (VE) is fuzzy.
The main difference is that a UVC is meant to be used in more than one setting.
Regardless of whether the combined VE is itself a UVC or not, when we integrate n UVCs into a
VE, we have n+1 pieces: the n UVCs and the explicitly added code for the VE. The plug-and-play
considerations described below relate to all n+1 items.
Note: The recommendations in this manual represent the best practices for not just UVCs but also
regular VEs. For example, unified tracing (as is recommended for plug-and-play UVCs) is very
useful when applied to blocks within a big VE, even if neither the blocks nor the VE are ever meant
to be reused.

UVCs as Plug-and-Play Components


Ideally, UVCs must be plug-and-play components in the sense that a new verification environment
can be constructed from UVCs that were not initially planned to work together. Not only that, it
should be possible to do this hierarchically. In other words, it should be relatively easy to construct
the combined VE also as a UVC, which in turn could be plugged into yet a bigger VE.
To enable this capability, UVCs (and indeed general VEs, which could potentially be turned into
UVCs) should be written as if they are part of a universal verification environment.
Following are the benefits of making UVCs plug-and-play:
Promotes code sharing:
Within companies
Between companies
Offers customers a convenient, ready-to-use product
Makes UVC creation easier and faster

UVC Reuse Requirements


The following requirements are essential for UVC reuse. These requirements are all from the
perspective of the UVC user. They translate into recommendations for the UVC writer.

September 2021 16 Product Version 21.09


UVM e User Guide
Introduction to UVM e

Note: All of the following recommendations are described in much greater detail throughout the rest
of this manual.

No interference between UVCs


No name space collision - use packages
No complex SPECMAN_PATH or directory
dependencies
Handling dependencies on common modules
No dependencies on different versions of
simulators and utilities
No timing dependencies
No dependencies on global settings

Common look and feel, similar activation,


Common way to install UVCs
similar documentation
Common way to patch UVCs
Common tracing and debugging
Handling DUT errors
Getting UVC identification
Waveform viewer data
Custom visualization
Common way of specifying simulator-specific
material
Common way to do backdoor initialization
Common programming interface to standard
blocks
Common UVC taxonomy
Common style of documentation

September 2021 17 Product Version 21.09


UVM e User Guide
Introduction to UVM e

Support for combining UVCs (control,


Common way to configure UVCs
checking, layering, and so on)
Common way to write tests
Common way to create sequences
Common way to do checking
Combined determination of end of test
Common way to do layering of protocols
Common way to do combined coverage

Support for modular debugging


Understanding combined constraints
Reconstructing the behavior of a single UVC
in the verification environment

Commonality in implementation
Common data structures
Common UVC testing methodology
Common way to use ports and packages

September 2021 18 Product Version 21.09


UVM e User Guide
Verification 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

What Is a Verification Component Package


A package is a directory structure containing all relevant material for verifying a particular kind of
component. By “packaging” all relevant material into one directory, this enables reuse of the
verification environment. By convention, packages have naming standards by which you can easily
see the name and version of the verification environment. Packages typically have other standards,
such as a common installation procedure. This section describes these standards, which conform to
UVM guidelines.
Each reusable verification component should be packaged in its own directory, which:
Is separately released and versioned.
Contains code and documentation files.
Has a standard structure and a standard-format PACKAGE_README.txt file.

These package directories are the basic unit for sending verification components from place to
place.

September 2021 19 Product Version 21.09


UVM e User Guide
Verification Packages

A package could contain a UVC, a shareware utility, and so on.

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).

Package-Related Naming Conventions


This section discusses naming conventions for files and types as they relate to packages. Other
naming conventions will be discussed in other documents.
Each package has a package name. Because we want to mix and match packages from various of
sources, we should strive to make package names unique. (Two packages cannot be loaded
together if they have the same name.)

September 2021 20 Product Version 21.09


UVM e User Guide
Verification Packages

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.

Choosing a Package Name


Package names should be clear, short, and unique.
Cadence suggests that the package name consist of two parts:
company-prefix_proper-name

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

Prefix Reserved For

uvm UVM utilities supplied by Cadence or Accellera

sn Internal Specman entities.

rf Specman reflective facility.

ex Small, example packages.

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”.

September 2021 21 Product Version 21.09


UVM e User Guide
Verification Packages

Package Name Examples


Some examples of package names are:
vr_pci (Cadence PCI UVC)

arm_act (ARM ACT package)

shr_atm (ATM shareware)

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

For example, your $SPECMAN_PATH could contain:


...:/usr/joe/private_lib:/project/specman/proj_lib:/cad/uvc_lib

In this example, $SPECMAN_PATH contains three package libraries: a private library, a project-

September 2021 22 Product Version 21.09


UVM e User Guide
Verification Packages

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
...

September 2021 23 Product Version 21.09


UVM e User Guide
Verification Packages

/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

this loads /usr/joe/private_lib/shr_atm/e/shr_atm_top.e even if there is a shr_atm directory


in proj_lib or uvc_lib. This is because private_lib comes first in $SPECMAN_PATH.
If multiple libraries in your $SPECMAN_PATH contain the same package name, then only the first one
in $SPECMAN_PATH is used. Use the show package -on_disk -full command to display redundant
packages.
This section contains:

September 2021 24 Product Version 21.09


UVM e User Guide
Verification Packages

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

September 2021 25 Product Version 21.09


UVM e User Guide
Verification Packages

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.

September 2021 26 Product Version 21.09


UVM e User Guide
Verification Packages

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.

Put the release notes in this directory.

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 {
...
};
'>

See Using Package-Relative File Names for more information.


Notes
Files and structs in the examples directory do not have to follow the naming conventions.
They are not really part of the package e code.

September 2021 27 Product Version 21.09


UVM e User Guide
Verification Packages

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.

Verification for SoC


If an SoC package uses other UVCs, the SoC verification environment can use tests from the used
packages. The SoC uvc_ve directory should have only the tests relating to the SoC. Those tests will
typically use sequences from the sequence libraries of the used UVCs.

September 2021 28 Product Version 21.09


UVM e User Guide
Verification Packages

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 File Names


In general, we recommend accessing package files and directories from within Specman using
package-relative file names, that is, file names that start with the package/ directory name. For
example:
import xbus/e/xbus_top;
@ex_mto_layering/load.ecom

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.

September 2021 29 Product Version 21.09


UVM e User Guide
Verification Packages

Importing Files Within the Same e Source Directory


Importing files in another package should always be done using the package-relative notation.
Importing files within the same directory tree (for example, the e directory) can be done either using
package-relative names, or (more conventionally) assuming the directory of the importing file. Thus,
the file bar/e/bar_top.e could import files as follows:
import xx; // xx.e is in same directory as importing file
import yy; // yy.e is in same directory as importing file
import checks/cell_check; // cell_check.e is in checks/

Never use the “../xx” notation for relative pathnames.

sn_which.sh Shell Script


Package-relative file names work fine for Specman but may be a problem in the shell, because the
shell does not know about $SPECMAN_PATH. In this cases, use a special script called sn_which.sh.
“sn_which.sh file” looks for file in (and only in) your $SPECMAN_PATH and writes the full path name to
stdout. If file is not found, an error message is written to stderr. For example, “sn_which.sh
foo/bar.x” would print “/usr/me/shareware/foo/bar.x”, assuming “/usr/me/shareware” is in
your $SPECMAN_PATH and this is the first such match.
Following are some examples of using sn_which.sh:
`sn_which.sh foo/misc/doit.sh` my_param

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.

Handling Package Versions


The following sections describe how to version packages and handle package version
dependencies:
Where the Version Number Appears
Shipping New Versions of a Package
Declaring Dependencies on Specman Version and Other Packages
Using the Package Compatibility Analyzer

September 2021 30 Product Version 21.09


UVM e User Guide
Verification Packages

Where the Version Number Appears


Each time you release a new version of a package, it should be accompanied by a new version
number. Typically, the version number is a two-part number, including a major and minor release
number.
The version number is seen in three places:
PACKAGE_README.txt file, as an ASCII name
package_top.e file, as a define

Tar file/directory
In all three places, the version number should match.

In PACKAGE_README.txt File (ASCII Name)


Examples:
Version: 3.1

Version: 0.2 (Experimental)

Note: As this is an ASCII name, you can add extra text to describe the release, for example,
“(Experimental)” illustrated above.

In package_top.e File (Define)


Examples:
define XBUS_VERSION_3_1;

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:

September 2021 31 Product Version 21.09


UVM e User Guide
Verification Packages

xbus_top.e - Top file for the xbus package


<'
define XBUS_VERSION_3_1;
define XBUS_VERSION_2_OR_LATER;
define XBUS_VERSION_3_OR_LATER;

import xbus_types;
...
'>

Name of Tar File/Directory


In the library/versions directory:
library/versions/
xbus_version_3_1/
xbus_version_3_1.tar.gz

Note: The name of the directory is the same as the name of the define except that it is all in
lowercase.

Shipping New Versions of a Package


After creating a new version of the package and testing it, you need to ship it.
To ship a new version of your package:
1. Make sure that the information in your PACKAGE_README.txt file and any other documentation
such as release Notes or user guide is up to date. In particular, pay attention to the data
following the “Version” and “Modified” headers.
2. Pack the new version of your package, using the approach in either Simple Method to Pack
the Version or Robust Method to Pack the Version.
3. Distribute the new version of your package using whatever means you prefer, such as by
uploading it to your FTP site or emailing it to users.

Simple Method to Pack the Version


This approach uses the same directory names for all versions.
To pack a new version of the package:

September 2021 32 Product Version 21.09


UVM e User Guide
Verification Packages

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.

Robust Method to Pack the Version


This approach uses different directories for different versions.
Versions Directory
By convention, you should have a directory called versions under the 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.

Packing a New Version of the Package


To pack a new version of the package:
1. Copy the directory containing one or more packages into the corresponding versioned
directory (called, for example, package_version_num_num). For example:
% cp -r vr_xbus ../versions/vr_xbus_version_3_1

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

To unpack the new version of the package:


1. Save the gzipped, tarred file to the “versions” directory of the library to which you want to add
it.

September 2021 33 Product Version 21.09


UVM e User Guide
Verification Packages

2. Unzip and untar the file. For example:


% cd ~/my_lib/versions
% gzip -d xbus_version_3_1.tar.gz
% tar xvf xbus_version_3_1.tar
This creates a directory with the name of the tar file in the directory containing the tar file.
3. Create a directory named as the version number (for example, version_3_1), and copy the
versioned directory to the library under the package name. For example.
% cd ~/my_lib
% mkdir version_3_1
% cp -r versions/version_3_1/ xbus
Alternatively, you can create a symbolic link from your library to your versions directory rather
copying the directory.
Assuming that library is in your $SPECMAN_PATH, you can now run the demo from
anywhere. For example:
% cd ~/work
% ~/my_lib/xbus/demo.sh

Declaring Dependencies on Specman Version and Other


Packages
UVC providers declare the dependency rules for a package in the PACKAGE_README.txt file. The
dependency rules specify the necessary version(s) for Specman and any other required packages.

To declare dependencies on Specman version and other packages:


In the PACKAGE_README.txt file, declare each dependency on a separate line under the
heading “* Requires”. For example:

* 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.

September 2021 34 Product Version 21.09


UVM e User Guide
Verification Packages

Version Numbers and Range


Versions numbers are specified using two or three decimal numbers x.y or x.y.z. Non-numeric
characters should not be used in the specification of a version or version range, because version
numbers are truncated at the first non-numeric character. So, for example, 4.12b3.3 is treated as
4.12.
To specify a closed range of versions:
Insert two dots between the low and the high version. For example:
3.1 .. 4.7.1 //Indicates all versions from 3.1 up to 4.7.1
//(inclusive)

To specify an open-ended range of versions:


Place two dots as appropriate before or after the low or high end of the range. For example:
3.0 .. //All versions from 3.0 onwards
.. 4.7 //All versions up to and including 4.7

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.

Using the Package Compatibility Analyzer


By default, whenever a package is loaded, the Package Compatibility Analyzer checks the
package’s dependencies, declared in the PACKAGE_README.txt file. In case of inconsistencies,
the Package Compatibility Analyzer produces error or warning messages.
The Package Compatibility Analyzer runs automatically when the files are loaded onto Specman.
Table 2.2 describes the notifications generated by the Package Compatibility Analyzer.Table 2.1:
Package Compatibility Analyzer Notifications

Condition Notification Default Severity

Package x is not available in PACKAGE_NOT_FOUND: Package x Warning


the SPECMAN_PATH cannot be found

September 2021 35 Product Version 21.09


UVM e User Guide
Verification Packages

The version number of package PACKAGE_VER_INCOMP: Package x Warning


x is not within the required version y is incompatible with package z
range of package z

Package z specifies an open PACKAGE_VER_POSSIBLE_INCOMP: Ignore


version range for package x, Package x version y might be
and package x is in that open incompatible with package z
range but not on the edge

Package x requires another SPECMAN_VER_INCOMP: Package x Warning


Specman version is incompatible with Specman version y

Package x specifies an open SPECMAN_VER_POSSIBLE_INCOMP: Ignore


version range for Specman, and Package x might be incompatible with
Specman is in that open range Specman version y
but not on the edge

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

*** Warning: WARN_PACKAGE_VER_INCOMP: Package ex_atm version 1.0 is


incompatible with package ex_soc
Requirement: ex_atm 0.4
See: file:/uvc_lib/ex_soc/PACKAGE_README.txt

*** Warning: WARN_PACKAGE_VER_POSSIBLE_INCOMP: Package ex_c_bus version 1.0


might be incompatible with package ex_soc
Requirement: ex_c_bus .. 1.5
See: file:/uvc_lib/ex_soc/PACKAGE_README.txt

Customizing Severity of Notifications


The Package Compatibility Analyzer can be customized to change the severity of the checks.

September 2021 36 Product Version 21.09


UVM e User Guide
Verification Packages

To change the severity of the checks:


Use the set notify -severity command.
For example, you can change all of the Specman compatibility messages to ERROR as follows:
set notify -severity = ERROR WARN_SPECMAN_VER*

Generating a List of Incompatibilities for Loaded Packages


The Package Compatibility Analyzer can print out a list of all incompatibilities for loaded packages.
To generate a list incompatibilities for loaded packages:
Use the show incomp[atibility] command.
show incomp

This results in output like the following:


Incompatibilities for loaded packages:

1. Package shr_ram might be incompatible with Specman version

6.01
Requirement: Specman 6.20..
See: file:/uvc_lib/shr_ram/PACKAGE_README.txt

2. Package ex_soc is incompatible with Specman version 6.01


Requirement: Specman 4.3
See: file:/uvc_lib/ex_soc/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

September 2021 37 Product Version 21.09


UVM e User Guide
Verification Packages

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:

.... Lines of texts extending until the next header ...

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.)

September 2021 38 Product Version 21.09


UVM e User Guide
Verification Packages

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

September 2021 39 Product Version 21.09


UVM e User Guide
Verification Packages

---------- Start of PACKAGE_README.txt file --------------


* Title: Cadence AHB UVC
-- Should be on one line.
-- Another example: An IEEE 335 foo-transfer protocol UVC
* Name: vr_ahb_uvc
-- Must be the same as the package name
* Version: 3.3.01
-- This is also accepted: 0.1.01 (Experimental Version)
-- This is also accepted: 3.1
* Requires:
specman {5.0.2,5.0.3,5.1,6.0 ..}
vr_uvc 2.0.1 ..
* Modified: 14-Jul-2001
-- Please use dates in exactly that format
* Category: UVC
-- Can be UVC, shareware, example, or utility for now
* Support: [email protected]
-- Where to send requests for support, questions, etc..
* Documentation: docs/user_man.pdf
-- File name containing the documentation
-- Note: File names should be specified relative to the
-- package dir
* Release Notes: docs/rel_Notes.txt
* Description:
The Cadence AHB UVC is ...
....
....
* Directory structure:
This package contains the following directories:

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 ----

PACKAGE_README.txt for simple shareware


In this example there is no separate documentation directory. In fact, the whole shareware
consists of a directory that contains this PACKAGE_README.txt file and a subdirectory called e/
with a single .e file.

September 2021 40 Product Version 21.09


UVM e User Guide
Verification Packages

------------ Start of PACKAGE_README.txt file ---

* Title: A utility to print your unit hierarchy


* Name: vr_hier_unit
* Version: 0.1 (very experimental)
* Modified: 13-Nov-2001
* Category: Shareware
* Comments to: [email protected] (John Doe)
-- Note that in this case John used "Comments to", since he is
-- unwilling to claim there is real "support" for this package
* Description:
To see what it does:
1. Load your design.

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 ----

Version Number Scheme


The Requires field describes the requirements for the various registered components. Registered
components can be Specman, commercial UVCs, or other packages. The requirements for each
component are presented as a list of allowed versions of that component, where each possibility is
either a specific version number or a range of version numbers. (The range can be closed or open.)
The syntax for each requirement is as follows:

required_versions version_possibility, ...

version_possibility specific_version | version_range

version_range version_range [specific_version]..[specific_version]

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),

September 2021 41 Product Version 21.09


UVM e User Guide
Verification Packages

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

Shipping and Receiving Whole Libraries


Sometimes, it is more practical to ship whole libraries of packages rather than each package
separately. For example, this might be the case when multiple related packages were all updated,
and working with one of the new packages requires the latest version of the other packages too.
UVM allows for shipping whole libraries. Nevertheless, we do have a few recommendations:
When shipping a library:
The library should be tarred and gzipped.
The name of the tar file should indicate the version or date of release.
The LIBRARY_README.txt file should identify the details of the library release.
When receiving a library:
When receiving whole libraries from a library developer/vendor, the library would
typically replace the previous library.
In maintaining libraries received from external sources, it is best not to add foreign

September 2021 42 Product Version 21.09


UVM e User Guide
Verification Packages

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”.

Instantiating Non-Env Utilities


We suggest that non-env shareware utilities be instantiated under the sn_util struct under global.
For example, suppose you define a new shareware package that draws graphs (call it shr_graph),
with a main struct called shr_graph. You can instantiate it under sn_util with the following code
in shr_graph_top.e:

extend sn_util {
shr_graph: shr_graph;
init() is also {shr_graph = new};
};

You can then access fields in the shr_graph struct as:


util.shr_graph.foo();

Adding Suffixes to Types


This is not directly related to packages, but we recommend having all unit names end with _u, all
structs with _s, all scalar types with _t, and so on.

Connecting uvm_env to Package


If a specific env (child of uvm_env) is not defined in a package, this results in a runtime error after
generation.

September 2021 43 Product Version 21.09


UVM e User Guide
UVC File Organization

UVC File Organization

This chapter contains the following sections:


UVC Directory Structure
Partitioning UVC Source into Files
File Naming Guidelines
Instantiating Entities
Importing Files in e UVCs
File Clusters
Files: Cyclic Dependencies

UVC Directory Structure


The distribution for a UVC should be organized logically with UVC source files separated from
documentation, demonstration code, and so on.
As a minimum, Cadence recommends placing the following three subdirectories at the top level of
the UVC directory.

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).

September 2021 44 Product Version 21.09


UVM e User Guide
UVC File Organization

This section contains:


uvc/e/
uvc/examples/
uvc/docs/

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;

In exceptional circumstances, it might be appropriate for an e UVC to be loaded in a number of


different subsections or different configurations. In such a case, multiple top-level files can be
provided. For example:
uvc/e/uvc_top.e
uvc/e/uvc_configuration_a_top.e
uvc/e/uvc_configuration_b_top.e

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

September 2021 45 Product Version 21.09


UVM e User Guide
UVC File Organization

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.

September 2021 46 Product Version 21.09


UVM e User Guide
UVC File Organization

Partitioning UVC Source into Files


When possible, e UVC source files should be kept short and split logically. Files should be split by:
Topic/entity (for example, env, master, arbiter, burst)
By the various appropriate aspects. For example:
Feature aspects: checker, coverage, sequences, APIs, and so on.
Protocol aspects: USB power, bus resets, and so on.
According to whether their content is part of the public interface of the e UVC (such files
should have the suffix “_h.e”) or part of the private body of the e UVC. As a general rule, this
public/private split should follow any split between encrypted and unencrypted code.

File Naming Guidelines


Table 3.1: Standard Naming (for plug-and-play and readability)

Guideline Details

Prefix for all e UVC source files uvc_

The e UVC top file always located at uvc/e/uvc_top.e

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

Uniform order/prefix/suffix of names uvc_master.e, uvc_checker.e


(agent first, feature last) uvc_master_checker.e, uvc_slave_checker.e
uvc_master_monitor.e, uvc_slave_monitor.e

Table 3.2: Files Owned by e UVC Developer (users should not modify)

Fine Name Description

uvc_types.e Global constants and types belonging to the e UVC

uvc_data_item.e Definition of the data item struct uvc_data_item

September 2021 47 Product Version 21.09


UVM e User Guide
UVC File Organization

uvc_agent_h.e Header file: definition of an agent unit (optional)

uvc_agent.e Implementation of an agent unit


uvc_env.e Definition of the top-level unit uvc_env

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_agent_checker.e Specific checks for each agent

uvc_cover.e Coverage definitions

uvc_agent_cover.e Coverage definitions


uvc_wave.e Definitions of wave commands for the e UVC

uvc_top.e
Imports all files
Instantiates e UVC entities
Passed to sn_compile

Table 3.3: Files Owned by Users (supplied in uvc/examples directory)

File Name Description

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.

September 2021 48 Product Version 21.09


UVM e User Guide
UVC File Organization

uvc_defines_template.e Environment constants that use define (if needed)


(uvc_defines_*.e)

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.

September 2021 49 Product Version 21.09


UVM e User Guide
UVC File Organization

Importing Files in e UVCs


This section describes the recommended methodology for organizing files and their import order.
The main concept in this methodology is to differentiate between a few groups of files:
Base: Other packages that are used by the e UVC. These packages might be provided by
other groups, or even other companies. They are stable, proven, and sometimes even
encrypted. They should be the first files to load, and can be compiled.
UVC: The e UVC main files - the definitions of its components. Can contain configuration of
sub-components (for example, sub-UVC or utilities), as long as this configuration is reusable
for all environments using this UVC.
SVE (Simulation & Verification Environment) / Testbench: Files that are specific to one
environment, one configuration of the DUT. SVE files should not be in the uvc/e directory, as
they are not reusable across projects. There is no standardization of the names of the SVEs.
Cadence recommends having meaningful and descriptive names.
We recommend creating four files that handle the import, according to these groups (assuming UVC
name is uvc and the SVE is created for mode_A):
uvc/e/uvc_compile_base.e. This file should import files that are to be compiled (and not
loaded). This includes:
Utility packages, such as the Registers & Memory package, or another UVC
Defines required by these packages at time for compilation, for example -
VR_AD_ADDRESS_WIDTH

Extensions to types defined in these packages, to be used later in the UVC.


uvc/e/uvc_top.e: This file imports uvc_compile_base.e, and all the other files in the e UVC.

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

September 2021 50 Product Version 21.09


UVM e User Guide
UVC File Organization

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.

Usage of Package and File Path in Import


All imports in UVC code are performed using either local or package-relative filenames:
Local filenames refer to files in the same directory as the importing file or in a subdirectory
below it. For example:
import uvc_another_file.e; // File in same directory
import ./sub_dir/uvc_another_file.e; // File in subdirectory

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

September 2021 51 Product Version 21.09


UVM e User Guide
UVC File Organization

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.

Files: Cyclic Dependencies


In some cases, you might want to split code into two or more files, but this results in a cyclic
dependency between the files. For example, the file uvc_env.e declares a unit uvc_env_u and
instantiates the unit uvc_agent_u under it. The file uvc_agent.e declares the unit uvc_agent_u, which
contains a backpointer to the unit uvc_env.e. In this case, each file requires that the other already be
loaded.
This situation can be solved using the cyclic import feature in Specman. If uvc_env.e imports
uvc_agent.e, and uvc_agent.e imports uvc_env.e, then Specman detects the cyclic dependency
between them and loads both files together as if they were one file. These files are considered part
of the same file cluster. We recommend that the appropriate _top.e file imports both files.
Notes

September 2021 52 Product Version 21.09


UVM e User Guide
UVC File Organization

Mutually extending structs/units in cyclic files might introduce problems.


We recommend keeping the use of cyclic imports to a minimum. When there are many cyclic
dependencies, it leads to large clusters that can be difficult to manage.
Example
user_config.e
<'
import top;
extend sys {
env: env_u is instance;
};
'>
env.e
<'
import agent;
unit env_u like uvm_env {
agent: agent_u is instance;
keep agent.env_bp == me;
};
'>
agent.e
<'
import env;
unit agent_u like uvm_agent {
env_bp: env_u;
};
'>
top.e
<'
import env;
import agent;
'>

An Alternative Approach to Cyclic Dependencies


In some cases, the use of cyclic imports can make the code difficult to maintain. In such cases, we
suggest using a single file to declare all affected units, but without adding any content. The
individual files can then extend each unit to add the content and can refer to the previously declared
unit types without cyclic dependencies.
A similar approach can be used to solve cyclic method dependencies. Using this approach, you
declare the methods as empty in an initial file, then extend the methods to add the method bodies in
subsequent files.
Note: This approach provides a weaker encapsulation of objects.

September 2021 53 Product Version 21.09


UVM e User Guide
UVC File Organization

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;
'>

September 2021 54 Product Version 21.09


UVM e User Guide
Typical UVC Architecture

Typical UVC Architecture

This chapter explains how typical UVCs should be constructed.


Because each UVC relates to different protocols, architectures, and designs, one UVC can vary
quite significantly from another. Even so, there is commonality in the design of various UVCs. In this
chapter, we discuss the commonality and some of the main differences between typical UVCs. The
examples we look at relate to communication protocols, like a serial interface or an on-chip bus.
Later, we will see that a UVC for an SoC is constructed according to similar guidelines.
The end of this chapter lists extensive terminology definitions and a complete legend for the
architecture diagrams presented in this book.
This chapter contains the following sections:
Basic UVC Architecture
A Look at Agents
A More Complex UVC Example
Combining UVCs
Layering UVCs
Modeling FAQs
Terminology

Basic UVC Architecture


This section contains:
DUT and UVC
Diagram Language
BFMs and Monitors
DUT Signals
Clocks and Events

September 2021 55 Product Version 21.09


UVM e User Guide
Typical UVC Architecture

DUT and UVC


Figure 4.1 below shows a sample DUT that we use to demonstrate UVCs.
Figure 4.1: ​Example DUT

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.)

Figure 4.2: ​XSerial UVC—Dual-Agent Implementation

September 2021 56 Product Version 21.09


UVM e User Guide
Typical UVC Architecture

Figure 4.2: ​XSerial UVC—Dual-Agent Implementation

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

September 2021 57 Product Version 21.09


UVM e User Guide
Typical UVC Architecture

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:

Config like uvm_agent_config. Allow configuration of the agent’s attributes and


behavior.

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.

Sequence like any_sequence_driver. A unit instance that serves as a coordinator for


Driver running user-defined test scenarios (implemented as sequences). The
sequence drivers are explained in detail in Chapter 5, Sequences: Constructing
Test Scenarios.

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).

Figure 4.3: ​XSerial UVC—Single-Agent Implementation

September 2021 58 Product Version 21.09


UVM e User Guide
Typical UVC Architecture

Figure 4.3: ​XSerial UVC—Single-Agent Implementation

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.

September 2021 59 Product Version 21.09


UVM e User Guide
Typical UVC Architecture

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.

September 2021 60 Product Version 21.09


UVM e User Guide
Typical UVC Architecture

BFMs and Monitors


Monitors must be completely passive. The BFM does all of the activity of driving transactions. The
BFM can make use of the monitor or duplicate some of the monitor’s logic. (Both are legitimate.
Developers must decide which fits their needs.)
Generally, the monitor handles most passive activity, and the BFM handles all active interactions
with the DUT. For example, the monitor might collect transactions and then emit an event for each
transaction received. Upon this event, the BFM could be responsible for sending an
acknowledgment back to the DUT.
We recommend using the monitor rather than the BFM to collect transactions that come from the
DUT. This can happen in the following ways:
In many protocols, each agent in the UVC has both a BFM and a monitor. (See the XBus
golden UVC for an example.)
In some protocols, the Receive side might not have any active components, so there is no
BFM or sequence driver at all. In such cases, the Receive monitor can be placed inside a
single agent that combines Receive and Transmit activities. This is especially convenient if
the Receive and Transmit sides interact. (See the XSerial golden UVC for an example.)

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.

Clocks and Events


One decision facing UVC developers is whether to have the clock(s) propagated down to each
agent or to use a central clock in the env that all agents will refer to.
Cadence recommends the following:
To the extent possible, one centralized clock should be used. (Similarly, any other frequently
occurring event, such as reset, should be centralized.) This should be placed in a special unit
called a synchronizer that contains ports connected to the RTL or in a unit called clock_agent
that implements clock services (see below). An instance of the synchronizer unit should
normally be located in the env. The primary reason for this is to eliminate unnecessary

September 2021 61 Product Version 21.09


UVM e User Guide
Typical UVC Architecture

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

September 2021 62 Product Version 21.09


UVM e User Guide
Typical UVC Architecture

What Are Agents?


Agents are the key to UVC architecture. In most UVCs, agents represent independent devices and
have standard main elements. Some of the fields in the agents are also standard and should
appear in all UVCs. Examples of this standard organization and these standard fields can be seen
in the golden UVCs.
Agents are either ACTIVE or PASSIVE. ACTIVE agents are agents that drive DUT signals.
PASSIVE agents never do that, either because they just monitor an interface within the DUT or
because, according to the protocol, no signals need to be driven. In addition to monitoring the
activity of a corresponding DUT agent, PASSIVE agents can also add checks, scoreboards, and
coverage on top of what is predefined in the UVC. Typically, these should be used in verifying the
DUT but should not be part of the active agent.
This is coded by using a predefined type:
uvm_active_passive_t: [ACTIVE, PASSIVE]; // Predefined in UVM

When the agent is defined like uvm_agent, it contains an active_passive field.


unit xbus_agent_u like uvm_agent {

};

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:

unit xbus_agent_u like uvm_agent {


monitor: xbus_agent_monitor_u is instance;
when ACTIVE xbus_agent_u {
bfm: xbus_bfm_u is instance;
driver: xbus_sequence_driver is instance;
};
};

September 2021 63 Product Version 21.09


UVM e User Guide
Typical UVC Architecture

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

September 2021 64 Product Version 21.09


UVM e User Guide
Typical UVC Architecture

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

Agent Configuration and Signals


The agent configuration specifies an agent’s interface with the rest of the verification environment. It
contains the mode of operation (for example, active_passive, has_checks, has_coverage) and
additional static information needed for the agent’s operation, such as the bus width, endianness,
etc. We recommend encapsulating these parameters in a config struct to present a well-defined
interface for the agent.
Some configuration fields like active_passive are used for subtyping. These fields must reside in
the agent or be propagated from the config struct to the agent. All other fields are defined in the
config struct. This struct can be constrained from the enclosing unit, thus eliminating the need for a
parent pointer and allowing instantiation of the agent in different environments. For the sake of
efficiency, config fields that are used frequently can be copied into a local field.

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.

September 2021 65 Product Version 21.09


UVM e User Guide
Typical UVC Architecture

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.

September 2021 66 Product Version 21.09


UVM e User Guide
Typical UVC Architecture

Monitor Checking and Coverage


Coverage and checkers should be implemented on top of the monitor or in subtypes of the monitor
such as has_checks and has_coverage. That minimizes performance overhead if those units are not
used. The has_checks and has_coverage flags should be propagated to the monitor so that the
monitor code can be optimized.

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.

Errors in the BFM


The BFM should never issue a dut_error. Errors such as unexpected BFM state should be caught
by assertions. If possible, a message might be issued while the flow continues.
Protocol errors should be caught and reported by the checker part of the monitor.

September 2021 67 Product Version 21.09


UVM e User Guide
Typical UVC Architecture

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.

September 2021 68 Product Version 21.09


UVM e User Guide
Typical UVC Architecture

Default UVC Coverage


The default coverage definition of the UVC should be based on events and data collected in the
monitor. The coverage group should be based on a monitor event that indicates the completion of a
transaction (for example, packet_ended).
The coverage definition includes:
Significant fields of the data item (for example, packet)
Related temporal data (for example, delay or transmission time)
Cross-coverage items required for covering the interface specification
Additional agent fields indicating the mode of operation

Some Important Guidelines for Agents


Separate the BFM from the monitors. When agents are PASSIVE, they should be able to use their
monitors without interfering with the DUT operation.

A More Complex UVC Example


The first example we looked at was a UVC for a very simple communication protocol, the XSerial
interface. Now we examine a more complex UVC, a UVC for a bus. Figure 4.6 shows the xbus UVC
added to the DUT verification environment.Figure 4.1: ​XBus UVC

September 2021 69 Product Version 21.09


UVM e User Guide
Typical UVC Architecture

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.

September 2021 70 Product Version 21.09


UVM e User Guide
Typical UVC Architecture

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.

September 2021 71 Product Version 21.09


UVM e User Guide
Typical UVC Architecture

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.

Typical Layering Applications


There are two common forms of layered UVCs: single-layer UVCs and multi-layer UVCs.
Single-layer UVCs provide functionality relating to a single layer in a protocol, but they also
provide hooks to let other protocol layers be connected above or below as appropriate. Such
UVCs enable a plug-and-play approach to building multi-layer solutions.

September 2021 72 Product Version 21.09


UVM e User Guide
Typical UVC Architecture

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.

Requirements for Lower-Layer UVCs


Lower-layer UVCs must be able to communicate with the DUT in the normal way via some form of
signal interface. They also must have sufficient hooks to allow connection to higher-layer UVCs.
For data being generated by a higher-layer UVC and then driven into the DUT via the lower-layer
UVC, the connection between the layers is achieved through the sequence interface. Lower-layer
UVCs must have a standard sequence interface implemented using Pull Mode. The connection
between the layers is then implemented as one or more special connector sequences.
For data being collected from the DUT by the lower-layer UVC and passed to the higher layer UVC,
the connection between the layers is achieved through the scoreboard hooks. Lower-layer UVCs
must have sufficient scoreboard hooks that the higher-layer UVC can extend to extract the data that
it requires.
Any standard, UVM-compatible UVC can be used as a lowest-layer UVC in a layered solution
without modification so long as its sequence drivers use Pull Mode.

Requirements for Higher-Layer UVCs


Higher-layer UVCs have one significant difference from lowest-layer UVCs— they normally do not
have a mechanism for driving and sampling signals in the DUT. Instead, a higher-layer UVC
provides a set of method calls that allow transfer of data to and from a lower-layer UVC.
In some cases, a UVC might need to be capable of acting either as a higher-layer UVC or as a
lowest-layer UVC. For example, an XGMII (Ethernet) UVC might be used to layer over an XSBI
UVC to generate and collect XGMII layer data while connected to an XSBI protocol DUT. However,
the same UVC might be connected directly to XGMII signals for a DUT that has an XGMII port. In
such cases, Cadence recommends that the agent have method calls suitable for use in layering the
UVC over lower-level UVCs. The env can then have an optional signal-based interface that calls
the methods in the agent as appropriate.
Typically, two method calls are provided, one for passing data in each direction. Normally, these

September 2021 73 Product Version 21.09


UVM e User Guide
Typical UVC Architecture

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.

Partitioning of Layers and Connections between Packages


Following are three situations in which UVCs might be layered.

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).

September 2021 74 Product Version 21.09


UVM e User Guide
Typical UVC Architecture

Layering within a Single UVC


In certain cases, it might not make sense to deliver the individual layers as separate UVCs. In such
cases, a multi-layer UVC might be the most appropriate vehicle for delivery. In a multi-layer UVC,
each layer will be handled by one or more layer-specific agents, for example, layer_1_agent,
layer_2_agent, and so on. Each agent is essentially the same as in the multiple-UVC
implementation. The only difference is that all agents for all layers would be contained in a single
env. The developer might want to provide flags to allow selective enabling and disabling of layers.

Separation of Control in Multi-Layer Sequences


Once a protocol is divided so that there is a sequence driver at each layer, control over scenario
generation becomes significantly more flexible. By default, a lower-layer UVC in a multi-layer
environment is configured to perform continuous connector sequences. As such, scenarios can be
written at higher layers and the lower layer data is automatically created.
Specific lower-layer behavior can be achieved by writing sequences that mix connector sequences
with other required behavior (such as error conditions).
For more coordinated testing, a virtual sequence driver can be used to regulate traffic injection at
each layer. For example, a virtual sequence could be written to drive continuous ATM cells over an
XSerial link while occasionally grabbing the XSerial sequence driver to override the default
connector sequence with one that introduces XSerial-specific error conditions.

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?

September 2021 75 Product Version 21.09


UVM e User Guide
Typical UVC Architecture

A: It depends on the protocol:


In case of serial interface, typically each pair of ports is independent and has 2 agents in it
(RX/TX). Therefore it is natural to have two envs (as shown on the right side of the above
figure).
In buses, typically there are many agents on a bus. Thus if the DUT has one bus, it is natural
to have a single env for all N agents (as shown on the left side of the above figure).
Q: If an env has a single agent, should env and agent be merged (that is, have only an env unit with
no agent in it)?

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

September 2021 76 Product Version 21.09


UVM e User Guide
Typical UVC Architecture

Full Legend for Architecture Diagrams

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.)

Package An independent and modular UVC, utility, or shareware.

Data An <bi_bolditalics>e object that corresponds to a specific data structure in the


Item UVC architecture.
Data items are implemented as structs.
Data item examples: packet, transaction, instruction, and so on.

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.

Agent An <bi_bolditalics> e object that represents a device or similar entity in a


verification package (for example, master, slave, TX/RX machine).
Agents are implemented as units and contain a BFM, sequence driver, sequences,
and a monitor.
Inherits from uvm_agent.

September 2021 77 Product Version 21.09


UVM e User Guide
Typical UVC Architecture

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):

ACTIVE Drives signals into the DUT.


Proactive— Initiates transactions and send them to the DUT
Reactive— Reacts to transactions as they come from the DUT

September 2021 78 Product Version 21.09


UVM e User Guide
Typical UVC Architecture

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

UVC Proactive Reactive PASSIVE

AHB UVC master UVC slave

USB host device

XSerial TX (RX) (RX)

NOTE: All agents in a UVC can be used as PASSIVE agents.

Data Item Naming Convention


Each communication protocol has a basic data item that is generated and sent by the BFM and
collected by the monitor. Following is the
recommended naming convention for the type of the basic data item:
package-name_agent_[driven|monitor]_item-name

For example, xbus_master_driven_burst is the burst generated and sent by the


master. xbus_slave_monitor_burst is the burst reconstructed by the monitor in the slave. The types
can be inherited from a common type using like inheritance. If the types are closely
related, when inheritance might be more applicable.

September 2021 79 Product Version 21.09


UVM e User Guide
Typical UVC Architecture

Full Legend for Architecture Diagrams


Figure 4.1: ​Full Legend for Architecture Diagrams

September 2021 80 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

Sequences: Constructing Test Scenarios

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

September 2021 81 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

The testflow sequence in Sequences Using Testflow

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 struct that represents a stream of items signifying a high-level scenario of


stimuli. This is done by generating items one after the other, according to some
specific rules. The sequence struct has a set of predefined fields and methods.
The sequence struct can also be extended by the user.

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.

To complete the picture:


A TCM does the actual driving of items into a specific DUT channel.
The TCM resides in a BFM unit.
For the purpose of driving data into the DUT, the sequence driver interacts only with the BFM.
The sequence driver and the BFM work as a pair, where the sequence driver serves as the
interface upwards towards the sequences so that the sequences can always see a standard
interface to the DUT. The BFM serves as the interface downwards to the DUT, letting you write
sequences in any way you find appropriate.
At first, it might seem unnecessary to separate the sequence driver and the BFM. The importance of
this separation becomes clear when implementing virtual sequences.
Figure 5.1 describes the general flow of data and control in sequences.

Figure 5.1: ​Flow of Control and Data in Sequences

September 2021 82 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

Figure 5.1: ​Flow of Control and Data in Sequences

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.

How to Use Sequences in Your Environment


To use sequences in your environment:

September 2021 83 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

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

Defining the Sequence Item


For an item to be used with sequences it must have some common functionality. Therefore, define
the item struct like you always do but inherit from the predefined type any_sequence_item. For
example:

struct ex_atm_cell like any_sequence_item {


kind: [A1, A2, A3, A4];
color: ex_atm_color;
..
};

September 2021 84 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

Defining the Sequence and Its Driver Using the sequence


Statement
Define the sequence struct and the sequence driver unit using the sequence statement. The full
details on the sequence statement are in the Sequences chapter in the Creating e Testbenches
Specman manual.
Following are some usage examples.
Example 1: Defining a BFM Sequence for ATM Cells
sequence ex_atm_sequence using item=ex_atm_cell;

This statement assumes that an ex_atm_cell struct already exists. It defines:

Struct ex_atm_sequence (inherits from any_sequence)

Type ex_atm_sequence_kind

Unit ex_atm_sequence_driver (inherits from any_sequence_driver)

Example 2: Defining a Virtual Sequence for an SoC Environment


sequence soc_sequence;

This statement defines:

Struct soc_sequence (inherits from any_sequence)

Type soc_sequence_kind

Unit soc_sequence_driver (inherits from any_sequence_driver)

Collecting Coverage on Sequences


Sequences are the main means of generating and injecting input into the DUT. Therefore,
collecting coverage information about the generated sequences is important. Cadence
recommends sampling the sequence kind upon its ended event. In that way, you are sure to sample
only sequences that actually made it into the device.
Example: Defining Coverage for the XBus Master Sequence

September 2021 85 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

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.

Hooking the Sequence Driver to the


Environment
To hook the sequence driver into your environment:
1. Add a reference to the sequence driver in the BFM.
2. Instantiate the sequence driver in the environment.
3. Connect the sequence driver’s clock to the BFM’s clock.
4. Transfer the item from the driver to the BFM by adding a TCM that explicitly requests items
from the driver and calls the appropriate BFM’s TCM.
5. (Optional) Add useful fields and methods to the base sequence type.
Hookup Example
Assume an ex_atm_cell item that is defined as follows:

September 2021 86 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

<‘
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 {
...
};
};
‘>

The BFM is instantiated in an ATM verification environment:

<‘
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;
‘>

Then hook the sequence into the environment:

September 2021 87 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

// 1. Add a reference to the sequence driver in the BFM


extend ex_atm_bfm {
driver: ex_atm_driver;
};
<‘

// 2. Instantiate the sequence driver in the


// ATM environment
extend ex_atm_agent {
driver: ex_atm_driver is instance;
keep bfm.driver == driver;
};

// 3. Connect the (predefined) clock event to the BFM's clock


extend ex_atm_bfm {
on a_clock {
emit driver.clock;
};
};

// 4. Pull item from driver, process it, then inform


// using item_done
extend ex_atm_bfm {
execute_items() @clock is {
var seq_item: ex_atm_cell;
while TRUE {
seq_item = driver.get_next_item();
drive_cell(seq_item);
emit driver.item_done;
};
};
run() is also {
start execute_items();
};
};

//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

September 2021 88 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

them into the DUT using the drive_cell() TCM.


After each item is driven to the DUT, the event driver.item_done is emitted to let the sequence
complete the do action and inform the driver that the item was processed.
Note: If your verification environment contains several ex_atm_agent instances, each of them will
have a sequence driver and hence a MAIN sequence and a random stream.

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:

extend RANDOM ex_atm_sequence {


count: uint;
!sequence: ex_atm_sequence;
body() @driver.clock is {
for i from 1 to count {
do sequence;
};
};
};

September 2021 89 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

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:

extend RANDOM ex_atm_sequence {


keep count in [10..20];
};

Note: Such a constraint can also be applied locally to a RANDOM sequence field in another
sequence or even in a specific do.

Implementing Transactions in Virtual Sequences


The term “transaction” includes both items and sequences. In an SoC context, there can be DMA
transactions, MPEG transactions, and so on.
Such transactions normally consist of writing to several registers to initialize the DMA or MPEG
device and possibly sending an MPEG input packet to the MPEG input channel, either in parallel or
after writing to all of the registers.
It is easiest to package the transaction as a virtual sequence. Then you can have a higher-level
sequence go as follows:
do sequence keeping {.kind == DMA_TRANS};
do sequence keeping {.kind == MPEG_TRANS};

Enhancing the User Interface


In addition to using the do action and the sequence driver methods—such as deliver
item(), execute_item()—you might want to define a more high-level interface by encapsulating a
set of actions in methods. This can be done using methods or macros.

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:

September 2021 90 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

struct ex_c_bus_op like any_sequence_item {


kind : ex_c_bus_kind;
address : uint;
color : ex_c_bus_color;

when WRITE ex_c_bus_op {


data : uint; // Sent data
};
when READ ex_c_bus_op {
!data : uint; // Returned data
};
};

You can perform read operations by using the do action as follows:


do c_bus_op keeping {.kind == READ; .address == 0x100};

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:

my_c_bus_op = new ex_x_bus_op with {


.address = address;
.data = data;
.color = color;
};
;
driver.execute_item(my_c_bus_op);

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:

September 2021 91 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

<'
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:

define <wr_cells'action> "WRITE[ <num'num>] CELL[S] <first_addr'num> <first_data'num>" as {


for i from 0 to <num'num|0> {
write(<first_addr'num>+i, <first_data'num>+i);
};
};

Then you can write shorthand like:

September 2021 92 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

body() @driver.clock is only {

// Write one cell, to address 0x00


WRITE CELL 0x00 0x78;
// Write 8 cells, starting address 0x00
WRITE 8 CELLS 0x00 0xaa;
};

Creating a Sequence Library


Once you define the sequence struct, you can create various scenarios (sequence kinds) by
creating subtypes of the sequence using the kind field. Each kind of sequence can implement a
typical scenario or a combination of existing scenarios. The set of sequences is the “sequence
library”.
Cadence recommends defining the sequence library in a separate file or files that can be loaded on
top of the environment. This lets you include or exclude some of the sequence kinds upon demand
of specific tests.
Cadence recommends encapsulating any meaningful scenario as a new sequence kind. This
enhances the reuse and maintainability of the environment.

Writing Tests Using Sequences


After defining sequences and implementing several sequences, it is time to use them. As a
sequence models the input stream of an agent, it becomes the common interface for writing tests.
Cadence recommends creating a sequence library and then creating tests that use the sequences
in the library by parameterizing them, enhancing them, or using them as is with some weights.
In some simple cases you can avoid defining new sequence kinds altogether by directly redefining
the body() method of the MAIN sequence.
Alternatively, you can define a specific new sequence kind directly in the test and then use it.
This section demonstrates all of the above options. This section contains:
Writing the Simplest Test: Using UVC’s Sequence API for Test Writers
Redefining MAIN Sequence body()
Writing a Typical Test: Using the Sequence Library
Writing a Unit-Related Test Using Unit ID

September 2021 93 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

Writing System Tests - Virtual Sequences


Note: The UVM Examples library contains example directories with many sequence definitions and
tests that you can study and run.

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:

September 2021 94 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

<’
extend transfer_driver_u {
!transfer : transfer;

send_to_address(addr : addr_t) @clock is {


wait_for_grant(NULL);
gen transfer keeping {it.address == addr;};
deliver_item(transfer);
};

send_transfer(addr : addr_t, size : size_t) @clock is {


wait_for_grant(NULL);
gen transfer keeping {it.address == addr; it.size == size};
deliver_item(transfer);
};
// Define the scenario method.
// Define as empty, so user does not need to usee "is only"
test_scenario() @clock is empty;
run() is also {start test_scenario()};
// No random activity
extend MAIN transfer_sequence {
keep count == 0;
};
};
’>

Test example:

<’
extend transfer_driver_u {
test_scenario() @clock is {
send_to_address(0);
send_transfer(1000, 4);
};
};
’>

Redefining MAIN Sequence body()


The simplest way to write tests is to redefine the behavior of the MAIN sequence by overriding its
body() method.

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

September 2021 95 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

extend MAIN ex_atm_sequence {


body() @driver.clock is only {
do cell;
do cell keeping {.color == GREEN};
};
};

Writing a Typical Test: Using the Sequence Library


The most powerful way to write tests with sequences is by creating a rich library of sequence kinds
and then using them, mixing them, slightly modifying them, and so on. Typically, the resulting tests
are a combination of all of the above. This section demonstrates two modes of use for existing
sequence kinds:
Writing a Random Test: Setting Weights
Modifying an Existing Sequence

Writing a Random Test: Setting Weights


The easiest and quickest way to create a test based on the sequence library is to choose a subset
that will be activated in the specific test, then set weights for each kind using keep soft … select
according to the required appearance frequency.
Example

extend MAIN ex_atm_sequence {


keep soft sequence.kind == select {
30: ALTERNATING_COLOR;
60: SHORT_LONG;
10: FIXED_LEN;
};
};

Writing a Dedicated Test


You can add a new sequence to the sequence library by extending the sequence kind and its
functionality. By adding a sequence to the library, rather than extending MAIN (as shown in
Redefining MAIN Sequence body(), you make the scenario reusable - the sequence definition can
be imported and used in other tests.
Example: Adding a sequence to the library

September 2021 96 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

extend ex_atm_sequence_kind : [SHORT_FOLLOWED_BY_LONG];

extend SHORT_FOLLOWED_BY_LONG ex_atm_sequence {


!short_data : SHORT ex_atm_sequence;
!long_data : LONG ex_atm_sequence;

body() @driver.clock is only {


// do 10 sequences, each doing short data cells
do short_data keeping {.count == 10};
// do 1 sequence, doing long data cells
do long_data keeping {.count == 1};
};
};

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.

Modifying an Existing Sequence


Often, using the existing kinds is not sufficient. Sometimes it is enough to just apply additional
constraints on the sequence parameters, but sometimes you want to introduce somewhat different
behavior (for example, waiting three more cycles) into an existing sequence.
The way to modify an existing sequence is to extend the sequence subtype in the test itself.
Example

September 2021 97 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

// Modify the behavior of FIXED_LEN sequence -


// wait additional 3 cycles after it ends
extend FIXED_LEN ex_atm_sequence {
body() @driver.clock is also {
wait [3] * cycle;
};
};
// Use the (modified) kind in the test
extend MAIN ex_atm_sequence {
keep soft sequence.kind == select {
60: SHORT_LONG;
40: FIXED_LEN;
};
};

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.

// Add control to the behavior of 'fixed_len' sequence.


// Add option to wait additional 3 cycles.
// Add a unit-related constraint to activate the
// additional behavior.
extend FIXED_LEN ex_atm_sequence {
use_extra_waits: bool;
// Set default behavior.
keep soft use_extra_waits == FALSE;
// Activate additional behavior for ATM_0.
keep in_unit(ATM_0 ex_atm_driver) => use_extra_waits == TRUE;
};

// Implement the additional behavior under the new subtype.


extend use_extra_waits FIXED_LEN ex_atm_sequence {
body() @driver.clock is also {
wait [3] * cycle;
};
};

// Use the (modified) kind in the test.


extend MAIN ex_atm_sequence {
keep soft sequence.kind == select {
60: SHORT_LONG;
40: FIXED_LEN;
};
};

Note: This is very modular, even though an existing sequence is extended inside a file describing
another sequence.

September 2021 98 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

Writing a Unit-Related Test Using Unit ID


Cadence recommends that units have ID fields associated with their roles within the parent unit.
These ID fields can be enumerated types (for example, ex_atm_name) or numbers (for
example, agent_num). In effect, these fields correspond to the role the field is playing. For example,
you can add to your environment a code similar to this:

type ex_atm_env_name: [NORTH, SOUTH];


extend ex_atm_env {
name: ex_atm_env_name;
};
unit comm_env like uvm_env {
north_atm: NORTH ex_atm_env is instance;
south_atm: SOUTH ex_atm_env is instance;
ethernet_ports: list of ethernet_port is instance;
keep for each in ethernet_ports {.ind == index};
};

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

Unit IDs to Constrain Sequences


Use unit IDs to constrain sequences when the scenario depends on a particular unit.
Suppose you want to write a test in which only one ATM env will run the multi_write sequence (with
an address of 20). The rest will run random. You could achieve that as follows:

September 2021 99 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

extend MAIN ex_atm_sequence {


keep in_unit(NORTH ex_atm_env) =>
soft sequence is a MULTI_WRITE ex_atm_sequence (s) and
s.address == 20;
};

Hierarchical Unit IDs


Use hierarchical IDs when roles are not unique throughout the DUT.
For example, you could define an SoC containing four comm_envs as follows:

unit soc like uvm_env {


comm_envs[4]: list of comm_env;
keep for each in comm_env {.index == index};
};

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:

extend MAIN ex_atm_sequence {


keep in_unit(NORTH ex_atm_env) and in_unit(comm_env) (C) and
C.index != 0 => soft sequence is an XX ex_atm_sequence;
};

Writing System Tests - Virtual Sequences


When writing tests using a system UVC, you can use any combination of the virtual sequences and
the interface UVCs. According to the sequences libraries provided by the UVCs, decide which are
the sequences most applicable to your test scenario.
See more in Advanced Issues in Virtual Sequences.

Sequence File Organization


Typically, sequence implementation is split into several files:

Sequence Define a sequence for a specific item (packet, transaction, and so on) in
definition files any environment

September 2021 100 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

DUT-specific Extends the sequence for use with a specific DUT and hooks the sequence
sequence to the environment
hook file

Sequence Define various sequence subtypes that implement specific scenarios,


libraries either generic or DUT-specific

Test files Import relevant sequence libraries and use them

See Also
Chapter 3, UVC File Organization

Advanced Generation-Related Aspects of


Sequences
This section explains some advanced aspects of sequences in relation to generation.
This section contains:
Freshness of Items - Generate Right Before Send
Specifying Subtype in do Actions
Propagating Parameters in the Sequence Tree
Migrating Unit Attributes to the Sequences
Generating the Item/Sequence in Advance
Constraining Complex Sequence Items

Freshness of Items - Generate Right Before Send


In many environments, the freshness of the data items is essential, and you want to generate the
item right before it is sent. This is required, for example, when the data items should be generated
based on current status of the device. The best way for creating fresh data items is
using deliver_item() instead of do. Only after the sequence receives a grant from the driver, it
generates the item. Getting a grant guarantees that this item is the next item to be driven, without
waiting in the driver queue. Only if the user extends its is_relevant() method will the item not be
sent.

September 2021 101 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

For example:

extend SEND_WITH_CURRENT packet_sequence {

body() @driver.clock is only {

driver.wait_for_grant(me);

// Generate based on current status


gen packet keeping {.ctrl == driver.current_ctrl_val};
driver.deliver_item(packet);

};
};

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.

extend SEND_WITH_CURRENT packet_sequence {


!prepared_data : data_t;

body() @driver.clock is only {


do item keeping {it.payload_data == prepared_data};
};

pre_do(is_item: bool) @sys.any is {


// item is about to be generated, prepare the data for it
prepared_data = driver.get_updated_date();
};
};

Note: deliver_item() is supported starting Specman 8.2.

Specifying Subtype in do Actions


When you want to do subsequences or items of known subtype(s), you can improve performance in
one of two ways:
Statically defining the subtype of your subsequences or items
Explicitly specifying the subtype of your subsequences or items in the appropriate do actions
When all subsequences or items are of the same subtype, then statically define the subtype. When
they are not all of the same subtype, then specify the subtype in the appropriate do actions.

September 2021 102 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

Syntax for Specifying Subtype in do Actions


do when-determinant1…n field [keeping { it. … };]

Parameters

when-determinant1…n Must be when determinants of the declared type of the field.

field The sequence or item

Note: “it” already refers to the subtype. There is no need for casting to access the subtype attributes.
Example 1

extend MAIN my_sequence {


seq: my_sequence;

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

September 2021 103 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

extend MAIN my_sequence {


!seq: GREEN my_sequence;

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;
};
};
};

Propagating Parameters in the Sequence Tree


Sometimes you must propagate the value of a sequence parameter (for example, unit ID) down the
sequence tree so that the root sequence (for example, the MAIN sequence) is assigned a specific
value. This value then propagates automatically to all sequences generated using do anywhere
along the hierarchy underneath the root sequence.
To propagate parameters down the sequence tree:
1. Add the parameter to the base sequence type.
2. Use the predefined field parent_sequence to propagate the value.
For example:
keep parent_sequence != NULL => soft my_param ==
parent_sequence.as_a(ex_atm_sequence).my_param;

3. Add an additional constraint for the root sequence in the root-sequence parent.
Example

September 2021 104 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

type ex_atm_port_id: [ATM_0, ATM_1];

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
};

Migrating Unit Attributes to the Sequences


It is often good methodology to migrate unit names and attributes to corresponding sequences.
To migrate unit names and attributes to corresponding sequences:
1. Define a field in the sequence struct to represent the unit attribute.
2. Migrate the value from the unit using get_enclosing_unit().
Example

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;
};

Then you can say things like:


extend NORTH MAIN ex_atm_sequence {
...
};

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.

Generating the Item/Sequence in Advance


Sometimes you might want to disconnect the generation from the send; that is, generate or
instantiate the item or sequence before sending it to the driver.

September 2021 105 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

Generation in advance - using deliver_item


Generation in advance - using do

Generation in advance - using deliver_item


Starting specman 8.2, the creation of the item and sending it to the driver is not necessarily one
atomic action (“do”). You can create the item, using gen, new, copy, or any other way, any time in
the flow. Later, you can send it to the driver, using driver.deliver_item. For example:

extend EXAMPLE_SEQ packet_sequence {


body() @driver.clock is only {
gen packet;

// Now you can do some other stuff,


// wait as much as you want ...

// wait for your turn


driver.wait_for_grant(me);

// You can change the packet, just before sending it.


// You can, of course, just send it as is
driver.deliver_item(packet);
};
};

Generation in advance - using do


Sometimes you might want to generate or instantiate the item or sequence before the do action.
You could then use the instantiated item or sequence in the do action without regenerating it.
To generate/instantiate the item/sequence in advance:
1. Define an additional field/variable of the same type as the item/sequence.
2. Either generate the field/variable using the gen action or instantiate it using the new action.
3. Constrain the field in the do action to be equal to the additional field/variable (defined in Step
1).
For example:

September 2021 106 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

extend FOO ex_atm_sequence {

// Items/subsequences
!cell: ex_atm_cell;

// The body() method


body() @driver.clock is {
var preprepared_cell: ex_atm_cell;
gen preprepared_cell;
...
do cell keeping {it == preprepared_cell};
};
};

To return immediately (non-blocking do), implement the method that processes the item to be non-
time-consuming and return immediately.

Constraining Complex Sequence Items


Suppose you have a data item that contains a list with items that you want to constrain. In this case,
you cannot use the it variable to refer to the list items, because it already refers to the data item
itself. Instead, you must specify a new name for the list items. In the following example, the name
“item” is used for this purpose.

extend NEW my_sequence {


body @driver.clock is only {
do burst keeping {
.addr <= 1000;
.type in [READ, WRITE];
for each (data_item) in .data { // Specify name for list item
data_item > 10 and data_item < 90;
};
};
};
};

Implementing Complex Scenarios


This section contains:
Defining Concurrent Sequences
Initializations and Configurations before Starting Sequences

September 2021 107 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

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

Defining Concurrent Sequences


You can create concurrent sequences in two ways:
Using the do action within an all of block
Starting several sequences using the start_sequence() method

Example 1: Using the do action


In this example, the number of sequences is determined statically.

// Do multiple SHORT_LONG sequences in parallel


extend ex_atm_sequence_kind: [PARALLEL_3];
extend PARALLEL_3 ex_atm_sequence {
!sl_seq: SHORT_LONG ex_atm_sequence;
body() @driver.clock is {
all of {
{do sl_seq keeping {.short_count == 1}};
{do sl_seq keeping {.short_count == 2}};
{do sl_seq keeping {.short_count == 3}};
};
};
};

Example 2: Starting several sequences in parallel


In this example, the PARALLEL_N sequence activates n sequences in parallel. It does not wait for the
sequences to complete, but immediately finishes after activating the sequences.

September 2021 108 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

// Do multiple SHORT_LONG sequences in parallel


extend ex_atm_sequence_kind: [PARALLEL_N];
extend PARALLEL_N ex_atm_sequence {

// num_of_sequences can be constrained from above


num_of_sequences: int;
keep soft num_of_sequences == 3;
// The list of sequences to activate
sl_seq_l: list of SHORT_LONG ex_atm_sequence;
keep sl_seq_l.size() == num_of_sequences;
keep for each in sl_seq_l {
it.driver == driver;
};

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.

Initializations and Configurations before Starting Sequences


The sequence driver generates the MAIN sequence and starts its body() method upon run().
Sometimes you might want to perform some operations before starting to randomize the sequences.
For example, a test might start with a configuration stage and an initialization stage before the
actual testing starts.
You can implement these preliminary stages of the test with sequences. For example:

extend MAIN ex_atm_sequence {


body() @driver.clock is only {
do initialization_sequence;
...
};
};

Another way to perform initializations before the test is by extending the pre_body() method to
delay the execution of body(). For example:

September 2021 109 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

extend MAIN ex_atm_sequence {


pre_body() @sys.any is {
// Perform configuration
configure_dut();
// Wait until initialization is done
sync true(driver.initialization_done);
};
};

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

September 2021 110 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

// 1. Define an interrupt sequence.


extend ex_atm_sequence_kind: [INTERRUPT_ABORT];
extend INTERRUPT_ABORT ex_atm_sequence {
// Upon interrupt, grab driver, terminate all its main activity, and
// send a configuration sequence
body() @driver.clock is {
sync @driver.interrupt; // Wait for the interrupt event.
grab(driver); // Grab the driver.
driver.sequence.stop(); // Stop the MAIN sequence activity.
do cell keeping {.color == RED}; // Execute the interrupt scenario.
do cell keeping {.color == GREEN};
do cell keeping {.color == BLUE};
ungrab(driver); // Ungrab the driver.
};
};

extend ex_atm_driver {
// 2. Instantiate the interrupt sequence under the sequence driver
iseq: INTERRUPT_ABORT ex_atm_sequence;
keep iseq.driver == me;

// 3. Start the interrupt sequence


run() is also {
iseq.start_sequence();
};
};

// The test part


extend MAIN ex_atm_sequence {
keep count == 1;
keep sequence is a ALTERNATING_COLOR ex_atm_sequence;
};

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.

DUT-Independent Read/Write Interface


The uniform interface of sequences lets you develop a generic read/write interface that might be
applied to many environments and is not related directly to a specific DUT. Such an interface
enables development of system-level tests that are suitable for many configurations of the
environment.
A typical example is a bus-based configuration test that can be executed over multiple buses, for
example, a PCI bus as well as an ATM bus.
To implement a DUT-independent interface:
1. Define a virtual read/write sequence that uses a generic low-level sequence driver to execute

September 2021 111 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

the low-level read/write transactions.


2. Hook the virtual sequence driver to a specific sequence driver as follows:
a. Instantiate the virtual sequence driver.
b. Connect its low-level sequence driver to a specific sequence driver.
c. Implement the read/write interface methods of the low-level sequence driver.
Note: The last portion of the step (2c) might be done already as part of the specific
environment. See Enhancing the User Interface.
d. Use the virtual sequence driver in tests.
Note: Each of these steps should reside in a separate file (to allow reuse).
Example
This is from ex_cbus_rw_test.e in the
1. Define a virtual read/write sequence.

sequence config_sequence;
extend config_sequence_driver {
low_driver: any_sequence_driver;
event clock is only @sys.any;

// Implement the read/write interface so that it uses the


// low-level sequence driver interface.

write(address: list of bit, data: list of bit) @clock is {


low_driver.write(address, data);
};

read(address: list of bit) : list of bit @clock is {


result = low_driver.read(address);
};
};

2. Hook up the virtual sequence driver.

September 2021 112 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

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;
};

3. Test Example: A generic (that is, DUT-independent) configuration test.

extend MAIN config_sequence {


!w_data: uint(bits:4);
!r_data: uint(bits:4);
!addr: uint(bits:6);
body() @driver.clock is only {
gen w_data;
gen addr;
outf("sending data: %d to address: %x\n", w_data, addr);
driver.write(%{addr},%{w_data});
r_data = %{driver.read(%{addr})};
outf("received data: %d from address: %x\n", r_data, addr);
};
};

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.

September 2021 113 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

Controlling the Scheduling of Items


When there are several sequences do-ing items concurrently, the driver implements a basic
scheduling algorithm — first-come-first-served. You can change the algorithm
using grab() and is_relevant().
If a sequence is grabbing the driver, the driver will choose the first do action that satisfies the
following conditions:
It is done by the grabbing sequence or its descendants.
The is_relevant() method of the sequence do-ing it returns TRUE.

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.

Grabbing Without Affecting Scheduling


As explained in Controlling the Scheduling of Items, grab() affects the scheduling; a sequence
which grabs the driver has priority over other sequences. You can use the sequences methods API
for grabbing the driver without affecting the scheduling. This is achieved by calling grab() only after
the sequence got the grant. For example:

September 2021 114 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

extend PERF packet_sequence {


!packet : packet;
body()@driver.clock is {

driver.wait_for_grant(me);

// After got the grant - grab.


grab(driver);

// Perform the series of actions, using deliver_item()


gen packet keeping {.address == 0};
driver.deliver_item(packet);
driver.wait_for_grant(me);
gen packet keeping {.address == 1};
// etc ...

// ungrab
ungrab(driver);
};

};
};;

Note:The methods API is supported starting in Specman 8.2.

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:

extend DVZ inst_sequence {


op1_reg: register;
op2_reg: register;

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
};
};

September 2021 115 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

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).

Handling Pipelined Protocols


In pipelined protocols, data items undergo a number of processing phases. For example, a data
transfer might be either in the address phase or in the data phase. In addition, the DUT can handle
concurrently multiple data items with each item in a different phase.
When implementing pipelined scenarios with sequences some typical problems arise:
How to launch a new data item while the current data item is still being processed
How to ensure that the fields of the data item are stable before sampling them
To implement pipelined scenarios:
1. In the appropriate sequence, list the desired series of pipelined do actions.
2. Emit the item_done event when a trigger event in the BFM indicates that a new data item
should be sent to the DUT.
This releases the current do action and lets the sequence perform the next do action.

For example:

September 2021 116 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

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);

-- Emit the item_done event when the trigger event occurs


wait @need_new_item;
emit driver.item_done;
};
};
};

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:

September 2021 117 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

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

September 2021 118 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

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);

-- Emit the item_done event when the trigger event occurs


wait @need_new_item;
emit driver.item_done;
};
};
};

Example 2: All do actions in a single thread but sampling in separate threads

September 2021 119 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

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);

-- Emit the item_done event when the trigger event occurs


wait @need_new_item;
emit driver.item_done;
};
};
};

September 2021 120 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

Handling Back Pressure and Head-of-the-Line Blocking


When implementing layered protocols, there is a risk of encountering a head-of-the-line blocking
problem. For example, take a multi-interface init sequence in which instructions are conveyed over
several connections. This scenario can be modeled by an upper-layer sequence that generates the
instructions, and a lower-layer unit that sorts them according to destination interface and sends
them to the appropriate BFMs. It might happen that one of the connections is blocked (for example,
if the connected DUT performs back pressure). In such cases, we want the ability to continue
sending items on other connections. This might be tricky to achieve with simple generation of the
items. Sometimes, the test writer is not familiar with all details and cannot tell which connection is
blocked. Sometimes, the generation takes several cycles. So it might happen that an item that is
valid when generation starts is no longer valid after generation ends. Figure 5.2 shows a virtual
sequence driver passing the items it gets to three different BFM sequence drivers. If one of these
sequence drivers is blocked, the whole queue waits.

Figure 5.1: ​H ead-of-the-Line Blocking

September 2021 121 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

Figure 5.1: ​H ead-of-the-Line Blocking

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.

September 2021 122 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

struct low_layer_item like any_sequence_item {


kind : low_layer_kind;
address : low_layer_addr_kind;

is_relevant(): bool is {
// Check if can currently send to address
result = driver.resource_is_available(address);

if not result then {


message(MEDIUM, "Cannot send to address ", address,
". item not relevant");
};
};
};

There is a usage example of the data item’s is_relevant() method in


the ex_mto_layering example. The test
example ex_mto_layering/example/ex_otm_layering_test_item_relevant demonstrates a back-
pressure scenario.

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.

September 2021 123 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

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.

September 2021 124 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

ex_otm_layering This UVM package demonstrates One-to-Many layering. Each packet (the
upper-layer data item) is fragmented over several ATM cells.

ex_mto_layering This UVM package demonstrates Many-to-One layering. Several ul_pkts


(upper-layer item) are concatenated into one ll_pkt (lower-layer item).

ex_mtm_layering This UVM package demonstrates Many-to-Many layering. Several ul_pkts


(upper-layer items) are concatenated into one ll_pkt (lower-layer item). A
ul_pkt can also be fragmented over two ll_pkts. This example is more
complex than the others. Its implementation requires adding a mediator
layer (ml_pkt). To add to the complexity and interest of this example, it has
two upper layer drivers. This is for demonstrating a complicated
environment in which there are multiple upper layers, all activating the
same lower layer.

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

Simple Layering (Inside One Sequence Driver)


The simplest way to implement a layered protocol is within one sequence (and one driver). You do
that by defining a sequence kind for the lower-layer sequence driver. Assume, for example, a
layered protocol in which each frame is fragmented over several packets. In such a UVC, the
sequence is of the lower-layer item packet. For creating a layered sequence, define a new kind:
extend packet_sequence_kind: [FRAME_SENDER];

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:

September 2021 125 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

extend FRAME_SENDER packet_sequence {


frame: frame;
!packet: packet;

body() @driver.clock is {
var full_data: list of byte = pack_the_frame(); // Pack the frame

while not done_with_full_data() {


var next_piece: list of byte=get_next_chunk_from_full_data();
do packet keeping {.data == next_piece};
};
};
};

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.

Layered Sequence Drivers


All layering methodologies presented in UVM are based on the same concept: define two (or more
agents), each containing a sequence driver and a BFM.
The lower layer has a sequence that can get items from the upper layer driver and generate lower-
layer items based on the upper-layer items. The lower-layer sequence library may contain many
more sequences. Sequences can run in parallel. Some get their payload from the upper layer, and
some generate unlayered data items.
The upper-layer BFM must pass the data items to the lower-layer sequence. An upper-layer
sequence may be unaware of the connection to the lower layer (in other words, it keeps doing
items, regardless of how they are injected.) Alternatively, an upper-layer sequence may take into
account the current status of the lower layer (for generating interesting scenarios). For example, an
upper-layer sequence can generate items that are too long for the lower layer to handle.
Figure Figure 5.4 shows the architecture of the drivers in a layered environment (as opposed to a
basic one-driver environment).

Figure 5.2: ​Layering Architecture

September 2021 126 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

Figure 5.2: ​Layering Architecture

The UVM examples library contains four packages demonstrating each of the layering kinds. This

September 2021 127 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

section describes these package.


The UVM layered examples show layered UVCs in which the two layers are implemented in one
UVC. You can also layer two UVCs one on top of the other (when, for example, each of the layers is
developed in another location), as long as the UVCs are layer-ready.
To make a UVC layer-ready, you must add to the UVC the types and methods explained in this
section.
This section contains:
Layered UVC Structure and Types
Lower-Layer Layered Sequences
Upper-Layer BFM
Upper-Layer Layered Sequence
The System
Sequence Library and Writing Tests

Layered UVC Structure and Types


The following types must be added to a UVC to make it layer-ready:
Interfacing Struct
Interfacing Methods
Binding the Layers

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:

September 2021 128 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

struct layering_data_struct_s {
// Raw data
data : list of byte;

// Can deliver structs across layers for user-defined


// application-specific uses
upper_layer_struct : any_struct;
};

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.

Defining the method type:


method_type item_layer_transfer(stream_id: uint, remaining_bytes: uint):
layering_data_struct_s @sys.any;

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;
};

September 2021 129 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

For examples of implementing this method, see Lower-Layer Layered Sequences.


In some protocols, there should be a strong relationship between the lower and upper layers. That
might necessitate more methods for passing additional information. The UVM One-to-One
examples contain one such method: check_do_available(). The lower layer sequence checks the
availability of the upper layer sequence, before continuing its run. For the full code,
see ex_oto_layering_frame_seq_lib.e.

Binding the Layers


Connecting two agents to each other requires binding of the interface method ports. For example:

unit system_env_u like uvm_env {


keep bind (ll_pkt_env.driver.get_item_layer_transfer,
ul_pkt_env.bfm.down_item_layer_transfer);
};

Lower-Layer Layered Sequences


The sequence library of the lower layer should contain one sequence that gets data items from the
upper layer, performs any required manipulation of the data, and generates the appropriate lower-
layer data item.
The following code shows the two methods:
Getting Items from the Upper Layer
Lower-Layer Layered Sequence Body

Getting Items from the Upper Layer


The layered sequence can get items from the upper-layer driver via the port defined in the lower-
layer sequence driver (see Interfacing Methods).
The following code is taken from the Many-to-One example. A very similar code exists in all of the
UVM layering examples in the
files ex_mtm_layering/e/ex_mtm_layering_ll_pkt_env.e, ex_mto_layering/e/ex_mto_layering_ll
_pkt_env.e, ex_otm_layering/e/ex_otm_layering_atm_env.e,
and ex_oto_layering/e/ex_oto_layering_frame_env.e.

September 2021 130 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

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.

Lower-Layer Layered Sequence Body


The basic code of the layered sequence has to run in a loop — getting items from the upper layer,
performing required calculations, and generating the lower-layer data item. When requesting the
next item from the upper layer, the sequence passes its ID. This lets each lower-layer sequence get
items only from specified upper-layer sequences. In this way, you can, for example, imitate ATM
channels. Each lower-layer layered sequence is one channel. The following sequence collects
data from the upper layer, generates an items using this data, and deliver the item to the driver. This
is implemented within first-of, and the other block in the first-of is checking for time out - no more
items from upper layer. The timeout checking and implementation is not shown here, check
out ex_mto_layering_ll_pkt_seq_lib.e for the full code.

September 2021 131 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

extend LAYERED low_layer_sequence {


body() @driver.clock is only {

var continue_work: bool;


// .. some more fields ..

while continue_work {

driver.wait_for_grant(me);

first of {
{
// 1) Sending item

// get data from upper layer


repeat {
remaining_bytes = max_length - tmp_payload.size();
layering_struct = get_item_from_upper_layer(
ll_pkt_id, remaining_bytes);
if layering_struct != NULL then {
tmp_payload.add(layering_struct.data);
} else {
// if no item from upper layer - wait for
// next grant, my next turn in driver
driver.wait_for_next_grant();
};
} until (layering_struct != NULL and
layering_struct.data.size() == 0);

// Generate an item using the data, and deliver it


// to the driver
gen ll_pkt keeping {
.payload == tmp_payload;
};
tmp_payload.clear();
driver.deliver_item(ll_pkt);
}; // Generating and sending an 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

September 2021 132 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

allows separating the various activities within do:


1. wait for grant
2. create the item
3. deliver the item to the driver
Using these methods allows the sequence to avoid blocking the driver; if there are no items from the
upper layer, the sequence waits for next grant.
If you use a Specman version older than 8.2, you should use do for generating and sending the
item. The four layering examples packages contain also examples of sequences using do.
This basic code can be extended to add capabilities:
Instead of working in an infinite loop (while TRUE), this loop can run on some condition,
which can be turned off either from within the loop or from outside. In the UVM examples, the
condition is a variable in the sequence, continue_work. Therefore, it can be turned off only
from within the sequence body.
When the lower-layer item is created from several items of the upper layer (the case of Many-
to-One and Many-to-Many protocols), the loop must get several items before it generates one
lower-layer item. In these cases, you can add a watchdog to the loop. Once the watchdog
expires, the LAYERED sequence should generate and send its item, even if it did not reach
its maximum length. In some protocols, this requires padding the data item in order to reach
some minimum length. In most of the UVM layering examples, there is such a timer. Once it
expires, the lower layer sends the current item and resets continue_work to FALSE, assuming
that the upper layer has finished its test scenario. In the Many-to-Many example, there is no
such timer. The lower-layer loop runs infinitely.
For Many-to-Many layering, UVM recommends creating a mediator layer. This layer gets the
items from the upper layer, generates required data items, and sends the generated data
items to the lower layer. The mediator layer provides full independence of the two protocol
layers.
For the full implementation of these sequences, see the layering examples sequence libraries:
ex_oto_layering/e/ex_oto_layering_frame_seq_lib.e

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

September 2021 133 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

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:

September 2021 134 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

unit ex_mtm_layering_ul_pkt_bfm_u like uvm_bfm {


driver_locker : locker;
down_item_layer_transfer :
in method_port of item_layer_transfer is instance;

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.

Upper-Layer Layered Sequence


Layering can be transparent to the upper layer sequences. They would do items without caring that
the items are passed to a lower layer. This section describes some enhancements that can be
added for creating interesting scenarios.
This section contains:
Using Unique ID
Constraining Item Length

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.

September 2021 135 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

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:

extend LAYERED_LONG ex_otm_layering_packet_sequence {


upper_layer_id: uint;
is_relevant(): bool is only {
result = (upper_layer_id == driver.stream_id);
};
};

The lower layer sequence gets items from the upper layer driver, specifying the ID:

extend LAYERED ex_otm_layering_atm_sequence {


atm_id: uint;
atm_header : uint (bits:40);
// ATM cells are 5 bytes header and 48 bytes cell

body() @driver.clock is only{

var continue_work: bool;


continue_work = TRUE;

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);

// Fragmentation computation and generation of the ATM cell


// ...

}:
{
wait [ATM_TIMEOUT];
continue_work = FALSE;
};
};
};
};
};

The virtual sequence that activates both sequences should assign the matching IDs:

September 2021 136 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

extend MAIN system_sequence {

body() @driver.clock is only {


all of {
{
do LAYERED atm_sequence keeping {
.atm_id == 1;
};
};
{
do LAYERED packet_sequence keeping {
.upper_layer_id == 1;
};
};
};
};
};

Constraining Item Length


When the lower-layer sequence requests next item, it passes as a parameter the number of bytes it
needs to complete the current data item. The upper layer can use this value in constraints. For
example, the upper layer can use the value to force fragmentation of the item.
The following sequence has two constraints. The first constraint guarantees that the upper layer
does not generate a data item that is outside the boundaries of the lower layer. The second
constraint specifies that when remaining_bytes is less than the minimum possible length (according
to the upper-layer protocol), the generated data item will be of a special type, ZERO_P (an empty item,
signaling the lower layer to send the current data item as is).

extend LAYERED ex_mto_layering_ul_pkt_sequence {

body() @driver.clock is {
do ul_pkt keeping {
.len <= driver.remaining_bytes;

driver.remaining_bytes < UL_MIN_SIZE =>


.length_range == ZERO_P;
};
};
};

September 2021 137 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

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

Virtual Sequence Activating the Layered Sequences


The system contains a virtual driver that activates the layered sequences.
The following code is taken from the Many-to-One example. Almost identical code exists in all of the
examples.

September 2021 138 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

sequence system_sequence using created_driver = system_driver_u;

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;

event clock is only @sys.any;

get_sub_drivers(): list of any_sequence_driver is {


return ({ul_pkt_driver; ll_pkt_driver});
}; // get_sub_drivers...
};

Note: In the Many-to-Many example, there are four and not two drivers. All of them must be
accessed from the virtual driver.

Instantiating and Connecting the Components


The main task of the system UVC is to instantiate the interface UVCs and connect them. The
system UVC also connects their drivers to the virtual driver.
The following code is taken from the Many-to-One example. Almost identical code exists in all of the
examples.

September 2021 139 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

unit system_env_u like uvm_env {

ll_pkt_env : ex_mto_layering_ll_pkt_env_u is instance;


ul_pkt_env : ex_mto_layering_ul_pkt_env_u is instance;

system_driver: system_driver_u is instance;

keep system_driver.ul_pkt_driver == ul_pkt_env.agent.driver;


keep system_driver.ll_pkt_driver == ll_pkt_env.agent.driver;

// Binding the layers together


keep bind (ll_pkt_env.agent.driver.get_item_layer_transfer,
ul_pkt_env.agent.bfm.down_item_layer_transfer);

};

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.

Sequence Library and Writing Tests


In layered environments, a typical test scenario requires doing in parallel sequences of all layers.
To assist test writers, Cadence recommends defining basic virtual sequences in the system UVC. In
this way, test writers do not need to remember how to run the lower and upper sequences in
parallel.

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.

September 2021 140 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

extend MAIN system_sequence {

body() @driver.clock is only {


all of {
{
do LAYERED atm_sequence keeping {
.atm_id == 1;
.atm_header == 2;
};
};
{
do LAYERED packet_sequence keeping {
.upper_layer_id == 1;
};
};
};
};
};

One Lower Layer Serving Multiple Upper Layers


The following code is an example of a scenario in which one lower-layer sequence gets items from
several upper-layer sequences. In this example, the one lower-layer sequence gets items from
eleven upper-layer sequences.
Note: As all of the upper layer sequences are created with the same ID number, all are handled by
the one lower layer sequence.

extend MAIN system_sequence {


body() @driver.clock is only {
all of {
{
do LAYERED ll_pkt_sequence keeping {
.ll_pkt_id == 1;
};
};
{
for i from 0 to 10 {
do LAYERED_LONG ul_pkt_sequence keeping {
.upper_layer_id == 1;
};
};
};
};
};
};

Two Scenarios Running in Parallel

September 2021 141 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

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.

extend MAIN system_sequence {

body() @driver.clock is only {

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;
};

do LAYERED_SHORT packet_sequence keeping {


.upper_layer_id == 1;
};

do LAYERED_LONG packet_sequence keeping {


.upper_layer_id == 1;
};
};
{
do LAYERED frame_sequence keeping {
.ll_id == 2;
.destination_address == 0x330033003300;
.source_address == 0x440044004400;
};
};
{
do LAYERED packet_sequence keeping {
.upper_layer_id == 2;
};
};
};
};
};

September 2021 142 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

High Performance Sequences


Using the methods api to the driver allows dramatically increase of the verification environment
performance, instead of the do action. There are two main ways to increase performance, and they
can be combined.
1. Reducing The Number of Generations`
2. Reducing Number of Context Switching - Queuing the Items
Note: The methods api is supported starting Specman 8.2

Reducing The Number of Generations


Constraints-driven generation is a complex task, which, for complicated data items, might take a lot
of resources, both CPU and memory. Consider creating the data items by means other than
generation, to improve the environment performance. You can create the item using copy(), or, for
simple items with only few fields, you can also create the item using new and assigning the item’s
fields procedurally. The following sequence demonstrates sending items that are created
using copy(). An item is generated once, and then a copy of it is sent 50 times to the driver.

extend SEND_SAME_ONE xbus_master_sequence {


body() @driver.clock is only {
// Generate trans once at beginning of run, so that
// it is created based on the UVC constraints.
// Then, send a copy of it to the driver many times.
// Note that we send a copy and not the item itself,
// in case it is modified during its send.
gen trans keeping {.driver == driver; .read_write == READ};

// There is a synchronization with the driver -


// After item is sent, check its data
var cur_trans : MASTER xbus_trans_s;
for i from 0 to 49 {
cur_trans = trans.copy();
driver.wait_for_grant(me);
driver.deliver_item(cur_trans);
driver.wait_for_item_done(cur_trans);
// check cur_trans data ...
};
};
};

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

September 2021 143 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

Reducing Number of Context Switching - Queuing the Items.


Note: The sequences methods API is supported starting Specman 8.2.

Reducing Number of Context Switching - Queuing the Items


You can increase CPU performance by reducing the number of switches between the driver and the
sequence, using driver.queue_item(). Using do or deliver_item requires several interactions
between the sequence and the driver, performing the synchronization protocol between them.
When synchronization is not required, you can send many items to the driver all at once. Once the
driver receives the items, it can continue independently, without continuous communication to the
sequence.
A few things to note when using queue_item():
As opposed to deliver_item(), after an item is queued, one cannot tell when it is being sent.
It can be many cycles after queue_item() was issued. Be careful and do no override the item’s
fields before you are sure it has been sent (wait for item_done, for example).
It can happen that the sequence finishes before all the items it queued had been executed.
This might result, for example, by the sequence calling drop_objection and causing the test
to stop (while there are still items in the queue). Or, when using the testflow, the unit might
proceed to next phase, as it assumes the phase activities are finished.
The XBus UVC contains one test demonstrating this feature, test_high_performance_no_gen.e.
The following is a snippet of this test sequence:

extend SEND_SAME_ONE xbus_master_sequence {


body() @driver.clock is only {
// Generate trans once at beginning of run, so that
// it is created based on the UVC constraints.
// Then, send a copy of it to the driver many times.
// Note that we send a copy and not the item itself,
// in case it is modified during its send.
wait [10] * cycle;
gen trans keeping {.driver == driver; .read_write == READ};

// Fastest - no synchronization between sequences and driver -


// 50 copies of the item are put in the queue. "send and forget"
for i from 0 to 49 {
driver.queue_item(me, trans.copy());
};
};
};

Note: The sequences methods API is supported starting Specman 8.2s2.

September 2021 144 Product Version 21.09


UVM e User Guide
Sequences: Constructing Test Scenarios

Miscellaneous Advanced Features


Cadence recommends reading about the following advanced features in the sequences chapter
within Creating e Testbenches in the Specman documentation:
Creating a Common Base for Your Sequences and Sequence Drivers
Applying Default Behavior When No Item Is Done
Synchronization within a Cycle
Quitting Sequences and Sequences Items

September 2021 145 Product Version 21.09


UVM e User Guide
Using the UVM Registers (vr_ad)

Using the UVM Registers (vr_ad)

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

September 2021 146 Product Version 21.09


UVM e User Guide
Using the UVM Registers (vr_ad)

Updating the Shadow Model After READ/WRITE


a Register
The shadow model, the e register file, is used by the verification environment throughout the test for
checking, synchronization of activities, coverage, and more. Cadence recommends updating the
shadow model whenever there is a change in the register’s value, to maintain it as identical to the
values of the registers in the DUT whenever possible. The update of the shadow model should take
place only after the registers in the DUT have been modified. This means that the component
performing the update should be the monitor, and only after it receives the signals on the line.
Sometimes, the update should be performed after a delay based on the DUT specification and the
time it takes the written values to propagate in the system.
The UVC monitor should take note of READ/WRITE accesses to the registers, and update the
reference model accordingly. Updating the shadow model is done using these methods:
After WRITE: call update()
After READ: call compare_and_update(), which updates the model and also compares the
new value to the previous value.
Note: In backdoor accesses, which is not seen by the monitor, the vr_ad backdoor method updates
the shadow model right after it accesses the RTL.
The following code is taken from the XCore sample UVC. The XCore monitor is notified by the bus
monitor of any access to the DUT. If the access is legal, it updates its register file. (The
monitor’s legal_access method is implemented in same file, and not shown here.)
The full code is in uvm_examples/xcore/e/xcore_ref_model.e.

September 2021 147 Product Version 21.09


UVM e User Guide
Using the UVM Registers (vr_ad)

unit xcore_monitor_u like uvm_monitor {


-- A TLM port for getting XBus transfers
xbus_transfer_ended : in interface_port of tlm_analysis of
MONITOR xbus_trans_s
using prefix=xbus_ is instance;

-- Implementation of the in port, called by the XBus monitor


-- Update the reg_file after every bus transaction
xbus_write(transfer : MONITOR xbus_trans_s ) is also {
if legal_access(transfer) {
if transfer.read_write == WRITE then {
reg_file.update(transfer.addr - base_address,
%{transfer.data},{});
} else {
compute reg_file.compare_and_update(transfer.addr - base_address,
%{transfer.data});
};
};
};

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.

Multiple BFMs Environment


The vr_ad_driver does not have a BFM of itself. Rather it passes register operations to one of the
BFM drivers in the environment, and that driver performs the access with its BFM. When there is
one bus in the environment, you should define this one bus as the default bus for performing
register accesses, constraining the vr_ad_sequene_driver default_bfm_sd field.
When there are multiple buses in the environment, you can switch between them during the run as
follows:
To choose the bus for a specific register access, constrain the dest_driver field in
the read_reg/write_reg marco.
To choose the bus for a specific register sequence, constrain the dest_driver field of the
sequence.
To choose the bus for all future registers transactions, set the default_bfm_sd field of
the vr_ad_sequence_driver.

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

September 2021 148 Product Version 21.09


UVM e User Guide
Using the UVM Registers (vr_ad)

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 {

// The driver to drive this sequence to the DUT is c_bus driver,


// the default driver
do write_read_seq;

// The driver to drive this access to the DUT is c_bus driver,


// the default driver
write_reg tx_mode_reg keeping {.dest == 1};

// The driver to drive this sequence to the DUT is z_bus driver,


do write_read_seq keeping {.dest_driver == driver.ex_z_bus_driver};

// The driver to drive this access to the DUT is z_bus driver,


write_reg {.dest_driver == driver.ex_z_bus_driver}
tx_mode_reg keeping {.dest == 1};
};
};

See the UVM e Reference manual for more information about:


The default_bfm_sd, in the section vr_ad_sequence_driver.
Bus BFM implementing register access, the vr_ad_execute_op() method, in the section
Integration of the Register Model

Using the vr_ad Sequence Driver


The vr_ad_sequence_driver is defined in the vr_ad package; its sequence item
is vr_ad_operation.
To perform frontdoor read/write accesses (via the bus), the vr_ad_sequence_driver calls the
default BFM sequence driver defined in the field default_bfm_sd, as explained in Integrating
the RSD with the BFM Sequence Driver in the UVM e Reference.
Backdoor accesses are performed by the vr_ad_sequence_driver as explained in Backdoor
Operations in the UVM e Reference.

September 2021 149 Product Version 21.09


UVM e User Guide
Using the UVM Registers (vr_ad)

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;
'>

When the vr_ad_sequence_driver is defined as a Testflow sequence driver (sequencer), the


following configurations are applied:
Does not define UVM_ZERO_TIME_EMPTY_PHASE.
set_next_phase_in_same_cycle is FALSE, assuming most environment do not want this mode

No objection drain time


tf_to_clean_previous_bfm_call() is FALSE

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.

Using the vr_ad Pre-Defined Sequences Library


You can use the sequences defined in the vr_ad sequences library in your tests. The code below is
an example:

September 2021 150 Product Version 21.09


UVM e User Guide
Using the UVM Registers (vr_ad)

extend MAIN vr_ad_sequence {


!read_all : uvm_read_all_regs vr_ad_sequence;
!write_and_read : VR_AD_READ_ALL_REGS vr_ad_sequence;
!write_and_read : VR_AD_READ_ALL_REGS vr_ad_sequence;
!write_read_all : VR_AD_READ_A_REG vr_ad_sequence;
my_env: my_env; // To be constrained by driver
body() @driver.clock is only {
do read_all keeping {.reg_file == my_env.reg_file};
do write_and_read keeping {.reg_kind == REG0};
do write_read_all keeping {.skip_regs == {REG0; REG2}};
};
};

See the UVM e Reference manual for more information about:


The pre-defined sequences in the section Pre-Defined_Sequence_Library.

Extensions of vr_ad Driver and vr_ad


Sequences
When extending any of the vr_ad types, take into account that your UVC might be integrated in a
system verification environment including another vr_ad, using UVCs.
Cadence recommends adopting several coding guidelines to avoid name space pollution:
When extending the vr_ad_sequence_kind, use a UVC prefix.
When adding fields to the vr_ad_sequence_driver or vr_ad_sequence, consider adding them
under a UVC specific subtype
Some recommendations when defining a UVC specific subtype of vr_ad types:
Add a similar field in all vr_ad types (for instance, vr_ad_sequence_driver and
vr_ad_sequence).
The default value of the new subtype field should be FALSE, and set to TRUE only in the
specific containing unit.
The following code is taken from the vr_ad/examples/vr_ad_name_space.e example. This example
contains two env units; each has its own address map and an instance of
the vr_ad_sequence_driver (RSD). For the RSD of env #2, we define a new
method: post_access_checks().

September 2021 151 Product Version 21.09


UVM e User Guide
Using the UVM Registers (vr_ad)

extend vr_ad_sequence_driver {
env_2 : bool;
keep soft env_2 == FALSE;
keep sequence.env_2 == env_2;

when env_2 vr_ad_sequence_driver {


post_access_checks() is {
message(LOW, "Doing checks ...");
// Do some checks ...
};
};
};
unit env_2 like uvm_env {
addr_map : vr_ad_map;
reg_driver : vr_ad_sequence_driver is instance;

keep reg_driver.env_2 == TRUE;


};
extend vr_ad_sequence {
env_2 : bool;
keep soft env_2 == FALSE;
};
extend MAIN env_2 vr_ad_sequence {
body() @driver.clock is only {
driver.raise_objection(TEST_DONE);
message(LOW, " I am env_2 sequence ");
// Assume I am doing some read & write accesses ...
wait [100] * cycle;
driver.as_a(env_2 vr_ad_sequence_driver).post_access_checks();
driver.drop_objection(TEST_DONE);
};
};

Register Broadcast—Data Propagation


There are cases in which data written to one register is propagated to other registers in the system.
In such cases, the shadow model— the vr_ad_reg_file in e—should function in the same manner
as the DUT. When a data is written to the broadcasted register, the target registers should be
updated accordingly.
Implementing this behavior takes advantage of the vr_ad indirect access feature. The steps for
implementing a broadcasting effect are:
1. Define the target registers as attached to the broadcasted register.
2. Extend the indirect_access() method of the target registers. This method is called whenever
the attached register, the broadcasted register, is accessed.

September 2021 152 Product Version 21.09


UVM e User Guide
Using the UVM Registers (vr_ad)

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.

// Attaching the target register file to the broadcasted register


extend ex_c_bus_env {
post_generate() is also {
xcore_regs.vr_ad_rx_data.attach(xbus_regs);
};
};
// Implement the broadcast:
// When writing to register VR_AD_RX_DATA in XCORE vr_ad_reg_file,
// propagate the value to the VR_AD_XBUS_DATA register in ACTIVE_XBUS.
extend ACTIVE_XBUS vr_ad_reg_file {
indirect_access( direction : vr_ad_rw_t, ad_item : vr_ad_base) is {
if ad_item is a VR_AD_RX_DATA vr_ad_reg (d) {
vr_ad_xbus_data.write_reg_val(d.get_cur_value());
};
};
};

Multiple Register Instances


Occasionally, the same register type is instantiated in several places. For example, a PCI virtual
channel configuration register may be replicated to match the number of channels in the DUT.
There can be multiple instances of a register in one register file, or multiple instances of the register
file itself.
When accessing a register for which there is more than one instance, you must specify the required
instance using the static_item field of the vr_ad_operation.
The UVM e Reference manual contains detailed description and code example of defining and
accessing multiple instances of same register. See the following sections in that manual:
Defining Multiple Instances of a Register Type
Writing to a Specific Instance of a Register Type

September 2021 153 Product Version 21.09


UVM e User Guide
Using the UVM Registers (vr_ad)

“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

September 2021 154 Product Version 21.09


UVM e User Guide
Using the UVM Registers (vr_ad)

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;

keep soft main_reg_file.size == 256;

post_generate() is also {
// Set a notification between the proxy_reg and TWIN_REGS
main_reg_file.ex_proxy_reg.attach(unmapped_regs);

// Set the name of the INDIRECT sequence to execute


unmapped_regs.set_indirect_seq_name(ACCESS_TWIN_REG);

// Add the files. One mapped, one - isn't


addr_map.add_with_offset(0, main_reg_file);
addr_map.add_unmapped_item(unmapped_regs);
};
};
extend TWIN_REG vr_ad_reg_file {
// This method is called whenever the proxy register is accessed.
// Implemented by user, to update automatically the appropriate
// twin register
indirect_access( direction : vr_ad_rw_t, ad_item : vr_ad_base) is {
if direction == WRITE {
write_access_reg.update(0, pack(packing.high, ad_item),{});
} else {
ad_item.as_a(vr_ad_reg).write_reg_raw(0,
read_access_reg.read_reg_val(), ~0);
};
};
};
// Indirect register sequence.
// This sequence is called whenever there is a write_reg or
// read_reg of one of the registers in the twin_regs register file.
//
// The sequence should read/write the proxy register.
extend vr_ad_sequence_kind : [ACCESS_TWIN_REG];
extend ACCESS_TWIN_REG INDIRECT vr_ad_sequence {
!proxy_reg : EX_PROXY_REG vr_ad_reg;
body() @driver.clock is only {
if direction == WRITE {
write_reg proxy_reg val reg.read_reg_val();
} else {
read_reg proxy_reg;
reg.write_reg_val(proxy_reg.read_reg_val());
};
};
};

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.

September 2021 155 Product Version 21.09


UVM e User Guide
Using the UVM Registers (vr_ad)

Accessing Unmapped Addresses


A frequent request from systems integrators is to check that the bus provides some kind of error
checking to handle un-used addresses. The handling might vary from simply returning an error
response on the bus to halting the errant CPU or rebooting the system. The user's challenge is to
write a simple test that knows how to find a random un-used location to test. It can be frustrating to
have to maintain a specific test, as address maps evolve. Because vr_ad already knows about the
free and used addresses, it is possible to write a simple and robust vr_ad sequence that can find
unmapped addresses with no further maintenance required.
To support tests to access unmapped addresses:
Use the alloc_from_set() API method, to find unmapped address in the vr_ad_map
free_addrs set.

Optional: extend vr_ad_exectue_op() for special handling of accesses to unmapped address.


The free_addr set is built in into the vr_ad_map. But you can create your own set of unmapped
addresses. For more information, see Address Management in the UVM e Reference manual.
When reading from and writing to an unmapped address, a message is displayed informing you that
an unmapped address is being accessed. The message uses the VR_AD_MSG tag, and is
of MEDIUM verbosity.
The following code is copied from the example vr_ad/examples/vr_ad_unmapped_address.e.

September 2021 156 Product Version 21.09


UVM e User Guide
Using the UVM Registers (vr_ad)

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);
};
};
};

extend MAIN vr_ad_sequence {


!unmapped_region : vr_ad_set;
body() @driver.clock is only {
var data : list of byte;

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

write_mem unmapped_region.get_address() 0x33;


read_mem unmapped_region.get_address()
data unmapped_region.get_size();
};
};

read_reg/write_reg from any TCM


This feature allows performing register access from any TCM, rather than only from within a
sequence body.
To call read_reg or write_reg from any TCM, constrain the driver field of the vr_ad_operation to be
the relevant instance of vr_ad_sequence_driver. (Typically, there is one instance of the vr_ad driver
in each environment.)
For example:

September 2021 157 Product Version 21.09


UVM e User Guide
Using the UVM Registers (vr_ad)

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.

Performance Improvement on Demand—Static


Info Creation
The following feature reduces memory consumption, thus increasing performance.

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.

Option to Reduce Memory


Using the new option described here, static_info structs are not created in advance of when the
memory model is created. Instead, static info is created at the first time a register is accessed.
Users should define whether they want this new behavior by adding a #define before loading vr_ad.
The two options are:
VR_AD_ON_SETUP_STATIC_INFO
Creates static_info structs at beginning, when the whole environment is created. (This is the
old behavior.)
VR_AD_ON_DEMAND_STATIC_INFO

September 2021 158 Product Version 21.09


UVM e User Guide
Using the UVM Registers (vr_ad)

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.

Accessing Registers by Reference


The usual method for performing write_reg and read_reg is to define a field of the accessed
register type. This feature allows accessing a register by passing a reference to it.
When accessing a local variable or a !field, the var/field gets updated (no change from old
behavior).
However, you can also access a reference to the reg_file, and the reg file itself is not updated. The
register file should be updated by monitor, following current methodology of shadow model being
updated by the monitor, using the *update* methods.
For example:

extend INITIALIZTION my_sequence {


body() @driver.clock is also {
write_reg me.env.reg_file.ctrl_reg val 0x1234;
};
};

September 2021 159 Product Version 21.09


UVM e User Guide
Using the UVM Registers (vr_ad)

Configuring Coverage for vr_ad


vr_ad provides the means to collect coverage of the registers and memory accesses.

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.

The following code is taken from the vr_ad/examples/vr_ad_coverage_demo.e:

// cov_r : collect coverage only on READ accesses


reg_def TEST_COV_R TEST_REG_FILE 0x0 {
reg_fld fr : vr_ad_demo_type : RW : A : cov_r;
};
// cov_w : collect coverage only on WRITE accesses
reg_def TEST_COV_W TEST_REG_FILE 0x4 {
reg_fld fw : vr_ad_demo_type : RW : A : cov_w;
};
// cov_rw : collect coverage on all accesses,
// and cross by direction -
// READ/WRITE will get different values
reg_def TEST_COV_RW TEST_REG_FILE 0x8 {
reg_fld frw : vr_ad_demo_type : RW : A : cov_rw;
};
// cov : collect coverage on all accesses
reg_def TEST_COV TEST_REG_FILE 0xc {
reg_fld f : vr_ad_demo_type : RW : A : cov;
};

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

September 2021 160 Product Version 21.09


UVM e User Guide
Using the UVM Registers (vr_ad)

when calling the vr_ad methods.


reg_updated_cover is collected AFTER applying the register's masks. This cover group
reflects the expected values of the register after the access.
If, for example, the register field is defined as RC (clear after read), the reg_access cover group
collects the value read from the register, while the reg_updated_cover cover group collects the
value “0” (the value after the field is cleared).
To disable the automatic coverage provided by reg_access and reg_updated_cover,
define VR_AD_NO_AUTO_COVER before loading vr_ad. For example:

<'
#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;
'>

vr_ad Coverage Per Instance


Coverage per instance is based on the containing unit. In the vr_ad structure, there are no units;
the vr_ad_reg_file is a struct. Thus, you cannot add “using per instance” to
the vr_ad_reg coverage group.
You can, however, alternatively cross the covered register with its containing register file. You can
use the utility r_ad/utils/reg_file_cov/vr_ad_cov_tool to implement this coverage. This section
describes how to run the demo that illustrates this capability.
The demo is in vr_ad/examples/vr_ad_cover_per_instance_demo.e. To run the demo, follow these
steps:

September 2021 161 Product Version 21.09


UVM e User Guide
Using the UVM Registers (vr_ad)

1. Create the per-instance code with following command:

% specman -c 'load vr_ad/examples/vr_ad_cover_per_instance_demo; \


load vr_ad/utils/reg_file_cov/vr_ad_cov_tool; gen; \
vr_ad_create_reg_files_cov("demo_reg_file_cover.e")'

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:

% specman -c 'load vr_ad/examples/vr_ad_cover_per_instance_demo;


load vr_ad/examples/demo_reg_file_cover; test'

You can then view the collected coverage, per type as well as “per instance”, for each env
unit.
Following is the generated code:

September 2021 162 Product Version 21.09


UVM e User Guide
Using the UVM Registers (vr_ad)

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.

Configuring vr_ad Messages


vr_ad provides debugging messages that inform you of activities executed by the package.
Most vr_ad messages are triggered by the update(), fetch(), and compare_and_update() methods,
which are called when there is a read or write access. Messages are also triggered during memory
allocation to report allocation of segments in vr_ad_mem.
This section describes how to control which messages are to be printed by vr_ad:

September 2021 163 Product Version 21.09


UVM e User Guide
Using the UVM Registers (vr_ad)

Configuring vr_ad Message Verbosity with Message Tags


Configuring Message Verbosity with the vr_ad trace Command
Enabling/Disabling Register Access Messages with the vr_ad add/remove Command
Configuring vr_ad Messages from Within e Code
See Also
An example demonstrating vr_ad messaging control is available in:
vr_ad/examples/ vr_ad_msg_demo.e

Configuring vr_ad Message Verbosity with Message Tags


The messages printed by vr_ad use the following message tags:
VR_AD_MSG—Used to report read/write actions (register access).
The default verbosity of VR_AD_MSG varies depending on where the message originates, as
described below.
VR_AD_ALLOC—Used during memory allocation to report allocation of segments in vr_ad_mem.
The default verbosity of VR_AD_ALLOC is HIGH.

Configuring Register Access Messages


When the verification environment updates or fetches the shadow model, it usually calls the
update/fetch/compare method of vr_ad_map, passing to it the address being read or
written. vr_ad_map calls the update/fetch/compare method of the relevant vr_ad_reg_file, and
the vr_ad_reg_file calls the update/fetch/compare method of the relevant vr_ad_reg—that is, the
register in the accessed address.
The update/fetch/compare methods emit messages for each of these entities. By default:
Messages originating from vr_ad_map and the vr_ad_reg file are HIGH verbosity.
Messages originating from vr_ad_reg are LOW verbosity.
Thus, if you want to see the messages for all these entities, change the verbosity
of VR_AD_MSG to HIGH. The following example ensures that you will see all access messages for the
unit sys.env.sub_env1 in the shadow model:
set message sys.env.sub_env1 -tag=VR_AD_MSG HIGH

September 2021 164 Product Version 21.09


UVM e User Guide
Using the UVM Registers (vr_ad)

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

When verbosity is set to MEDIUM:


[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

Configuring Message Verbosity with the vr_ad trace Command


The vr_ad trace command sets the verbosity of all messages under sys with the message
tag VR_AD_MSG.
In other words, this trace command is a shortcut for the command “set message sys -
tag=VR_AD_MSG verbosity”.

Example:
trace vr_ad MEDIUM

September 2021 165 Product Version 21.09


UVM e User Guide
Using the UVM Registers (vr_ad)

Enabling/Disabling Register Access Messages with the vr_ad


add/remove Command
To enable and disable read/write/compare messages, use the vr_ad add
messages and vr_ad remove messages commands, from Specman prompt.

Following is the command syntax:


vr_ad add|remove read|write|compare messages

For example, to disable all read-related messages (messages reported by the fetch() method):
Specman>vr_ad remove read messages

Configuring vr_ad Messages from Within e Code


message_manager provides an API for configuring messages from within e code.

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

Debugging Tips for Code that Uses vr_ad


Cadence provides several aids for debugging e code that uses vr_ad: an Indago probe-options file
that optimizes probinge code that uses vr_ad and an example testcase that
illustrates vr_ad debugging.
These debugging aids are described in the following sections:
Optimizing Probing with the vr_ad_indago_probe_opts File
Running the vr_ad Debugging Example
Note: For a more in-depth introduction to Indago, run the example described in Introduction to the
Indago Debug Analyzer App in Indago Debug Analyzer App User Guide.

September 2021 166 Product Version 21.09


UVM e User Guide
Using the UVM Registers (vr_ad)

Optimizing Probing with the vr_ad_indago_probe_opts File


The vr_ad_indago_probe_opts probe-options file optimizes probing vr_ad related issues with
Indago. This file filters out methods that are internal to vr_ad and thus not of interest during your
debug process. Use it while collecting the Indago database or probing a test. You can also edit the
file to choose which options you want or to copy and paste from it into your own options file.
This file is in the following location:
<Xcelium-install-dir>/specman/uvm/uvm_lib/vr_ad/utils
/debug/vr_ad_indago_probe_opts

See Also
Probing for Data in Indago Debug Analyzer App User Guide

Running the vr_ad Debugging Example


The vr_ad/utils/debug directory contains an example that you can run to familiarize yourself with
the debugging process.
This section describes how to run the example and provides suggestions for debugging
the vr_ad related code in the example:
Starting the vr_ad Debug Example
Using Root Cause Analysis to Identify the Cause of the Failure
Using Variables Window and the Call Stack to Investigate Values

Starting the vr_ad Debug Example


To run the vr_ad debug example:
1. Define VR_AD_DBG to <install-dir>/specman/uvm/uvm_lib/vr_ad/utils/debug.
2. Run the following command:

> xrun $VR_AD_DBG/my_dut.v $VR_AD_DBG/my_test.e -sncompargs


-enable_DAC -snprerun "ida_probe -flow -log
-f=$VR_AD_DBG/vr_ad_indago_probe_opts; test; indago"

The test executes, and Indago is launched, reporting a DUT error of a compare mismatch.

September 2021 167 Product Version 21.09


UVM e User Guide
Using the UVM Registers (vr_ad)

Using Root Cause Analysis to Identify the Cause of the Failure


The Root Cause Analyzer (RCA) displays possible reasons why the simulation has reached the
current simulation point, and it provides additional leads that show other possible causes that might
have contributed to reaching that point.
Once Indago is launched, you can use RCA to quickly identify the cause of the failure:
1. Start a new RCA investigation.
2. Identify when and how the compare failure occurred and what the current values are
for read_data and ref_data.
The cause of the failure for this test case is the FALSE value of read_data_match_shadow,
which is a result of comparing read_data and ref_data.

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.

Using Variables Window and the Call Stack to Investigate Values


The next step in this example is to look at the two values being compared:
The data read from the bus (bus_data, read_data),
The current value of the register in the shadow model (ref_data).

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.

September 2021 168 Product Version 21.09


UVM e User Guide
Using the UVM Registers (vr_ad)

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

Using Power Awareness in vr_ad


For implementing vr_ad behavior upon power change, you can use the following fields
of vr_ad_reg_file and vr_ad_map_file: events power_up, power_down and power_standby, and
the flag power_is_on.
A monitor (for example, the monitor of the uvm_lp UVC) should emit the vr_ad events upon power
change.
The vr_ad/examples/power_aware directory contains a code example that
demonstrates vr_ad power aware usage. The code example is
in vr_ad_power_aware_example_top.e, and the following extracts are taken from that example:

September 2021 169 Product Version 21.09


UVM e User Guide
Using the UVM Registers (vr_ad)

Example: Getting the power indicators from a uvm_lp

<'
extend vr_ad_map {
!uvm_lp_domain_monitor : uvm_lp_domain_monitor;

event power_down is only @uvm_lp_domain_monitor.lp_power_down;


event power_up is only @uvm_lp_domain_monitor.lp_power_up;
event power_standby is only @uvm_lp_domain_monitor.lp_power_standby;
};
'>

Example: Reset registers when power is off

<'
extend vr_ad_map {
on power_down {
reset();
};
};
'>

Example: Avoid/postpone an access if the domain is powered off


Note that this example assumes that there is one address space so this sequence handles only one
map.

<'
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

September 2021 170 Product Version 21.09


UVM e User Guide
Using Testflow Phases

Using Testflow Phases

This chapter contains the following sections:


Testflow Overview
Using Testflow
Typical Roles of Each Phase
Sequences Using Testflow
Multiple-Domain Environments
Using Testflow in Reactive Agents
Clocking Issues
Reset Methodology
Adding Testflow to an Existing UVC
Mixing Testflow & Non-Testflow UVCs
Writing Tests Using Testflow
Using Reflection API to start and stop expects

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.

September 2021 171 Product Version 21.09


UVM e User Guide
Using Testflow Phases

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.

Unit Testflow Phase Activities


For each testflow phase, units have the following predefined constructs:
Phase TCMs
Phase Sequences
End-of-Phase Drain Time
Empty Phases and Backwards Compatibility with UVM_OLD_EMPTY_PHASING

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.

September 2021 172 Product Version 21.09


UVM e User Guide
Using Testflow Phases

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:

September 2021 173 Product Version 21.09


UVM e User Guide
Using Testflow Phases

extend MAIN ENV_SETUP xbus_master_sequence {


-- Raise an objection to TEST_DONE when ENV_SETUP phase is started.
pre_body() @sys.any is first {
message(TESTFLOW_EX, LOW, "MAIN ENV_SETUP sequence started");
if prevent_test_done {
driver.raise_objection(TEST_DONE);
};
}; -- pre_body()
};
extend MAIN POST_TEST xbus_master_sequence {
drain_time : uint;
keep soft drain_time == 10;
post_body() @sys.any is also {
message(TESTFLOW_EX, LOW, "MAIN POST_TEST sequence finished");
if prevent_test_done {
wait [drain_time] * cycle @driver.clock;
driver.drop_objection(TEST_DONE);
};
}; -- post_body()
};

End-of-Phase Drain Time


You can define a drain time that is connected to the Testflow objection to end-of-phase. If, during
the drain time, a new activity occurs that requires a response, the objection is raised again.
The drain-time cycles begin when a phase activity of a unit ends as a result of one of the following:
The TCM for the current phase reaches its end.
A thread registered to the current phase unregisters.
A sequence registered to the current phase unregisters.
The end-of-phase drain time is defined per unit; if, for example, a given unit has three registered
threads, all three must finish before the drain cycles begin.
The current phase does not necessarily end when the drain-time for a given unit ends, because
other components might still be performing activities for the current phase. The current phase ends
only when all components have finished phase-related activity.
Note: You can additionally define an end-of-test drain time, as described in End-of-Test
Mechanism in Creating an e Testbench. Keep in mind that the end-of-phase drain time and the
objection to TEST_DONE are completely independent mechanisms.
Example

September 2021 174 Product Version 21.09


UVM e User Guide
Using Testflow Phases

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

Empty Phases and Backwards Compatibility with


UVM_OLD_EMPTY_PHASING
The UVM testflow utility divides simulation into several phases with generally applicable test-
related intention. These phases provide a standard way to define test scenarios with the required
synchronization infrastructure and utilities. All testflow phases run during the simulation time, right
after the run() method is executed. ‘
An “empty” testflow phase is a phase with no activity to performed in any of the units of the domain.
For example, there are protocols in which there is no INIT_LINK phase.
By default, an empty phase does not consume time. The domain proceeds directly to the next
phase.
Previous to 11.1, however, an empty phase always consumed one cycle. If you are concerned that
there might be backwards compatibility issues with existing UVM e code if this behavior changes,
set UVM_OLD_EMPTY_PHASING. This Specman flag tells the testflow manager to continue having empty
phases consume one cycle:

September 2021 175 Product Version 21.09


UVM e User Guide
Using Testflow Phases

#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

September 2021 176 Product Version 21.09


UVM e User Guide
Using Testflow Phases

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:

unit sample_bfm_u like uvm_bfm {


tf_testflow_unit;
};

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.

Using Multiple Domains


By default, all units belong to the default domain. However, you may define a new domain and
group several units in it.
To use multiple domains:
1. Define a new domain.
For example:
extend tf_domain_t: [MY_BUS_TF];

September 2021 177 Product Version 21.09


UVM e User Guide
Using Testflow Phases

2. Define the units that belong to the new domain.


For example:

extend sample_bfm_u {
keep soft tf_domain == MY_BUS_TF;
};

(Optional) Define dependencies among domains.


For example, if the domains MY_BUS_TF and MY_SYSTEM_TF have been defined, the following
code define dependencies between them:

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 more information, see Multiple-Domain Environments.

Defining Test Scenarios Using Testflow


Once you have defined a unit as using testflow, you can use the unit’s testflow TCMs and
sequences to define the test scenario.
To define a test scenario using testflow:
1. (As required) Extend the appropriate phase TCM to perform activities in a specific phase.
For example:

tf_env_setup() @tf_phase_clock is also {


write_log_header();
};
tf_main_test() @tf_phase_clock is also {
ready = TRUE;
start monitor();
};

September 2021 178 Product Version 21.09


UVM e User Guide
Using Testflow Phases

2. (As required) Use phase sequences to define test scenarios.


For example:

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;
};
};

Typical Roles of Each Phase


Adopting the standard methodology for recommended activities in each phase is important for reuse
and easy integration of UVCs from different projects or sources. Surplus phases can remain empty
if no activity is performed in those phases. Empty phases will be seamlessly skipped.
Table 7.1 describes the recommended purpose for each of the testflow phases.Table 7.1: ​Testflow
Phases

Phase Purpose Examples

ENV_SETUP Environment setup Force signals: Set configuration by the test


control unit.
Bring the DUT to its hardwired
setup.
Perform activities that are
required before the DUT is
powered on.

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.

September 2021 179 Product Version 21.09


UVM e User Guide
Using Testflow Phases

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.

INIT_LINK Negotiation / Driver: Initialize link training or negotiation.


Synchronization
Simulate external link partner.
Synchronize the DUT with
external links/peers.

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.

POST_TEST Checking Monitor: Check the value of registers.


Driver: Read the status registers.

Sequences Using Testflow


To create a testflow sequence definition:
In the sequence definition, enable using testflow.
By default this definition binds the tf_phase_clock event to driver.clock.
Note: Using testflow = TRUE must be the first option in the sequence definition.

Example

September 2021 180 Product Version 21.09


UVM e User Guide
Using Testflow Phases

sequence xbus_arbiter_sequence using


testflow = TRUE,
item = xbus_arbiter_decision_s,
created_driver = xbus_arbiter_driver_u;

Typical Activities in Testflow Sequences


There is a MAIN sequence for each phase. Table 7.2 describes some typical activities of these
sequences.Table 7.1: ​Typical Testflow Sequence Activities

Sequence Activity

MAIN Define the main test scenario.


MAIN_TEST
Test writers can extend the body and also create a random scenario (for
example, by constraining the count):
extend MAIN MAIN_TEST xbus_master_sequence {
keep count in [10..12];
};

MAIN Raise an objection to TEST_DONE. For example:


ENV_SETUP
extend MAIN ENV_SETUP xbus_master_sequence {
// Raise an objection to TEST_DONE when
// MAIN ENV_SETUP sequence is started
pre_body() @sys.any is first {
if prevent_test_done {
driver.raise_objection(TEST_DONE);
};
};
};

September 2021 181 Product Version 21.09


UVM e User Guide
Using Testflow Phases

MAIN Drop the objection to TEST_DONE. For example:


POST_TEST
extend MAIN POST_TEST xbus_master_sequence {
// Drop the objection when the
// MAIN POST_TEST sequence ends
post_body() @sys.any is also {
if prevent_test_done {
driver.drop_objection(TEST_DONE);
};
};
};

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};

Rerun Phase in Sequences


When using rerun_phase(new-phase) on a sequence driver, all of its testflow-registered sequences
that started before the new-phase are terminated. (For more information on testflow-registered
sequences, see the Sequence Phase Field section of the UVM e Reference.)
Note: Keep in mind the following: When you use do, the created sub sequence is run under the
virtual sequence-driver. When you use do on, the created sub sequence is run under its relevant
sequence-driver. This is especially meaningful when the sequence-driver undergoes reset.
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. For more information, see
Advanced Issues in Virtual Sequences.
There are two kinds of cleanups that are sometimes required upon rerun_phase.
1. Clean up (reset flags indicating state) of remains of get_next_item that was called before
the rerun_phase. This cleanup is required if the BFM restarts after the rerun. When this

September 2021 182 Product Version 21.09


UVM e User Guide
Using Testflow Phases

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:

tf_to_clean_previous_bfm_call(next_phase: tf_phase_t) : bool is {


result = TRUE;
};
tf_to_clean_do_queue(next_phase : tf_phase_t) : bool is {
result = FALSE;
};

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.

// BFM starts in HARD_RESET.


// rerun_phase to phase <= HARD_RESET - should clean driver.
// rerun_phase to phase later phases - should not clean the queue
extend my_item_driver {
tf_to_clean_previous_bfm_call( next_phase: tf_phase_t) : bool is also {
if next_phase.as_a(int) <= 1 then {
// clean, last get_next_item was cut
result = TRUE;
} else {
// do not clean, bfm not being restarted
result = FALSE;
};
};
};

The following example shows how to request a removal of all pending do’s.

September 2021 183 Product Version 21.09


UVM e User Guide
Using Testflow Phases

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.

extend tf_domain_t: [xBUS_TF];


extend xbus_agent_u {
keep tf_domain == xBUS_TF;
};

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).

Multiple Domains in SoC


System-on-Chip verification environments typically involve several agents from the same UVC. For
example, an active master controls the simulation, an active slave simulates a participating block,
and a passive agent monitors. In addition, usually there are also agents for other UVCs. Figure
7.1 shows an example SoC verification environment.

Figure 7.1: ​Testflow Phases in SoC

September 2021 184 Product Version 21.09


UVM e User Guide
Using Testflow Phases

Figure 7.1: ​Testflow Phases in SoC

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.

September 2021 185 Product Version 21.09


UVM e User Guide
Using Testflow Phases

Multiple Domains in Multi-Layered Protocols


Multiple layered protocols typically involve different agents for each of the layers. There is a cross-
dependency of layer and port that results in domains containing components from multiple UVM
environments. Figure 7.2 shows an example of a system verification environment that has three
interface UVCs — TCP, IP, and ETH. Each env has two instances of agent. The first instance is
connected to port A and belongs to Domain 1. The second instance is connected to port B and
belongs to Domain 2. Agent instances in the same domain are synchronized.
Figure 7.2: ​Testflow Phases in a Layered Environment

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.

September 2021 186 Product Version 21.09


UVM e User Guide
Using Testflow Phases

Synchronization can be declared unilaterally or bilaterally.

Unilateral Synchronization Declaration Example


Domain A will not advance beyond phase P1 until domain B is done with Phase P2. Domain B may
be in waiting status for phase P2 or in a more advanced phase. This kind of synchronization is
typical for hierarchical environments. For example, an active master will not go into
the main_test phase until all slaves are out of reset.

extend sys {
connect_pointers() is also {
tf_add_dependency(XSERIAL_TF,RESET,XBUS_TF,RESET);
};
};

Bilateral Synchronization Declaration Example


A group of domains will not advance beyond phase P1 until all domains have finished their
activities for phase P1. This kind of synchronization is typical for kin in an environment. For
example, two link-partner UVCs will go into init_link after both have finished the init_dut phase.

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);
};
};

Using Testflow in Reactive Agents


Verification environments contain processes of two types:
Test scenario processes — Usually these processes involve an active master or virtual
sequences. These processes have clear starting and ending points, and therefore they are
easily adapted to testflow methodology.
Ongoing slave processes — These processes typically include Pull BFMs, slave sequences,

September 2021 187 Product Version 21.09


UVM e User Guide
Using Testflow Phases

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:

tf_main_test() @tf_phase_clock is also {


start drive_responses();
message(LOW,"drive_responses");
tf_get_domain_mgr().register_thread_by_name(me, "drive_transfers",
POST_TEST, FALSE);
};

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;
};
};

September 2021 188 Product Version 21.09


UVM e User Guide
Using Testflow Phases

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

Clock Scheme — Defining Different Clocks For Different Phases


There might be environments in which there are changes in clock behavior during the test. For
example, during ENV_SETUP, there might be no simulation clocks at all. During RESET, the
frequency and existence of clocks might change. In these environments, you have to link
the tf_phase_clock to different events (external or internal), according to the phase the unit is going
through. Linking the clock to different events in different phases is called clock switching.
To automate clock switching:
Use the macro CLOCK_SWITCH_SCHEME located in tf_util_clock_switch.e.
Note: This macro must be called only after all test phases and their sampling events have
been defined. It is preferable to call the macro in a clock configuration file. That facilitates
reuse of the testflow unit.
Syntax
CLOCK_SWITCH_SCHEME {first-phase-name[;other-phase-name]} {initial-clock-
event[;additional-clock-event]};

Example

September 2021 189 Product Version 21.09


UVM e User Guide
Using Testflow Phases

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);

September 2021 190 Product Version 21.09


UVM e User Guide
Using Testflow Phases

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()

Threads registered to RESET, INIT_DUT, INIT_LINK and MAIN_TEST


MAIN MAIN_TEST <sequence>

Sequences registered to RESET, INIT_DUT, INIT_LINK and MAIN_TEST


One of the results of rerunning a phase is that a few phases are executed more than once in a test.
In the example of rerunning RESET during MAIN_TEST, the phases RESET, INIT_DUT,
INIT_LINK and MAIN_TEST are executed twice. There are activities that have to be performed only
in some of the executions, specifically, calling the rerun_phase(). If the rerun_phase() will be issued
in every entrance of MAIN_TEST, the test will never end.
The following code shows an example of rerunning to RESET twice during 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();
};
};
};

September 2021 191 Product Version 21.09


UVM e User Guide
Using Testflow Phases

Rerun and Objections


When issuing rerun_phase(), the phase TCMs of all participant units are halted, but might be
restarted the next time the domain reaches the current phase. If one of these TCMs raised an
objection, the objection is not dropped.
Therefore, consider extending the skip_to_phase() method of the tf_domain_manager, so as to drop
objections before performing the rerun.

Reset Activities: Causing Reset, Responding to Reset


Implementation of the component’s reset phase, can contain two parts:
1. Responding to a reset
2. Performing reset on the DUT
Implementing the first part, responding to reset, typically requires initialization of the internal
database such as counters or flags.
A few components should also include the second part: performing a reset of the DUT. Usually this
means writing to some signal of the DUT, and waiting for the reset process to finish. Note that even
components that can reset the DUT shouldn’t always do so. For example, perhaps the reset phase
is started by the monitor after it recognized a HW reset. Or, perhaps the DUT just had been reset by
another component of the verification environment. The code implementing the reset of the DUT
might need to include a check of current DUT status, preventing few components performing reset
one after the other in the same cycle.
Note that in some verification environments, even the HARD_RESET phase (typically defined as
recognizing reset performed by the HW) is performed by a VE component, for example, a BFM
emulating a piece of the HW not yet integrated.

rerun_phase() and rerun()


Before the introduction of rerun_phase(), the rerun() method was used to perform reset during the
test. However, before calling the rerun() method, rerun() kills all the struct’s threads, including the
phases’ TCMs. Therefore, rerun() should no longer be used.
There are two main advantages of rerun_phase() over rerun():
1. Finer tuning; you can go back to any phase not just to “cycle 0” - HARD_RESET, RESET, etc.
2. All units in same domain are rerun together. There is no need for the user to call any rerun
method of its sub components.

September 2021 192 Product Version 21.09


UVM e User Guide
Using Testflow Phases

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.

Reset Synchronization among UVCs


In a typical verification environment (VE), there are at least two UVCs, and some synchronization of
the reset performed in them is required. There are two main kinds of synchronization requirements:
Synchronization of phases.
For example, two components should perform their reset together. Or, one phase can start its
reset only after another components is done with its own reset.
Activities depending on another component phase
For example, a component should write to a specific address when another component starts
its reset. Or, the scoreboard should avoid transaction sent before reset has finished.
For synchronization of reset between several components, you can use one of the following
constructs, as fits:
If both components use the UVM testflow, define dependencies between the domains. A
dependency is a rule defining that domain A will not proceed beyond phase X, before domain
B is done with its phase Y activities.
A typical rule is having all domains waiting for the control domain to finish its reset
before they start their own reset phase.
Read more about dependencies in Domain Dependencies.
Extend the appropriate phase TCM for performing activities on another component.
For example, in tf_reset(), call the rerun_phase() of another domain or the rerun() of
another unit, if that unit is not using the testflow.
Can use the testflow hook methods for getting notification on changes in a testflow domain
status.
For example, a module UVC can synchronize on the of its sub UVC monitor, even if the
module UVC itself does not using testflow, by
calling monitor.tf_phase_started(RESET)
See more about the hook methods in the Testflow chapter of the UVM e Reference
manual.

September 2021 193 Product Version 21.09


UVM e User Guide
Using Testflow Phases

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.

Adding Testflow to an Existing UVC


You can modify an existing UVC to make it use testflow. This facilitates integration and
synchronization with other UVCs that already use testflow. Adding use of testflow to an existing
UVC mostly requires deletion of code. This section describes the required modifications:
Defining Phase Clocks
Synchronization with Reset
Using the Testflow Sequences
Replacing run()

Defining Phase Clocks


Phase TCMs run on the clocking event tf_phase_clock. You must connect this event to one of the
UVC events, most likely an event that is bound to a clock signal. If there are different clocks for
different phases, Cadence recommends defining the clock using the CLOCK_SWITCH_SCHEME macro.
This allows definition of different clocks for different phases.
Example
When starting ENV_SETUP (the first phase), the phase clock is bound to
synch.unqualified_clock_rise. When starting MAIN_TEST, the phase clock is bound
to synch.clock_rise.

extend ACTIVE MASTER xbus_agent_u {


CLOCK_SWITCH_SCHEME {ENV_SETUP; MAIN_TEST}
{synch.unqualified_clock_rise; synch.clock_rise};
};

Synchronization with Reset


UVCs that do not use testflow have several ways to synchronize with reset, either for starting or
stopping activities upon reset. The main ways employ a custom (user-defined) reset event and
synchronizing on it using wait, synch, or on.

September 2021 194 Product Version 21.09


UVM e User Guide
Using Testflow Phases

To synchronize with reset:


Find the activities in the UVCs that wait, synch, or on reset, and replace the code using the
phase TCM.
For example, you could replace the code with the tf_reset() method or the MAIN RESET
sequence.
Example
The XCore UVC contains a synchronization mechanism for disabling the drivers from running
sequences before reset is done. This includes adding reset events to all drivers in all participating
UVCs (XSerial, XBus, and XCore), propagation of the reset event in the environment, and adding a
sync @driver.device_reset_done to the pre_body of all MAIN sequences. All of this is no longer
required and is therefore removed when adding testflow use. The test scenario is defined in
the MAIN MAIN_TEST sequence and hence will not start before reset is done.
One use of reset is for aborting activities upon reset, typically done using first of. This can now
be achieved using the RUN_IN_PHASES macro as follows.
Old code for the XBus slave

private drive_responses() @synch.clock_rise is {


while TRUE {
// ...
respond_to_transfer(resp);
};
};
private respond_to_transfer(resp : xbus_slave_response_s)
@synch.unqualified_clock_rise is {
first of {
{
// Implement the response protocol...
};
{
// Halt if there is reset.
wait @synch.reset_start;
};
};
};

New code for the XBus slave (using RUN_IN_PHASES)

September 2021 195 Product Version 21.09


UVM e User Guide
Using Testflow Phases

private drive_responses() @tf_phase_clock is {


RUN_IN_PHASES {MAIN_TEST; FINISH_TEST} {
while TRUE {
// ...
respond_to_transfer(resp);
};
};
private respond_to_transfer(resp : xbus_slave_response_s)
@synch.unqualified_clock_rise is {
// Implement the response protocol...
};

Using the Testflow Sequences


In UVCs that do not use testflow, the MAIN sequence defines the scenario of the whole test.
Cadence recommends replacing the MAIN sequence with some phase sequences. This eliminates
the need for synchronization with phase events in the sequence, sequences having to wait for reset,
and so on.
Most tests define the main scenario of the test and do not interfere with performing the reset or
initialization process. Therefore, most tests require use of only the MAIN MAIN_TEST sequence.
Using this sequence instead of the MAIN sequence guarantees that the test scenario starts only
after the device is fully initialized, and test writers do not need to understand the initialization
process.
Note: Remember to soft constrain the MAIN sequence count to 0, so that the MAIN phases
sequences will drive the scenario, and not MAIN.
extend MAIN xbus_master_sequence { keep soft count == 0;};

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.

September 2021 196 Product Version 21.09


UVM e User Guide
Using Testflow Phases

Mixing Testflow & Non-Testflow UVCs


You can define dependencies between UVCs, even when some of them use the UVM testflow, and
some do not.
Any UVC can synchronize on testflow-using UVCs, using the synch_with_phase method, as shown
in this example:

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;
};

Writing Tests Using Testflow


Sequences are the main means for defining test scenarios.
In a typical test suite, there is a mixture of sequences:
Independent-BFM-Sequence—defining activity on one interface of the DUT, independent of
any other UVC.
If the UVC uses testflow, extend its phase sequences (for example,. MAIN
RESET sequence, MAIN MAIN_TEST sequence, etc). If it does not use the testflow, extend
the MAIN sequence.
BFM-Sequence with dependencies—defining activity on one interface of the DUT, with some
dependencies on other UVCs.
If the UVC uses testflow, extend its phase sequences (for example, MAIN
RESET sequence, MAIN MAIN_TEST sequence, etc). If it does not use the testflow, extend

September 2021 197 Product Version 21.09


UVM e User Guide
Using Testflow Phases

the MAIN sequence.


When the sequence depends on activity or status in other UVCs, there are few ways for
defining such dependencies:
a. If both UVC use the testflow, and if the dependency is a general one (for example, reset
of one UVC should not start before the reset of the other UVC ends), define the
dependency using the UVM testflow add dependency API, as demonstrated in Domain
Dependencies.
b. If the UVC you depend on uses the UVM testflow, you can synchronize on its testflow
status using the sync_with_phase method. For example:
xbus_evc.tf_get_domain_mgr().sync_with_phase(INIT_LINK);

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;

Virtual sequences—combining sequences of several UVCs.


If the module/system UVC uses testflow - extend the MAIN phase sequences of the virtual
sequence driver (e.g. MAIN RESET sequence, MAIN MAIN_TEST sequence, etc). If it does not use
the Testflow - extend the MAIN sequence.
If all UVCs use UVM testflow, we assume dependences have been defined in the UVC itself
or as part of the integration, as shown in Multiple-Domain Environments. In such cases, it is
guaranteed that the virtual sequence will run only when all participant UVCs are in right
phase.
If some of the UVCs do not employ UVM testflow, you can still define dependencies.
Dependences can be defined either in a general way - define a phase TCM as waiting for an
event of another UVC. Or - add implicit wait actions in the sequence body. For example:

do env1_reset_seq;
wait @env1_env.reset;
do env2_reset_seq;

Using Reflection API to start and stop expects


You can use two new start and stop methods of rf_expect in the rerun phase mechanism.
Both receive one parameter: the struct instance for which the specific expect should be stopped or

September 2021 198 Product Version 21.09


UVM e User Guide
Using Testflow Phases

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

var rfs: rf_struct = rf_manager.get_exact_subtype_of_instance(u);


var rfe: list of rf_expect = rfs.get_expects();
for each in rfe {
it.quit(u);
};

September 2021 199 Product Version 21.09


UVM e User Guide
Module-to-System Verification

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.

September 2021 200 Product Version 21.09


UVM e User Guide
Module-to-System Verification

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

September 2021 201 Product Version 21.09


UVM e User Guide
Module-to-System Verification

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

This section contains:


Verifying Systems
System UVC Packages

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)

September 2021 202 Product Version 21.09


UVM e User Guide
Module-to-System Verification

Checking transformation from AHB transfers to XBus transfers (not implemented)


Verifying a system requires expertise in respect to:
Multiple interface UVCs
Development of system-specific components
System UVCs encapsulate all of the above and let you reuse the same in multiple verification
projects.
Structure and Functionality of System UVCs
The role of the system UVC is to encapsulate all knowledge about its components and add the
specific verification components needed to verify the system. The main objective is wide-ranging
reuse:
From module to system to larger systems
Between projects
TLM to cycle-accurate to post-silicon
The main candidates for reuse are:
Data items (for example, packets and registers)
Sequences (sequence libraries)
Coverage definitions
Checks and scoreboards
Reference models
vPlan
The following partially reusable components can be copied to a new environment and modified
slightly:
Topology
Configuration of sub-UVCs
Building a system UVC makes use of the following elements provided in the components:

September 2021 203 Product Version 21.09


UVM e User Guide
Module-to-System Verification

Interface UVCs
Traffic generation
Data items
BFM and sequence driver
Basic sequences
Checks
Protocol checks
Scoreboard
Coverage definitions

Module and subsystem


Relations between participating UVCs
UVCs
Connections between scoreboards and appropriate
monitors
Module-specific checks
Internal checks
Reference model
Module coverage
Fine-tuning of interface UVCs’ coverage definitions
Cross coverage
Internal coverage

System UVC Packages


The system UVC is developed and maintained as a UVM package with some additional
subdirectories. The objective is to encapsulate in a reusable package all necessary code,
examples, and documentation for verification of the system.
In particular, the UVC contains examples of how to reuse this system as a component in a larger
system. These examples are contained in one or more testbench directories, the content of which is
described in Module and System UVC Architecture. A testbench example should be used as the
basis for integration of the system UVC in a larger system. A testbench is sometimes called SVE

September 2021 204 Product Version 21.09


UVM e User Guide
Module-to-System Verification

(Simulation and Verification Environment).


Read also about file organization in Importing Files in e UVCs.

Module and System UVC Architecture


Figure 8.1: The XCore DUT

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

September 2021 205 Product Version 21.09


UVM e User Guide
Module-to-System Verification

System UVC Monitor


Stand-In Mode
Reference Mode

Module UVC Architecture


Figure 8.2: The XCore DUT

In general, a module UVC includes:


A monitor for collecting and interpreting events and data going to/from the DUT
Events, checks, and coverage
(Optional) A scoreboard and a reference model
Memory blocks and register files
A Virtual Sequence Driver (VSD) and a Register Sequence Driver (RSD) instantiated in the
testbench or a higher-level system UVC that uses the module UVC as a component
The VSD is defined in the UVC but is used on a higher level

September 2021 206 Product Version 21.09


UVM e User Guide
Module-to-System Verification

The RSD is defined in the register and memory package


The UVC has active and passive subtypes. The active subtype implements the stand-in mode,
where the module UVC plays the role of the DUT (for example, when the DUT is not available). The
active subtype contains an additional sequence driver used to drive the simulation instead of the
DUT.
The module UVC is connected (using pointers or ports) to other UVCs, in particular to the related
interface UVCs’ monitors and BFMs.

System UVC Architecture


Figure 8.3: The QSoC UVC

System UVCs are similar to module UVCs. In general a system UVC includes:
Module UVCs representing the modules that comprise the system

September 2021 207 Product Version 21.09


UVM e User Guide
Module-to-System Verification

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.

System UVC Monitor


The monitor in a system or module UVC is a critical component in the verification environment. It
obtains and maintains events and data related to the activity in the module or system. This
information is made available to checkers, scoreboards, and coverage collectors.
The monitor collects most of the information from the relevant interface UVCs. Some additional
information relating to the internal state of the module or system might be derived either from the
DUT (whitebox access) or from the reference model.
The connection of the monitor to the corresponding interface UVC monitors is done using ports.
Ports, as opposed to pointers or method calls, give maximum reuse. They provide an interfere to
other languages so the UVC can be connected to SC Transaction Level Models or an accelerated
DUT.
This section contains:
Connecting the Monitor with Ports
System UVC Monitor Example

Connecting the Monitor with Ports


When using ports to connect the system/module UVC to the interface UVCs, the interface UVC
monitors must export the collected data through ports. The recommended ports for this job are the
interface_port of tlm_analysis. The main advantage of these kind of ports is that they support one-to-
many connection—one monitor can export information to many destinations. When connecting to a
TLM analysis port, you should:
1. Declare an instance of an interface_port, of the appropriate data item. If the unit contains
more than one in interface_port, define a prefix to be used in the write() method.

September 2021 208 Product Version 21.09


UVM e User Guide
Module-to-System Verification

2. Implement the write() method.


3. Connect the in port to the out port.
Note: To take advantage of the one-to-many capability, the ports should be connected using
the connect() method, and not the do_bind() method.
See System UVC Monitor Example.
Other kinds of ports implemented in Specman:
Event ports for protocol events (for example, start and end transaction events)
Simple ports for current data items (for example, packet, burst, etc.)
Method ports to pass data to the scoreboards
Read more about TLM ports and connecting them to other languages in Cadence Help.
Figure 8.4: Connecting the Monitor with Ports

September 2021 209 Product Version 21.09


UVM e User Guide
Module-to-System Verification

September 2021 210 Product Version 21.09


UVM e User Guide
Module-to-System Verification

System UVC Monitor Example


This code example taken from the XCore UVC shows how the module UVC monitor uses
information collected from other monitors.

unit xcore_monitor_u like uvm_monitor {


// Pointers to the i/f monitors. The pointers will be assigned
// from above, e.g. - in the configuraiton file
!xserial_tx_monitor : xserial_monitor_u;
!xserial_rx_monitor : xserial_monitor_u;
xserial_tx_frame_completed_i : in interface_port of tlm_analysis
of MONITOR xserial_frame_s
using prefix = tx_ is instance;
xserial_rx_frame_completed_i : in interface_port of tlm_analysis
of MONITOR xserial_frame_s
using prefix = rx_ is instance;

// Implement the ports


tx_write(frame : MONITOR xserial_frame_s) is {
cur_tx_frame = frame;
// analys the frame, etc ...
};
rx_write(frame : MONITOR xserial_frame_s) is {
cur_rx_frame = frame;
// analys the frame, etc ...
};

// Connect the ports


connect_ports() is also {
xserial_rx_monitor.frame_completed_o.connect(
xserial_tx_frame_completed_i);
xserial_tx_monitor.frame_completed_o.connect(
xserial_rx_frame_completed_i);

};
};

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.

Figure 8.5: Module UVC in Passive (Normal) Mode

September 2021 211 Product Version 21.09


UVM e User Guide
Module-to-System Verification

Figure 8.5: Module UVC in Passive (Normal) Mode

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.

Figure 8.6: Module UVC in Stand-In Mode

September 2021 212 Product Version 21.09


UVM e User Guide
Module-to-System Verification

Figure 8.6: Module UVC in Stand-In Mode

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

September 2021 213 Product Version 21.09


UVM e User Guide
Module-to-System Verification

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

Integrating UVCs into a Testbench


When building a system-level UVC, the first question is where to instantiate each lower level UVC.
If a module UVC such as the XCore needs services from an interface UVC such as the XBus, it
might make sense to instantiate the interface UVC in the module UVC. However, in that case,
multiple instances of the module UVC would end up with multiple copies of the same interface
UVC, which is undesirable.
So the rule is to instantiate each UVC on the level that encapsulates all of the UVCs that depend on
it.
The system UVC instantiates the UVCs (module, subsystem, or interface) that are fully
encapsulated in the system. (For example, the QSoC UVC has two instances of XCore.)
The testbench (also called SVE — Simulation and Verification Environment) instantiates the
UVCs that are not fully encapsulated in the UVCs. (In the QSoC example, the XSerial and
XBus UVCs are instantiated in the testbench.)
Both the UVC and the testbench inherit the base type uvm_env. For example:

// The UVC, the reusable unit


unit cpu_env like uvm_env {
};
// A specific testbench
unit my_system_sve like uvm_env {
};

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:

September 2021 214 Product Version 21.09


UVM e User Guide
Module-to-System Verification

Decide when to use pointers and when to use ports.


If you use ports, decide whether to use flow hubs and flow checkers.
Resolve generation issues such as:
Generation order
Connecting cross pointers between brothers
In This Section
Connecting UVCs with Flow Hubs and Creating Flow Checkers
Connecting UVCs with Pointers

Connecting UVCs with Flow Hubs and Creating Flow Checkers


A UVM “flow hub” handles connecting UVC TLM producers and consumers during system-level
integration. These units automate the tasks traditionally implemented manually by extending
monitors/checkers/agents and adding port connections. In other words, hubs handle the “plug” in
“plug and play.”
A UVM “flow checker” simplifies the process of connecting checkers to other checkers or monitors
by standardizing the process. Cadence recommends defining one flow checker for each “set of
rules”—for example, a given UVC might contain one data checker and one performance checker.
This section contains examples that illustrate the use of flow hubs and flow checkers.
The following figure shows the environment that is created by these examples. This is a system-
level verification environment that contains two module-level environments.
The first module-level environment—called UVC1 in the examples below—contains the following:
Packet monitor
Trans monitor
Packet-trans flow checker
Performance checker
The second module-level—called UVC2 in the examples below—contains the following:
Burst monitor
Burst flow checker
The system level—called UVC3 in the examples below—adds the following to the overall

September 2021 215 Product Version 21.09


UVM e User Guide
Module-to-System Verification

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

Flow Hub and Flow Checker Examples


The environment created by the following examples contains three UVCs: UVC1, UVC2, and
UVC3.
UVC1 Example
UVC1 defines two types—“packet” and “trans”—plus a hub for these data types, and it implements
the data checker (a UVM flow checker) and performance checker.

September 2021 216 Product Version 21.09


UVM e User Guide
Module-to-System Verification

<'
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);
};
};
'>

The following figure illustrates UVC1

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);
};
};
'>

The following figure illustrates UVC2.

September 2021 217 Product Version 21.09


UVM e User Guide
Module-to-System Verification

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);
};
};
'>

The following figure illustrates UVC3:

Connecting UVCs with Pointers


To connect UVCs with pointers:

September 2021 218 Product Version 21.09


UVM e User Guide
Module-to-System Verification

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

unit vr_qsoc_sve_u like uvm_env {


-- One instance of QSoC UVC
qsoc_uvc : vr_qsoc_env_u is instance;
-- One instance of the XBus UVC
xbus_uvc: VR_QSOC_XBUS xbus_env_u is instance;
-- Two instances of the XSerial UVC
xserial_0_uvc: VR_QSOC_XSERIAL_0 xserial_env_u is instance;
xserial_1_uvc: VR_QSOC_XSERIAL_1 xserial_env_u is instance;
};

Step 3: Connect the pointers procedurally


The pointers between UVCs are connected procedurally after generation of the architecture.
The connect_pointers() method is activated in a top-down fashion. The method is called
automatically after all units have been generated and the pointers of the parent unit have been
connected by the parent unit’s connect_pointers() method.

September 2021 219 Product Version 21.09


UVM e User Guide
Module-to-System Verification

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.

September 2021 220 Product Version 21.09


UVM e User Guide
Module-to-System Verification

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

September 2021 221 Product Version 21.09


UVM e User Guide
Module-to-System Verification

Configuration Unit, and Configuration Parameters Struct


Each level in the unit hierarchy can have a configuration unit, which is recommended to be
like uvm_agent_config or uvm_env_config. The configuration unit should contain fields that control
the component work mode. An example of a configuration unit:

unit foo_bus_config_u like uvm_env_config {


!masters_names : list of agent_name_t;
!slaves_names : list of agent_name_t;
endianity : endianity_t;
data_width : uint;
addr_width : uint;
relevant_slave (address : uint) : agent_name_t is {
// ...
};
};

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:

struct foo_bus_config_params_s like uvm_config_params {


endianity : endianity_t;
data_width : uint;
addr_width : uint;
};

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

September 2021 222 Product Version 21.09


UVM e User Guide
Module-to-System Verification

also instantiates xcore_env_config_u in xcore_env_u, in a field named config. It also defines a


configure() method in the xcore-env_u unit.
You could access these constructs as follows:

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

September 2021 223 Product Version 21.09


UVM e User Guide
Module-to-System Verification

Testbench Configuration File

Configuration with Constraints


Each UVC defines a default configuration for each sub-UVC in either of the following ways:
Using soft constraints
Providing the constraints in a file that can be imported either by the testbench or by a higher-
level UVC but not by the UVC top file
In the testbench config file, you can override the soft constraints when necessary or decide which of
the configuration files to import.

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:

extend XCORE xserial_env_u {


keep agent.direction == TX_AND_RX;
};

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.

September 2021 224 Product Version 21.09


UVM e User Guide
Module-to-System Verification

unit vr_qsoc_sve_u like uvm_env {


qsoc_uvc: VR_QSOC_SVE vr_qsoc_env_u is instance;
xbus_uvc: VR_QSOC_XBUS xbus_env_u is instance;
xserial_0_uvc: VR_QSOC_XSERIAL_0 xserial_env_u is instance;
xserial_1_uvc: VR_QSOC_XSERIAL_1 xserial_env_u is instance;
};

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;

Testbench Configuration File


The testbench (also known as SVE) has a configuration similar to the configuration of UVCs. Its role
is to instantiate all top-level units and to configure them.
Defining the top-level verification components includes:
Instantiating all interface and system UVCs
Adding virtual sequence drivers
Defining the address map and register sequence driver
Adding end-to-end scoreboards
Configuring the verification environment includes:
Setting modes of operation
Coordinating test phases (end of test)

September 2021 225 Product Version 21.09


UVM e User Guide
Module-to-System Verification

Propagating configuration to subcomponents

Reconfiguration and Configuration Method


Some attributes of the environments might be changed during the run, typically after reset, power
mode change, or other system events. To support multiple configurations during the run, each
component that might be reconfigured should have a configure() method and a configuration
parameters struct. The configuration method and parameters struct should allow maximum reuse
and flexibility.
Compared to the initial configuration, which is built using constraints, reconfiguration is more direct.
Reconfiguration involves updating of a few configuration parameters to specific values.
Using the UVM configuration method provides:
Fully direct to fully random change of configuration:
Constraint-driven reconfiguration by generating the configuration parameters struct
Fully directed reconfiguration by using the uvm_configure action
Changing of one or more parameters, with or without dependencies:
Modifying some parameters to specific values, and maintaining other parameters
unchanged
Modifying some parameters to specific values, and having other parameters randomly
generated
Standardization within and between UVCs
Hooks for advanced tools like Builders
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. Using a
struct as an input gives two use models:
Procedural — Users can assign the fields procedurally. This use model might be preferred by
test writers who do not feel comfortable with constraints.
The uvm_configure utility provides an easy way for procedural assignment of
configuration parameters. (For a code example, see Example of a Configuration
Method.)
Generation — Users can generate a struct and thereby take advantage of constraints defined
in the UVC. This use model gives more flexible control. It is also likely to produce better

September 2021 226 Product Version 21.09


UVM e User Guide
Module-to-System Verification

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:

struct xcore_env_config_params_s like uvm_config_params {


mode : xcore_mode_t;
max_speed : uint;
};

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:

uvm_build_config env xcore_env_u xcore_env_config_u


xcore_env_config_params_s;
extend xcore_env_config_params_s {
mode : xcore_mode_t;
max_speed : uint;
};

Example of a Configuration Method


The following code example shows how the configuration method parses the input and how it calls

September 2021 227 Product Version 21.09


UVM e User Guide
Module-to-System Verification

the configuration methods of its subcomponents.


The example uses uvm_configure for procedural assignment of the parameters and passing them to
the configure() method of the lower-layer component. This utility creates a configuration
parameters struct of the subcomponent, assigns the fields that are updated (in this example, the
mode field), and calls the configure() method of the sub component (xserial_uvc, in this
example). All fields that are not listed in uvm_configure remain unchanged.
Instead of using uvm_configure, you could also generate a configuration struct of appropriate type
and pass it as a parameter. (In this example, you would need to generate a struct of
type xserial_env_config_params_s.)

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.

September 2021 228 Product Version 21.09


UVM e User Guide
Module-to-System Verification

Typical Reusable Configuration Elements


Location of the subsystems — Done by constraining the hdl_path of subsystem UVCs
The module chore within the bus — For example, master or slave
Signal names — Done in the port configuration by defining the port hdl_path
Memory configuration — For example, start address and range of the memory of each
subsystem
Typical Non-Reusable Configuration Elements
Connecting the ports of all participant UVCs (not contained within the system UVC)
Setting the various agents to be active or passive
Instantiation and configuration of the address map
To configure a system UVC:
1. For each UVC used by the system UVC, copy its configuration file template into the system
UVC and edit the content according to your system.
2. As part of the UVC, create a reusable configuration file for the system.
3. Create a configuration file template for all configuration that is testbench-specific.
This configuration file is not reusable. Its location is in the testbench directory.

Table 8.1: Golden UVC Configuration Reuse

Golden Configuration
UVC

xserial It has a configuration file template.

xbus It has a configuration file template.

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).

September 2021 229 Product Version 21.09


UVM e User Guide
Module-to-System Verification

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

September 2021 230 Product Version 21.09


UVM e User Guide
Module-to-System Verification

Virtual Sequences for SoC


Figure 8.1: ​U VCs Within SoC Design

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.

September 2021 231 Product Version 21.09


UVM e User Guide
Module-to-System Verification

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

SoC Sequence Example


sequence comm_sequence; // Note no "item=" option
extend comm_sequence_driver {
atm_driver: ex_atm_master_sequence_driver;
eth_driver: ethernet_sequence_driver;
};
extend comm_subsystem_unit {
driver: comm_sequence_driver is instance;
keep driver.atm_driver == ex_atm_unit.driver;
keep driver.eth_driver == ethernet_unit.driver;
};
extend MAIN comm_sequence {
!atm_sequence: ex_atm_sequence;
!eth_config: ethernet_sequence;
body() @driver.clock is only {
do eth_config keeping {.driver == driver.eth_driver};
do atm_sequence keeping {.driver == driver.atm_driver};
};
};
extend MAIN ex_atm_sequence {
keep count == 0;
};

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.

September 2021 232 Product Version 21.09


UVM e User Guide
Module-to-System Verification

extend MAIN comm_sequence {


!atm_sequence: ex_atm_sequence;
!eth_config: ethernet_sequence;
body() @driver.clock is only {
do eth_config on driver.eth_driver;
do atm_sequence on driver.atm_driver;
};
};

Figure 8.11 demonstrates the above example.


Figure 8.2: ​SoC Sequence Example

September 2021 233 Product Version 21.09


UVM e User Guide
Module-to-System Verification

Advanced Issues in Virtual Sequences


Using do-on
There are two ways for doing a sequence:
do sequence {keeping it.driver == <driver_name>; <constraints...>}

do sequence on <driver_name> {keeping <constraints...>}

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:

extend MAIN comm_sequence {


!atm_sequence: ex_atm_sequence;
!eth_config: ethernet_sequence;
body() @driver.clock is only {
do eth_config on driver.eth_driver;
do atm_sequence on driver.atm_driver;
};
};

If, after the eth_config sequence started, eth_driver.rerun() is called - eth_config sequence will
quit.

Writing Tests Using Virtual Sequences


When writing tests using a system UVC, you can use any of the sequences libraries defined in the
UVC— the virtual sequences library, or the interface UVC/s sequences.
Using the virtual sequences is recommended for defining system high level scenarios. They do not
require deep understanding of the sub-systems or system interface/s.
The XCore UVC contains several tests examples, demonstrating usage of interface and virtual
sequences in the test:
High level - using the virtual sequences library
Virtual sequence also doing items of the I/F sequence
Fine control - using the I/F sequences library

September 2021 234 Product Version 21.09


UVM e User Guide
Module-to-System Verification

High level - using the virtual sequences library


When writing tests for a system, we recommend to first check out the virtual sequences library;
probably the UVC developer or integrator defined sequences creating the required scenario. If they
haven’t, we recommend doing so. A small sequences library can save the time of many tests
writers.
The XCore test xcore_virtual_seq_test.e, for example, demonstrates usage of virtual sequences
in a test.The two sequence kinds it uses are defined in the XCore UVC
- XCORE_XSERIAL_TO_XBUS and XCORE_XSERIAL_TO_XBUS_LOOPBACK. The test writer does not have to
know the protocols of neither interface, and not even the protocol of the system, for implementing
such a test.

extend MAIN MAIN_TEST xcore_combined_sequence {


!xserial_to_xbus : XCORE_XSERIAL_TO_XBUS
xcore_combined_sequence;
!xserial_to_xbus_loopback : XCORE_XSERIAL_TO_XBUS_LOOPBACK
xcore_combined_sequence;
body() @driver.clock is only {
do xserial_to_xbus;
do xserial_to_xbus;
do xserial_to_xbus;
wait [300] * cycle;
do xserial_to_xbus_loopback;
};
};

Virtual sequence also doing items of the I/F sequence:


Typically, a virtual sequence does only sequences - either virtual sequences, or sequences of the
I/F UVCs. In some cases, test writers see a need in doing items from the virtual sequence. This is
easy to implement, using either the new sequences API or do on. For example, the
test xcore/main_sve/tests/xcore_virtual_seq_doing_items_test.e demonstrates a virtual
sequence doing XSerial items. The XCore serial interface is simple - all legal frames should be
accepted by the XCore. The bus interface protocol is a bit more complicated - one should read/write
to specific addresses, adhering the protocol rules. It doesn’t make sense for the test writer to
implement the XBus protocol in the test - he better use the sequences defined in the UVC.
The following code is taken from xcore_virtual_seq_doing_items_test.e - the virtual sequence is
doing XBus sequences, and XSerial items:

September 2021 235 Product Version 21.09


UVM e User Guide
Module-to-System Verification

body() @driver.clock is only {


for i from 0 to 3 {
// Doing an item:
do xserial_frame on driver.xserial_driver;
// Doing a sequence:
do xbus_read_frame_seq keeping {.driver == driver.xbus_driver};
};
// Note - the item can also be sent using deliver_item.
gen xserial_frame keeping {.driver == driver.xserial_driver};
driver.xserial_driver.wait_for_grant(me);
driver.xserial_driver.deliver_item(xserial_frame);

do xbus_read_frame_seq keeping {.driver == driver.xbus_driver};


};
};

Note: The sequences methods API is supported starting Specman 8.2.


Fine control - using the I/F sequences library
The system UVC contains the sequence libraries of the sub UVCs. Some of them are part of the
sub-UVC, some were developed as part of the system UVC. The XCore test xcore_halt_test.e, for
example, uses three sequences defined in the XCore UVC. One Is a vr_ad sequence -
XCORE_XBUS_WRITE, and two XSerial sequences - XCORE_SEND_HALT and XCORE_SEND_RESUME. Using i/f
sequences requires a bit more understanding of the environment as compared to using the virtual
sequences, but does not require too deep knowledge.

September 2021 236 Product Version 21.09


UVM e User Guide
Module-to-System Verification

extend MAIN MAIN_TEST xcore_combined_sequence {


!program_xcore_to_tx : XCORE_XBUS_WRITE vr_ad_sequence;
keep program_xcore_to_tx.driver == driver.xcore_regs_driver;

!send_halt_frame : XCORE_SEND_HALT xserial_sequence;


keep send_halt_frame.driver == driver.xserial_driver;
!send_resume_frame : XCORE_SEND_RESUME xserial_sequence;
keep send_resume_frame.driver == driver.xserial_driver;

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

September 2021 237 Product Version 21.09


UVM e User Guide
Module-to-System Verification

Reusing Sequence Drivers


A virtual sequence driver is always instantiated on the testbench level. Its primary role is to drive
coordinated scenarios involving multiple inputs to the system. These scenarios are built from
reusable sequences from the lower-level UVCs.
Virtual sequence drivers from the module level can be reused (instantiated) on the system level.
These sequence drivers are:
Defined in module UVCs
Instantiated in the testbench of the module UVC (as a usage example)
Reused by instantiating in a system testbench

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.

Sequences: Module to System


To facilitate module-to-system reuse, each UVC must provide some basic sequences that are
reusable on a higher level.
Each interface UVC must define the basic READ/WRITE access to the DUT.
The module UVC must provide a sequence library with:
Basic READ/WRITE for each of the interfaces, using the interface UVCs and register
sequences.
Basic module control sequences.
Basic virtual sequences (activating multiple interfaces), using its own basic
READ/WRITE sequences and the control sequences.
The system UVC must provide a sequence library with:
Basic READ/WRITE sequences, using the module UVC sequences.
Basic virtual sequences (activating multiple modules), using its own basic
READ/WRITE sequences.

September 2021 238 Product Version 21.09


UVM e User Guide
Module-to-System Verification

Parameters should be used whenever appropriate. Parameterized sequences are more reusable.

Reuse of Register Sequences


Parameters should be used whenever appropriate. Parameterized sequences are more reusable.
The register sequences in the module UVC access the registers in the XCore.
To reuse in a multi-XCore environment, you can constrain the static_item field of
vr_ad_sequence.

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.

extend VR_QSOC_WRITE vr_ad_sequence {


xcore : vr_qsoc_xcore_name;
!regs_sequence : vr_ad_sequence;
body() @driver.clock is only {
do XCORE_XBUS_WRITE regs_sequence keeping {
.static_item == driver.addr_map.
reg_file_list[xcore.as_a(uint)];
};
};
};

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.

Organizing and Using Sequence Libraries


In general, sequences should be maintained in separate libraries. Each library is a file containing
several sequences sharing some common feature. Each library is separately loadable so that only
the appropriate sequences are loaded during simulation. Examples of sequence libraries are:
Basic sequences (reusable)
Module-level sequences only (not likely to be reusable)
Configuration sequences (reusable at every level)
Register sequences for setup and configuration (reusable at every level)

September 2021 239 Product Version 21.09


UVM e User Guide
Module-to-System Verification

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

Module UVC Coverage


The module and system UVCs provide coverage data in different categories such as:
Basic traffic coverage for each of the interfaces
Crossing traffic coverage for all interfaces
Internal coverage (for example, internal states)
Sequence coverage
Register coverage

September 2021 240 Product Version 21.09


UVM e User Guide
Module-to-System Verification

Performance-related coverage, such as delays and throughput


Configuration coverage, such as all operation modes
Reset and recovery
Hot configuration (where applicable)
Error conditions and recovery

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:

September 2021 241 Product Version 21.09


UVM e User Guide
Module-to-System Verification

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;
};

Adding Coverage Definitions to System UVCs


To define new coverage groups under the module/system monitor:
Use events from the system monitor, or from one of the lower levels monitors.
Use data items from system monitor, or from one of the lower levels monitors.
Duplicate coverage definitions from the interface UVC or lower-level UVCs (where needed).
For example, definition of coverage in the XCore is done as follows:

extend has_coverage xcore_monitor_u {


cover tx_frame_written is {
item name using per_instance;

item data : byte = cur_xbus_transfer.data[0] using


radix = HEX,
ranges = {
range([0]);
range([1..0xFE]);
range([0xFF]);
};
}; -- cover tx_frame_written

September 2021 242 Product Version 21.09


UVM e User Guide
Module-to-System Verification

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.

Scalability Issues in System Verification


A system verification environment is assembled from numerous lower level verification
environments. In large systems, the performance of the verification environment might become an
issue. The first thing to do in case of performance problems is to compile the verification
environment and use the profiler to look for portions of the code that use too much CPU time or
memory space.
If, after compilation and profiling, performance is still unsatisfactory, there are some general
guidelines to improve the performance. These guidelines suggest trading off some features
provided by the various components of the verification environment for better performance. Every
UVC contains features needed for thorough verification of modules. However, there is a
performance cost associated with each such feature.
This section describes the main features to examine for improving performance. It also describes
solutions that some UVCs provide to achieve better performance by giving up some of the power
and flexibility of the UVC.
This section contains:
Stimulus Generation
Checking
Coverage
Messages
UVC-Specific Solutions

September 2021 243 Product Version 21.09


UVM e User Guide
Module-to-System Verification

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];

September 2021 244 Product Version 21.09


UVM e User Guide
Module-to-System Verification

Additional UVC Libraries


Additional sequence libraries may be provided by the UVC developer or by higher level verification
environments. These may include:
Configuration sequences to set up the DUT for normal operation
Sequences to drive normal or erroneous traffic to the DUT
Register sequences to exercise the DUT registers
Virtual sequences to activate multiple ports concurrently
These additional sequence libraries should reside in the /examples/seq_lib directory of the UVC.
testbench-specific sequence libraries should reside in the /*_sve/seq_lib directory of a module or
system UVC.
These libraries are imported by the various test files rather than being imported automatically from
the UVC top file. Each test file imports only the necessary sequence libraries, thus minimizing the
performance penalty.

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.

September 2021 245 Product Version 21.09


UVM e User Guide
Module-to-System Verification

Avoid temporal expressions in sequence items


Defining events or temporal expressions in sequences and sequence items is a major obstacle for
scalability. These structs are never collected by the garbage collection system, causing gradual
slow down of the simulation. Instead of defining an event in a sequence, you can define it in the
driver and access it as driver.event from within the sequence.
If temporal expressions or events are used in sequence items, they should be terminated
using struct.quit() once the item is fully processed. When appropriate you can use
the auto_quit() method of a sequence or sequence item. This will automatically quit the struct
when the sequence mechanism does not need it any more.

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.

Simplify complex sequences by limiting the capabilities


Sequences and sequence items might require complex generation. Performance can often be
improved by simplifying the generation, while giving up some of the randomization that might not be
necessary on the system level.
In the following example, the ONLY_OK sequence simplifies the generation of the slave response. In
the general case, the response is a random list of SPLIT/RETRY terminated by OK.
The ONLY_OK sequence always generates a single OK response. You can run many useful system
tests using this simplified sequence, assuming that the full capabilities of the slave response were
already exercised on the module level.

extend SIMPLE_RESPONSE vr_ahb_slave_seq {


!resp_seq: ONLY_OK vr_ahb_slave_driven_burst_response;
body() @driver.clock is {
do resp_seq;
};
};

Simplify the generation of complex sequences


In extreme cases, it pays to apply the do statement to a pregenerated item. In the following example,
the write() method generates a complex sequence item that has numerous transfers. The naive

September 2021 246 Product Version 21.09


UVM e User Guide
Module-to-System Verification

implementation is as follows:

write(address: vr_ahb_address, data: list of vr_ahb_data, b_kind:


vr_ahb_burst_kind, size: vr_ahb_transfer_size) @driver.clock is {
do burst keeping {
.kind == b_kind;
.direction == WRITE;
.first_address == address;
.data == data;
.size == size;
.transmit_delay == 0;
.delay == 0;
};
};

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:

write(address: vr_ahb_address, data: list of vr_ahb_data, b_kind:


vr_ahb_burst_kind, size: vr_ahb_transfer_size) @driver.clock is {
var new_burst := new vr_ahb_master_driven_burst with {
.kind = b_kind;
.direction = WRITE;
.first_address = address;
.data = data;
.size = size;
.transmit_delay = 0;
.delay = 0;
};
for i from 0 to size - 1 {
var trans := new vr_ahb_master_driven_transfer with {
.size = new_burst.size;
.direction = new_burst.direction;
.kind = (i == 0 ? NONSEQ : SEQ);
};
new_burst.transfers.add(trans);
};
do burst keeping {it == new_burst};
};

The second solution is clearly more complicated. Only apply it in cases where the profiler indicates
that the simple implementation is too expensive.

Minimize the number of generation roles


Every do statement creates a new generation role. By minimizing the generation roles, you can
make code more efficient. You can do that by encapsulating do operations in for loops, methods, or
sequences.

September 2021 247 Product Version 21.09


UVM e User Guide
Module-to-System Verification

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):

extend vr_ad_sequence_kind : [INIT_PORTS];


extend INIT_PORTS vr_ad_sequence {
!port_reg : PORT_REG vr_ad_reg;
body() @driver.clock is {
write_reg port_reg {.port_num == 1; .enable = TRUE};
write_reg port_reg {.port_num == 2; .enable = TRUE};
write_reg port_reg {.port_num == 3; .enable = TRUE};
write_reg port_reg {.port_num == 4; .enable = TRUE};
};
};

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:

extend vr_ad_sequence_kind : [INIT_PORTS];


extend INIT_PORTS vr_ad_sequence {
!port_reg : PORT_REG vr_ad_reg;
body() @driver.clock is {
for i from 1 to 4 {
write_reg port_reg {.port_num == i; .enable = TRUE};
};
};
};

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.

Preparing the UVC for Scalability


Every UVC agent must have a has_checks field that is set by default to FALSE for active agents and
TRUE for passive agents. A corresponding has_checks field in the monitor inherits the value from
the agent.

September 2021 248 Product Version 21.09


UVM e User Guide
Module-to-System Verification

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

Determining the Appropriate Trade-off


The final decision as to which checks are active in system verification must be left to the end user.
Within the same UVC, some agents might be internal to the system (and hence their protocol
checking was already verified in various subsystems) while other agents represent external devices
(and hence require full protocol checking even at the system level).
Module and system UVCs have their own checkers responsible for system-level checking, such as
connectivity and inter-operability. Implement these checks under has_checks but keep them active
during system verification. They can be turned off later, for example, for HW/SW co-verification.

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.

September 2021 249 Product Version 21.09


UVM e User Guide
Module-to-System 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.

unit vr_axi_slave like vr_axi_agent {


has_coverage: bool;
keep active_passive == ACTIVE => soft has_coverage == FALSE;
keep active_passive == PASSIVE => soft has_coverage == TRUE;
keep monitor.has_coverage == read_only(has_coverage);
}

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;

Individual coverage groups can be disabled using:


covers.set_cover("packet.*", FALSE);

Determining the Appropriate Trade-off


System verification has its own specific coverage goals implemented in the testbench and various
subsystem UVCs. These include connectivity and inter-operability, which must be active during
system verification, but can be turned off, for example, for HW/SW co-verification.

September 2021 250 Product Version 21.09


UVM e User Guide
Module-to-System Verification

Minimizing Overhead with the Coverage API


The coverage API contains options to turn off coverage groups that are not appropriate at the
system level. For example:
cover cover_group using also no_collect = TRUE;

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.

Minimizing Context Switching


The BFM and monitor typically access the signals with a high frequency, causing a large number of
context switches between Specman and the simulator. This can be significant in serial protocols,
where context switching is at the bit level rather than the packet level.
Implementing a small layer of the BFM and monitor in HDL can significantly decrease context
switching and thus improve performance. UVCs providing such an HDL layer as an optional
component let users choose between better performance and better visibility.

September 2021 251 Product Version 21.09


UVM e User Guide
Module-to-System Verification

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.

Implementing Deterministic Behavior


In some simple cases, the UVC developer can provide a deterministic alternative to the general-
purpose, sequence-based solution. A typical example would be a preprogrammed arbiter that
performs a predefined arbitration algorithm. Such an arbiter can be very efficient, having no need for
sequence generation. The cost is a loss of control by the sequence mechanism.

Optimizing Sequence Drivers


Typically, the clock in virtual sequence drivers is tied to sys.any. In some cases, this can cause an
unnecessary performance loss. In such cases, the clock can be emitted only when needed, thus
improving the performance of the driver.
In the following layering example, a low-level sequence obtains the next item from a higher-level
driver. Instead of running the clock of the high-level driver on sys.any, the low-level sequence emits
the clock in pre_do().

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;
};

September 2021 252 Product Version 21.09


UVM e User Guide
Using the UVM e Scoreboard

Using the UVM e Scoreboard

The UVM Scoreboard is a scoreboard infrastructure implemented in e, delivered as an open source


on UVM World and also within Specman and Xcelium Simulator. The UVM scoreboard provides a
default search and matching algorithm, and can be used to verify various kinds of systems with
various kinds of requirements.
This chapter contains descriptions of these typical use models:
UVM Scoreboard Overview
Basic Steps For Defining a Scoreboard
Scoreboard For a Data-Mover
Scoreboard For a Peripheral
Scoreboard for a Bridge
Scoreboard for an Interconnect
Scoreboard for a Memory Controller
Ordering Models
Scoreboard Power Aware and Reset
Debugging Aids for the UVM Scoreboard
See Also
UVM Scoreboard Reference chapter of the UVM e Reference manual for the detailed
description of the Scoreboard API

UVM Scoreboard Overview


Scoreboards are a form of reference model. The scoreboard task can be basically described as
storing and comparing data. The device input (data item sent to it or written to it) is added to the
scoreboard. The device output (data item sent from the device or read from it) is matched against
the stored data. The scoreboard tracks both sets of data and can report of several kinds of errors:
No match: the device sent an unexpected data item.

September 2021 253 Product Version 21.09


UVM e User Guide
Using the UVM e Scoreboard

No match: the device sent an unexpected data item.


Mismatch: the data item sent by the device was expected, but its data is not as expected.
Out of order: an item was sent, but not in the expected order—there are pending items that
should have been sent before.
Item not sent: an item that was expected to be sent by the device had not been sent.
Multiple sends: an item was sent more than once.
The UVM scoreboard infrastructure contains the uvm_scoreboard base unit, which implements the
scoreboard default algorithm:
Copies of items sent to the DUT input are passed to the scoreboard add port. See more
details in Add Flow.
Copies of items sent from the DUT are passed to its match port. When an item is passed to a
match port, the scoreboard searches for a matching item in the database. See more details
in Match Flow.
The items are organized in the scoreboard in a list. By default, the scoreboard expects a FIFO
behavior – items are expected to be passed to the match port in the same order they were passed to
the add port. The user can control this by modifying one of the out-of-order control flags. Use
models for defining scoreboards for DUTs that do not send items in FIFO order are described in
Ordering Models.

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

September 2021 254 Product Version 21.09


UVM e User Guide
Using the UVM e Scoreboard

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.

September 2021 255 Product Version 21.09


UVM e User Guide
Using the UVM e Scoreboard

Basic Steps For Defining a Scoreboard


Several steps are required to define a scoreboard. After the scoreboard is defined, the user can
extend its method and constrain its fields, defining the exact matching algorithm required for the
DUT, as described in the next sections of this chapter.
The basic steps for defining a scoreboard:
1. Import the scoreboard package top file.
2. Define the new scoreboard type, inheriting the uvm_scoreboard.
3. Define the scoreboard architecture, that is, the number and types of add and match ports.
After the new scoreboard is defined, using it in the verification environment requires these steps:
1. Instantiate the scoreboard within the verification environment.
2. Connect the scoreboard ports to the relevant ports in the environment.
The next sections of this chapter describe scoreboard definition for common devices

Scoreboard For a Data-Mover


Assume a verification environment as seen in Figure 9.1. This example shows a verification
environment (VE) for a data-mover DUT. The items the DUT receives on each interface are
forwarded to the other side. The first example shown here is of a DUT in which the two interfaces
are of same protocol.
The VE contains two agents, each interfacing to the DUT in two channels: DUT TX is connected to
the VE agent RX, and vise versa.
Figure 9.1: ​VE of a Data-Mover

Defining the Scoreboard


For a data-mover DUT, the scoreboard should contain two add ports and two match ports, as shown
in Figure 9.2.

Figure 9.2: ​Scoreboard For A Data-Mover

September 2021 256 Product Version 21.09


UVM e User Guide
Using the UVM e Scoreboard

Figure 9.2: ​Scoreboard For A Data-Mover

Assume the data item of the DUT interface is packet_s, containing these fields:

stuct packet_s like any_sequence_item {


kind : packet_kind_t;
p_len : uint;
%addr : packet_addr_t;
%data : list of byte;
%check_sum : uint (bits : 8);
};

Defining a packet-to-packet scoreboard for a DUT as shown in the figure VE of a Data-Mover


is shown in the following code. This code implements:
Two add ports of packets, one for each DUT input
Two match ports of packets, one for each DUT output
Defining the correct flow, meaning: items passed to packet_add_1 should be matched against
items passed to packet_match_1, and same for packet_add_2 and packet_match_2.
This is implemented by extending the predict methods, and in it calling the set_match_port.
The exact name of the predict method is port-name_predict, and it has one argument, the
same type as the port item type. Note that when you override this method, you must add the
item to the scoreboard.
Constraining global_ooo_depth to UNDEF, indicating no ordering rules between the ports.
The DUT may send out packets to agent#1, even if there are pending items to agent #2. FIFO
ordering within each port is maintained. See more details in Ordering Models.

September 2021 257 Product Version 21.09


UVM e User Guide
Using the UVM e Scoreboard

unit packet_to_packet_scbd like uvm_scoreboard {


// The ports for flow from #1 to #2
scbd_port packet_add_1 : add packet_s;
scbd_port packet_match_1 : match packet_s;

// The ports for flow from #2 to #1


scbd_port packet_add_2 : add packet_s;
scbd_port packet_match_2 : match packet_s;

// No ordering between the ports is required


keep global_ooo_depth == UNDEF;

// predict method of packet_add_1


packet_add_1_predict(item : packet_s) is only {
// Items of add port #1 are to be matched
// against items of match port #1
add_to_scbd(item);
set_match_port(item, packet_match_1);
};

// predict method of packet_add_2


packet_add_2_predict(item : packet_s) is only {
// Items of add port #2 are to be matched
// against items of match port #2
add_to_scbd(item);
set_match_port(item, packet_match_2);
};

};

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

September 2021 258 Product Version 21.09


UVM e User Guide
Using the UVM e Scoreboard

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.

unit env like uvm_env {


agent_1 : agent_u is instance;
agent_2 : agent_u is instance;
scbd : packet_scbd_u is instance;
connect_ports() is also {
// Connect the monitors for one flow:
// Agent #1 TX connected to the add port
// Agent #2 RX connected to the match port
agent_1.tx_monitor.packet_ended.connect(scbd.packet_add_1);
agent_2.rx_monitor.packet_ended.connect(scbd.packet_match_1);
// Connect the other flow:
// Agent #2 TX connected to the add port
// Agent #1 RX connected to the match port
agent_2.tx_monitor.packet_ended.connect(scbd.packet_add_2);
agent_1.rx_monitor.packet_ended.connect(scbd.packet_match_2);
};
};

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:

September 2021 259 Product Version 21.09


UVM e User Guide
Using the UVM e Scoreboard

The DUT Has Multiple Interfaces of Same Protocol


The DUT Can Perform Loopback
The DUT Does Not Forward All of the Items
The DUT Broadcasts Items
The DUT Performs Transformation on the Items
Ignoring Some of the Fields in the Matching Process
Performing Data Transformation
Scoreboard of Data-Mover Interfacing Two Different Protocols
Ordering Models

The DUT Has Multiple Interfaces of Same Protocol


The scoreboard implemented in Defining the Scoreboard contains two add ports and two match
ports, all of packet_s. Some devices have many interfaces of the same kind. For defining a
scoreboard for such a DUT, you can define a group of ports. The following code defines a
scoreboard with a three add ports and three match ports, all of packet_s. The scoreboard also
defines the matching algorithm of items passed to each add port, which are expected to be matched
against items passed to match port of same index.

unit packet_to_packet_scbd like uvm_scoreboard {


scbd_port packet_add[3] : add packet_s;
scbd_port packet_match[3] : match packet_s;
// No ordering between the ports is required
keep global_ooo_depth == UNDEF;
// predict method of packet_add_1
packet_add_predict(item : packet_s, port_index : uint) is only {
// Item expected to be matched with item coming on
// match port of same index
add_to_scbd(item);
set_match_port(item, packet_match[port_index]);
};
};

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

September 2021 260 Product Version 21.09


UVM e User Guide
Using the UVM e Scoreboard

capabilities.

The DUT Can Perform Loopback


Scoreboard For a Data-Mover contains a code example for implementing a scoreboard for a packet-
to-packet. This scoreboard definition assumes that all packets are forwarded from one interface of
the DUT to the other. But in many cases, the DUT is also capable of loopbacks – some items are
expected to be sent back to the interface on which they were received. For supporting such DUTs,
we have to extend the predict methods. For some items, or in some modes of the DUT, change the
expected match port. The example below extends the packet_to_packet scoreboard, adding a
loopback mode to it. This code example assumes a simple protocol: the packet address field
indicates whether this packet should be looped back.

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);
};
};
};

The DUT Does Not Forward All of the Items


Usually, not all items sent to the DUT are expected to be sent by it. Typical cases are control items,
items sent when the DUT is not ready to receive, or illegal items – all items that should not be
added to the scoreboard. This can be implemented by extending the add-predict method. This
method is called by the scoreboard whenever an item is passed to the add port, and its default
implementation adds the item to the scoreboard. The name of this method is port-name_predict,
and it has one argument, of the same type of the port item type. Note that when you override this
method, then if the item has to be added to the scoreboard, you must add the item by
calling add_to_scbd().
The following code extends the packet_to_packet_scbd defined earlier (scoreboard_data_mover),

September 2021 261 Product Version 21.09


UVM e User Guide
Using the UVM e Scoreboard

adding the capability to ignore some of the packets:

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);
};
};

The DUT Broadcasts Items


Some of the devices are capable of broadcast: one item is sent to many destinations. By default,
each item added to the scoreboard is expected to find its match in one of the scoreboard match
ports. For supporting broadcast, the same item should be added to the scoreboard several times.
We recommend calling set_match_port() after each addition for checking that the items were sent
once to each destination.
The following code is an example of defining broadcast in a scoreboard of a DUT, in which packets
to address 0 are expected to be broadcast to all destinations. This is implemented by
calling add_to_scbd() and set_match_port() once per each destination.

extend packet_to_packet_scbd {

packet_add_predict(item : packet_s) is only {


add_to_scbd(item);

if (item.addr == 0) then {

// Packet should be broadcast.


// Set the added item to match_port_0,
// and add the packet two more times

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);
};
};
};

September 2021 262 Product Version 21.09


UVM e User Guide
Using the UVM e Scoreboard

The DUT Performs Transformation on the Items


In some cases, the DUT does not send items identical to the items it received. For example, it might
change the address field of the item or recalculate a check-sum. When defining a scoreboards for
such a DUT, you should override the default matching algorithm of the scoreboard. Shown here are
two examples of such scoreboards:
Ignoring Some of the Fields in the Matching Process
Some of the fields should be ignored by the scoreboard, either because they are no-care, or
because they are checked elsewhere in the environment.
Performing Data Transformation
The DUT performs some transformation on the data item, and the scoreboard should check all
the fields according to this transformation.
For an example of data transformation requiring type conversion (for example, the DUT receives
AHB transfers and transmits Ethernet packets), see Scoreboard of Data-Mover Interfacing Two
Different Protocols.

Ignoring Some of the Fields in the Matching Process


The scoreboard default matching is based on calculating the CRC (crc_32()) on all the item’s
physical fields. If the requirement is to compare only some of the fields while ignoring others, you
should override the compute_key() method, and calculate the key only on the fields of interest.
The compute_key() method accepts any_struct, and should return a uint, the key of this struct. The
following code extends the packet_to_packet_scbd defined in Scoreboard For a Data-Mover,
adding to it the capability to compare only the data fields:

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.

September 2021 263 Product Version 21.09


UVM e User Guide
Using the UVM e Scoreboard

Performing Data Transformation


Sometimes the scoreboard should check the correctness of the data transformation performed by
the DUT. In such cases, the scoreboard should perform the same transformation as the DUT.
Assume the DUT described in Scoreboard For a Data-Mover has to modify the addr field, and then
recalculate the check_sum. This should be implemeted in the add-predict method, as shown in the
following example. This example extends the packet_to_packet_scbd defined in Scoreboard For a
Data-Mover, adding to it the data transformation:

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);
};
};

See more examples of data transformations in Matching Items of Different Protocols.

Scoreboard of Data-Mover Interfacing Two Different Protocols


The example in scoreboard_data_mover assumes a repeater DUT, forwarding the same items it
receives. Typically, the DUT is connected to two different interfaces. For such DUTs, the
scoreboard architecture should reflect the DUT’s: the add and match ports should be of the same
types as the related DUT’s interfaces. Assume a DUT with two interfaces: one of packets, the other
of frames. Packets sent from agent #1 are transformed to frames and sent to agent #2, and vise
versa, as shown in Figure 9.5.
Figure 9.5: ​Packet to Frame DUT

September 2021 264 Product Version 21.09


UVM e User Guide
Using the UVM e Scoreboard

Defining the Scoreboard


For verifying a data mover DUT that has two different interfaces, the scoreboard ports should reflect
the DUT interfaces, as illustrated in Figure 9.6. There are four
ports: add/match_packet and add/match_frame.
Figure 9.6: ​Packet-Frame Scoreboard

Assume the two data items the DUT handles are packets and frames, containing these fields:

stuct packet_s like any_sequence_item {


%addr : uint(bits : 16);
%data : list of byte;
};
stuct frame_s like any_sequence_item {
%addr : byte;
%payload : list of byte;
};

The scoreboard for such an environment should contain:


Two add ports, one per each type
Two match ports, one per each type
Setting the predicted match port for each added item:
Packets passed to packet_add, are matched against frames passed to frame_match
Frames passed to frame_add, are matched against packets passed to packet_match

The code for such a scoreboard follows:

September 2021 265 Product Version 21.09


UVM e User Guide
Using the UVM e Scoreboard

unit frame_packet_scbd like uvm_scoreboard {


// The ports for #1 to # 2 flow
scbd_port packet_add : add packet_s;
scbd_port frame_match : match frame_s;

// The ports for #2 to #1 flow


scbd_port frame_add : add frame_s;
scbd_port packet_match : match packet_s;

// predict method of added packets -


// match against frame_match port
packet_add_predict(item : packet_s) is only {
add_to_scbd(item);
set_match_port(item, frame_match);
};

// predict method of added frames -


// match against packet_match port
frame_add_predict(item : frame_s) is only {
add_to_scbd(item);
set_match_port(item, packet_match);
};
};

The scoreboard should be instantiated and connected to related monitors ports:

September 2021 266 Product Version 21.09


UVM e User Guide
Using the UVM e Scoreboard

unit env like uvm_env {


// an agent implementing the packet protocol
packet_agent : packet_agent_u is instance;

// an agent implementing the frame protocol


frame_agent : frame_agent_u is instance;

scbd : frame_packet_scbd is instance;

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);

// Connect the #2 to #1 flow


// Frame TX connected to the frame_add port
// Packet RX connected to the packet_match port
frame_agent.tx_monitor.item_b_ended.connect(scbd.frame_add);
packet_agent.rx_monitor.item_a_ended.connect(scbd.packet_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

September 2021 267 Product Version 21.09


UVM e User Guide
Using the UVM e Scoreboard

the packet_match port.


Packets that are transmitted by the DUT are collected by the packet monitor, agent #1,
and passed to the packet_match port.
The scoreboard searches for a matching item - the frame passed to the frame_add port is
matched with the packet passed to the packet_match port.
The next step is verifying the scoreboard performs the correct checking when comparing the add
and matched items. This step is described in Matching Items of Different Protocols.

Matching Items of Different Protocols


By default, in the example of packet-frame scoreboard implemented in Scoreboard of Data-Mover
Interfacing Two Different Protocols, the scoreboard will compare the physical fields of the items. If
packing of the packet physical fields gives same results as packing the frame physical fields, the
two items are considered as matching. Typically, this is not the case. A few examples are shown
below:
Comparing Specific fields
Few of the items’ fields are expected to be identical, while the others are don’t-care for the
scoreboard.
Performing Data Transformation
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 data item of one interface is a combination of several items of the other interface.

Comparing Specific fields


Sometimes, comparing two items of different types requires comparing specific fields. As an
example, assume that the match criteria between packets and frames is that the payload field of the
frame is identical to the data field of the packet. For verifying such a DUT, you should override the
scoreboard default matching algorithm by extending the compute_key() method. The following code
extends the scoreboard defined in Scoreboard of Data-Mover Interfacing Two Different Protocols,
adding to it the matching algorithm.

September 2021 268 Product Version 21.09


UVM e User Guide
Using the UVM e Scoreboard

extend packet_frame_scbd {
compute_key(item : any_struct) : uint is only {
var bit_list : list of bit;

if item is a packet_s (p) then {


// The only field that should be checked is the data field
bit_list = pack(NULL, p.data);
result = bit_list.crc_32, bit_list.size()/8);
};

if item is a frame_s (f) then {


// The only field that should be checked is the payload field
bit_list = pack(NULL, f.payload);
result = bit_list.crc_32, bit_list.size()/8);
};
};
};

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.

Performing Data Transformation


In some cases, such as performing a real data compare, the scoreboard should perform the same
data transformation as the transformation performed by the DUT. In such cases, the user should
extend the add-predict method. Instead of its default behavior of adding the item to the scoreboard,
the user should implement code creating the predicted result of this item, and add that predicted
result to the scoreboard. For the packet-frame example shown in Scoreboard of Data-Mover
Interfacing Two Different Protocols, packets should be transformed to frames, and vise versa, as
shown in the example below. We recommend implementing a conversion method and calling it
from add-predict method.
The example implements:
Two add ports, one per each type
Two match ports, one per each type
Setting the predicted match port for each added item.
Converting packets to frames
Converting frames to packets
global_ooo_depth == UNDEF: Indicating no ordering rules between the ports.

match_port_ooo_depth == 1: Indicating that each port for itself works in FIFO ordering.

September 2021 269 Product Version 21.09


UVM e User Guide
Using the UVM e Scoreboard

The code for such a scoreboard follows:

packet_frame_scbd like uvm_scoreboard {


// The ports for packets-to-frames
scbd_port packet_add : add packet_s;
scbd_port frame_match : match frame_s;

// The ports for frames-to-packets flow


scbd_port frame_add : add frame_s;
scbd_port packet_match : match packet_s;

// No ordering between the ports is required


keep global_ooo_depth == UNDEF;
keep port_match_ooo_depth == 1;

// predict method of packets -


// create the predicted frame, which is expected
// to be sent from the DUT
packet_add_predict(item : packet_s) is only {
var expected_f : frame_s;
expected_f = convert_packet_to_frame(item);
add_to_scbd(expected_f);
set_match_port(expected_f, frame_match);
};

// predict method of frames -


// create the predicted packet, which is expected
// to be sent from the DUT
frame_add_predict(item : frame_s) is only {
// similar to the method above - create a
// packet and add it to the scoreboard
var expected_p : packet_s;
expected_p = convert_frame_to_packet(item);
};

};

Performing a One-to-Many or Many-to-One Transformation


Some systems fragment large data items into a list of shorter ones, and on the other direction,
combine several items into one. For example, building a frame from several transfers. The
scoreboard for such a system should perform a similar transformation. It is very similar to the
example shown in Performing Data Transformation, but requires the scoreboard to maintain
temporal items before adding or matching them. Assume a scoreboard matching frames and bursts,
where each burst is created by concatenating the data of few frames. The following code is a simple
example: each burst item is created from 5 frames. Other transformation protocols could be packing
the data field until reaching maximum length, or an end-of-data indication field. The example
implements:

September 2021 270 Product Version 21.09


UVM e User Guide
Using the UVM e Scoreboard

Two add ports, one per each type


Two match ports, one per each type
Setting the predicted match port for each added item.
Bursts are fragmented to frames
Frames are combined creating bursts
global_ooo_depth == UNDEF: Indicating no ordering rules between the ports.

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.

The code for such a scoreboard follows:

September 2021 271 Product Version 21.09


UVM e User Guide
Using the UVM e Scoreboard

unit frame_burst_scbd like uvm_scoreboard {


// The ports for frames-to-burst flow
scbd_port frame_add : add frame_s;
scbd_port burst_match : match burst_s;

// The ports for burst-to-frames flow


scbd_port burst_add : add burst_s;
scbd_port frame_match : match frame_s;

// No ordering between the ports is required


keep global_ooo_depth == UNDEF;
keep match_port_ooo_depth == 1;

// Frames might start being seen before the whole


// burst is sent
keep delay_match_period == 200;
!collected_frames : list of frame_s;

// predict method of frames -


// collect frames, until have 5.
// create the predicted burst, and add to the scoreboard
frame_add_predict(item : frame_s) is only {
// collect items
collected_frames.add(item);

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();
};
};

// Predict method of burst


// fragment the burst to 5 frames and add them one
// by one to the scoreboard
add_burst_predict(item : burst_s) is {
var data_list : list of byte;
data_list = pack(NULL, burst.data);

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);
};
};
};

September 2021 272 Product Version 21.09


UVM e User Guide
Using the UVM e Scoreboard

Scoreboard For a Peripheral


A peripheral is a device connected to a network and to a bus. A verification environment for a
peripheral DUT is illustrated in Figure 9.7. The VE contains several agents: a network agent
connected to the network interface of the DUT, and several agents connected to the bus. One of the
bus agents is a passive agent, a shadow of the DUT: it monitors all transactions to and from the
DUT.
Figure 9.1: ​VE of a Peripheral DUT

Defining the Scoreboard


Data check of a peripheral DUT requires checking all the bus transactions against the network data
items (frames, in this example).
When writing to the DUT a command to transmit, check that indeed the DUT transmits the
required frame.
When the DUT receives a frame, perform a read transaction to its RX FIFO, and check that
correct data is read.
These checks can be implemented using two scoreboards, shown in Figure 9.8:
Bus to Network: Check that all write transactions result with correct DUT transmits

September 2021 273 Product Version 21.09


UVM e User Guide
Using the UVM e Scoreboard

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:

stuct frame_s like any_sequence_item {


%addr : packet_addr_t;
%data : list of byte;
};
stuct transfer_s like any_sequence_item {
%addr : packet_addr_t;
%kind : [READ, WRITE];
%data : list of byte;
};

The following code implements the scoreboard shown above for a peripheral DUT:

September 2021 274 Product Version 21.09


UVM e User Guide
Using the UVM e Scoreboard

unit bus_net_scbd like uvm_scoreboard {


// Bus to net flow
scbd_port transfer_add : add transfer_s;
scbd_port frame_match : match frame_s;

// Net to bus flow


scbd_port frame_add : add frame_s;
scbd_port transfer_match : match transfer_s;
};

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().

September 2021 275 Product Version 21.09


UVM e User Guide
Using the UVM e Scoreboard

extend bus_net_scbd like uvm_scoreboard {


base_addr : uint;
// Add only WRITE transfers
transfer_add_predict(item : transfer_s) is only {
if (item.addr == base_addr + TX_FIFO_ADDR and
item.kind == WRITE) then {
// Only WRITE to TX_FIFO are added to the scoreboard
add_to_scbd(item);
};
};

// Match only READ transfers


transfer_match_reconstruct(item : transfer_s) is only {
if (item.addr == base_addr + RX_FIFO_ADDR and
item.kind == READ) then {
// Only READ from RX_FIFO should be matched scoreboard
match_in_scbd(item);
};
};
};

Matching Network Item to Bus Item


The scoreboard default matching algorithm is based on comparing the items physical fields. When
an item is added to an add port, a key is calculated on it, based on its physical fields. 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 an identical key on expected port. Items with identical keys are considered matching. Error
messages that can result from this search are described in Match Flow.
When comparing the network data item to the bus item, the physical fields will likely not be equal.
There are few typical cases:
Comparing Specific fields
Few of the items’ fields are expected to be identical, while the others are don’t-care for the
scoreboard.
Performing Data Transformation
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.

September 2021 276 Product Version 21.09


UVM e User Guide
Using the UVM e Scoreboard

Scoreboard for a Bridge


Assume a verification environment as seen in Figure 9.9: a verification environment of a bridge; the
DUT has two interfaces to buses. It can be, for example, a Master on BusA and a slave on BusB.
Transfers sent to the DUT from BusB, are expected to be transferred by it on BusA. Responses it
gets from BusA should be passed by it on BusB.
Figure 9.1: ​VE Of A Bridge DUT

An example of data flow in such a VE:


1. An active master agent on Bus B sends a read transfer.
2. The DUT creates a transfer of the BusA type, based on the transfer it just got, and transfers it
on BusA.
3. The appropriate active slave agent on BusA responses to this transfer.
4. The DUT creates a response of the BusB type, based on the response it just got, and transfers
it on BusB.
The verification environment contains two interface UVCs. In each of the UVCs, there are several
agents connected to the buses. In each UVC, One of the agents is a passive agent, a shadow
model of the DUT; items sent to/from the DUT are monitored by this agent’s monitor.

September 2021 277 Product Version 21.09


UVM e User Guide
Using the UVM e Scoreboard

Defining the Scoreboard


For verification, the Scoreboard should contain two add ports and two match ports, as shown
in Figure 9.10.
One add port of transfers sent to the DUT on BusB, as monitored by the passive agent
representing the DUT.
One match port of transfers coming from the DUT on BusA, as monitored by the passive agent
representing the DUT.
One add port of responses sent to the DUT on BusA, as monitored by the passive agent
representing the DUT.
One match port of responses coming from the DUT on BusA, as monitored by the passive
agent representing the DUT.
Figure 9.2: ​Scoreboard for a Bridge

Assume the four data items of the two buses are defined as followed – transfer and response per
each bus:

September 2021 278 Product Version 21.09


UVM e User Guide
Using the UVM e Scoreboard

stuct bus_a_transfer_s like any_sequence_item {


%addr : addr_t;
%data : list of byte;
%control : uint (bits : 8);
};
struct bus_a_response_s like any_sequence_item {
%data : list of byte;
%status : uint (bits : 8);
};
stuct bus_b_transfer_s like any_sequence_item {
%addr : byte;
%data : byte;
};
struct bus_a_response_s like any_sequence_item {
%addr : byte;
%data : byte;
};

The following code implements a scoreboard for a bridge. It implements:


The four ports: add/match_transfer and add/match_slave
Defining the correct flow, meaning: comparing transfer to transfer and response to response.
This is implemented by extending the predict methods, and calling the set_match_port. The
exact name of the predict method is port-name_predict, and it has one argument of the same
type as the port item type. Note that if you override this method, you must add the item to the
scoreboard.

unit bridge_scbd like uvm_scoreboard {


// The ports for transfers from BusB to BusA
scbd_port transfer_add : add bus_b_transfer_s;
scbd_port transfer_match : match bus_a_transfer_s;
// The ports for responses from BusA to BusB
scbd_port response_add : add bus_a_response_s;
scbd_port response_match : match bus_b_response_s;
// predict method of transfers
transfer_add_predict(item : bus_b_transfer_s) is only {
add_to_scbd(item);
set_match_port(item, transfer_match);
};
// predict method of responses
response_add_predict(item : bus_a_response_s) is only {
add_to_scbd(item);
set_match_port(item, response_match);
};
};

Usually, you should implement some code overriding the scoreboard default algorithm. Two typical
examples are:

September 2021 279 Product Version 21.09


UVM e User Guide
Using the UVM e Scoreboard

The DUT Does Not Forward All of the Items


Matching Items of Different Protocols

The DUT Does Not Forward All of the Items


Usually, not all items sent to the DUT are expected to be sent by it. Typical cases are control items,
items sent when the DUT is not ready to receive, or illegal items – all of which should not be added
to the scoreboard. This can be implemented by extending the add-predict method. This method is
called by the scoreboard whenever an item is written to the add port, and its default implementation
is adding the item to the scoreboard. The exact name of this method is port-name_predict, and it
has one argument, the same type as the port item type.
If an item has to be added to the scoreboard after overriding this method, you must add the item by
calling add_to_scbd().
The following code extends the bridge_scbd defined in Defining the Scoreboard, adding to it the
capability to ignore some of the transfers:

extend bridge_scbd {
transfer_add_predict(item : bus_b_transfer_s) is only {

// Add to the scoreboard only legal data transfers


if (item.legal and item.kind == data) then {

add_to_scbd(item);
set_match_port(item, transfer_match);
;
};
};

Matching Items of Different Protocols


By default, the bridge_scbd example implemented in Defining the Scoreboard compares the
physical fields of the items. If packing of the bus_a_transfer_s physical fields gives same result as
packing the bus_b_transfer_s physical fields, the two items are considered as matching. Typically,
this is not the case. A few examples are shown in these sections:
Comparing Specific fields
A few of the items’ fields are expected to be identical, while the others are don’t-care for the
scoreboard.
Performing Data Transformation

September 2021 280 Product Version 21.09


UVM e User Guide
Using the UVM e Scoreboard

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

Scoreboard for an Interconnect


An interconnect is a device connected to several agents that may be of same or different protocols,
as shown in Figure 9.11. Each connected master is routed to one or more slaves, meaning that
each transaction sent to the interconnect from a master is forwarded by the DUT to the appropriate
slave, based on the transaction address and the DUT’s defined address mapping.

Figure 9.1: ​An Interconnect


A scoreboard for an interconnect should
Have one add port and one match port per each of the DUT’s interfaces.
Implement same address mapping as the DUT.
We recommend using the UVM Interconnect Scoreboard for verifying interconnects.

Scoreboard for a Memory Controller


Theoretically, you could use a scoreboard for verifying a memory controller. To do this, define a
data item consisting of address and data, add to the scoreboard each write to the DUT RAM, and
match each read from the DUT RAM against the scoreboard database.
Defining such a scoreboard also requires handling issues like multiple writes to same address and
supporting different data widths. For example, the scoreboard should handle writing data of 32 bits
to address 0 followed by reading from address 2. The scoreboard correctly handle the data.

September 2021 281 Product Version 21.09


UVM e User Guide
Using the UVM e Scoreboard

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.

FIFO on each DUT output interface, but not between them


When the scoreboard has more than one output interface (each mapped to a scoreboard match
port), usually the DUT is expected to maintain First In First Out ordering between the items of each
match port, but no ordering between the ports. That is, the DUT can send out an item on match port
#2, even when there is a pending item waiting for match port #1. There are two variations of this
requirement:
Maintain FIFO order on items regardless of their source (DUT input)
When each output port of the DUT has to keep FIFO ordering on all the items it sends, regardless of
their source, constrain these scoreboard fields:
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.
Maintain FIFO order on items coming from same source (DUT input)

September 2021 282 Product Version 21.09


UVM e User Guide
Using the UVM e 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

Ordering streams interleaving in one port


It can happen that the DUT input interface receives streams of items. Items from each stream should
maintain FIFO order, but there is no ordering rules between the streams. For example, two streams,
SA and SB, have items SA1, SB1, SA2, SB2, and SA3. The DUT can send SA2 before SB1.
To define streams, declare each item as belonging to a specific stream by calling set_stream_id().
Note: When there are multiple streams and no order is expected between the streams, it is usually
recommended to set the global OOO flag, global_ooo_depth, to UNDEF.
A single Item out of order
In some cases, one item should have its own Out Of Order attribute, different from the general
ordering algorithm. For example, an error item may cause an error response that is out of order
while all other items are in FIFO ordering. To define ordering attributes on a specific item, you can
call one of the following:
set_item_global_ooo_depth()

September 2021 283 Product Version 21.09


UVM e User Guide
Using the UVM e Scoreboard

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.

Scoreboard Power Aware and Reset


For implementing scoreboard behavior upon power change or reset, you can use
the uvm_scoreboard hooks reset_started/reset_ended, power_up/power_down, and
the unstable_state flag.
A monitor (for example, the monitor of the uvm_lp UVC) should emit the scoreboard events upon
power change.
The uvm_scoreboard/examples directory contain two examples demonstrating how the scoreboard
can consider power and reset.
The following code is copied from reset_example.e. It demonstrates extending the scoreboard and
defining the rules for items coming during this period. In this example, upon reset the scoreboard
items are marked as UNCERTAIN, so that if no match is found no error will be reported

September 2021 284 Product Version 21.09


UVM e User Guide
Using the UVM e Scoreboard

extend env {power_


tf_reset() @tf_phase_clock is also {
emit me.scbd.reset_started;
};
tf_main_test() @tf_phase_clock is also {
emit me.scbd.reset_ended;
};
};
extend xserial_to_xserial_scbd {
on reset_started {
unstable_state = TRUE;
me.reset_scbd();
};
on reset_ended {unpredictable_state = FALSE};
// During unstable_state state : mark items as UNCERTAIN
add_to_scbd(predicted_item: any_struct) is also {
if unstable_state {
set_item_status(predicted_item, UNCERTAIN);
};
};
match_in_scbd(predicted_item: any_struct) is also {
if unstable_state {
set_item_status(predicted_item, UNCERTAIN);
};
};
};

The uvm_scoreboard/examples/power_aware directory contains an example of connecting


the uvm_scoreboard to a low power monitor.
The following code, copied from sample_scbd_using_power.e, shows the connection of the events
coming from the LP monitor to the scoreboard

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");
};
};
};

September 2021 285 Product Version 21.09


UVM e User Guide
Using the UVM e Scoreboard

Debugging Aids for the UVM Scoreboard


There can be two main causes for DUT errors reported by the scoreboard:
A bug in the DUT
A bug in the scoreboard implementation or connection
When an error is reported, you might want to get some more information of what is actually
happening. There are several debugging aids that give you insight into the values in the scoreboard
database and the process that got them there. The scoreboard debugging aids are described in the
following sections:
Viewing Debug Messages
Querying the Scoreboard Database
Browsing the Scoreboard Database
Optimizing Indago Probing with the uvm_scbd_indago_probe_opts File
Running the Scoreboard Debugging Example

Viewing Debug Messages


The UVM scoreboard messages use the UVM_SCBD tag. During debugging, Cadence recommends
setting the verbosity level of the scoreboard messages to HIGH.
You can do this with the Specman set message command or with
the message_manager.set_screen_messages() method.
Command example:
Specman > set messagsys -tag==UVM_SCBD

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.

September 2021 286 Product Version 21.09


UVM e User Guide
Using the UVM e Scoreboard

Querying the Scoreboard Database


The UVM scoreboard contains several query methods, providing information about the scoreboard
current database and the match process.
For example, the get_key() method returns the item’s key, the base for the match algorithm. When
there is an unexpected mismatch, you can call get_key() for the items on the added and matched
item. If their keys are not equal, but according to the DUT specifications these items are legal
match, you should modify the key calculation by overriding compute_key().
See the UVM e Reference manual for the full list of the scoreboard query methods.

Browsing the Scoreboard Database


To get information from the current status of the scoreboard database, you can look at the
scoreboard all_items field. This field contains information about all the items that have been added
to the scoreboard and have not been matched. The items in all_items are of
type uvm_scbd_add_item, and contain the original item that was passed to the scoreboard add port
(originator). Other fields contain information about this item, such as the item key, expected match
port, the time it entered the scoreboard, and more.
In some cases, the cause for the DUT error results from earlier items. For example, perhaps an
earlier item was matched by mistake, thus causing disorder in the matching algorithm. For this
reason, the scoreboard maintains a history list of all items that have been matched. The history
maximum depth is 10 by default, and can be controlled by the user by constraining the
scoreboard history_limit field.
See Also
Using the Variables Window to Investigate Misplaced Values

Optimizing Indago Probing with the


uvm_scbd_indago_probe_opts File
The uvm_scbd_indago_probe_opts probe-options file optimizes probing scoreboard related issues
with Indago. This file filters out methods that are internal to the scoreboard and thus not of interest
during your debug process. Use it while collecting the Indago database or probing a test. You can
also edit the file to choose which options you want or to copy and paste from it into your own
options file.
This file is in the following location:

September 2021 287 Product Version 21.09


UVM e User Guide
Using the UVM e Scoreboard

<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

Running the Scoreboard Debugging Example


The uvm_scbd/utils/debug directory contains an example that you can run to familiarize yourself
with the debugging process.
This section describes how to run the example and provides suggestions for debugging the
scoreboard related code in the example:
Starting the Scoreboard Debug Example
Using the Variables Window to Investigate Misplaced Values
Note: For an in-depth introduction to Indago, run the example described in Introduction to the
Indago Debug Analyzer App in Indago Debug Analyzer App User Guide.

Starting the Scoreboard Debug Example


To run the scoreboard debug example:
1. Define UVM_LIB to “<install-dir>/specman/uvm/uvm_lib”.
2. Run the following command.

> xrun $UVM_LIB/uvm_scbd/examples/error_test.e


-sncompargs -enable_DAC -snprerun "ida_probe -flow -log
-f=$UVM_LIB/uvm_scbd/utils/debug/uvm_scbd_indago_probe_opts;test;indago"

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.

September 2021 288 Product Version 21.09


UVM e User Guide
Using the UVM e Scoreboard

Using the Variables Window to Investigate Misplaced Values


To solve the problem found in the example, you need to investigate how the ADD item
(“failed_item.associated_struct”) and the MATCH item
(“current_match_item.associated_struct”) got their values.
You could use RCA (Root Cause Analysis) to trace these items back to their creation. However,
because you know they were added to the scoreboard by writing to the add/match ports, you can
save time by using the Variables window. This window lets you go directly to thecode where the
items were written to the scoreboard:
1. Open the Variables window.
2. Right click on the item you are interested in
—failed_item.associated_struct or current_match_item.associated_struct—and
choose Explore.
3. Click the left-arrow next to one of the struct's fields. This will bring you to the last time this field
was modified, before it was written to the scoreboard's port.
You could also view the changes in the scoreboard database (the all_items) in a Time Table:
Open the Variables window.
1. Expand me.
2. Right click on all_items and choose Show Assignments.

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.

September 2021 289 Product Version 21.09


UVM e User Guide
Using the Low Power Universal Verification Component (LP UVC)

10

Using the Low Power Universal


Verification Component (LP UVC)
This chapter contains the following sections:
Introduction
Integrating the LP UVC in the Verification Environment
Using the LP UVC Sequencers
See Also
UVM Low Power in the UVM e Reference

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

September 2021 290 Product Version 21.09


UVM e User Guide
Using the Low Power Universal Verification Component (LP UVC)

2. Integrating the LP UVC in the Verification Environment


Once the LP UVC is integrated in the verification environment, you can use it in the tests:
Using LP UVC Monitoring
Using the LP UVC Sequencers
See Also
These sections in the UVM Low Power chapter of the UVM e Reference provide additional
information:
Low-Power Universal Verification Component
Automatic UVC Creator

Integrating the LP UVC in the Verification


Environment
The LP UVC is created automatically using the Automatic UVC Creator in the UVM e Reference.
To add the LP UVC to the verification environment:
1. Import the UVC top file. This is the file created by the LP UVC Creator.
2. Instantiate the created uvm_lp_env unit in the verification environment. Cadence recommends
instantiating it in the top most env unit or in the SVE unit.
3. Optional:
Modify the Ports Configuration
Extend the created sequences library. See Using the LP UVC Sequencers for more
information
In our example, the uvm_lp_env is instantiated in the SVE unit. Note that the field definition is of the
sub type defined in the created code. The sub type value, tb_apb_subsystem, is the power group
name. The name defined in the CPF set_design command.
The CPF file contains these lines:
set_design tb_apb_subsystem -testbench

set_instance i_apb_subsystem

The created code contains these lines:

September 2021 291 Product Version 21.09


UVM e User Guide
Using the Low Power Universal Verification Component (LP UVC)

extend uvm_lp_group_name_t : [tb_apb_subsystem, apb_subsystem];

extend uvm_lp_env {
keep soft name == tb_apb_subsystem;
};

Instantiating this environment, by the user, should be as follows:

extend apb_subsystem_sve_u {
lp_env : tb_apb_subsystem uvm_lp_env is instance;
};

Modify the Ports Configuration


For most environments, the LP UVC does not need any configuration; units and ports are bound to
the RTL according to the information in the CPF file(s). In some cases, some configuration is
required.
The LP UVC Creator creates a file containing code example of typical configuration. The file name
is <group name>_lp_port_config.e, and is not loaded by default. Cadence recommends that you
copy the relevant pieces from this file into the verification environment configuration file, and modify
it according to the DUT specifics.
Typical configurations are:
Bind the sig_clock to appropriate clock in the design. By default, the clock is tied to sys.any.
If the design is mixed HDL, you might have to override the agent() attribute of some of the
ports

Using LP UVC Monitoring


The LP UVC has monitoring capabilities, providing static and dynamic information about the DUT
power state. This information can be used in other components in the verification environment, for
any of the following purposes:
Synchronize on events of power change; perform activities according to current power mode
Provide cross coverage of power information with other coverage definitions in the VE
Add checks of DUT behavior upon power change
Consider power modes in existing checks
See the UVM e Reference manual for description of the monitoring capabilities:

September 2021 292 Product Version 21.09


UVM e User Guide
Using the Low Power Universal Verification Component (LP UVC)

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

Example: Adding a reference to the LP UVC from other VE


components
extend my_subsystem_monitor_u {
!lp_agent : my_subsystem uvm_lp_agent;
};
extend my_sve_u {
subsystem_env : my_subsystem_env is instance;
lp_env : my_subsystem uvm_lp_env is instance;
connect_pointers() is also {
system_env.monitor.lp_agent =
lp_env.agents[0].as_a(my_subsystem uvm_lp_agent);
};
};

Example: Scoreboard flushing when power goes down


In this example, whenever the DUT enters ALL_OFF mode the scoreboard removes all pending
items. The scoreboard uses the uvm_lp_monitor events, so we add to it a pointer to the
relevant uvm_lp_monitor instance. The pointer should be assigned from the top unit, as shown in
Example: Adding a reference to the LP UVC from other VE components. We use two of the UVM
scoreboard methods—get_top_items() for getting all pending items, and set_item_status for
marking items as DROPPED.

September 2021 293 Product Version 21.09


UVM e User Guide
Using the Low Power Universal Verification Component (LP UVC)

extend my_scbd {
!lp_monitor : my_subsystem uvm_lp_monitor;

event ALL_OFF_mode is @lp_monitor.lp_entered_power_mode_ALL_OFF;


on ALL_OFF_mode {
for each in get_top_items(UNDEF) {
set_item_status(it, DROPPED);
};
};
};

Example: Getting power current status


In this example, a sequence waits for changes in the power status, and inquires the current power
status before sending the next transaction. The sequence driver has pointer to the
relevant uvm_lp_agent instance. The pointer should be assigned from the top unit, as shown in
Example: Adding a reference to the LP UVC from other VE components.

extend apb_subsystem_sequence_driver_u {
!lp_agent : my_subsystem uvm_lp_agent;
};

extend WRITE_IN_DIFFERENT_MODES my_subsystem_sequence {

!ahb_wr : WRITE ahb::ahb_master_driven_burst;

body() @driver.clock is only {

wait @driver.lp_agent.monitor.lp_power_mode_changed;

// ahb transaction depending on current power mode


if driver.lp_agent.get_cur_mode_name() == ALL_ON or
driver.lp_agent.get_cur_mode_name() == STNAD_BY then {
do ahb_wr on driver.ahb_seq_drv keeping {
.data[0] == rx_pkt_max;
.kind == SINGLE;
};
} else {
do ahb_wr on driver.ahb_seq_drv keeping {
.data[0] == rx_pkt_min;
.kind == SINGLE;
};
};
};
};

September 2021 294 Product Version 21.09


UVM e User Guide
Using the Low Power Universal Verification Component (LP UVC)

Example: Defining events based on power events


In this example, a monitor that was defined in a system UVC is extended. A new event is defined,
based on power events and power information.

extend system_monitor {
event ransfer_when_PM0 is
lp_agent.get_cur_mode() == PM0
@transfer_started;
};
};

Using the LP UVC Sequencers


The LP UVC contains two sequence drivers (sequencers), supporting two use models:
uvm_lp_pcm_driver – Emulating a power control manager

uvm_lp_virtual_driver – Stimulating a system containing a power control manager

To use the sequence drivers:


1. Define the uvm_lp_agent as active, by constraining its active_passive field to ACTIVE. (The
default is PASSIVE).
2. Recommended: Extend the sequence libraries.
3. Recommended: Connect the driver clock to a clock in the design. By default, it is tied
to sys.any.

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 {

event clock is only rise(sig_clock$) @sim;

keep sig_clock.hdl_path() == "pclk";


connect_ports() is also {
sig_clock.disconnect();
do_bind(sig_clock, external);
};
};

This section contains:

September 2021 295 Product Version 21.09


UVM e User Guide
Using the Low Power Universal Verification Component (LP UVC)

Using the PCM Sequence Driver


Using the LP Virtual Sequence Driver

Using the PCM Sequence Driver


The uvm_lp_pcm_driver is the sequencer of the uvm_lp_pcm_seq sequence, and comes with a
BFM, uvm_lp_bfm. This BFM can emulate a power control manager, performing power transitions as
described in the CPF.
Note: The PCM sequencer and BFM emulate a power control manager (PCM), and should be used
only when the device under test does not contain a PCM.
To use the PCM driver:
Set the uvm_lp_agent.active_passive to ACTIVE

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 MAIN uvm_lp_pcm_seq {


body()@driver.clock is only {

// Transit to specific mode, PM_MODE_0


do CHANGE_MODE pcm_bfm_seq keeping {
.target_mode == PM_MODE_0;
};

// Shutoff one of the domains, randomly


do SHUTOFF pcm_bfm_seq ;
// Turn them all on
do TURN_ON_ALL pcm_bfm_seq;
};
};

See the description of uvm_lp_pcm_seq and the sequence library in uvm_lp_seq in


the UVM e Reference manual.

September 2021 296 Product Version 21.09


UVM e User Guide
Using the Low Power Universal Verification Component (LP UVC)

Using the LP Virtual Sequence Driver


The LP UVC contains a virtual sequence driver (sequencer), uvm_lp_virtual_driver. Users can
use this sequencer for creating power-related system-level scenarios. The sequences of this
sequencer emulate requests power changes driven to the DUT from other components of the
system, using other sequence drivers in the environment—bus sequencers, or sequencers of a SW
UVC. The LP UVC Creator does not create a sequence library for this sequencer, as these
sequences are system level scenarios, and are not described in the CPF. The verification engineer
should implement the sequences, based on the system specification.
To use the virtual sequencer in your environment:
Set the uvm_lp_agent.active_passive to ACTIVE
Add sub-sequencers to the virtual sequencer
Define Sequences
Consider binding the UVM LP synchronizer clock to a relevant clock. This is the clock the LP
sequencer runs on, and by default it is connected to sys any. You should bind to one of the
system clocks, for example;
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 {
event clock is only rise(sig_clock$) @sim;

keep sig_clock.hdl_path() == "pclk";


connect_ports() is also {
sig_clock.disconnect();
do_bind(sig_clock, external);
};
};

Add sub-sequencers to the virtual sequencer


To add sub sequencers to a virtual sequencer:
1. Add a non generatable field for each required sequencer.
2. Extend the get_sub_drivers() method, to return a list of all sub-sequencers
3. In the SVE file, connect the LP sequencer to all the sequencers it needs, using the SVE
connect_pointers() method.

September 2021 297 Product Version 21.09


UVM e User Guide
Using the Low Power Universal Verification Component (LP UVC)

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:

extend apb_subsystem uvm_lp_virtual_driver {

!ahb_driver : ahb_master_seq_driver;

get_sub_drivers(): list of any_sequence_driver is {


return ({ahb_driver});
};
};
extend apb_subsystem_sequence_driver_u {
!lp_seqer : uvm_lp_virtual_driver;
};
extend apb_subsystem_sve_u {
lp_env : apb_subsystem uvm_lp_env is instance;

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;
};
};

See also Creating and Using Sequences in Cadence Help.

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.

September 2021 298 Product Version 21.09


UVM e User Guide
Using the Low Power Universal Verification Component (LP UVC)

extend VIA_BUS CHANGE_MODE uvm_lp_virtual_seq {


!lp_ctrl_wr : WRITE ahb::ahb_master_driven_burst;

body() @driver.clock is only {


if target_mode == PM_smc_uart then {
do lp_ctrl_wr on driver.as_a( apb_subsystem
uvm_lp_virtual_driver)
.ahb_driver keeping {
.data[0] == 0x06;
.first_address == 0x008d_0004;
.kind == SINGLE;
};
};
};

September 2021 299 Product Version 21.09


UVM e User Guide
Acceleratable UVCs

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

September 2021 300 Product Version 21.09


UVM e User Guide
Acceleratable UVCs

implemented in a a language designed for verification, using advanced verification tools.


Reuse
The methodology is based on UVM and is applicable to all UVM-based UVCs.
For a UVC to be Acceleratable
For a UVC to be acceleratable, it has to:
1. Have high-level components working in transaction level
2. Contain transactors that can run on the HW accelerator
3. Implement performance maximization
4. Be able to function in an unclocked environment
If you choose to use SCE-MI, you can use SCE-MI proxies defined in the UVM acceleration
package, which is is delivered within UXE.. This package also contains examples of using DPI for
function calls.
UVM acceleration is an extension of the standard simulation UVM and is fully backwards
compatible with it. This means that UVCs architected to be acceleratable can be used in a
simulation-only environment or a hardware-accelerated environment. However, UVCs that were not
architected to leverage hardware acceleration require modifications to allow them to be used in a
hardware-accelerated environment. Cadence recommends that all UVCs be created acceleration
ready, even if the accelerated part is not implemented at first stage. “Better ready now, than sorry in
two years, when you decide to use an accelerator.”
This chapter is for verification engineers who are familiar with the Universal Verification
Methodology (UVM) and the e language. It contains:
When to Apply Acceleration
Optimizing Performance
Acceleration-Ready UVCs
Modifying a UVC to be Acceleration-Ready
See Also:
UVM Acceleration—SCI-MI Pipes Proxies in the UVM e Reference for more information on
the UVM SCE-MI pipes proxies, which provide an easy-to-use interface for the e writers to
transfer transactions from the DUT running on HW emulators to verification components on
the simulator.
The TBA Macro-Based Interface Transactor Modeling Methodology Guide, available in the
UXE or IXE release for more information about writing the transactors, the pieces of code that

September 2021 301 Product Version 21.09


UVM e User Guide
Acceleratable UVCs

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.

Terminology Used in this Chapter


Table 11.1: Terminology

Terms and Definitions


Acronyms

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.

Collector An entity contained in a UVC monitor or in a transactor that is responsible for


extracting the information from the bus and translating it into data structures
or messages. This entity does only the collection & translation, and does not
perform any of the high level verification tasks (coverage and check). The
entity is only passive as it only collects data and does not participate in the
interface protocol. The collector can be either a separate entity (that is, a
unit/class or a synthesizable HDL module named collector), or a set of
methods/tasks within the monitor.

Data Item An abstracted implementation of a transaction. Its name and content depends
on the protocol, typical example: packet, transfer, burst.

DUT Device Under Test

HDL Hardware Description Language (In this chapter and related code example,
Verilog)

HDL Side Partition containing the DUT and BFMs in RTL

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.

RTL Register Transfer Level

September 2021 302 Product Version 21.09


UVM e User Guide
Acceleratable UVCs

SCE-MI Standard Co-Emulation Modeling Interface

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.

TBA Transaction Based Acceleration. The HVL part of the verification


environment working in transaction level.

UVC Universal Verification Component. A UVC is a completely encapsulated,


ready-to-use, configurable verification environment for an interface protocol,
design module, or a full system.
An accelerated UVC is a UVC whose architecture contains a transactor that
is configured to work in either simulation or acceleration modes.

When to Apply Acceleration


The main reason for running accelerated verification is the ability to run much longer tests. Some
use acceleration also to utilize the power estimation capabilities of the Palladium tools. Creating a
verification environment that can run with accelerators requires investment in both tools and labor.
This investment is justified only if performance is significantly improved. Following are some
environment characteristics that tend to justify the cost of acceleration:
Very large system
In smaller systems, the performance improvement might not be significant. Typically, it is not
worthwhile to accelerate a design less than 2 million gates.
Minimal reactivity
When the verification environment is very reactive, it means many interactions between the
HDL side and the HVL side. Each such interaction slows down the run.
Infrequent generation
Each generation of a data item requires an interaction between the HVL (generating the data
item) and the HDL side (sending the data item to the device). Typically, serial interfaces like
Ethernet and AHB have few generations, so they are good candidates for acceleration. Bus
interfaces, like PCI, generate items much more frequently, even every cycle. Accelerating
such interfaces will not achieve a significant performance enhancement.

September 2021 303 Product Version 21.09


UVM e User Guide
Acceleratable UVCs

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.

September 2021 304 Product Version 21.09


UVM e User Guide
Acceleratable UVCs

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

Function Calls Between HDL and HVL


Cadence recommends creating all UVCs as acceleration ready. An acceleration-ready UVC can be
extended with acceleration capabilities, such as integration with HW transactors, without any
editing to the UVC code, just adding the accel_mode extensions on top of the existing UVC. The
basic requirement for an acceleration ready UVC is clear definition of what is done by the low-level
components (BFM and collector), and what is implemented in the high-level components
(sequences, monitor, checkers).
This section describes the steps of creating an acceleration-ready UVC:
1. Design the UVC so that there is clear separation between its high-level and low-level
components. UVM and UVM compliant UVCs do have such a separation.
See Accelerated UVC Architecture.
2. Add abstraction-level flag:
If the UVC does not contain an abstraction_level flag, add such a field to all main
components: env, agent, seq-er, monitor, BFM, and collector. The value of this field
should be SIGNAL by default, and propagated top down, so that setting the env’s
abstraction level to ACCEL will propagate to all the units in the environment.
3. Implement the high-level components of the UVC.
See High Level of the UVC.
4. Implement the low-level components of the UVC. Implement the low-level components in
Verilog or SystemVerilog.
See BFM as Proxies to HW.

September 2021 305 Product Version 21.09


UVM e User Guide
Acceleratable UVCs

5. Consider performance enhancements.


See High Performance Sequences.
6. More things to consider when creating an accelerated UVC are:
Working with Unclocked Models
Acceleration Customized Data Item

Accelerated UVC Architecture


Figure 11.2 shows a traditional UVC architecture, in which the low-level components, BFM and
collector e units, implement the data injection and collection. These are the components that
interface with the DUT. The high-level components are the monitor (with checkers inside or beside
it), and the sequence driver (seq-er). The seq-er passes data items to the BFM and the monitor gets
data items from the collector.
Figure 11.1: ​Traditional UVC Architecture

Figure 11.3 shows the acceleration-ready UVC architecture for a basic UVC.

Figure 11.2: ​Acceleration Ready UVC Architecture

September 2021 306 Product Version 21.09


UVM e User Guide
Acceleratable UVCs

Figure 11.2: ​Acceleration Ready UVC Architecture

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.

Figure 11.3: ​Acceleration-Ready Layered UVC Architecture

September 2021 307 Product Version 21.09


UVM e User Guide
Acceleratable UVCs

Figure 11.3: ​Acceleration-Ready Layered UVC Architecture

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.

September 2021 308 Product Version 21.09


UVM e User Guide
Acceleratable UVCs

High Level of the UVC


Even when not running with accelerators, Cadence recommends defining a clear partition between
the high level and low level of the UVC. The high level of the UVC—the monitor and sequence
driver (seq-er)—should run in transaction level, and be independent of the low-level details of the
protocol.
For the UVC to be acceleration ready, there are few basic guidelines the high level of the UVC
should follow:
Work only in transaction level.
Refer only to the data item fields; do not make assumptions about how the fields are
passed to and from the DUT.
Avoid relying on exact timing. The monitor and checkers should rely only on events
emitted by the collector. For further information, see Working with Unclocked Models.
Never access the DUT internals.
Avoid synchronizing on DUT signals; use events coming from the collector.
Avoid “picking” into DUT signals. If some data is required, collect it in the collector, and
export to monitor via ports. Defining all ports to DUT in a signal map unit helps
monitoring and keeps to this methodology. No other unit can access the signals.

The High Level in Acceleration Mode


When running in acceleration mode, when the DUT is running on a HW acceleration machine, a
few extensions are required to enhance performance:
Do not run on HDL clocks. Only transaction-level events are used as sampling events of the
UVC TCMs. See Working with Unclocked Models for more information.
Disable protocol checks; set the has_checks flag to FALSE. Only system-level checks should
be performed in acceleration mode.
Disable protocol coverage; set the has_cover flag to FALSE. Only system-level coverage
should be collected in acceleration mode.
Exploit high performance generation. See High Performance Sequences and Acceleration
Customized Data Item for more information.

September 2021 309 Product Version 21.09


UVM e User Guide
Acceleratable UVCs

Low Level of the UVC


The “low levels” of the UVC are the components interacting with the DUT—the BFM sending
transactions, and the collector collecting data sent by the DUT. These components’ implementation
depends on the DUT abstraction level and the platform on which it runs: SystemC Transaction
Level Model, Transaction Based Acceleration, signal Level RTL, etc.
The e BFM should contain a main loop, constantly getting items from the Seq-erm and passing to
the HW BFM. For example:

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

September 2021 310 Product Version 21.09


UVM e User Guide
Acceleratable UVCs

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

September 2021 311 Product Version 21.09


UVM e User Guide
Acceleratable UVCs

Passing Transactions Between HVL & HDL


The main interface between the HVL BFM/monitor to the HDL BFM/collector is the transaction level.
There are multiple ways to pass data between the HVL and HDL parts:
e methods can call SystemVerilog tasks using method ports.
e event ports can connect to SystemVerilog events.
e methods can call SysgtemVerilog functions using DPI.
SCE-MI Pipes are a standard for passing structs.

Accelerated BFM
The following code is extracted from the XBus sample UVC:

extend MASTER xbus_bfm_u {


// The (Verilog) Interface, implementing the interface to the DUT
keep soft hdl_path() == "master_bfm";
// Sending items to the Interface
drive_transaction_to_if : out method_port of drive_transaction_to_if is instance;
keep bind(drive_transaction_to_if, external);
keep soft drive_transaction_to_if.hdl_path() == "drive_transaction";
private drive_transfer(t : MASTER xbus_trans_s) @tf_phase_clock is {
cur_transfer = t;
var simple_t := t.convert_to_simple_trans();
// Call the interface
drive_transaction_to_if$(simple_t);
t.data = cur_transfer.data.copy();
}; // drive_transfer()
private drive_transfers() @tf_phase_clock is {
while TRUE {
var next_trans := driver.get_next_item();
drive_transfer(next_trans);
emit driver.item_done;
};
};
};

Accelerated Collector
The e monitor gets events and data form the SystemVerilog collector.
The following code is extracted from the XBus sample UVC:

September 2021 312 Product Version 21.09


UVM e User Guide
Acceleratable UVCs

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);
//…
};
};
};

Signal Access From HVL


There are two approaches to signal access from HVL. One approach claims that sometimes there is
a need of the HVL part to get signal access. An example is a direct access to the reset signal. For
implementing signal access, define a simple port in the HVL, bound to a signal in the HDL. Note
that this is applicable only to rarely accessed signals. Frequent signal accesses from HVL to HDL
will decrease the performance.
The other approach is that the upper level of the UVC should never access directly the DUT. All
information should come from designated verification components implemented in HDL. According
to this approach, there should be a Reset agent, implemented in HDL, driving relevant events to the
upper level.

Function Calls Between HDL and HVL


Some of the interaction between the HVL and HDL is best defined using functions. A typical
example is timing services—the HVL works in transaction level, and has no notion of clocks. If it
needs to delay a specific amount of delay, it should call the HDL.
For this purpose, you could use method ports or DPI functions. The UVM Acceleration contains an
implementation of some commonly required calls between HW and SW. For information about the
DPI, see UVM Acceleration reference manual, and the SystemVerilog Reference.
For example, use the wait-delay function after sending the last transaction, and then stop the test:

sys.call_to_sv_set_scope("hw_top.xi0");
sys.wait_dpi_hw_delay(100);
driver.drop_objection(TEST_DONE);

September 2021 313 Product Version 21.09


UVM e User Guide
Acceleratable UVCs

High Performance Sequences


Using the sequence driver API, rather than the do action can achieve much performance gain.
Using this API increases performance in two ways:
1. Drastically decreasing usage of the constraints solver.
2. Decreasing frequency of context switches between the sequences and the sequence driver
The sequence driver API contains several methods, enabling passing items from the sequences to
the sequence driver in various ways. Following is an example of using the API.
In this example, the data item is generated once using the constraints solver. This, it complies to all
the constraints defined on the item, creating an item adhering to all protocol and DUT
specifications. This item is sent to the DUT many times, without regeneration. The item can be
modified procedurally, to avoid sending the exact same data. The sequence sends the item to the
sequence driver using the execute_item() method. You can also use the
driver queue_item() method. The difference is that execute_item() waits for item done indication,
before proceeding.
The actions performed by the sequence are:
1. Generate a data item.
Note: Do not forget to constrain the item’s driver field.
2. In a loop:
a. Optional: modify the item procedurally.
b. Send a copy of the item to the driver.
Note: Use deep_copy(), so that if the BFM overrides the data field of previous item, it will not
affect the next items.
An example of code implementing these steps:

body() @driver.clock is only {


gen next_item keeping {
.driver == driver;
.address == 0x100;
};
for i from 0 to num_of_transfers-1 {
next_item.address += 10;
driver.execute_item(deep_copy(next_item));
};
};

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.

September 2021 314 Product Version 21.09


UVM e User Guide
Acceleratable UVCs

Working with Unclocked Models


TCMs and temporal expressions running on a clock defined in the HDL require accessing the HW
every cycle, which would degrade the performance tremendously. Having even one TCM that runs
on the HDL clock means synchronization at every clock cycle. The performance penalty is huge.
To avoid that, Cadence recommends removing from the high level of the UVC all references to the
bus clock(s). The only clock should be the sys.any, which is triggered whenever there is a callback
from the HW.
In acceleration, sys.any ticks whenever there is a context switch between the hardware and the
software. This means that the clock is not a real clock; it just indicates a callback from the hardware.
“Counting cycles” of this clock is meaningless. “Wait x cycles” actually means “wait for 3 callbacks
from the hardware.” The length of this wait is unpredictable. More than that, if there are no more
callbacks, the TCM will never proceed, and deadlocks can be created.
The most typical example of a possible deadlock is the end-of-test coordination. In normal mode,
the main sequence post_body() waits few cycles after the last transaction, allowing the data to
drain in the system, and then issues stop_run(). In acceleration mode, it might happen that after the
last transaction is passed from SW to HW, there are no more callbacks. To prevent such deadlock,
Cadence strongly recommends to avoid any “wait cycles” in the HVL in acceleration mode. If
required, use a function call to the HDL, as shown in Function Calls Between HDL and HVL.
An extreme example of an unclocked model is an environment in which no events are emitted in the
e part of the UVC, including clocks, hence no TCMs are running. In such environments, the BFMs
implemented in SystemVerilog call the e components – sequencers and checkers – using DPI. In
this structure, the Specman scheduler is not active; the control of the test advancement is in
SystemVerilog.
Note: When the Specman scheduler is not active, Specman does not initiate Garbage Collection
(GC) during the run. In such architecture, the user has to explicitly activate Specman GC. To call the
GC, call the SystemVerilog function snSvDoGcIfNeeded() (for details, seeActivating Manual
Garbage Collection in the Specman Integrators Guide). For example:

import snSvDPI::*;
module bfm();
//…
always @(posedge clk) begin
// do things…

// call Specman GC
snSvDoGcIfNeeded();
end // always @ (posedge clk)
end // module

September 2021 315 Product Version 21.09


UVM e User Guide
Acceleratable UVCs

Acceleration Customized Data Item


When passing a transaction between e and any other language, the transaction is packed on one
side, sent as a list of bytes, and then unpacked. By default, all physical fields are packed, and only
them. Sometimes passing some virtual fields as well is required either to simplify the HDL code, or
to improve performance. For example, instead of passing the full list of data, the HVL proxy BFM
can pass the required length of the data, and the HDL BFM will send predefined data (for example,
consecutive data- 0x00, 0x01, ...) in the required length.
For achieving this, you can define a new data item type, used for transfers between HVL and HDL.
Define a new data item in both languages: e and Verilog. In e, the data item should have only
physical fields, and it is created by the BFM based on the item given by the sequence driver. For
example:

drive_transfer (cur_transfer : transfer_s) @p_sys_smp.clk is only {


if_transfer = new with {
.address = cur_transfer.address;
.length = cur_transfer.length;
};
if(cur_transfer.direction == WRITE) {
m_in$.put(if_transfer);
};
};

Modifying a UVC to be Acceleration-Ready


As most existing UVCs are currently not acceleration-ready, this section describes how to modify an
existing UVC so that it can work in Transaction Based Acceleration (TBA) mode. To convert a UVC
to be acceleration ready, perform these modifications:
1. The only access to the HDL should be in designated TCMs. These will enable overriding
these TCMs in acceleration mode.
a. Search the code for port accesses. If all ports are contained within a signal map unit, per
UVM definition, search for references to this unit. Remove all accesses that are not in
the designated TCMs.
b. Search the code for “@sim”. Replace them with some clock, which later can be
overridden in acceleration mode.
2. The high level of the UVC should work only in the transaction level. Search for “wait ..
cycles” in the monitor and sequences code, and replace them by waiting for an event, emitted
by a low level component.

September 2021 316 Product Version 21.09


UVM e User Guide
Acceleratable UVCs

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

September 2021 317 Product Version 21.09


UVM e User Guide
e UVC Standardization Using Compliance Checks

12

e UVC Standardization Using Compliance


Checks
This chapter discusses automatic checking of the verification environment adherence to
methodology guidelines. The HDL Analysis and Lint (HAL) tool contains a UVM_E category,
implementing UVM compliance checks. For the full list of description of these checks, please read e
Linting with HAL in Cadence Help.
There are checks that are not yet implemented by HAL. Cadence recommends adding them to your
environment. They can be implemented either with shell scripts, or using the e API reflection. See
more in the Reflection Interface for e chapter of Creating e Testbenches in Cadence Help. Note that
you can also add checks of your company methodology (for example, checks for specific prefix of
type names).
The compliance checks Cadence recommends adding are:
Packaging and Name Space Compliance Checks
Architecture Compliance Checks
Reset Compliance Checks
Checking Compliance Checks
Coverage Compliance Checks
Sequences Compliance Checks
Messaging Compliance Checks
Monitor Compliance Checks
Documentation Compliance Checks
General Deliverables Compliance Checks
End of Test Compliance Checks
Other Possible Compliance Checks

September 2021 318 Product Version 21.09


UVM e User Guide
e UVC Standardization Using Compliance Checks

Packaging and Name Space Compliance


Checks
Table 12.1: Packaging and Name Space Compliance Checks

Check RQ/ ER Comments/Information


RC/
ST

Loading with golden e UVC — RQ


Does the e UVC run properly with the golden e UVC
loaded?

Valid distribution format— RQ See Handling Package


Is the package distributed as Versions.
package_version_version.tar.gz?

Demo script running— RQ See demo.sh File.


Does it run OK?

Simulator support documented— RQ See


Are the simulators supported by the package PACKAGE_README.txt
documented in the manual? File.

Simulator support— RQ See


Are the simulators supported by the demo documented PACKAGE_README.txt
in the PACKAGE_README.txt file? File .

Tar file— RQ See Handling Package


Is the tar file named and organized according to UVM Versions.
standards?

Architecture Compliance Checks


Table 12.2: Architecture Compliance Checks

Check RQ/RC/ST ER Comments/Information

September 2021 319 Product Version 21.09


UVM e User Guide
e UVC Standardization Using Compliance Checks

BFM driving signals— RQ 3 See DUT and UVC.


Are all DUT signals driven
only by BFMs?

Scoreboarding— RC 3 See The UVM Scoreboard Infrastructure


Are hooks provided for Package chapter of UVM e Reference.
scoreboarding?

Reset Compliance Checks


Table 12.3: Reset Compliance Checks

Check RQ/RC/ST ER Comments/Information

Clock generator— RQ 3 Qualified and unqualified


Does the package provide a (sufficiently clocks
programmable) clock generator? See Reset Methodology.

Reset checks— RQ 3 See Reset Methodology.


Are there sufficient checks to ensure that the
DUT behaves correctly after reset is de-
asserted

Working with DUT supplied clock— RQ 3 See Reset Methodology.


Does the package work with DUT supplied
clock?

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?

September 2021 320 Product Version 21.09


UVM e User Guide
e UVC Standardization Using Compliance Checks

Multiple reset checks— RQ Name a test that shows


Are there sufficient specific checks relating to multiple resets, and a
the DUT's response to assertion/de-assertion coverage point which
of reset? checks for multiple resets.
See Supporting Multiple
Resets.

Reset support— RQ 3 See Supporting Multiple


Does the package correctly respond to resets Resets.
(of any length) generated within the DUT at
the start of the test

Use of clocks— ST 3 See Reset Methodology.


Which parts are running on unqualified clock?

Checking Compliance Checks


Figure 12.1: Checking Compliance Checks

Check RQ/RC/ST ER Comments/Information

Error message documentation— RQ 3 Where in the manual are


Are all checks (both for DUT errors and user they documented?
errors) sufficiently documented?

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?

Coverage Compliance Checks


Table 12.4: Coverage Compliance Checks

September 2021 321 Product Version 21.09


UVM e User Guide
e UVC Standardization Using Compliance Checks

Check RQ/RC/ST ER Comments/Information

Coverage results— RQ 3 Preferred approach: Supply the ucm


Is the coverage report produced files and a shell script called
after testing this e UVC included “show_cover.sh”, all in a directory called
in the package? package/ coverage.
Alternative approach: provide an ASCII
coverage report.

Sequences Compliance Checks


Table 12.5: Sequences Compliance Checks

Check RQ/RC/ST ER Comments/Information

Error injection— RQ 3
Do the sequence items provide sufficient ability
to inject errors into the generated data
stream(s)?

Error injection—virtual fields— RC 3


Are virtual fields appropriately employed?

Messaging Compliance Checks


Table 12.6: Messaging Compliance Checks

Check RQ/RC/ST ER Comments/Information

Logger constraints— RC 3
Are all loggers constrained using only soft
constraints?

Monitor Compliance Checks


Table 12.7: Monitor Compliance Checks

September 2021 322 Product Version 21.09


UVM e User Guide
e UVC Standardization Using Compliance Checks

Check RQ/RC/ST ER Comments/Information

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?

Documentation Compliance Checks


Table 12.8: Documentation Compliance Checks

Check RQ/RC/ST ER Comments/Information

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?

Features and controls— RQ


Does the documentation cover all features and
controls?

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?

September 2021 323 Product Version 21.09


UVM e User Guide
e UVC Standardization Using Compliance Checks

Installation and demo— RQ


Does the documentation describe the
installation and demo processes?

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?

September 2021 324 Product Version 21.09


UVM e User Guide
e UVC Standardization Using Compliance Checks

General Deliverables Compliance Checks


Table 12.9: General Deliverables Compliance Checks

Check RQ/RC/ST ER Comments/Information

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.

New functionality— RQ Not applicable for first


New functionality added since previous release. release, should refer to
release notes.

UVC name RQ As defined in the user


manual.

Protocol name and version— RQ


Name and version number of protocol or
architecture the UVC models.

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).

End of Test Compliance Checks


Table 12.10: End of Test Compliance Checks

Check RQ/RC/ST ER Comments/Information

September 2021 325 Product Version 21.09


UVM e User Guide
e UVC Standardization Using Compliance Checks

End of test— See Status Coordination


Does the environment provide end-of-test and End of Test.
coordination mechanism?

Other Possible Compliance Checks


Check RQ/RC/ST ER Comments/Information

Code Structure

Coding Style

Test Interface

September 2021 326 Product Version 21.09


UVM e User Guide
Reference

13

Reference

This chapter provides a more detailed discussion of various related topics.


Supporting Multiple Resets
Instance Names and IDs
Status Coordination and End of Test
Encrypting Packages

Supporting Multiple Resets


The testflow scheme supports multiple resets, reset synchronization in systems, and more. Read
about reset methodology when using testflow in Reset Methodology.
If the units in the verification environment do not participate in the testflow scheme, the e rerun()
method lets you implement smooth reset of the verification environment. Some manual work is
required for synchronization between components.
This section contains:`
Reset Assumptions and Requirements
Reset Methodology
Reset Example
See Also
rerun() documentation in the Specman e Language Reference

Reset Assumptions and Requirements

Primary Requirements
For multiple resets of the DUT in tests:

September 2021 327 Product Version 21.09


UVM e User Guide
Reference

Temporals: Graceful shutdown / rerun (events, expects, TCMs)


Data and State: Smooth resetting of vars, scoreboard, and so on
Monitors: No false coverage information, no missed DUT errors, no coverage errors
Agents: No protocol violations

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

The current reset methodology works as follows:


There are two main reset events:
@reset_start— Signifies the point in time that reset becomes visible to the DUT.
@reset_end— Signifies the point in time that reset no longer is affected the DUT.

September 2021 328 Product Version 21.09


UVM e User Guide
Reference

There are two main clock events:


@clk— Ticks only when there is no reset. Most temporals and TCMS should use this
clock, and thus they pause during reset.
@unqualified_clk— Ticks always, including during reset. This is used for TCMs and
checkers that continue their work throughout reset.
The env is immune to reset.
It might have parts that run on the unqualified clock and check specifically the reset-
related behavior.
Agents are affected by reset.
Each agent identifies its own reset independently.
Agents should propagate rerun() recursively to sub-units.
Most TEs and TCMs use the qualified clock (@clk).
This makes them sleep until reset is over.
Notes
The following four reset events are of interest, because they allow finer granularity and hooks
to specify delay between reset assertion/deassertion and the time it is noticed by the DUT.
reset_start, reset_end— Main events, signifying that the DUT notices reset change
reset_asserted, reset_deasserted— Precise @sim events
In multiple clock DUTs (or if clock_rise and clock_fall are used), each clock needs a qualified
and an unqualified version.
Most of the UVC state must be reset, but not all of it.
Memory areas often need to keep content.
Reset controlling and checking needs to continue unharmed.
Be careful regarding race conditions (for example, if reset is asserted at a clk cycle and @clk
and @reset_started are both active). Particular care must be taken when one UVC wants to
send reset to another UVC. To enable that:
Ideally, UVC-1 drives some HW signals that UVC-2 is sensitive to. Thus reset is sensed
by UVC-2.
If reset is initiated by another e component and you want to do it all in e, do that by

September 2021 329 Product Version 21.09


UVM e User Guide
Reference

calling rerun(), not by emitting an event.


rerun() is less prone to races, because it schedules the quit() to be done as the last
thing of the specman tick, and the run() to be done as the first thing of the next Specman
cycle.
Note: Specman tick refers to the current call to specman_tick(). The next Specman
cycle is a call to specman_tick that has a new sys.time.

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;
};

extend xyz_env { 2. Define reset events.


event reset_start is fall('(sig_reset)')
@sim;
event reset_end is rise('(sig_reset)') @sim;
};

extend xyz_agent { 3. Propagate rerun() recursively.


on reset_start1 {
rerun();
};

rerun() is also {
-- If my_sub_structs is a list of structs
under xyz_agent...
for each (s) in my_sub_structs {
s.rerun();
};
};
};

September 2021 330 Product Version 21.09


UVM e User Guide
Reference

extend xyz_agent { 4. Define agent activity: TCMs and TEs


main() @clk is {...}; // main TCM that halt on reset.
run() is also {
start main();
};
};
extend xyz_agent_monitor {
event data_phase is {@arb_phs;
@addr_phs}@clk;
expect true('(sig_start)' == 0) @data_phase;
};

extend xyz_env_monitor { .5. Reset checker: TEs that continue on


expect @reset_start => reset.
{[3] * not @reset_end} @unqualified_clk;
};

1. Immune to reset (unqualified clock)


2. Subject to reset (qualified clock)

Instance Names and IDs


This section contains:
About Instance Names and IDs
Enumerated Logical Names
Scalar IDs
Use of Instance Names and IDs
Instance Names: XBus Example

About Instance Names and IDs


An important feature of the user interface of a UVC is the user’s ability to separately constrain
individual instances of structs and units within the UVC. This becomes particularly important for
main units of the UVC such as envs and agents.

September 2021 331 Product Version 21.09


UVM e User Guide
Reference

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.

Enumerated Logical Names


Ideally it should be possible to subtype the struct or unit on the logical name. To do this, the name
must be specified as an enumerated type. The UVC developer should create an enumerated type
(normally, initially empty) and a field in the struct or unit of that type. We recommend calling this field
“name”.
Each time users want to create a new instance of the struct or unit, they must extend the
enumerated type to create a new logical name and then create an instance of the struct or unit that
has its name field constrained to the new logical name. For example:
e UVC Developer File

type my_uvc_env_name_t : []; // initially empty


unit my_uvc_env_u like uvm_env {
name : my_uvc_env_name_t;
speed : uint;
...
};

User Configuration File

extend my_uvc_env_name_t : [INSTANCE_A];


extend sys {
my_uvc_a : my_uvc_env_u is instance;
keep my_uvc_a.name == INSTANCE_A;
};

With this approach, users can then subtype the struct or unit using the logical name to apply
constraints to a specific instance. For example:

September 2021 332 Product Version 21.09


UVM e User Guide
Reference

extend INSTANCE_A my_uvc_env_u {


keep speed == 5;
};

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

unit my_uvc_agent_u like uvm_agent {


id : uint;
port_name : string;
...
};
extend my_uvc_env_u {
agents : list of my_uvc_agent_u is instance;
keep for each (agent) in agents {
agent.id == index;
};
};

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”;
};

Use of Instance Names and IDs


Instance names and IDs can be used for:
Constraining behavior (configuration and tests)
Extending sequences

September 2021 333 Product Version 21.09


UVM e User Guide
Reference

Debugging and printing


Coverage per instance
We recommend giving all major units in a UVC a logical name or ID field (for example the top level
env unit, the agent unit, and so on).
Moreover, it often helps for data structs to have fields that indicate the logical name or ID of the unit
by which they were generated. For example, a packet struct might have a field indicating the logical
name of the agent that generated it. This lets users constrain fields within the data struct according
to the agent that generates it.
e UVC Developer File

struct my_uvc_packet_s {
agent_name : my_uvc_agent_name_t;
packet_delay : uint;
...
};

User Test File

// All packets generated by AGENT_A should have a delay of 5


extend AGENT_A my_uvc_packet_s {
keep packet_delay == 5;
};

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.

Instance Names: XBus Example


The following example shows the recommended approach to providing logical instance names for
a typical UVC. The example shows how the instance name fields are created and how a user
interface for creating UVC configurations can be built around instance names. This code is a
simplified form of that found in the XBus Golden UVC (the xbus example package of UVM).
This section contains:
e UVC Developer Files
e UVC User Files

September 2021 334 Product Version 21.09


UVM e User Guide
Reference

e UVC Developer Files


xbus_types.e

type xbus_agent_name_t : [];


type xbus_bus_name_t : [];

xbus_agent.e

unit xbus_agent_u like uvm_agent {


name : xbus_agent_name_t;
};

xbus_env.e

unit xbus_env_u like uvm_env {


name : xbus_bus_name_t;
agent_names : list of xbus_agent_name_t;
keep soft agent_names.size() == 0;
agents : list of xbus_agent_u is instance;
keep agents.size() == agent_names.size();
keep for each (agent) in agents {
agent.name == agent_names[index];
};
};

xbus_trans.e

struct xbus_trans_s like any_sequence_item {


agent_name : xbus_agent_name_t;
};

xbus_sequence.e

September 2021 335 Product Version 21.09


UVM e User Guide
Reference

sequence xbus_sequence using


item = xbus_trans_s,
created_driver = xbus_driver_u;
extend xbus_driver_u {
agent_name : xbus_agent_name_t;
};
extend xbus_sequence {
agent_name : xbus_agent_name_t;
keep agent_name == driver.agent_name;
// For "do trans"
!trans: xbus_trans_s;
keep trans.agent_name == agent_name;
};
extend xbus_agent_u {
sequence_driver: xbus_driver_u is instance;
keep sequence_driver.agent_name == name;
};

xbus_coverage.e

extend xbus_trans_s {
event trans_done;
cover trans_done is {
item name : xbus_agent_name_t = agent_name
};

e UVC User Files


xbus_config.e

September 2021 336 Product Version 21.09


UVM e User Guide
Reference

extend xbus_bus_name_t : [NORTH_XBUS, SOUTH_XBUS];


extend xbus_agent_name_t : [AGENT_N_A, AGENT_N_B,
AGENT_S_A];
extend sys {
xbus_north : NORTH_XBUS xbus_env_u is instance;
xbus_south : SOUTH_XBUS xbus_env_u is instance;
};
extend NORTH_XBUS xbus_env_u {
keep agent_names == {AGENT_N_A; AGENT_N_B};
};
extend SOUTH_XBUS xbus_env_u {
keep agent_names == {AGENT_S_A};
};
extend AGENT_N_A xbus_agent_u {
keep min_addr == 0x0000; keep max_addr == 0x7fff;
};
extend AGENT_N_B xbus_agent_u {
keep min_addr == 0x8000; keep max_addr == 0xffff;
};
extend AGENT_A_A xbus_agent_u {
keep min_addr == 0x0000; keep max_addr == 0xffff;
};

Test_1.e

extend AGENT_N_A MAIN xbus_master_sequence {


body() @driver.clock is only {
write(1, 0x0, 0xff);
};
};
extend AGENT_N_B MAIN xbus_master_sequence {
body() @driver.clock is only {
write(1, 0x8000, 0xff);
};
};
extend AGENT_S_A MAIN xbus_master_sequence {
keep sequence.kind.reset_soft();
keep soft sequence.kind == select {
90 : SEQUENCE_A;
10 : SEQUENCE_B;
};
};

Status Coordination and End of Test


The status coordination methodology is based on the objection mechanism in e. This section
contains:
Introduction to Status Coordination

September 2021 337 Product Version 21.09


UVM e User Guide
Reference

Introduction to Status Coordination


End-Of-Test Mechanism

Introduction to Status Coordination


This section describes the status-coordination mechanism and its application to end-of-test
handling. The golden UVCs demonstrate its use.
This section contains:
General Requirement for Status Coordination
Requirements for End-Of-Test Handling

General Requirement for Status Coordination


Coordination tasks like end-of-test, end-of-reset, and end-of-initialization are all similar in nature. In
essence, we need a general way to wait until every unit that cares about a proposed status agrees
to that status, complete with the ability of parent units to control the propagation from their subunits
towards sys (as in the case of end of test).
Other, related requirements for the status coordination mechanism are:
It must allow waiting until the proposed status is propagated all the way to sys or (if required)
until the status is propagated to some intermediate units.
The proposed status might be applicable only to some UVCs and not to others.
Status coordination can be easily debugged.

Requirements for End-Of-Test Handling


For a flexible, plug-and-play UVC methodology, UVCs normally must not stop the run unilaterally.
On the other hand, UVCs (and units in general) must be able to:
Object to stopping the run and later withdraw that objection.
Decide how to propagate changes in the objection status of its subunits upward toward sys.
For example, a unit might decide that when all its subunits withdraw their objections, it still
requires 40 cycles of drain time until it removes its own objection.
When a consensus to stop is propagated all the way to sys, the stop_run() method is called
automatically. The stop_run() method can also be called directly at any time (for example, for

September 2021 338 Product Version 21.09


UVM e User Guide
Reference

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.

Basic End-Of-Test Mechanism


There is an objection_kind called TEST_DONE.
The following code is already included with the objection logic:

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.

September 2021 339 Product Version 21.09


UVM e User Guide
Reference

End-Of-Test Handling Examples


Simple End-Of-Test Handling
A unit that wants to be involved in the end-of-test game simply calls:
raise_objection(TEST_DONE);

at the start, and then, at the end:


drop_objection(TEST_DONE);

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:

extend MAIN ENV_SETUP vr_xbus_sequence {


pre_body() @sys.any is first {
driver.raise_objection(TEST_DONE);
};
};
extend MAIN POST_TEST vr_xbus_sequence {
post_body() @sys.any is also {
driver.drop_objection(TEST_DONE);
};
};

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:

extend MAIN vr_xbus_sequence {


pre_body() @sys.any is first {
driver.raise_objection(TEST_DONE);
};
post_body() @sys.any is also {
driver.drop_objection(TEST_DONE);
};
};

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:

September 2021 340 Product Version 21.09


UVM e User Guide
Reference

extend MAIN ENV_SETUP vr_xbus_sequence {


pre_body() @sys.any is first {
driver.raise_objection(TEST_DONE);
};
};
extend MAIN POST_TEST vr_xbus_sequence {
post_body() @sys.any is also {
wait [20] * cycle @driver.clock;
driver.drop_objection(TEST_DONE);
};
};

More Complex End-Of-Test Handling


For a unit to do special processing related to the TEST_DONE, extend all_objections_dropped().
For example, to allow for drain time, assume that “bridge_unit” knows it must wait 40 cycles after all
its children are ready to stop. You can achieve this 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

September 2021 341 Product Version 21.09


UVM e User Guide
Reference

Adding Extra Debugging Aids to Encrypted Packages


Protecting Non-Encrypted Code
Publishing the Interface of Encrypted Files

How Encryption Works


The Specman encryption utility takes a list of file names and produces encrypted versions of them.
A feature key can be used to protect the code from being loaded and used without a corresponding
license key.
End users cannot read or edit code in an encrypted file, but they can load the file like any non-
encrypted file. If you use the -visible flag of sn_encrypt.sh, you can see the fields in the Data
Browser or a waveform viewer. However, the debugger cannot read encrypted code, and so error
messages do not display the content of the line.

How Much to Encrypt


In theory, an entire UVC could be encrypted. Users could refer to the user guide to know all
necessary field and method names. In practice, however, some code must be kept open to enable
debugging and user support.

Criteria for What 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.

September 2021 342 Product Version 21.09


UVM e User Guide
Reference

Examples of What to Encrypt

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.

Examples of What Not to Encrypt

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.

Adding Extra Debugging Aids to Encrypted Packages


Bearing in mind that debugging becomes more complex with encrypted code, developers might
want to add extra debugging aids to encrypted packages. For example, you can have assertions
verifying that all assumptions are valid and message actions providing information on internal
variables.

Protecting Non-Encrypted Code


Non-encrypted code that is considered private to the package (that is, end users should not modify
it) can be protected using the encapsulation feature.

Publishing the Interface of Encrypted Files


A header file should accompany each encrypted file to publish its external and internal interface.
The header file must indicate the access permission for each type, struct, method, and so on.

September 2021 343 Product Version 21.09

You might also like