UVM HowTo

Download as pdf or txt
Download as pdf or txt
You are on page 1of 93

Google Proprietary & Confidential

Flexible Futureproof UVM Design


Verification Environments
Sean O'Boyle - soboyle@
(go/uvm-howto)

GOOGLE CONFIDENTIAL

Purpose of This Document


Demonstrate how to build a good verification environment using UVM

gChips DV Google Confidential pg 1 / 93


Google Proprietary & Confidential

Contents

Overview
Goals of a Flexible Futureproof UVM Verification Environment
Document Outline
Big Picture (and some background)
Testbench Top - TB Top (Synthesizable)
Testbench Top - DV Top
Agent
Environment
Test
Virtual Sequence

How to Code it
The Interface Protocol Agent
Configuration Object
Interface
Item
Driver
Monitor
Sequencer
Agent
Sequence
Nominal Sequence
Helper Sequence
Sequence List
Package
Package (Synthesizable)
Parameterizing Widths
DUT Testbench / Environment
DUT Specific Agents
Testbench Top - TB Top (Synthesizable)
Testbench Top - DV Top (UVM Side)
Configuration Object
Scoreboard
Virtual Sequencer
Environment
Test
Base Test
Derived Tests
gChips DV Google Confidential pg 2 / 93
Google Proprietary & Confidential

Test List
Virtual Sequences
Base Virtual Sequence
Virtual Agent
Test Base Virtual Sequence
Derived Test Virtual Sequence
Test Directed Base Virtual Sequence
Test Sequence List
Environment Package
Useful Agents / Objects
Clock Agent
Reset Agent
Delay Object
Activity Watchdog Component
Time Package
IRQ Reactive Agent
Ready / Valid Active Request Agent
Ready / Valid Reactive Response Agent
'No Protocol' Agent

Directory Structure

Considerations for Emulation / Testbench Acceleration

UVM with Genesis

gChips DV Google Confidential pg 3 / 93


Google Proprietary & Confidential

Overview

Goals of a Flexible Futureproof UVM Verification Environment


Goals:
● easy to follow - structured
● scale with the design - add / remove / change interface protocol agents easily
● change test behavior without rewriting the test (eg. delays, processed data size, etc.)
● easily add (and remove) test sequences
● change the environment without needing to change test sequences
● change test sequences without needing to change the environment
● team driven - ability for multiple contributors to make changes to the environment simultaneously

To accomplish these goals our environment implementation needs to obey the principle of "separation of
concerns."

Separate:
● interface protocols
● interface dimensions / widths / payload types
● environment integration
● environment configuration
● test sequencing (both random and directed)
● dut power on sequencing
● dut configuration sequencing
● dv environment configuration
● static concerns (how many / how much / how wide / overridden constraints) from dynamic
concerns (when / timing / relative timing / sequencing)

All of this is enabled by the UVM library.

Document Outline
This document will provide a big picture overview along with a small amount of background information. Next,
we'll go bottom up - describing how to implement each element of a good UVM verification environment. Then,
we'll discuss requirements for emulation. Finally, we'll quickly cover the recommended directory structure. For
those that use the genesis pre-processor, we'll cover what additional code needs to be present - both in-line and
in some extra files at the end.
The reader should already be familiar with the UVM library.

gChips DV Google Confidential pg 4 / 93


Google Proprietary & Confidential

Big Picture (and some background)

The above diagram shows how all of the pieces of a UVM verification environment come together around the
DUT.

Testbench Top - TB Top (Synthesizable)


The synthesizable portion of the testbench top instances the DUT (device under test) in a module called tb_top
(often named <dut_name>_tb.) This module also instances the interfaces that connect to the DUT, including
interfaces for the clock(s), and hard reset(s). The tb_top module connects those interfaces to signals connected

gChips DV Google Confidential pg 5 / 93


Google Proprietary & Confidential

to the DUT ports. This module contains only the synthesizable portion of the design -- the DUT RTL and the
synthesizable interfaces; the UVM portion of the verification environment is done outside the testbench top (aka
the DV top.) This split top has two benefits: 1) ensures a quick path to testbench acceleration / emulation - where
the tb_top runs in the emulator and the dv_top runs in the simulator and 2) faster re-elaboration times when only
the DV code has changed and the RTL has remained the same.

Testbench Top - DV Top


The verification side of the testbench top is a module called dv_top (often named <dut_name>_dv.) It sets the
interfaces instantiated in tb_top, as virtual interfaces, into the UVM configuration space. It sets the time display
format and launches the UVM test - where the selected test is determined by a runtime plusarg. Where
appropriate, the dv_top also starts the waveform dumping database generation and similar simulator calls;
although these sorts of calls are preferably handled in the scripts that launch the simulator.

Agent

An agent both drives and monitors a protocol. An agent refers to both a collection of files required in an agent's
implementation and the agent file / class itself. An agent collection includes the following files:

gChips DV Google Confidential pg 6 / 93


Google Proprietary & Confidential

● interface - the collection of pins that the agent drives


● sequence_item class - a randomizable, untimed, description of what the protocol supports
● driver - converts a sequence_item description into timed 'wiggles' of signals on the interface
described by a randomized sequence_item
● monitor - the inverse of a driver; converts those timed 'wiggles' of signals on the interface into a
sequence_item description of what was observed
● sequence class(es) - a class (often a collection of classes) that provide a prescribed set of what
the protocol agent supports in an easy to launch api
● sequencer - a bridge between the sequence class(es) and the driver
● agent class - encapsulates the aforementioned driver, monitor, and sequencer
● configuration object class - a class with state variable members that describe how to configure the
agent / driver / monitor class instances

Environment
The environment class encapsulates all of the agents along with the support classes - virtual sequencer and
scoreboard.
● scoreboard class - connects to all of the agents via analysis ports; when an item is received by an
analysis port that item is compared, stored, or dropped -- depending on the details of how the
scoreboard has been configured
● virtual sequencer class - a collection of handles pointing to each of the agent sequencers
instanced in the environment; used by the test virtual sequences to send ordered traffic across
any or all of the available DUT interfaces

Test
A test class is used to configure and instance the environment class and to launch a test virtual sequence.
An environment class commonly has several corresponding test classes - each configuring and / or constraining
the environment differently.
A test class configures the environment by instancing the configuration objects for each of the instanced agents -
and setting the configuration object members. Configuration objects are set into the uvm configuration database
so as to be available to be retrieved by the agents.

gChips DV Google Confidential pg 7 / 93


Google Proprietary & Confidential

Virtual Sequence

A virtual sequence is a sequence that isn't specialized to run on a particular protocol interface. A virtual
sequence constructs, randomizes, and starts one or more protocol agent sequences or virtual sequences. A
virtual sequence can be used to control traffic on a multi-interface protocol -- a protocol supported by multiple
interface agents. More commonly, a virtual interface is used to drive ordered traffic to the DUT to create a test

gChips DV Google Confidential pg 8 / 93


Google Proprietary & Confidential

sequence; these are called test virtual sequences. A test virtual sequence launches reactive sequences and
then active sequences in an ordered fashion to stimulate the DUT.
Note that we distinguish between a test - which configures the environment and environment constraints, and
launches the test sequence, from the test sequence itself. This distinction allows us to reuse our test sequences
with varied constraints from varied tests without having to reimplement our (often complicated and/or verbose)
sequences.

gChips DV Google Confidential pg 9 / 93


Google Proprietary & Confidential

How to Code it
In this section we'll explore how to actually code each of the pieces. We'll start with the agent - since it is
ubiquitous and used everywhere.
Then we'll go bottom up and describe how to build: tb_top, environment, agent specializations, scoreboard,
virtual sequencer, and the set of common virtual sequencers.

The Interface Protocol Agent


The interface agent includes the configuration object, item, monitor, driver, sequencer, agent, sequence(s),
sequence library, all declared within the package.
Interface agents can be active: transfer initiated by the driver - terminated in the DUT, reactive: transfer initiated
by the DUT - terminated in the driver, passive: transfer initiated by the DUT and terminated by the DUT.
For simplicity of explanation, we'll implement a reset protocol.

Configuration Object
The configuration object is the description of how the agent is configured. This is a testbench static description
as to how the agent should behave. (As opposed to the testbench dynamic description of how a particular
transfer should occur - which is described in the item.)
This object contains the members that define how the environment is built - and in some ways - how the
environment is to behave. The configuration is built and consumed at the build phase. Configuration objects
should not be consumed at any point in the run phase.
The configuration object is the single / only encapsulation of all of the configurable variables across the agent. All
configuration members should reside in the configuration object - rather than independently within any of the
driver, monitor, sequencer, agent, etc.
Examples of what is included in the configuration object are: whether the agent is active or passive; how many
cycles before timing out (when waiting for the DUT to respond); debug mode activation; whether the monitor
should include X checking; etc etc.
Note that the configuration object doesn't include interface widths. Any signal width parameterization comes from
a parent specialization of this agent. The agent and corresponding interface needs to be parameterized if
interface widths can vary. Below are some guidelines:

A configuration object is an extension of the uvm_object class and contains state member variables along with
optional public helper functions - to set / get those members. Like all classes, it is recommended to use an 'm_'
prefix (exclusively) for class member variables.
A configuration object should be named with a '_cfg' suffix.
A configuration object should contain no tasks.
A configuration object should register with the factory using the object_utils macro.
Each member variable of a configuration object should be included in the field macros - or be implemented in
each of the do_ methods (do_copy, compare, print).

gChips DV Google Confidential pg 10 / 93


Google Proprietary & Confidential

The 'do_' methods provide a little more control over the outcome in exchange for some extra typing . The 'do_'
methods really come in handy when you have a type that isn't in the pre-build field macros.
The 'do_' methods can be mixed with the field macros.

The configuration object should follow the following structure:

//--------------------------------------------------------------------------------------------------
// Reset Agent Config
//--------------------------------------------------------------------------------------------------
class reset_cfg extends uvm_object;

// Member Variables to define how the Host Agent is to be built


uvm_active_passive_enum m_uvm_active_passive_h;
reset_active_t m_reset_active;
reset_assert_t m_reset_assert;
reset_deassert_t m_reset_deassert;

// Constructor
extern function new(string name = "");

// Field Macros
`uvm_object_utils_begin(reset_cfg)
`uvm_field_enum(uvm_active_passive_enum, m_uvm_active_passive_h, UVM_ALL_ON)
`uvm_field_enum(reset_active_t, m_reset_active, UVM_ALL_ON)
`uvm_field_enum(reset_assert_t, m_reset_assert, UVM_ALL_ON)
`uvm_field_enum(reset_deassert_t, m_reset_deassert, UVM_ALL_ON)
`uvm_object_utils_end

endclass: reset_cfg

//------------------------------------------------------------------------------
function reset_cfg::new(string name = "");
super.new(name);
// Defaults
m_uvm_active_passive_h = UVM_ACTIVE;
m_reset_active = RESET_ACTIVE_LOW;
m_reset_assert = RESET_ASSERT_ASYNC;
m_reset_deassert = RESET_DEASSERT_SYNC;
endfunction: new

The configuration object should follow the following structure - with do_ methods:

//--------------------------------------------------------------------------------------------------
// Reset Agent Config
//--------------------------------------------------------------------------------------------------
class reset_cfg extends uvm_object;

// Factory Registration

gChips DV Google Confidential pg 11 / 93


Google Proprietary & Confidential

`uvm_object_utils(reset_cfg)

// Member Variables to define how the Agent is to be built


uvm_active_passive_enum m_uvm_active_passive_h;
reset_active_t m_reset_active;
reset_assert_t m_reset_assert;
reset_deassert_t m_reset_deassert;

// Constructor
extern function new(string name = "");

// Do Methods
extern virtual function void do_copy(uvm_object rhs);
extern virtual function bit do_compare(uvm_object rhs, uvm_comparer comparer);
extern virtual function void do_print(uvm_printer printer);
extern virtual function void do_pack(uvm_packer packer);
extern virtual function void do_unpack(uvm_packer packer);

endclass: reset_cfg

//------------------------------------------------------------------------------
function reset_cfg::new(string name = "");
super.new(name);
// Defaults
m_uvm_active_passive_h = UVM_ACTIVE;
m_reset_active = RESET_ACTIVE_LOW;
m_reset_assert = RESET_ASSERT_ASYNC;
m_reset_deassert = RESET_DEASSERT_SYNC;
endfunction: new

//------------------------------------------------------------------------------
function void reset_cfg::do_copy(uvm_object rhs);
this_type_t rhs_;
super.do_copy(rhs);
if (!$cast(rhs_, rhs)) begin
`uvm_fatal(get_name(), "do_copy() type mismatch")
end
m_uvm_active_passive_h = rhs_.m_uvm_active_passive_h;
m_reset_active = rhs_.m_reset_active;
m_reset_assert = rhs_.m_reset_assert;
m_reset_deassert = rhs_.m_reset_deassert;
endfunction: do_copy

//------------------------------------------------------------------------------
function bit reset_cfg::do_compare(uvm_object rhs, uvm_comparer comparer);
this_type_t rhs_;
bit ret_val;

if (!$cast(rhs_, rhs)) begin


`uvm_fatal(get_name(), "do_compare() type mismatch")

gChips DV Google Confidential pg 12 / 93


Google Proprietary & Confidential

end
ret_val = (super.do_compare(rhs, comparer) &&
(m_uvm_active_passive_h == rhs_.m_uvm_active_passive_h) &&
(m_reset_active == rhs_.m_reset_active) &&
(m_reset_assert == rhs_.m_reset_assert) &&
(m_reset_deassert == rhs_.m_reset_deassert)
);
return(ret_val);

endfunction: do_compare

//------------------------------------------------------------------------------
function void reset_cfg::do_print(uvm_printer printer);
super.do_print(printer);
//printer.print_field_int( "m_payload", m_payload, $bits(m_payload) ); // int
//printer.print_generic ( "m_payload", "enum", $bits(m_payload), m_payload.name()); // enum

printer.print_generic(
"m_uvm_active_passive_h",
"enum",
$bits(m_uvm_active_passive_h),
m_uvm_active_passive_h.name()
);
printer.print_generic(
"m_reset_active",
"enum",
$bits(m_reset_active),
m_reset_active.name()
);
printer.print_generic(
"m_reset_assert",
"enum",
$bits(m_reset_assert),
m_reset_assert.name()
);
printer.print_generic(
"m_reset_deassert",
"enum",
$bits(m_reset_deassert),
m_reset_deassert.name()
);

endfunction : do_print

//------------------------------------------------------------------------------
function void reset_cfg::do_pack(uvm_packer packer);
super.do_pack(packer);
packer.pack_field_int(m_uvm_active_passive_h, $bits(m_uvm_active_passive_h));
packer.pack_field_int(m_reset_active, $bits(m_reset_active));
packer.pack_field_int(m_reset_assert, $bits(m_reset_assert));

gChips DV Google Confidential pg 13 / 93


Google Proprietary & Confidential

packer.pack_field_int(m_reset_deassert, $bits(m_reset_deassert));
endfunction : do_pack

//------------------------------------------------------------------------------
function void reset_cfg::do_unpack(uvm_packer packer);
super.do_unpack(packer);
m_uvm_active_passive_h = packer.unpack_field_int($bits(m_uvm_active_passive_h));
m_reset_active = packer.unpack_field_int($bits(m_reset_active));
m_reset_assert = packer.unpack_field_int($bits(m_reset_assert));
m_reset_deassert = packer.unpack_field_int($bits(m_reset_deassert));
endfunction : do_unpack

Interface
The interface contains a list of the signals when connected to the driver and monitor components.
The interface should be named with a '_if' suffix.
The interface should declare the clock as an input of type bit.
The interface should declare all of the interface nets as type logic.
In order to streamline the path to emulation - clocking blocks and modports should not be used.

The interface should follow the following structure:


//--------------------------------------------------------------------------------------------------
// Reset Agent Interface
//--------------------------------------------------------------------------------------------------
interface reset_if (
input bit clk
);

// Interface Net Declarations


logic reset;

endinterface: reset_if

Item
The item is the description of all of the transfers that can occur on the interface. It includes rand member
variables that contain: named transfers as enum type, members to hold data / control messaging to/from and any
interface delays.
Said better, from the perspective of the driver - the enum type says what transfer should occur, members that
hold data / control say what should be transferred, and the delay members describe what delays should occur.
The item class name should have a '_item' suffix.
There should be no methods implemented other than the 'do_' methods or those generated by the uvm field
macros. (Additional methods to aid the driver / monitor implementations are ok - example: crc / parity calculation,
packing / unpacking with specific formatting, etc).

gChips DV Google Confidential pg 14 / 93


Google Proprietary & Confidential

The item is a collection of rand (and optionally state) variables and constraints. The rest of the code of an item is
boiler plate.
The first rand variable of an item is commonly an enum that enumerates all of the kinds of operations that can
occur in the driver.

The constraints should be marked as valid, legal, and typical - either directly named as such - or with those
names as a prefix. Using these names as prefix makes them easy to find to override or disable.
● Valid - need to be true or agent won't work correctly (things like positive values for delays)
● Legal - need to be true to obey protocol; can override to create protocol invalid transfers
● Typical - overridable constraint; use to set some reasonable bounds
We use these names consistently to make it clear which ones should be overridden (or disabled) and which
should be enabled at all times.

An item is an extension of the uvm_sequence_item class and contains rand member variables and constraints
for those member variables.
An item should contain no tasks or functions other than the 'do_' methods or those generated by the uvm field
macros.
An item should register with the factory using the object_utils macro.
Each member variable of an item should be included in the field macros - or be implemented in each of the do_
methods (do_copy, compare, print).
The 'do_' methods provide a little more control over the outcome in exchange for some extra typing . The 'do_'
methods really come in handy when you have a type that isn't in the pre-build field macros.
The 'do_' methods can be mixed with the field macros.

See the configuration object for an example of how to use the 'do_' methods.

The item follows the following structure:

//--------------------------------------------------------------------------------------------------
// Reset Agent Item
//--------------------------------------------------------------------------------------------------
class reset_item extends uvm_sequence_item;

// Members
// async assert, positive edge sync deassert reset
rand reset_kind_t m_reset_kind; // Kind of reset to apply
rand int unsigned m_preactive_ns; // time (in ns) to wait before asserting;
// if first reset then time that reset pin is X
rand int unsigned m_preactive_cycles; // time (in cycles) to wait before asserting;
// if first reset then time that reset pin is X
rand int unsigned m_active_ns; // time (in ns) to hold reset active
rand int unsigned m_active_cycles; // time (in cycles) to hold reset active
rand int unsigned m_postactive_ns; // time (in ns) to wait after deasserting reset
// (before unblocking the sequence)
rand int unsigned m_postactive_cycles; // time (in cycles) to wait after deasserting reset
// (before unblocking the sequence)

gChips DV Google Confidential pg 15 / 93


Google Proprietary & Confidential

// Field Macros
`uvm_object_utils_begin(reset_item)
`uvm_field_enum(reset_kind_t, m_reset_kind, UVM_ALL_ON)
`uvm_field_int(m_preactive_ns, UVM_ALL_ON)
`uvm_field_int(m_preactive_cycles, UVM_ALL_ON)
`uvm_field_int(m_active_ns, UVM_ALL_ON)
`uvm_field_int(m_active_cycles, UVM_ALL_ON)
`uvm_field_int(m_postactive_ns, UVM_ALL_ON)
`uvm_field_int(m_postactive_cycles, UVM_ALL_ON)
`uvm_object_utils_end

// Constraints
// Three Types of Constraint - valid, legal, typical
// Valid - need to be true or agent won't work correctly
// (things like positive values for delays)
// Legal - need to be true to obey protocol; can override to create protocol invalid transfers
// Typical - overridable constraint; use to set some reasonable bounds
constraint valid;
constraint legal;
constraint typical;

// Constructor
function new(string name = "");
super.new(name);
endfunction: new

endclass: reset_item

//------------------------------------------------------------------------------
constraint reset_item::valid {}

//------------------------------------------------------------------------------
constraint reset_item::legal {}

//------------------------------------------------------------------------------
constraint reset_item::typical {
soft m_preactive_ns dist {
[75:140] :/ 25,
[141:160] :/ 50,
[161:200] :/ 25
};
soft m_active_ns dist {
[2:8] :/ 25,
[9:11] :/ 50,
[12:20] :/ 25
};
soft m_active_cycles dist {
[2:8] :/ 25,
[9:11] :/ 50,

gChips DV Google Confidential pg 16 / 93


Google Proprietary & Confidential

[12:20] :/ 25
};
soft m_postactive_ns dist {
0 :/ 50,
[1:10] :/ 25,
[11:20] :/ 25
};
soft m_postactive_cycles dist {
0 :/ 50,
[1:10] :/ 25,
[11:20] :/ 25
};
}

Driver
The driver converts item descriptions into 'pin wiggles' on the agent interface.
A driver is an extension of the uvm_driver class.
A driver should be named with a '_driver' suffix.
A driver should register with the factory using the component_utils macro.
A driver should implement the build, connect, and run phase methods.
In the build method, the driver should grab a handle to the configuration object which it can then optionally use to
configure the behavior of the driver. For consistency, the config object handle should be named m_cfg_h.
In the build method, the driver should grab a handle to the virtual interface. For consistency, the virtual interface
handle should be named m_vif and be of the interface type.
A driver can optionally include protected helper functions / variables to aid in implementation of the build and/or
config phases.
When moving data from 4 state to 2 state variables - do an X check and present an error if an X is present.
A driver can optionally included protected helper tasks / variables to aid in the implementation of the run phase
task. I recommend including a simple drv_init() task to initialize all of the interface members at time 0 and a
drv_interface() task to do the item interpretation and driving of the interface.
Note that we protect these to prevent a user from being tempted to interface to the driver through any
mechanism other than the standard UVM sequence driven flow.
The driver should also include analysis ports for each of the req and rsp items. The req item to show the transfer
when requested and the rsp item to show the completed transfer.
The analysis ports should be named - simply - m_req_analysis_port and m_rsp_analysis_port.

The driver follows the following structure:


//--------------------------------------------------------------------------------------------------
// Reset Agent Driver
//--------------------------------------------------------------------------------------------------
class reset_driver extends uvm_driver#(reset_item);
`uvm_component_utils(reset_driver)

gChips DV Google Confidential pg 17 / 93


Google Proprietary & Confidential

typedef virtual reset_if reset_if_t;

// Config
reset_cfg m_cfg_h;

// Interface
reset_if_t m_vif;

// Analysis Port
uvm_analysis_port#(reset_item) m_req_analysis_port;
uvm_analysis_port#(reset_item) m_rsp_analysis_port;

// Constructor
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction: new

// Phase Methods
extern virtual function void build_phase(uvm_phase phase);
extern virtual function void connect_phase(uvm_phase phase);
extern virtual task run_phase(uvm_phase phase);

// Helper Methods
extern protected virtual task drv_init();
extern protected virtual task drv_interface();

endclass: reset_driver

//------------------------------------------------------------------------------
function void reset_driver::build_phase(uvm_phase phase);
typedef uvm_config_db#(reset_cfg) config_db_cfg_t;

super.build_phase(phase);

// Grab the Config


if (!config_db_cfg_t::get(this, "", "reset_cfg", m_cfg_h)) begin
`uvm_fatal(get_name(), "Failed to Grab reset_cfg from Config DB")
end

// Build the Analysis Ports


m_req_analysis_port = new("m_req_analysis_port", this);
m_rsp_analysis_port = new("m_rsp_analysis_port", this);

endfunction: build_phase

//------------------------------------------------------------------------------
function void reset_driver::connect_phase(uvm_phase phase);
typedef uvm_config_db#(reset_if_t) cfg_db_vif_t;

// Grab the VIF

gChips DV Google Confidential pg 18 / 93


Google Proprietary & Confidential

if (!cfg_db_vif_t::get(this, "", "reset_vif", m_vif)) begin


`uvm_fatal(get_name(), "Failed to Grab reset_vif from Config DB")
end

endfunction: connect_phase

//------------------------------------------------------------------------------
task reset_driver::run_phase(uvm_phase phase);
super.run_phase(phase);

drv_init();
drv_interface();

endtask: run_phase

//------------------------------------------------------------------------------
task reset_driver::drv_init();

// Initialize Interface - drive all outputs to inactive state


// X reset
m_vif.reset <= 'dX;

endtask: drv_init

//------------------------------------------------------------------------------
task reset_driver::drv_interface();

forever begin

// Get and item from the seq item port


seq_item_port.get(req);
m_req_analysis_port.write(req);

// Construct Response
rsp = RSP::type_id::create("rsp", this);

// Copy Req ID to Rsp ID


rsp.copy(req); // copy contents
rsp.set_id_info(req); // copy sequence item id

if (req.m_reset_kind == RESET_KIND_ASSERT) begin


if (m_cfg_h.m_reset_assert == RESET_ASSERT_SYNC) begin
@(m_vif.clk);
end
m_vif.reset <= m_cfg_h.m_reset_active;
end
else if (req.m_reset_kind == RESET_KIND_DEASSERT) begin
if (m_cfg_h.m_reset_deassert == RESET_DEASSERT_SYNC) begin
@(m_vif.clk);
end

gChips DV Google Confidential pg 19 / 93


Google Proprietary & Confidential

m_vif.reset <= ~m_cfg_h.m_reset_active;


end
else begin // RESET_KIND_SEQUENCE

// Wait pre-assert Delay / Cycles


if (m_cfg_h.m_reset_assert == RESET_ASSERT_ASYNC) begin
#(req.m_preactive_ns * 1ns);
end
else begin
repeat (req.m_preactive_cycles) begin
@(m_vif.clk);
end
end

// Assert Reset
m_vif.reset <= m_cfg_h.m_reset_active;;

// Wait Assert Delay / Cycles


if (m_cfg_h.m_reset_deassert == RESET_DEASSERT_ASYNC) begin
#(req.m_active_ns * 1ns);
end
else begin
repeat (req.m_active_cycles) begin
@(m_vif.clk);
end
end
m_vif.reset <= ~m_cfg_h.m_reset_active;;

// Wait Post Assert Delay / Cycles


if (m_cfg_h.m_reset_deassert == RESET_DEASSERT_ASYNC) begin
#(req.m_postactive_ns * 1ns);
end
else begin
repeat (req.m_postactive_cycles) begin
@(m_vif.clk);
end
end

end

// Provide RSP to analysis port


m_rsp_analysis_port.write(rsp);

// Return Response to Sequence


seq_item_port.put(rsp);

end

endtask: drv_interface

gChips DV Google Confidential pg 20 / 93


Google Proprietary & Confidential

Monitor
The monitor converts 'pin wiggles' into item descriptions output via an analysis port.
A monitor is an extension of the uvm_monitor class.
A monitor should be named with a '_monitor' suffix.
The analysis port should be named - simply - m_analysis_port.
A monitor should register with the factory using the component_utils macro.

A monitor should implement the build, config, and run phase methods.
In the build method, the monitor should grab a handle to the configuration object which it can then optionally use
to configure the behavior of the monitor. For consistency, the config object handle should be named m_cfg_h.
In the build method, the monitor should grab a handle to the virtual interface. For consistency, the virtual
interface handle should be named m_vif and be of the interface type.
When moving data from 4 state to 2 state variables - do an X check and present an error if an X is present.
A monitor can optionally include protected helper functions / variables to aid in implementation of the build and/or
config phases.
A monitor can optionally included protected helper tasks / variables to aid in the implementation of the run phase
task.

The monitor follows the following structure:


//--------------------------------------------------------------------------------------------------
// Reset Agent Monitor
//--------------------------------------------------------------------------------------------------
class reset_monitor extends uvm_monitor;
`uvm_component_utils(reset_monitor)

typedef virtual reset_if reset_if_t;

// Config
reset_cfg m_cfg_h;

// Interface
reset_if_t m_vif;

// Analysis Port
uvm_analysis_port#(reset_item) m_analysis_port;

// Constructor
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction: new

// Phase Methods
extern virtual function void build_phase(uvm_phase phase);
extern virtual function void connect_phase(uvm_phase phase);
extern virtual task run_phase(uvm_phase phase);

gChips DV Google Confidential pg 21 / 93


Google Proprietary & Confidential

// Helper Methods
extern protected virtual task mon_interface();

endclass: reset_monitor

//------------------------------------------------------------------------------
function void reset_monitor::build_phase(uvm_phase phase);
typedef uvm_config_db#(reset_cfg) config_db_cfg_t;

super.build_phase(phase);

// Grab the Config


if (!config_db_cfg_t::get(this, "", "reset_cfg", m_cfg_h)) begin
`uvm_fatal(get_name(), "Failed to Grab reset_cfg from Config DB")
end

// Build the Analysis Ports


m_analysis_port = new("m_analysis_port", this);

endfunction: build_phase

//------------------------------------------------------------------------------
function void reset_monitor::connect_phase(uvm_phase phase);
typedef uvm_config_db#(reset_if_t) cfg_db_vif_t;

// Grab the Config


if (!cfg_db_vif_t::get(this, "", "reset_vif", m_vif)) begin
`uvm_fatal(get_name(), "Failed to Grab reset_vif from Config DB")
end

endfunction: connect_phase

//------------------------------------------------------------------------------
task reset_monitor::run_phase(uvm_phase phase);

super.run_phase(phase);
mon_interface();

endtask: run_phase

//------------------------------------------------------------------------------
task reset_monitor::mon_interface();
int unsigned active_count;
reset_item mon_item_h;

forever begin

while (!(m_vif.mon_cb.reset === 1'b0 ) ) begin


@(m_vif.clk);

gChips DV Google Confidential pg 22 / 93


Google Proprietary & Confidential

end

while(m_vif.mon_cb.reset === 1'b0) begin


// Count Active cycles
active_count += 1;
@(m_vif.clk);
end

// Construct Monitored Item


mon_item_h = reset_item::type_id::create("mon_item_h", this);

// Update the Monitored Item


mon_item_h.m_active_cycles = active_count;

// Write the Item out - if the item values changed (or if its the first pass)
m_analysis_port.write(mon_item_h);

// Wait a cycle
@(m_vif.clk);
end

endtask: mon_interface

Sequencer
The sequencer bridges a sequence item from a sequence to the driver.
The interface specific sequencer is factory registered empty extension of the base sequencer class. We provide
this implementation (as opposed to just using the base sequencer directly) to allow factory overrides. (And for
completeness / consistency / cleanliness)
A sequencer is an item specialized extension of the uvm_sequencer class.
A sequencer should be named with a '_sequencer' suffix.
A sequencer should register with the factory using the component_utils macro.

The sequencer follows the following structure:


//--------------------------------------------------------------------------------------------------
// Reset Agent Sequencer
//--------------------------------------------------------------------------------------------------
class reset_sequencer extends uvm_sequencer#( reset_item );
`uvm_component_utils(reset_sequencer)

// Constructor
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction: new

endclass: reset_sequencer

gChips DV Google Confidential pg 23 / 93


Google Proprietary & Confidential

Agent
The agent is a component that encapsulates the driver, monitor, sequencer for the interface.
It also includes handles to the analysis ports of the enclosed driver and monitor.
An agent is an extension of the uvm_agent class.
An agent should be named with a '_agent' suffix.
The driver, monitor, and sequencer variables should be named - simply - m_driver, m_monitor, and
m_sequencer.
In the build method, the agent should grab a handle to the configuration object which it can then optionally use to
configure the behavior of the agent. For consistency, the config object handle should be named m_cfg_h.
The analysis ports should be named - simply - m_driver_req_analysis_port, m_driver_rsp_analysis_port, and
m_monitor_analysis_port.
This naming makes interaction with an instance of this agent straightforward and consistent.
An agent should register with the factory using the component_utils macro.
An agent should implement the build and config methods. Build should construct the child components and
analysis ports. The build method should set the sequencer into the config space; this allows easy automatic
generation of the virtual sequencer.
The connect method should connect the ports / exports.
No other members or methods should be required.

The agent follows the following structure:


//--------------------------------------------------------------------------------------------------
// Reset Agent
//--------------------------------------------------------------------------------------------------
class reset_agent extends uvm_agent;
`uvm_component_utils(reset_agent)

reset_cfg m_cfg_h;

// Analysis Fifos
uvm_analysis_port#(reset_item) m_driver_req_analysis_port;
uvm_analysis_port#(reset_item) m_driver_rsp_analysis_port;
uvm_analysis_port#(reset_item) m_monitor_analysis_port;

// Child Components
reset_sequencer m_sequencer;
reset_driver m_driver;
reset_monitor m_monitor;

// Constructor
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction: new

// Phase Methods
extern virtual function void build_phase(uvm_phase phase);

gChips DV Google Confidential pg 24 / 93


Google Proprietary & Confidential

extern virtual function void connect_phase(uvm_phase phase);

endclass: reset_agent

//------------------------------------------------------------------------------
function void reset_agent::build_phase(uvm_phase phase);
typedef uvm_config_db#(reset_cfg) config_db_cfg_t;
typedef uvm_config_db#(reset_sequencer) config_db_seqr_t;

super.build_phase(phase);

if(!config_db_cfg_t::get(this, "", "reset_cfg", m_cfg_h)) begin


`uvm_fatal(get_name(), "Failed to Grab reset_cfg from Config DB")
end

begin
int active;
if(uvm_config_db#(int)::get(this, "", "is_active", active)) begin
is_active = uvm_active_passive_enum'(active);
end
else begin
is_active = m_cfg_h.m_uvm_active_passive_h;
end
end

if (is_active == UVM_ACTIVE) begin


m_sequencer = reset_sequencer::type_id::create("m_sequencer", this);
config_db_seqr_t::set(null, "*", $sformatf("%s.m_sequencer", get_name()), m_sequencer);
m_driver = reset_driver::type_id::create("m_driver", this);
m_driver_req_analysis_port = new("m_driver_req_analysis_port", this);
m_driver_rsp_analysis_port = new("m_driver_rsp_analysis_port", this);
end

m_monitor = reset_monitor::type_id::create("m_monitor", this);


m_monitor_analysis_port = new("m_monitor_analysis_port", this);

endfunction: build_phase

//------------------------------------------------------------------------------
function void reset_agent::connect_phase(uvm_phase phase);
super.connect_phase(phase);

// Connect
if (is_active == UVM_ACTIVE) begin
m_driver.seq_item_port.connect(m_sequencer.seq_item_export);
m_driver.m_req_analysis_port.connect(m_driver_req_analysis_port);
m_driver.m_rsp_analysis_port.connect(m_driver_rsp_analysis_port);
end

m_monitor.m_analysis_port.connect(m_monitor_analysis_port);

gChips DV Google Confidential pg 25 / 93


Google Proprietary & Confidential

endfunction: connect_phase

Sequence

Nominal Sequence
The 'nominal' sequence is a wrapper of the item class. It bridges the cumbersome create, start_item, randomize,
finish_item, get_response method sequence of an item to the easy to follow create, start method sequence of
the uvm_sequence.
The constraints of the item are carried to the nominal sequence - resulting in some redundant code, that is
implemented only once. (Since these constraints are the rules for the protocol - not the constraints that are used
to create specific testcases.)
Note that we continue to use the same constraint names that we defined in the item - valid, legal, and typical.
The nominal sequence is an item specialized extension of the uvm_sequence class.
The nominal sequence should be named with a '_nominal_seq' suffix.
The nominal sequence, like all sequences, should register with the factory using uvm_object_utils. It should use
the field macros (or 'do_' methods) if it has members.

The nominal sequence follows the following structure:


//--------------------------------------------------------------------------------------------------
// Reset Agent Nominal Sequence
//--------------------------------------------------------------------------------------------------
// Nominal Seq -- just a wrapper for the item / driver interaction
// simplifies the user api from start, finish, rand, get to just start
class reset_nominal_seq extends uvm_sequence#(reset_item );

// Members
// async assert, positive edge sync deassert reset
rand reset_kind_t m_reset_kind; // Kind of reset to apply

// Reset Timing
// NOTE: _ns times for async and _cycle times from sync
rand int unsigned m_preactive_ns; // time (in ns) to wait before asserting;
// if first reset then time that reset pin is X
rand int unsigned m_preactive_cycles; // time (in cycles) to wait before asserting;
// if first reset then time that reset pin is X
rand int unsigned m_active_ns; // time (in ns) to hold reset active
rand int unsigned m_active_cycles; // time (in cycles) to hold reset active
rand int unsigned m_postactive_ns; // time (in ns) to wait after deasserting reset
// (before unblocking the sequence)
rand int unsigned m_postactive_cycles; // time (in cycles) to wait after deasserting reset
// (before unblocking the sequence)

// Field Macros
`uvm_object_utils_begin(reset_nominal_seq)
`uvm_field_enum(reset_kind_t, m_reset_kind, UVM_ALL_ON)
`uvm_field_int(m_preactive_ns, UVM_ALL_ON)

gChips DV Google Confidential pg 26 / 93


Google Proprietary & Confidential

`uvm_field_int(m_preactive_cycles, UVM_ALL_ON)
`uvm_field_int(m_active_ns, UVM_ALL_ON)
`uvm_field_int(m_active_cycles, UVM_ALL_ON)
`uvm_field_int(m_postactive_ns, UVM_ALL_ON)
`uvm_field_int(m_postactive_cycles, UVM_ALL_ON)
`uvm_object_utils_end

// Constraints
// Three Types of Constraint - valid, legal, typical
// Valid - need to be true or agent won't work correctly (things like positive values for delays)
// Legal - need to be true to obey protocol; can override to create protocol invalid transfers
// Typical - overridable constraint; use to set some reasonable bounds
constraint valid;
constraint legal;
constraint typical;

// Constructor
function new(string name = "reset_nominal_seq");
super.new(name);
endfunction: new

// Sequence Body
extern virtual task body();

endclass: reset_nominal_seq

//------------------------------------------------------------------------------
constraint reset_nominal_seq::valid {}

//------------------------------------------------------------------------------
constraint reset_nominal_seq::legal {}

//------------------------------------------------------------------------------
constraint reset_nominal_seq::typical {
soft m_preactive_ns dist {
[4:16] :/ 25,
[17:22] :/ 50,
[23:40] :/ 25
};
soft m_preactive_cycles dist {
[2:8] :/ 25,
[9:11] :/ 50,
[12:20] :/ 25
};
soft m_active_ns dist {
[4:16] :/ 25,
[17:22] :/ 50,
[23:40] :/ 25
};
soft m_active_cycles dist {

gChips DV Google Confidential pg 27 / 93


Google Proprietary & Confidential

[2:8] :/ 25,
[9:11] :/ 50,
[12:20] :/ 25
};
soft m_postactive_ns dist {
0 :/ 50,
[1:20] :/ 25,
[21:40] :/ 25
};
soft m_postactive_cycles dist {
0 :/ 50,
[1:10] :/ 25,
[11:20] :/ 25
};
}

//------------------------------------------------------------------------------
task reset_nominal_seq::body();

req = REQ::type_id::create("req", null, get_full_name());

start_item(req);

if (!req.randomize() with {
// Constrain item with local rand variables
// Example: m_data == local::m_data;
req.m_reset_kind == local::m_reset_kind;
req.m_preactive_ns == local::m_preactive_ns;
req.m_active_cycles == local::m_active_cycles;
req.m_postactive_cycles == local::m_postactive_cycles;
}) begin
`uvm_fatal(get_name(), "Randomize Failed")
end

finish_item(req);

get_response(rsp);

endtask: body

Helper Sequence
Often a sequence library will include more than just the nominal sequence with one or more 'helper' sequences.
These helper sequences are created by the protocol agent author to assist with common tasks / common
sequences.
For example - if the reset protocol author thought that folks would be needing an immediate assert reset
sequence that author would create something like the following.

gChips DV Google Confidential pg 28 / 93


Google Proprietary & Confidential

Note that we start a protocol sequence on m_sequencer - which is guaranteed to be the protocol sequencer
type.
A user of this sequence would only have to create and start it - without having to use the nominal sequence. It
also provides a nice example for the user if / when the user needs to use the more flexible nominal sequence.
The helper sequence is an item specialized extension of the uvm_sequence class.
The helper sequence should be named with a '_seq' suffix.
The helper sequence, like all sequences, should register with the factory using uvm_object_utils. It should use
the field macros (or 'do_' methods) if it has members.

The helper sequence follows the following structure:

//--------------------------------------------------------------------------------------------------
// Reset Assert Sequence
//--------------------------------------------------------------------------------------------------
class reset_assert_seq extends uvm_sequence#(reset_item);

`uvm_object_utils(reset_assert_seq)

// Constructor
function new(string name = "");
super.new(name);
endfunction: new

// Sequence Body
extern virtual task body();

endclass: reset_assert_seq

//------------------------------------------------------------------------------
task reset_assert_seq::body();

reset_nominal_seq reset_nominal_seq_h;

reset_nominal_seq_h = reset_nominal_seq::type_id::create(
"reset_nominal_seq_h",
null,
get_full_name()
);
if (!reset_nominal_seq_h.randomize() with {
m_reset_kind == RESET_KIND_ASSERT;
}) begin
`uvm_fatal(get_name(), "Randomize Failed");
end
reset_nominal_seq_h.start(m_sequencer);

endtask: body

gChips DV Google Confidential pg 29 / 93


Google Proprietary & Confidential

Sequence List
The sequence list is a compilation aid. It "tick includes" all of the sequences that are available with this agent.

The sequence list follows the following structure:

`include "reset_nominal_seq.svh"
`include "reset_assert_seq.svh"

(Note that this sequence list doesn't have many sequences listed - some agents have quite a few)

Package
The interface protocol agent package is the namespaced encapsulation of the entire agent.
In the package we import from other packages, to get access to types used in this package. We declare types
that are shared across the agent. And we include the files of the agent - to place the contained classes in this
package.
If our agent uses 'pound' (#) delays or calls to time system calls (like $time) then the package also needs to set a
timeunit and precision.
Packaging our protocol agent allows other users to easily import our package to use our agent.
A package should be named with a '_pkg' suffix.

The package follows the following structure:

`include "uvm_macros.svh"

//--------------------------------------------------------------------------------------------------
// Reset Agent Package
//--------------------------------------------------------------------------------------------------
package reset_pkg;

timeunit 1ps;
timeprecision 1fs;

import uvm_pkg::*;

typedef enum bit {


RESET_ACTIVE_LOW = 0,
RESET_ACTIVE_HIGH = 1
} reset_active_t;

typedef enum bit {


RESET_ASSERT_ASYNC,
RESET_ASSERT_SYNC
} reset_assert_t;

gChips DV Google Confidential pg 30 / 93


Google Proprietary & Confidential

typedef enum bit {


RESET_DEASSERT_ASYNC,
RESET_DEASSERT_SYNC
} reset_deassert_t;

typedef enum {
RESET_KIND_SEQUENCE,
RESET_KIND_ASSERT,
RESET_KIND_DEASSERT
} reset_kind_t;

`include "reset_cfg.svh"
`include "reset_item.svh"
`include "reset_driver.svh"
`include "reset_monitor.svh"
`include "reset_sequencer.svh"
`include "reset_agent.svh"
`include "reset_seq_list.svh"

endpackage: reset_pkg

Package (Synthesizable)
In the event that a protocol agent has an interface that include types and / or parameters declared in the
package - those types should be moved to a separate package called the synthesizable package.
We need to do this to ensure that the interface maintains its synthesizability. A package that includes classes
cannot be synthesized - so we need to split the package into a '_synth_pkg' and a '_pkg.'
This would commonly look like:
//--------------------------------------------------------------------------------------------------
// Foo Agent Synthesizable Package
//--------------------------------------------------------------------------------------------------
package foo_synth_pkg;
typedef struct packed {
logic [31:0] something;
logic [1:0] otherthing;
} some_type_t;

endpackage: foo_synth_pkg

With an interface dependency like:


//--------------------------------------------------------------------------------------------------
// Foo Agent Interface
//--------------------------------------------------------------------------------------------------
interface foo_if (
input bit clk
);

// Interface Net Declarations


foo_synth_pkg::some_type_t payload;

gChips DV Google Confidential pg 31 / 93


Google Proprietary & Confidential

...

endinterface: foo_if

And the DV side (non-synthesizable) package like:

`include "uvm_macros.svh"

//--------------------------------------------------------------------------------------------------
// Foo Agent Package
//--------------------------------------------------------------------------------------------------
package foo_pkg;

timeunit 1ps;
timeprecision 1fs;

import uvm_pkg::*;
import foo_synth_pkg::*;

`include "foo_cfg.svh"
`include "foo_item.svh"
`include "foo_driver.svh"
`include "foo_monitor.svh"
`include "foo_sequencer.svh"
`include "foo_agent.svh"
`include "foo_seq_list.svh"

endpackage: foo_pkg

Parameterizing Widths
Many protocol agents connect to interfaces where the protocol allows a variety of widths. Because of how types
are defined a parameterized interface requires parameterizing the driver, monitor, and agent. And since that
parameterization also likely affects the item members, requires parameterizing the item, sequencer, and
sequences as well.
These parameters (and any typedefs) should be defined in the above mentioned synthesizable package.
This parameterization can result in some cumbersome interactions - so to streamline, use typedefs liberally.
A common typedef - 'this_type':
class myitem #(int WDTH) extends uvm_sequence_item;
typedef myitem#(WDTH) this_type_t;
..
endclass: myitem

With the "this_type" typedef the code inside myitem that uses the class' type will be more streamlined and easier
to read.
Do a similar thing at the top of an agent:
class myagent #(int WDTH) extends uvm_agent;

gChips DV Google Confidential pg 32 / 93


Google Proprietary & Confidential

typedef myagent#(WDTH) this_type_t;


typedef myitem#(WDTH) myitem_t;
typedef mydriver#(WDTH) mydriver_t;
typedef mysequencer#(WDTH) mysequencer_t;
typedef mydriver#(WDTH) mymonitor_t;
...
endclass: myagent

This typedefing results in code that is both more readable and more compact.

DUT Testbench / Environment


A DUT testbench / environment is for each DUT that we wish to test. The structure mirror that of a protocol
agent.
The DUT bench has its own collection of agents, a configuration object, an encapsulation layer - called the env, a
component that does checking - called the scoreboard, a component that has handles to all of the sequencers -
called the virtual sequence, a module to instance the DUT, and a package with all of the DUT parameters and
types.

DUT Specific Agents


The DUT will often have specialized protocols that are unique to the DUT. We call these 'DUT Specific Agents.'
DUT specific agents can be implementations of complex protocols; protocols that are just uniquely used in the
DUT.
Or, DUT specific agents can refer to specific parameterizations of parameterizable protocol agents. These
parameterizations are implemented as a collection of parameters and typedef'd specializations of protocol
agents within a package.
Note that it is often handy to use a similar typedefing to just produce DUT specific names of particular protocol
agent. One where the typedefs are used to simply rename a protocol agent to something that makes more sense
in the context of the DUT.
A DUT specific protocol package should be named with a prefix common to the prefix used by all of the other
DUT components (usually the name of the DUT) and named with a suffix of '_pkg.'
Similar to the protocol agent split of synthesizable and non-synthesizable packages - we also need to split the
DUT specific agent packages; for the same reason - to ensure that the TB is synthesizable.

These are implemented in a package as:


//--------------------------------------------------------------------------------------------------
// DUT Specific Protocol Agent Synthesizable Package
//--------------------------------------------------------------------------------------------------
package dut_specific_protocol_synth_pkg;

import some_protocol_synth_pkg::*;

localparam int unsigned DUT_WDTH = 16;

gChips DV Google Confidential pg 33 / 93


Google Proprietary & Confidential

typedef some_protocol_cfg dut_specific_protocol_cfg_t;

endpackage: dut_specific_protocol_synth_pkg

and:

`include "uvm_macros.svh"

//--------------------------------------------------------------------------------------------------
// DUT Specific Protocol Agent Package
//--------------------------------------------------------------------------------------------------
package dut_specific_protocol_pkg;

import uvm_pkg::*;

import some_protocol_pkg::*;
import dut_specific_protocol_synth_pkg::*;

typedef some_protocol_payload_obj #(
DUT_WDTH
) dut_specific_protocol_payload_obj_t;
typedef some_protocol_item #(
DUT_WDTH
) dut_specific_protocol_item_t;
typedef some_protocol_driver#(
DUT_WDTH
) dut_specific_protocol_driver_t;
typedef some_protocol_monitor#(
DUT_WDTH
) dut_specific_protocol_monitor_t;
typedef some_protocol_sequencer#(
DUT_WDTH
) dut_specific_protocol_sequencer_t;
typedef some_protocol_agent#(
DUT_WDTH
) dut_specific_protocol_agent_t;
typedef some_protocol_nominal_seq#(
DUT_WDTH
) dut_specific_protocol_nominal_seq_t;

`include "dut_specific_protocol_seq_list.svh"

endpackage: dut_specific_protocol_pkg

Notice the (optional) inclusion of the 'dut_specific_protocol_seq_list' file. Our DUT specific specialization of the
general protocol often comes with the need to create some DUT specific sequences. These can be created and
then included in this sequence list file. By doing this we keep the protocol implementation separate from our DUT

gChips DV Google Confidential pg 34 / 93


Google Proprietary & Confidential

specific requirements. It also allows us to keep code neatly separated in our DUT environment - keeping these
dut specific protocol sequences encapsulated independent of our DUT virtual sequences.

Testbench Top - TB Top (Synthesizable)


The tb_top is the top level module of our testbench. This module:
➔ sets the timescale for the module
➔ imports the DUT and protocol agent packages (to get parameters)
➔ declares DUT signals
➔ instances the DUT
➔ connects the DUT signals to protocol interfaces
➔ instances the protocol interfaces
➔ (optionally) implements temporal assertions
Note that we intentionally don't connect the protocol interfaces directly to the DUT ports. Having intermediate
signals allows easier incremental design - for both adding and removing interfaces. And makes the connection
for tied off or tristate nets both intuitive and consistent with the other signals. Having these intermediate signals
also paves the way for gatesims - where delays are often needed to match sdf induced interface delays. These
delays can be added in this section (with proper `GATESIM ifdef'ing.)
A testbench top should be named with a dut named prefix and a '_tb' suffix.

A typical tb_top looks like the following (for a DUT named "foo"):
//--------------------------------------------------------------------------------------------------
// FOO TestBench
//--------------------------------------------------------------------------------------------------
module foo_tb;

// DUT RTL Parameters


import foo_rtl_pkg::*;

// Import FOO I/O Bench Parameters


import foo_synth_pkg::*;
import foo_activity_watchdog_synth_pkg::*;
import foo_csr_synth_pkg::*;
...

//
// Declare Variables
//
logic clk;
logic clk_csr;
logic rst_n;
logic test_mode;
logic [FOO_STRIPES_WDTH-1:0] dyn_clk_en;
logic [FOO_STRIPES_WDTH-1:0] foo_irq;

gChips DV Google Confidential pg 35 / 93


Google Proprietary & Confidential

...

// CSR register bus interface


logic regbus_in_valid;
regbus_t regbus_in;
logic regbus_out_valid;
regbus_t regbus_out;

//
// Instantiate DUT
//
foo dut (
.clk (clk),
.clk_csr (clk_csr),
.rst_n (rst_n),
.test_mode (test_mode),
.dyn_clk_en (dyn_clk_en),
.foo_irq (foo_irq),

...

//CSR register bus interface


.regbus_in_valid (regbus_in_valid),
.regbus_in (regbus_in),
.regbus_out_valid (regbus_out_valid),
.regbus_out (regbus_out)
);

//
// Connect DUT
//
// Clock
assign clk = u_clock_if.clock_pin;
assign clk_csr = u_clock_if.clock_pin;

// Reset
assign rst_n = u_reset_if.reset;

assign test_mode = 1'b0;


assign dyn_clk_en = '1;

// IRQ Interface
generate
for (genvar foo_stripe = 0; foo_stripe < FOO_STRIPES; foo_stripe += 1) begin: gen_irq_if
assign u_foo_irq_if[foo_stripe].irq = foo_irq[foo_stripe];
end: gen_irq_if
endgenerate

...

gChips DV Google Confidential pg 36 / 93


Google Proprietary & Confidential

// CSR register bus interface


assign regbus_in_valid = u_foo_csr_if.req_valid;
assign regbus_in.offset = u_foo_csr_if.req_offset;
assign regbus_in.data = u_foo_csr_if.req_data;
assign regbus_in.word_mask = u_foo_csr_if.req_word_mask;
assign regbus_in.r_wn = u_foo_csr_if.req_read;

assign u_foo_csr_if.rsp_valid = regbus_out_valid;


assign u_foo_csr_if.rsp_offset = regbus_out.offset;
assign u_foo_csr_if.rsp_data = regbus_out.data;
assign u_foo_csr_if.rsp_word_mask = regbus_out.word_mask;
assign u_foo_csr_if.rsp_read = regbus_out.r_wn;

// Instantiate Interfaces
clock_if u_clock_if();

reset_if u_reset_if (
.clk(clk)
);

foo_irq_if u_foo_irq_if[FOO_STRIPES] (
.clk(clk),
.rst_n(rst_n)
);

foo_irq_if u_foo_irq_err_if[FOO_STRIPES] (
.clk(clk),
.rst_n(rst_n)
);

foo_csr_if #(
.CSR_ADDR_WDTH (FOO_CSR_ADDR_WDTH ),
.CSR_WORD_MASK_WDTH(FOO_CSR_WORD_MASK_WDTH),
.CSR_DATA_WDTH (FOO_CSR_DATA_WDTH )
) u_foo_csr_if (
.clk(clk)
);

//
// Assertions
//
name_of_assert: assert property (@(posedge clk) disable iff (!rst_n)
...
) else begin
`uvm_error("foo_tb", $sformatf("some error message"))
end

endmodule: foo_tb

gChips DV Google Confidential pg 37 / 93


Google Proprietary & Confidential

Testbench Top - DV Top (UVM Side)


The dv_top is the top level module of our UVM testbench. This module:
➔ sets protocol interfaces (as virtual interfaces) into the config database
➔ (optionally) makes waveform dumping PLI calls
➔ sets the timeformat
➔ launches UVM
➔ (optionally) implements temporal assertions
A testbench top - DV top should be named with a dut named prefix and a '_dv' suffix.
A typical tb_top looks like the following (for a DUT named "foo"):
//--------------------------------------------------------------------------------------------------
// FOO TestBench - DV Top
//--------------------------------------------------------------------------------------------------
module foo_dv;

// Set Timescale - in module


timeunit 1ns;
timeprecision 1ps;

// Include / Import UVM


`include "uvm_macros.svh"
import uvm_pkg::*;

// DUT RTL Parameters


import foo_rtl_pkg::*;

// Import FOO I/O Bench Parameters


import foo_pkg::*;
import foo_activity_watchdog_pkg::*;
import foo_csr_pkg::*;
...

initial begin
// Disable Assertions until Reset Rises (and repeat if reset falls)
forever begin
$assertoff;
@(posedge foo_tb.rst_n);
$asserton;
@(negedge foo_tb.rst_n);
end
end

//
// Pass Interfaces to Cfg DB
//
// Clock Interface
initial begin: clock_vif
typedef virtual clock_if clock_if_t;

gChips DV Google Confidential pg 38 / 93


Google Proprietary & Confidential

uvm_config_db#(clock_if_t)::set(null, "*", "clock_vif", foo_tb.u_clock_if);


end: clock_vif

// Reset Interface
initial begin: reset_vif
typedef virtual reset_if reset_if_t;
uvm_config_db#(reset_if_t)::set(null, "*", "reset_vif", foo_tb.u_reset_if);
end: reset_vif

// IRQ Interface
generate
for (genvar foo_stripe = 0; foo_stripe < FOO_STRIPES; foo_stripe += 1) begin: gen_irq_vif
initial begin: foo_irq_vif
typedef virtual foo_irq_if foo_irq_if_t;
uvm_config_db#(foo_irq_if_t)::set(
null,
$sformatf("*foo_irq_agent_h[%0d]*", foo_stripe),
"foo_irq_vif",
foo_tb.u_foo_irq_if[foo_stripe]
);
end: foo_irq_vif
end: gen_irq_vif
endgenerate

// CSR Interface
initial begin: foo_csr_vif
typedef virtual foo_csr_if #(
.CSR_ADDR_WDTH (FOO_CSR_ADDR_WDTH ),
.CSR_WORD_MASK_WDTH(FOO_CSR_WORD_MASK_WDTH),
.CSR_DATA_WDTH (FOO_CSR_DATA_WDTH )
) foo_csr_if_t;
uvm_config_db#(foo_csr_if_t)::set(
null, "*", "foo_csr_vif", foo_tb.u_foo_csr_if
);
end: foo_csr_vif

...

// Generate FSDB Dump


initial begin: fsdb_dump
plusargs_handler args;
args = plusargs_handler::type_id::create("args");
if (args.test_arg("fsdb", 1)) begin
$fsdbDumpfile("dump.fsdb");
$fsdbDumpvars("+all");
$fsdbDumpSVA();
end
end: fsdb_dump

// Run Test

gChips DV Google Confidential pg 39 / 93


Google Proprietary & Confidential

initial begin: test_run_entry_point


$timeformat(-9, 0, " ns", 5); // show time in ns

// Start UVM
run_test();
end: test_run_entry_point

endmodule: foo_dv

Configuration Object
In the same way that each agent comes with a single encapsulation with all of its configuration members - so
does the DUT environment. The same rules and suggestions that apply to the interface protocol agent's
configuration object also apply to this DUT environment configuration object.
This object contains the members that define how the environment is built - and in some ways - how the
environment is to behave. The configuration is built and consumed at the build phase. Configuration objects
should not be consumed at any point in the run phase. (Members of configuration objects can be used in the run
phase.)
The configuration object extends the uvm_object and registers with the factory using the uvm_object_utils
macro.
The environment configuration object should be named with a dut name prefix and '_cfg' suffix.
The configuration object class will have a handle pointing to the RAL object (if RAL is being used).
It is not unusual for this class to begin as an empty (no member configuration variables) with members being
added as the project progresses. Always create a configuration object - even if the DUT environment begins with
nothing that is configurable.

A typical configuration object looks like the following (for a DUT named "foo"):
//--------------------------------------------------------------------------------------------------
// FOO Environment Config
//--------------------------------------------------------------------------------------------------
class foo_cfg extends uvm_object;

// Member Variables to define how the FOO Environment is Built


ral_model m_ral_model_h;
stimulus_gen_pkg::data_mode_e m_stimulus_mode;

// Constructor
extern function new(string name = "");

// Field Macros
`uvm_object_utils_begin(foo_cfg)
`uvm_field_object(m_ral_model_h, UVM_ALL_ON)
`uvm_field_sarray_enum(
stimulus_gen_pkg::data_mode_e,
m_stimulus_mode,

gChips DV Google Confidential pg 40 / 93


Google Proprietary & Confidential

UVM_ALL_ON
)
`uvm_object_utils_end

endclass: foo_cfg

//------------------------------------------------------------------------------
function foo_cfg::new(string name = "");
super.new(name);
// Default to Stimulus Generator to INCREMENTING mode
m_stimulus_mode = stimulus_gen_pkg::INCREMENTING;
endfunction: new

Scoreboard
The scoreboard is the central component that interfaces to some (or all) of the agent analysis ports to check the
DUT against expected results.
The scoreboard will typically check on receipt of items and at end of test.
The scoreboard includes an object that represents the current configuration of the DUT. An object which is kept
up-to-date either within the scoreboard, or in another component - with the handle passed to the scoreboard
through the configuration database. This DUT configuration is commonly held as a RAL object - contained within
the DUT environment configuration object.
The scoreboard may include a handle to a scoreboard debug vif - a virtual interface with informational members
used to aid in the debug of failing tests. With members like events to mark when an error occurs, enum to mark
the type of error, and any additional information that could be useful during waveform debug.
Scoreboard implementations vary - as how a DUT is implemented varies. A common scoreboard will capture
input items as they are sent to the DUT and then, on receipt of a corresponding output item, will transform the
input item and compare to the received output. Often the transform is influenced by how the DUT is configured -
so the scoreboard will query the current configuration from the RAL (or similar DUT CSR object) at the time the
output is received.

The scoreboard component can be broken into a few simple pieces:


➔ analysis export / analysis fifo - per agent interface
➔ helper object of current configuration state(often through a RAL object encapsulated by a
configuration object)
➔ handle to the configuration object
➔ method to handle analysis fifo - per agent interface
➔ instance of model(s) of the transform(s) that the DUT performs
➔ instance of memory(ies) of the DUT
➔ phase methods - build, connect, run, check
The scoreboard extends the uvm_scoreboard component and registers with the factory using the
uvm_component_utils macro.
The scoreboard should be named with a dut name prefix and '_scoreboard' suffix.
The build phase of a scoreboard should get a handle to the DUT environment configuration object and construct
the analysis exports / fifos per required interface. The connect phase should get a handle to the scoreboard

gChips DV Google Confidential pg 41 / 93


Google Proprietary & Confidential

debug vif (if present) and connect the analysis exports to their corresponding fifos. The run phase should fork off
all of the fifo handler methods. The check phase should do an end of test cleanup check; for example, to ensure
that the DUT processed all of the information that it was sent.

Usage of typical TLM analysis write callbacks vs. exports/fifos is an implementation decision based on the
complexity and requirements of the particular scoreboard when there are more than one TLM connections. While
the TLM FIFO is preferred, sufficiently simple implementations may benefit from using the
`uvm_analysis_imp_decl() method. It is advised not to mix these two approaches in a single component --
choose one or the other, refactor if necessary.

If the items already have source identifying information (or the source does not matter) it is best to connect all
agents to a single implementation port and route them inside the scoreboard.

uvm_analysis_export + uvm_analysis_fifo:
● Pros:
○ Supported arrayed inputs; from an array of repeated agents or an associative array based
on which agent is connected (string, enum).
○ Safe interprocess communication; since objects go through a mailbox, parent processes of
the write method being killed cannot affect processing the item.
○ Single handler can pull from multiple ports; e.g. an array of all the input type can funnel
objects into common logic.
○ Processing happens in a task, so users can implement time-consuming logic or fork off
new processes unlike a function.
● Cons:
○ More boilerplate code; supporting two objects being made; extra code in the connect and
run phases which may otherwise be omitted.
`uvm_analysis_imp_decl():
● Pros:
○ Direct callback methods for each connection
○ Less boilerplate code, behaves similarly to a regular uvm_analysis_imp
● Cons:
○ Macro magic should be avoided
○ Possible namespace collisions within a package
○ Doesn’t scale to large numbers of ports
○ Cannot support arrays of inputs; e.g. `uvm_analysis_imp_decl(_port_0),
`uvm_analysis_imp_decl(_port_1), etc.
○ Often leads to repeated code in each write callback
○ Need to pass objects to tasks for time-consuming logic or forks
○ Does not work with parameters since classes and callback methods must be defined at
compile time.

An example of using the export/fifo is below:

gChips DV Google Confidential pg 42 / 93


Google Proprietary & Confidential

//--------------------------------------------------------------------------------------------------
class foo_scoreboard extends uvm_scoreboard;
`uvm_component_utils(foo_scoreboard)

uvm_analysis_export#(foo_input_item_t) m_foo_input_analysis_export;
protected uvm_tlm_analysis_fifo#(foo_input_item_t) m_foo_input_analysis_fifo;

function new(string name, uvm_component parent);


super.new(name, parent);
endfunction: new

virtual function void build_phase(uvm_phase phase);


super.build_phase(phase);
m_foo_input_analysis_export = new(“m_foo_input_analysis_export”, this);
m_foo_input_analysis_fifo = new(“m_foo_input_analysis_fifo”, this);
endfunction: build_phase

virtual function void connect_phase(uvm_phase phase);


m_foo_input_analysis_export.connect(m_foo_input_analysis_fifo.analysis_export);
endfunction: connect_phase

virtual task run_phase(uvm_phase phase);


fork
input_fifo_handler();
join
endtask: run_phase

virtual task input_fifo_handler();


foo_input_item_t item;
forever begin
m_foo_input_analysis_fifo.get(item);
// process item ...
end
endtask: input_fifo_handler
endclass

Here’s an example using the `uvm_analysis_imp_decl() macro instead:

`uvm_analysis_imp_decl(_input)

//--------------------------------------------------------------------------------------------------
class foo_scoreboard extends uvm_scoreboard;
`uvm_component_utils(foo_scoreboard)

uvm_analysis_imp_input#(foo_input_item_t, foo_scoreboard) m_foo_input_analysis_imp;

function new(string name, uvm_component parent);


super.new(name, parent);
endfunction: new

gChips DV Google Confidential pg 43 / 93


Google Proprietary & Confidential

virtual function void build_phase(uvm_phase phase);


super.build_phase(phase);
m_foo_input_analysis_imp = new(“m_foo_input_analysis_imp”, this);
endfunction: build_phase

virtual function void write_input(foo_input_item_t item);


// process item ...
endfunction: write_input

endclass

Here’s an example where a number of repeated agents are connected to a scoreboard (whose items do not
have source identifying information):

uvm_analysis_export#(port_item_t) m_port_analysis_export[NUM_PORTS];
uvm_tlm_analysis_fifo#(port_item_t) m_port_analysis_fifo[NUM_PORTS];

virtual task run_phase(uvm_phase phase);


foreach (m_port_analysis_fifo[port]) begin
fork
automatic uint_t ap = port;
port_fifo_handler(ap);
join_none
end
endtask: run_phase

virtual task port_fifo_handler(uint_t port);


port_item_t item;
uvm_tlm_analysis_fifo#(port_item_t) fifo = m_port_analysis_fifo[port];
forever begin
fifo.get(item);
// process item...
end
endtask: port_fifo_handler

A typical scoreboard component looks like the following (for a DUT named "foo" using analysis fifos):
//--------------------------------------------------------------------------------------------------
// FOO Scoreboard
//--------------------------------------------------------------------------------------------------
class foo_scoreboard extends uvm_scoreboard;
`uvm_component_utils(foo_scoreboard)

// Environment CFG
foo_cfg m_cfg_h;

// Helper Model
foo_transform_model_obj_t m_foo_transform_model_obj_h;

// Memory Model

gChips DV Google Confidential pg 44 / 93


Google Proprietary & Confidential

foo_mem_model_obj_t m_foo_mem_model_obj_h;

// Analysis Exports
uvm_analysis_export#(foo_input_item_t) m_foo_input_analysis_export;
uvm_analysis_export#(foo_output_item_t) m_foo_output_analysis_export;

// Analysis FIFOs
protected uvm_tlm_analysis_fifo#(foo_input_item_t) m_foo_input_analysis_fifo;
protected uvm_tlm_analysis_fifo#(foo_output_item_t) m_foo_output_analysis_fifo;

// Data Checking Fifos


protected uvm_tlm_fifo#(foo_input_item_t) m_foo_input_fifo[FOO_TRANSACTION_IDS];

// Debug Interface
virtual foo_scoreboard_debug_if m_vif;

// Constructor
extern function new(string name, uvm_component parent);

// UVM Phase Methods


extern function void build_phase(uvm_phase phase);
extern function void connect_phase(uvm_phase phase);
extern task run_phase(uvm_phase phase);
extern function void check_phase(uvm_phase phase);

// Analysis FIFO Handlers


extern task input_fifo_handler(uvm_phase phase);
extern task output_fifo_handler(uvm_phase phase);

endclass : foo_scoreboard

//------------------------------------------------------------------------------
function foo_scoreboard::new(string name, uvm_component parent);
super.new(name, parent);
endfunction: new

//------------------------------------------------------------------------------
function void foo_scoreboard::build_phase(uvm_phase phase);
typedef uvm_config_db#(foo_cfg) config_db_cfg_t;

if(!config_db_cfg_t::get(this, "", "foo_cfg", m_cfg_h)) begin


`uvm_fatal(get_name(), "Failed to Grab foo_cfg from Config DB")
end

m_foo_transform_model_obj_h = foo_transform_model_obj_t::type_id::create(
"m_foo_transform_model_obj_h", this
);

// FOO Memory Model


m_foo_mem_model_obj_h = foo_mem_model_obj_t::type_id::create(

gChips DV Google Confidential pg 45 / 93


Google Proprietary & Confidential

"m_foo_mem_model_obj_h",
this
);

// Make FOO Memory Model available to other objects / components


begin
typedef uvm_config_db#(foo_mem_model_obj_t) config_db_foo_mem_t;
config_db_foo_mem_t::set(
null, "*", "m_foo_mem_model_obj_h", m_foo_mem_model_obj_h
);
end

m_foo_input_analysis_export = new(
"m_foo_input_analysis_export", this
);
m_foo_input_analysis_fifo = new(
"m_foo_input_analysis_fifo", this
);

m_foo_output_analysis_export = new(
"m_foo_output_analysis_export", this
);
m_foo_output_analysis_fifo = new(
"m_foo_output_analysis_fifo", this
);
foreach (m_foo_input_fifo[ii]) begin
m_foo_input_fifo[ii] = new(
$sformatf("m_foo_input_fifo[%0d]", ii), this, 0
);
end
endfunction: build_phase

//------------------------------------------------------------------------------
function void foo_scoreboard::connect_phase(uvm_phase phase);
typedef uvm_config_db#(virtual foo_scoreboard_debug_if) config_db_vif_t;

// Grab the VIF


if (!config_db_vif_t::get(this, "", "foo_scoreboard_debug_vif", m_vif)) begin
`uvm_fatal(get_name(), "Failed to Grab foo_scoreboard_debug_vif from Config DB")
end

m_foo_input_analysis_export.connect(
m_foo_input_analysis_fifo.analysis_export
);
m_foo_output_analysis_export.connect(
m_foo_output_analysis_fifo.analysis_export
);

endfunction: connect_phase

gChips DV Google Confidential pg 46 / 93


Google Proprietary & Confidential

//------------------------------------------------------------------------------
task foo_scoreboard::run_phase(uvm_phase phase);

// Fork Off Fifo Handlers


fork
input_fifo_handler(phase);
output_fifo_handler(phase);
join

endtask: run_phase

//------------------------------------------------------------------------------
function void foo_scoreboard::check_phase(uvm_phase phase);
begin
foo_input_item_t foo_input_item_h;
foreach (m_foo_input_fifo[ii]) begin
while (!m_foo_input_fifo[ii].is_empty()) begin
void'(m_foo_input_fifo[ii].try_get(
foo_input_item_h)
);
-> m_vif.error;
`uvm_error(
get_name(),
$sformatf(
"Outstanding Input - Transaction ID 0x%0x:\n%s",
ii,
foo_input_item_h.sprint()
)
)
end
end
end
endfunction: check_phase

//------------------------------------------------------------------------------
task foo_scoreboard::input_fifo_handler(uvm_phase phase);
foo_input_item_t foo_input_item_h;

forever begin
m_foo_input_analysis_fifo.get(foo_input_item_h);
`uvm_info(
get_name(),
$sformatf(
"foo_input_item_h\n%s",
foo_input_item_h.sprint()
),
UVM_HIGH
)

// Make sure that transaction ids aren't being reused before freed

gChips DV Google Confidential pg 47 / 93


Google Proprietary & Confidential

if (!m_foo_input_fifo[
foo_input_item_h.m_transaction_id
].is_empty()) begin
`uvm_fatal(
get_name(),
$sformatf(
"Attempt to write into full fifo - transaction id 0x%0x",
foo_input_item_h.m_transaction_id
)
)
end

// Queue Per ID - pop when output comes


m_foo_input_fifo[
foo_input_item_h.m_transaction_id
].put(foo_input_item_h);

end

endtask: input_fifo_handler

//------------------------------------------------------------------------------
task foo_scoreboard::output_fifo_handler(uvm_phase phase);
foo_output_item_t foo_output_item_h;
foo_input_item_t foo_input_item_h;

forever begin

m_foo_output_analysis_fifo.get(foo_output_item_h);
`uvm_info(
get_name(),
$sformatf(
"foo_output_item_h\n%s",
foo_output_item_h.sprint()
),
UVM_HIGH
)

// Pop the Matching Input


if (!m_foo_input_fifo[
foo_output_item_h.m_transaction_id
].try_get(foo_input_item_h)
) begin
-> m_vif.error;
`uvm_error(get_name(),
$sformatf(
"try_get Failed - ID 0h%0h; No Matching ID! OUTPUT ITEM:\n%s",
foo_output_item_h.m_transaction_id,
foo_output_item_h.sprint()
)

gChips DV Google Confidential pg 48 / 93


Google Proprietary & Confidential

)
end
else begin
bit [FOO_DATA_WDTH-1:0] expected_data;

`uvm_info(
get_name(),
$sformatf("foo_input_item_h\n%s",
foo_input_item_h.sprint()
),
UVM_HIGH
)
if (m_cfg_h.m_ral_model_h.transform_enabled()) begin
expected_data = m_foo_transform_model_obj_h.transform(
foo_input_item_h.m_data
);
end
else begin //bypass mode
expected_data = foo_input_item_h.m_data;
end

// Compare
if (foo_output_item_h.m_data != expected_data) begin
-> m_vif.error;
`uvm_error(
get_name(),
$sformatf(
"Mismatch - Expected 0x%0x, Got 0x%0x; Input\n%s\nOutput\n%s",
expected_data,
foo_output_item_h.m_data,
foo_input_item_h.sprint(),
foo_output_item_h.sprint()
)
)
end
else begin
`uvm_info(
get_name(),
$sformatf("Match - Expected 0x%0x, Got 0x%0x; Input\n%s\nOutput\n%s",
expected_data,
foo_output_item_h.m_data,
foo_input_item_h.sprint(),
foo_output_item_h.sprint()
),
UVM_HIGH
)
end
end
end
endtask: output_fifo_handler

gChips DV Google Confidential pg 49 / 93


Google Proprietary & Confidential

Virtual Sequencer
The virtual sequencer component is a hierarchical element with handles to each of the sequencers contained in
the agents of the DUT environment.
The virtual sequencer provides virtual sequences a handle on which to run; or more accurately - a handle on
which to start the virtual sequence.
The virtual sequencer extends the uvm_sequencer component specialized to a uvm_sequence_item type and
registers with the factory using the uvm_component_utils macro.
The virtual sequencer should be named with a dut name prefix and '_virtual_sequencer' suffix.
A virtual sequencer only implements the connect phase. In the connect phase a virtual sequencer uses the
config database to get handles to the virtual sequencers instanced in the environment.
Instance variable names should match the name of the class being instanced (where possible - when there is
more than one then choose names appropriately.) This makes starting sequences on the virtual sequencer
intuitive.

A typical virtual sequencer component looks like the following (for a DUT named "foo"):
//--------------------------------------------------------------------------------------------------
// FOO Virtual Sequencer
//--------------------------------------------------------------------------------------------------
class foo_virtual_sequencer extends uvm_sequencer#(uvm_sequence_item);
`uvm_component_utils(foo_virtual_sequencer)

clock_sequencer m_clock_sequencer_h;
reset_sequencer m_reset_sequencer_h;
...
foo_input_sequencer_t m_foo_input_sequencer_h;
...

extern function new(string name, uvm_component parent);


extern virtual function void connect_phase(uvm_phase phase);

endclass: foo_virtual_sequencer

//------------------------------------------------------------------------------
function foo_virtual_sequencer::new(string name, uvm_component parent);
super.new( name, parent );
endfunction: new

//------------------------------------------------------------------------------
function void foo_virtual_sequencer::connect_phase(uvm_phase phase);

super.connect_phase(phase);

// Get Virtual Sequencer Handles Assigned using 'get'


// gets rid of need to directly assign with dot operator in environment

gChips DV Google Confidential pg 50 / 93


Google Proprietary & Confidential

// Clock Sequencer
begin
typedef uvm_config_db#(clock_sequencer) config_db_clock_seqr_t;
if (!config_db_clock_seqr_t::get(
this,
"*",
"m_clock_agent_h.m_sequencer",
m_clock_sequencer_h
)) begin
`uvm_fatal(
get_name(),
"Failed to Grab clock_sequencer from Config DB"
)
end
end

// Reset Sequencer
begin
typedef uvm_config_db#(reset_sequencer) config_db_reset_seqr_t;
if (!config_db_reset_seqr_t::get(
this,
"*",
"m_reset_agent_h.m_sequencer",
m_reset_sequencer_h
)) begin
`uvm_fatal(
get_name(),
"Failed to Grab reset_sequencer from Config DB"
)
end
end

...

// Foo Input Interface Sequencer


begin
typedef uvm_config_db#(foo_input_sequencer_t) config_db_irq__seqr_t;
if (!config_db_irq__seqr_t::get(
this,
"*",
"m_foo_input_agent_h.m_sequencer",
m_foo_input_sequencer_h
)) begin
`uvm_fatal(
get_name(),
"Failed to Grab foo_input_sequencer from Config DB"
)
end
end

gChips DV Google Confidential pg 51 / 93


Google Proprietary & Confidential

...

endfunction: connect_phase

Environment
The DUT environment component, like the agent component, is just a hierarchical element. The environment
only builds child components: agents, sub-environments, scoreboard, and virtual sequencer. And connects the
scoreboard analysis exports to agent analysis ports.
The environment implements no other phase methods.
The environment extends the uvm_env component and registers with the factory using the uvm_component_utils
macro.
The environment should be named with a dut name prefix and '_env' suffix.
Instance variable names should match the name of the class being instanced (where possible - when there is
more than one then choose names appropriately.)
Instance name of the environment config should be m_cfg_h. Instance name of the virtual sequencer should be
m_virtual_sequencer_h. Using these names keeps the interface consistent.

A typical virtual sequencer component looks like the following (for a DUT named "foo"):
//--------------------------------------------------------------------------------------------------
// FOO Environment
//--------------------------------------------------------------------------------------------------
class foo_env extends uvm_env;

`uvm_component_utils(foo_env)

typedef uvm_reg_predictor #(foo_csr_item_t) foo_csr_reg_predictor_t;


typedef foo_scoreboard foo_scoreboard_t;
typedef foo_virtual_sequencer foo_virtual_sequencer_t;

// Environment CFG
foo_cfg m_cfg_h;

// Virtual Sequencer
foo_virtual_sequencer_t m_virtual_sequencer_h;

// Register Model (RAL)


ral_model m_ral_model_h;
foo_csr_reg_predictor_t m_foo_csr_reg_predictor_h;

// Member Agents
clock_agent m_clock_agent_h;
reset_agent m_reset_agent_h;
foo_csr_agent_t m_foo_csr_agent_h;
...
foo_input_agent_t m_foo_input_agent_h;
...

gChips DV Google Confidential pg 52 / 93


Google Proprietary & Confidential

// Scoreboard
foo_scoreboard_t m_foo_scoreboard_h;

extern function new(string name, uvm_component parent);


extern virtual function void build_phase(uvm_phase phase);
extern virtual function void connect_phase(uvm_phase phase);

endclass: foo_env

//------------------------------------------------------------------------------
function foo_env::new(string name, uvm_component parent);
super.new(name, parent);
endfunction : new

//------------------------------------------------------------------------------
function void foo_env::build_phase(uvm_phase phase);

super.build_phase(phase);

if(!uvm_config_db#(foo_cfg)::get(this, "", "foo_cfg", m_cfg_h)) begin


`uvm_fatal(get_name(), "Failed to Grab foo_cfg from Config DB")
end

// Virtual Sequencer
m_virtual_sequencer_h = foo_virtual_sequencer_t::type_id::create(
"m_virtual_sequencer_h", this
);

// Register Model (RAL)


m_foo_csr_reg_predictor_h = foo_csr_reg_predictor_t::type_id::create(
"m_foo_csr_reg_predictor_h",
this
);

//
// Member Agents
//
// Clock/Reset/CSR Agents
m_clock_agent_h = clock_agent::type_id::create("m_clock_agent_h", this);
m_reset_agent_h = reset_agent::type_id::create("m_reset_agent_h", this);
m_foo_csr_agent_h = foo_csr_agent_t::type_id::create(
"m_foo_csr_agent_h", this
);
...
// Foo Input Agent
m_foo_input_agent_h = foo_input_agent_t::type_id::create(
"m_foo_input_agent_h", this
);

gChips DV Google Confidential pg 53 / 93


Google Proprietary & Confidential

// Scoreboard
m_foo_scoreboard_h = foo_scoreboard_t::type_id::create(
"m_foo_scoreboard_h", this
);

endfunction : build_phase

//------------------------------------------------------------------------------
function void foo_env::connect_phase(uvm_phase phase);

super.connect_phase(phase);

//
// Connect RAL
//
m_ral_model_h = m_cfg_h.m_ral_model_h;
if ( m_ral_model_h.get_parent() == null ) begin // if top level
m_ral_model_h.reg_map.set_sequencer(
.sequencer( m_foo_csr_agent_h.m_sequencer ),
.adapter( m_foo_csr_agent_h.m_reg_adapter )
);
end
m_ral_model_h.reg_map.set_auto_predict( .on( 0 ) );
m_foo_csr_reg_predictor_h.map = m_ral_model_h.reg_map;
m_foo_csr_reg_predictor_h.adapter = m_foo_csr_agent_h.m_reg_adapter;
m_foo_csr_agent_h.m_monitor_analysis_port.connect(m_foo_csr_reg_predictor_h.bus_in);

//
// Connect Scoreboard
//
// CSR Agent
m_foo_csr_agent_h.m_monitor_analysis_port.connect(
m_foo_scoreboard_h.m_foo_csr_analysis_export
);
...
// Input Agent
m_input_agent_h.m_monitor_analysis_port.connect(
m_foo_scoreboard_h.m_foo_input_analysis_export
);
...
endfunction : connect_phase

Test
The test component is launched by the tb top module when run_test() is called. The test that is run is determined
by the UVM_TESTNAME pluarg passed to the simulator.
The test configures the environment and then launches a test sequence.

gChips DV Google Confidential pg 54 / 93


Google Proprietary & Confidential

We distinguish between a test - that which configures and constrains the environment; and the test sequence -
the ordered set of things that the test case does.

Base Test
The base test creates and initializes all of the configuration objects available in the environment.
Every base test must include a run_vseq_from_plusarg task. This task takes the TEST_SEQUENCE plusarg
input - and launches the virtual sequence so named on the environment's virtual sequencer. It is this flexibility
that allows us to separate the test -- the environment configuration / factory overrides, from the test sequence --
the ordered set of things that the test does. This separation allows us to build a library of test sequences and run
those sequences against a set of factory override constraints (and / or environment configurations).
The base test extends the uvm_test component and registers with the factory using the uvm_component_utils
macro.
The base test should be named with a dut name prefix and '_base_test' suffix.
The base test includes a handle to the uvm_factory, named m_factory, used by the run from plusarg task and by
derived tests.
The base test includes a handle to the uvm command line processor - for plusarg handling.
The base test may include objects to produce delays (like the below referenced 'delay object') and to measure
time (like the below referenced 'time object.')
The base test will include a default time for the bench timeout - named m_tb_timeout_ms. (Note the use of the
_ms suffix to indicate milliseconds.)
The base test will include a default drain time - time to wait after the test sequence completes before ending the
test.
The base test will include a heartbeat time - a time interval wherein, on each expiration, the test will report status.
This is useful to indicate run progress when the test is run with NONE level verbosity.
The base test may also include a wall time timeout - triggered by the heartbeat interval.
The base test will include a handle to the DUT configuration object, named m_cfg_obj_h, created in the build
method.
The base test will include a handle to the RAL model (if RAL is used) - grabbed from the configuration object.
The base test will include handles to all of the agent configuration objects, for all agents instanced in the
environment. These objects will be created in the build phase function, members set to values that make sense
for the DUT environment, and then set into the config database.
The base test will include a handle to the DUT environment, named m_env_h, which it will create in the build
phase function.
The base test will include a handle to the virtual sequencer, named m_virtual_sequencer_h, which it will grab
from the DUT environment in the connect phase function.

A typical base test component looks like the following (for a DUT named "foo"):
//--------------------------------------------------------------------------------------------------
// FOO Base Test
//--------------------------------------------------------------------------------------------------
class foo_base_test extends uvm_test;
`uvm_component_utils(foo_base_test)

gChips DV Google Confidential pg 55 / 93


Google Proprietary & Confidential

// UVM Factory
const uvm_factory m_uvm_factory;

// UVM Commandline Processor -- Plusargs


uvm_cmdline_processor m_uvm_cmdline_processor_h;

// Delay Interface
delay_obj#("delay_vif") m_delay_obj_h;

// Time Markers
time_wall_obj m_time_wall_obj_h;
...

// Configuration
int unsigned m_tb_timeout_ms;
int unsigned m_tb_timeout_wall_secs;
int unsigned m_tb_drain_time_us;
int unsigned m_tb_heartbeat_time_us;

ral_model m_ral_model_h;
foo_cfg m_cfg_h;

clock_cfg m_clock_cfg_h;
reset_cfg m_reset_cfg_h;
...
foo_csr_cfg_t m_foo_csr_cfg_h;

foo_input_cfg_t m_foo_input_cfg_h;
...

// DUT Environment
foo_env m_env_h;

// Environment Virtual Sequencer


foo_virtual_sequencer m_virtual_sequencer_h; // Virtual Sequencer Handle

extern function new(string name, uvm_component parent);


extern virtual function void build_phase(uvm_phase phase);
extern virtual function void connect_phase( uvm_phase phase );
extern virtual task run_phase(uvm_phase phase);

// Helper Methods
extern protected virtual task run_vseq_from_plusarg();

endclass: foo_base_test

//------------------------------------------------------------------------------
function foo_base_test::new(string name, uvm_component parent);
super.new(name,parent);

gChips DV Google Confidential pg 56 / 93


Google Proprietary & Confidential

m_uvm_factory = uvm_factory::get();

m_uvm_cmdline_processor_h = uvm_cmdline_processor::get_inst();

...

// Default Configurations -- can override in build_phase after construction


m_tb_timeout_ms = 250;
...
m_tb_drain_time_us = 2;
m_tb_heartbeat_time_us = 100;
endfunction : new

//------------------------------------------------------------------------------
function void foo_base_test::build_phase(uvm_phase phase);
bit hold_payload;
stimulus_gen_pkg::data_mode_e stimulus_gen_mode;

// Configure UVM Timeout


// NOTE: Ahead of super build_phase call so can override with +UVM_TIMEOUT
begin
time timeout;
timeout = m_tb_timeout_ms * 1ms;
uvm_top.set_timeout(timeout, 1);
end

super.build_phase(phase);

...

// Grab User Runtime Plusargs


// Hold Data on Bus (for debug)
begin
string hold_payload_str;
hold_payload = 1'b0; // Default to not hold
if (m_uvm_cmdline_processor_h.get_arg_value("+DEBUG_HOLD_PAYLOAD=", hold_payload_str)) begin
hold_payload = int'(hold_payload_str);
`uvm_info(get_name(), $sformatf("DEBUG_HOLD_PAYLOAD = %0d", hold_payload), UVM_MEDIUM)
end
end

begin
string stimulus_gen_mode_str;
stimulus_gen_mode = stimulus_gen_pkg::RAND; // Default to RAND
if (
m_uvm_cmdline_processor_h.get_arg_value("+STIMULUS_GEN_MODE=", stimulus_gen_mode_str)
) begin
`uvm_info(
get_name(),
$sformatf("STIMULUS_GEN_MODE = %s", stimulus_gen_mode_str),

gChips DV Google Confidential pg 57 / 93


Google Proprietary & Confidential

UVM_MEDIUM
)
case(stimulus_gen_mode_str)
"INCR": begin
stimulus_gen_mode = stimulus_gen_pkg::INCR;
end
"RAND": begin
stimulus_gen_mode = stimulus_gen_pkg::RAND;
end
default: begin
`uvm_fatal(
get_name(),
$sformatf("Invalid STIMULUS_GEN_MODE: %s", stimulus_gen_mode_str)
)
end
endcase
end
end

...

// Build Block Register Model


...
m_ral_model_h = ral_model::type_id::create("m_ral_model_h", this);
m_ral_model_h.build();
// Make Register Model available to other objects / components
begin
typedef uvm_config_db#(ral_model) config_db_ral_model_t;
config_db_ral_model_t::set(
null, "*", "m_ral_model_h", m_ral_model_h
);
end
...

// Configure FOO Environment


m_cfg_h = foo_cfg::type_id::create("m_cfg_h", this);
m_cfg_h.m_ral_model_h = m_ral_model_h;
m_cfg_h.m_stimulus_mode = stimulus_gen_mode;
uvm_config_db#(foo_cfg)::set(this, "*", "foo_cfg", m_cfg_h);

// Configure Clock Agent


m_clock_cfg_h = clock_cfg::type_id::create("m_clock_cfg_h", this);
m_clock_cfg_h.m_timeunit = Clock_timeunit_ps;
m_clock_cfg_h.m_uvm_active_passive_h = UVM_ACTIVE;
uvm_config_db#(clock_cfg)::set(this, "*", "clock_cfg", m_clock_cfg_h);

// Configure Reset Agent


m_reset_cfg_h = reset_cfg::type_id::create("m_reset_cfg_h", this);
m_reset_cfg_h.m_uvm_active_passive_h = UVM_ACTIVE;
m_reset_cfg_h.m_reset_active = RESET_ACTIVE_LOW;

gChips DV Google Confidential pg 58 / 93


Google Proprietary & Confidential

m_reset_cfg_h.m_reset_assert = RESET_ASSERT_ASYNC;
m_reset_cfg_h.m_reset_deassert = RESET_DEASSERT_SYNC;
uvm_config_db#(reset_cfg)::set(this, "*", "reset_cfg", m_reset_cfg_h);

...

// Configure FOO CSR Agent


m_foo_csr_cfg_h = foo_csr_cfg_t::type_id::create("m_foo_csr_cfg_h", this);
m_foo_csr_cfg_h.m_uvm_active_passive_h = UVM_ACTIVE;
m_foo_csr_cfg_h.m_rsp_timeout_cycles = READY_TIMEOUT_CYCLES_DEFAULT;
m_foo_csr_cfg_h.m_hold_payload = hold_payload;
uvm_config_db#(foo_csr_cfg_t)::set(this, "*", "foo_csr_cfg", m_foo_csr_cfg_h);

...

// Configure FOO Input Agent


m_foo_input_cfg_h = foo_input_cfg_t::type_id::create(
"m_foo_input_cfg_h",
this
);
m_foo_input_cfg_h.m_uvm_active_passive_h = UVM_ACTIVE;
m_foo_input_cfg_h.m_timeout_cycles = INPUT_TIMEOUT_CYCLES_DEFAULT;
m_foo_input_cfg_h.m_hold_payload = hold_payload;
uvm_config_db#(foo_input_cfg_t)::set(
this,
"*foo_input*",
"foo_rv_req_cfg",
m_foo_input_cfg_h
);

...

// Create Environment
m_env_h = foo_env::type_id::create("m_env_h", this);

endfunction : build_phase

//------------------------------------------------------------------------------
function void foo_base_test::connect_phase( uvm_phase phase );
m_virtual_sequencer_h = m_env_h.m_virtual_sequencer_h;
endfunction: connect_phase

//------------------------------------------------------------------------------
task foo_base_test::run_phase(uvm_phase phase);

time drain_time;
drain_time = m_tb_drain_time_us * 1us;

// Set the amount of time to wait in this phase after all objections have been dropped.
phase.phase_done.set_drain_time(this, drain_time);

gChips DV Google Confidential pg 59 / 93


Google Proprietary & Confidential

fork
// Periodically display the time to indicate forward progress.
// List all active objections - helps when test does not make forward progress.
begin
forever begin
m_delay_obj_h.delay_time(m_tb_heartbeat_time_us, "us"); // #(heartbeat_us*1us)
`uvm_info(
get_full_name(),
$sformatf("run_phase() heartbeat: elapsed wall time %s",
m_time_wall_obj_h.seconds_to_string(m_time_wall_obj_h.elapsed())
),
UVM_NONE
)

if (uvm_report_enabled(UVM_LOW)) begin
process proc = process::self();
string proc_rng_state;
if (proc != null) begin
proc_rng_state = proc.get_randstate();
end
phase.phase_done.display_objections();
if (proc != null) begin
proc.set_randstate(proc_rng_state);
end
end
if (m_time_wall_obj_h.elapsed() >= m_tb_timeout_wall_secs) begin
`uvm_fatal(
get_name(),
$sformatf(
"run_phase() - Wall time timeout- sim has exceeded %0d seconds",
m_tb_timeout_wall_secs
)
)
end
end
end
join_none

phase.raise_objection(this, "dut_test_base run_phase");

super.run_phase(phase);
run_vseq_from_plusarg();

phase.drop_objection(this, "dut_test_base run_phase");

endtask: run_phase

//------------------------------------------------------------------------------
task foo_base_test::run_vseq_from_plusarg();

gChips DV Google Confidential pg 60 / 93


Google Proprietary & Confidential

uvm_object tmp;
string vseq_testname;
uvm_sequence#(uvm_sequence_item) test_plusarg_vseq_h; // Typed Sequence

if (!m_uvm_cmdline_processor_h.get_arg_value("+TEST_SEQUENCE=", vseq_testname)) begin


`uvm_fatal( get_type_name(), "Required plusarg +TEST_SEQUENCE not defined" )
end

tmp = m_uvm_factory.create_object_by_name(
vseq_testname, m_virtual_sequencer_h.get_full_name(), "test_plusarg_vseq_h"
);

if(!$cast( test_plusarg_vseq_h, tmp)) begin


`uvm_fatal(get_type_name(), $sformatf("Cast Failed - %s is not a uvm_sequence",vseq_testname))
end

if(test_plusarg_vseq_h == null) begin


`uvm_fatal(
get_type_name(),
$sformatf("Test vseq is null; This test probably doesn't exist: %s", vseq_testname)
)
end

if (!test_plusarg_vseq_h.randomize()) begin
`uvm_fatal(get_type_name(), "Randomize Failed for the initial vseq")
end

`uvm_info(get_type_name(), $sformatf("Running test sequence: %s", vseq_testname), UVM_LOW)


test_plusarg_vseq_h.start(m_virtual_sequencer_h);
`uvm_info(get_type_name(), "Test sequence completed", UVM_HIGH)

endtask: run_vseq_from_plusarg

Derived Tests
These tests extend from the base test. They override the base class configuration members with different values
and / or provide factory overrides that affect how the environment items / sequence items behave.
Always name derived tests based on what is being configured / factory overridden. This enables easy test / test
sequence combination selection when a regression list is being assembled.
Note that test derived tests do not change which test sequence is run. The test sequence is always selected by
the TEST_SEQUENCE plusarg.
A derived test does one or more of the following:
1. factory override of items / sequences (commonly just overrides of constraints)
2. change the configuration of the config objects instanced in the base test
Both of these are accomplished in the build phase function.
Note that we don't need to 'set' the config into the config db after updating a member - since the handle of the
configuration object has already been placed in the database.

gChips DV Google Confidential pg 61 / 93


Google Proprietary & Confidential

The derived test extends the uvm_test component and registers with the factory using the uvm_component_utils
macro.
The derived test should be named with a dut name prefix and '_test' suffix. The name of the test should
correspond with the action of the test to indicate the configuration and/or override.

A typical derived test component looks like the following (for a DUT named "foo"):
//--------------------------------------------------------------------------------------------------
// FOO Special Test
//--------------------------------------------------------------------------------------------------
class foo_special_test extends foo_base_test;
`uvm_component_utils( foo_special_test )

function new(string name, uvm_component parent);


super.new(name,parent);
endfunction : new

extern virtual function void build_phase(uvm_phase phase);

endclass: foo_special_test

//------------------------------------------------------------------------------
function void foo_special_test::build_phase(uvm_phase phase);
super.build_phase(phase);

// Change the Config - enable the output agent 'debug mode'


m_foo_output_cfg_h.m_debug_mode = foo_output_pkg::DEBUG_MODE_ON;

// Factory Override "Input" Agent's Item with the special override


m_uvm_factory.set_type_override_by_type(
foo_input_item::get_type(),
foo_input_override_special_item::get_type()
);

endfunction : build_phase

Test List
The test list is a compilation aid. It "tick includes" all of the tests that are available with this environment.

The test list follows the following structure (for a DUT named "foo"):

`include "foo_base_test.svh"
...
`include "foo_special_test.svh"
...

gChips DV Google Confidential pg 62 / 93


Google Proprietary & Confidential

Virtual Sequences
The virtual sequences are used to generate sequenced / ordered traffic across one or more independent agents.

Base Virtual Sequence


The base virtual sequence is a common base class for all virtual sequences.
The base virtual sequence extends the uvm_sequence object specialized to the uvm_sequence_item type and
registers with the factory using the uvm_object_utils macro.
The base virtual sequence should be named with a dut name prefix and '_base_vseq' suffix.
The base virtual sequence, like all sequences, uses the uvm_object_utils macro to register with the factory.
The base virtual sequence includes the macro call to declare the typed virtual sequencer for the DUT
environment - uvm_declare_p_sequencer. And it includes a handle to the uvm_factory -- called m_uvm_factory,
grabbed on construction.
If present in the environment - the base virtual sequence will also include a protected handle to the delay object
along with protected wrapper methods.
In general, the base virtual sequence will include additional protected members and methods to support activities
that are common to both test virtual sequences and virtual agents. For example, handles to memory model(s) - if
used by both tests and virtual agents, will be grabbed in pre_body() in this class.

The base virtual sequence follows the following structure (for a DUT named "foo"):
//--------------------------------------------------------------------------------------------------
// FOO Base Virtual Sequence
//--------------------------------------------------------------------------------------------------
class foo_base_vseq extends uvm_sequence#(uvm_sequence_item);
`uvm_object_utils( foo_base_vseq )
`uvm_declare_p_sequencer( foo_virtual_sequencer )

// UVM Factory
const uvm_factory m_uvm_factory;

// CSR Config State


protected ral_model m_ral_model_h;

// Memory Model
protected foo_mem_model_obj_t m_foo_mem_model_obj_h;

// Delay Interface
protected delay_obj#("delay_vif") m_delay_obj_h;

// Constructor
extern function new(string name = "");

// Body
extern virtual task pre_body();
extern virtual task body();

gChips DV Google Confidential pg 63 / 93


Google Proprietary & Confidential

// Helper Methods
extern protected virtual task delay_time(int unsigned timeval, string timeunitstr = "ns");
extern protected virtual task delay_cycles(
int unsigned cycles,
int unsigned timeout = 1,
string timeoutunit = "ms"
);

endclass: foo_base_vseq

//------------------------------------------------------------------------------
function foo_base_vseq::new(string name = "");
super.new( name );
m_uvm_factory = uvm_factory::get();
endfunction: new

//------------------------------------------------------------------------------
task foo_base_vseq::pre_body();
super.pre_body();

// Get Handle to Memory Model


begin
typedef uvm_config_db#(foo_mem_model_obj_t) config_db_foo_mem_t;
if (!config_db_foo_mem_t::get(
null, "*", "m_foo_mem_model_obj_h", m_foo_mem_model_obj_h)
) begin
`uvm_fatal(get_name(), "body(): Failed to Grab foo_mem_model_obj from Config DB")
end
end
endtask: pre_body

//------------------------------------------------------------------------------
task foo_base_vseq::body();
// NOTE: no super.body() here .. since UVM uses this to check that body has been implemented

begin
typedef uvm_config_db#(ral_model) config_db_ral_model_t;
if (!config_db_ral_model_t::get(
null, "*", "m_ral_model_h", m_ral_model_h)
) begin
`uvm_fatal(get_name(), "Failed to Grab ral_model from Config DB")
end
end

m_delay_obj_h = delay_obj#("delay_vif")::create_singleton("m_delay_obj_h");

endtask: body

//------------------------------------------------------------------------------
task foo_base_vseq::delay_time(int unsigned timeval, string timeunitstr = "ns");

gChips DV Google Confidential pg 64 / 93


Google Proprietary & Confidential

if (m_delay_obj_h == null) begin


m_delay_obj_h = delay_obj#("delay_vif")::create_singleton("m_delay_obj_h");
end

m_delay_obj_h.delay_time(timeval, timeunitstr);

endtask: delay_time

//------------------------------------------------------------------------------
task foo_base_vseq::delay_cycles(
int unsigned cycles,
int unsigned timeout = 1,
string timeoutunit = "ms"
);
if (m_delay_obj_h == null) begin
m_delay_obj_h = delay_obj#("delay_vif")::create_singleton("m_delay_obj_h");
end

m_delay_obj_h.delay_cycles(cycles, timeout, timeoutunit);

endtask: delay_cycles

Virtual Agent
A virtual agent is a virtual sequence that combines and / or organizes traffic from multiple independent agents to
produce a single logical protocol interface.
A virtual agent is just a concept - its implemented like any other virtual sequence. But since most virtual
sequences are tests we make this distinction.
These are the sequences that implement a protocol interface.
Example: slave interface that is made up of several instances of 'ready-valid' agents. The DUT initiates transfers
on one agent, the slave responds on another; and these transfers are unordered and non-blocking.
The virtual agent sequence extends the dut named base virtual sequence and registers with the factory using the
uvm_object_utils macro.
The virtual agent should be named with a dut name prefix and '_vseq' suffix. The name should correspond to the
protocol being implemented.
Since a virtual agent is, like all virtual sequences, randomized before being started a virtual agent can (and will
often) have rand members that control how the sequence should behave.

The virtual agent sequence follows the following structure (for a DUT named "foo"; this example shows a request
/ response memory interface):
//--------------------------------------------------------------------------------------------------
// DUT Fetch Request / Response Virtual Sequence
//--------------------------------------------------------------------------------------------------
class foo_rv_fetch_req_rsp_vseq extends foo_base_vseq;

`uvm_object_utils(foo_rv_fetch_req_rsp_vseq)

gChips DV Google Confidential pg 65 / 93


Google Proprietary & Confidential

rand req_delay_kind_t m_req_delay_kind;


rand rsp_delay_kind_t m_rsp_delay_kind;

protected mailbox #(foo_rv_fetch_req_nominal_seq_t)


m_foo_rv_fetch_req_nominal_seq_mbox_h[FOO_TRANSACTION_IDS];

protected mailbox #(bit) m_rv_fetch_req_entries_mbox_h;

extern function new(string name="");


extern task body();
protected task req_thread();
protected task rsp_thread();
protected function int unsigned rand_id();

endclass: foo_rv_fetch_req_rsp_vseq

//------------------------------------------------------------------------------
function foo_rv_fetch_req_rsp_vseq::new(string name="");
super.new( name );
foreach(m_foo_rv_fetch_req_nominal_seq_mbox_h[id]) begin
m_foo_rv_fetch_req_nominal_seq_mbox_h[id] = new(0); // unbounded mbox
end
m_rv_fetch_req_entries_mbox_h = new(0); // unbounded mbox
endfunction: new

//------------------------------------------------------------------------------
task foo_rv_fetch_req_rsp_vseq::body();
super.body();

// Two Threads - Request Thread / Response Thread


fork
begin
req_thread();
end

begin
rsp_thread();
end
join

endtask: body

//------------------------------------------------------------------------------
task foo_rv_fetch_req_rsp_vseq::req_thread();
// Request Thread
// starts request sequence, on request - mailboxes request info, relaunches request sequence
// This thread cannot block! Don't want to drop any requests
forever begin
foo_rv_fetch_req_nominal_seq_t foo_rv_fetch_req_nominal_seq_h;
int unsigned req_transaction_id; // ID of transaction

gChips DV Google Confidential pg 66 / 93


Google Proprietary & Confidential

foo_rv_fetch_req_nominal_seq_h = foo_rv_fetch_req_nominal_seq_t::type_id::create(
"foo_rv_fetch_req_nominal_seq_h", null, get_full_name()
);

if (!foo_rv_fetch_req_nominal_seq_h.randomize() with {
if (m_req_delay_kind == REQ_DELAY_ZERO) {
m_valid2ready_delay_cycles == 0;
}
...
rand rsp_delay_kind_t m_rsp_delay_kind;
}) begin
`uvm_fatal(get_name(), "Randomize Failed")
end
foo_rv_fetch_req_nominal_seq_h.start(p_sequencer.m_foo_rv_fetch_req_sequencer_h);
req_transaction_id = foo_rv_fetch_req_nominal_seq_h.m_transaction_id;

`uvm_info(
get_name(),
$sformatf("Req ID: 'h%0x", req_transaction_id),
UVM_MEDIUM
)

`uvm_info(
get_name(),
$sformatf("Put Side:\n%s", foo_rv_fetch_req_nominal_seq_h.sprint()),
UVM_HIGH
)

if (!m_foo_rv_fetch_req_nominal_seq_mbox_h[req_transaction_id].try_put(
foo_rv_fetch_req_nominal_seq_h
)) begin
`uvm_fatal(
get_name(),
$sformatf(
"Failed to put into m_foo_rv_fetch_req_nominal_seq_mbox_h[%0d]",
req_transaction_id
)
);
end

if (m_foo_rv_fetch_req_nominal_seq_mbox_h[req_transaction_id].num() > 1) begin


`uvm_fatal(
get_name(),
$sformatf(
"m_foo_rv_fetch_req_nominal_seq_mbox_h[%0d] - overflow; > 1 req at this id",
req_transaction_id
)
);
end

gChips DV Google Confidential pg 67 / 93


Google Proprietary & Confidential

if (!m_rv_fetch_req_entries_mbox_h.try_put(1'b1)) begin
`uvm_fatal(
get_name(),
$sformatf(
"Failed to put into m_rv_fetch_req_entries_mbox_h"
)
);
end
end
endtask: req_thread

//------------------------------------------------------------------------------
task foo_rv_fetch_req_rsp_vseq::rsp_thread();
// Response Thread - wait for request, choose transaction id channel, respond to that channel
typedef foo_trans_id#(FOO_TRANSACTION_IDS) trans_id_t;
trans_id_t foo_trans_id_h;
forever begin
bit tmp;
int unsigned response_id;
foo_rv_fetch_rsp_nominal_seq_t foo_rv_fetch_rsp_nominal_seq_h;

// Wait for valid entry


m_rv_fetch_req_entries_mbox_h.get(tmp);

// Randomly Choose Transaction ID from Available IDs


response_id = rand_id();

// Get Request Transaction -- Return Response Transaction


begin
foo_rv_fetch_req_nominal_seq_t foo_rv_fetch_req_nominal_seq_h;
foo_rv_fetch_rsp_nominal_seq_t foo_rv_fetch_rsp_nominal_seq_h;

bit [FOO_PHYSICAL_ADDR_WDTH-1:0] req_physical_address;


rv_fetch_mem_bytes_e req_transaction_size;
int unsigned req_rsp_transaction_id;
bit [7:0][63:0] rsp_data_word; // 8 sets of 8 bytes

// If Fetch Data Width isn't 256- let me know; since I hardcoded the packed static arrays
if (FOO_FETCH_DATA_WDTH != 256) begin
`uvm_fatal(get_name(),
$sformatf("FOO_FETCH_DATA_WDTH not set to 256; set to 'd%0d", FOO_FETCH_DATA_WDTH)
)
end

if (!m_foo_rv_fetch_req_nominal_seq_mbox_h[response_id].
try_get(foo_rv_fetch_req_nominal_seq_h)
) begin
`uvm_fatal(get_name(),
"Invalid ID - something bad happened with randomize of the foo_trans_id_h object"

gChips DV Google Confidential pg 68 / 93


Google Proprietary & Confidential

)
end

// Grab the Elements of the Request


req_physical_address =
foo_rv_fetch_req_nominal_seq_h.m_payload_obj.m_payload.m_physical_address;
req_transaction_size =
foo_rv_fetch_req_nominal_seq_h.m_payload_obj.m_payload.m_transaction_size;
req_rsp_transaction_id =
foo_rv_fetch_req_nominal_seq_h.m_payload_obj.m_payload.m_transaction_id;

`uvm_info(
get_name(),
$sformatf("Rsp ID: 'h%0x", req_rsp_transaction_id),
UVM_MEDIUM
)

`uvm_info(get_name(),
$sformatf("Get Side:\n%s", foo_rv_fetch_req_nominal_seq_h.sprint()),
UVM_HIGH
)

// Get Descriptor Words(s)


rsp_data_word = 'd0;
for (int ii=0; ii < 2**req_transaction_size; ii+=1) begin
rsp_data_word[ii] = m_foo_mem_model_obj_h.get_data_word_by_addr(
req_physical_address+(ii*8)
);
end

// Build the Response Sequence


foo_rv_fetch_rsp_nominal_seq_h = foo_rv_fetch_rsp_nominal_seq_t::type_id::create(
"foo_rv_fetch_rsp_nominal_seq_h", null, get_full_name()
);

if (!foo_rv_fetch_rsp_nominal_seq_h.randomize() with {
m_payload_obj.m_payload.m_transaction_id == local::req_rsp_transaction_id;
if (req_transaction_size == foo_rv_fetch_req_pkg::BYTES_64) {
m_payload_obj.m_payload.m_data_transfer_words == 2;
}
else {
m_payload_obj.m_payload.m_data_transfer_words == 1;
}
foreach(m_payload_obj.m_payload.m_data[ii]) {
m_payload_obj.m_payload.m_data[ii] == local::rsp_data_word[ii];
}
if (m_rsp_delay_kind == REQ_DELAY_ZERO) {
m_ready2valid_delay_cycles == 0;
}
...

gChips DV Google Confidential pg 69 / 93


Google Proprietary & Confidential

}) begin
`uvm_fatal(get_name(), "Randomize Failed")
end

// Send the Response


`uvm_info(get_name(),
$sformatf("Sending:\n%s", foo_rv_fetch_rsp_nominal_seq_h.sprint()), UVM_HIGH
);

foo_rv_fetch_rsp_nominal_seq_h.start(p_sequencer.m_foo_rv_fetch_rsp_sequencer_h);

end
end
endtask: rsp_thread

//------------------------------------------------------------------------------
function int unsigned foo_rv_fetch_req_rsp_vseq::rand_id();
typedef rand_choose_from_list_obj#(int unsigned) trans_id_t;
int unsigned valid_list[$];
trans_id_t foo_trans_id_h;

foo_trans_id_h = trans_id_t::type_id::create(
"foo_trans_id_h", null, get_full_name()
);

valid_list.delete();
// Check Each Mailbox - if it has data then mark it as valid
foreach(m_foo_rv_fetch_req_nominal_seq_mbox_h[id]) begin
if (m_foo_rv_fetch_req_nominal_seq_mbox_h[id].num() > 0) begin
valid_list.push_back(id);
end
end

foo_trans_id_h.set_list_ref(valid_list);

if (!foo_trans_id_h.randomize()) begin
`uvm_fatal(get_name(), "write_pull(): Randomize Failed")
end

`uvm_info(
get_name(),
$sformatf(
"write_pull(): Valid IDs: %s",
foo_trans_id_h.sprint_valid()
),
UVM_HIGH
)

return(foo_trans_id_h.m_selected);

gChips DV Google Confidential pg 70 / 93


Google Proprietary & Confidential

endfunction: rand_id

Test Base Virtual Sequence


The test base virtual sequence is a collection of members and wrapper methods to launch the child virtual
sequences - both active and reactive.
Common active methods include:
● starting (and stopping) the clock sequence
● firing the reset sequence
● launching sets of CSR configuration sequences to configure the DUT (for things like configuring and
clearing interrupts; configuring the DUT into various mode); these csr methods are often offered with
variants to support both random and directed testing
● launching sequences to initialize interfaces
Common reactive methods include:
● launching reactive memory sequences (like the virtual agent memory we described earlier in this
document)
● launching interrupt reactive agents, along with methods to automate the logging and clearing of the
interrupt)
All test sequences should be named: dut_test_* to differentiate test virtual sequences from those that are virtual
agents.
The pre_body() method can optionally be used to create support objects (like coverage collection singleton(s))
ahead of the body task.
The body task inspects control members to launch reactive sequences (and methods).
The body task starts reactive sequences, starts the clock(s), triggers the reset(s) and then ends. (The derived
test sequences call super.body() and have the test defined beyond the reset)
The body of the base virtual sequence often has additional common tasks launched post reset - but with a class
member state variable that derived sequences can use to turn off the launch.
The post_body() task commonly calls any end of test methods in the virtual agents.
Helper methods should be protected - as they should only be called by this base those sequences derived from
the base.

The test base virtual sequence follows the following structure (for a DUT named "foo"):

//--------------------------------------------------------------------------------------------------
// FOO Test Base Virtual Sequence
//--------------------------------------------------------------------------------------------------
class foo_test_base_vseq extends foo_base_vseq;
`uvm_object_utils( foo_test_base_vseq )

// Test Config
int unsigned m_test_seq_drain_time_us;
rand int unsigned m_num_init_stripes; // Number of Stripes to start with "initial stripes"
foo_reg_ctrl_obj m_reg_foo_ctrl_obj_h;
foo_reg_stripes_obj m_reg_foo_stripes_obj_h;

// Config Coverage

gChips DV Google Confidential pg 71 / 93


Google Proprietary & Confidential

foo_reg_cov_obj m_reg_cov_obj_h;

// EOT Monitor
foo_eot_obj m_foo_eot_obj_h;

// IRQ Monitor State


bit m_int_enable_all;
bit m_irq_handler_stripe_en[FOO_STRIPES];
bit m_irq_handler_en;

bit m_irq[FOO_STRIPES];
event m_irq_event[FOO_STRIPES];

event m_irq_triggered_event[FOO_STRIPES];
foo_reg_stripe_obj m_eof_foo_reg_stripe_obj_h;
event m_eof_foo_reg_stripe_obj_event;

// Test Clock Gate State


bit m_test_cg_rndm;
bit m_test_cg_pin;

// Fetch REQ/RSP Interface VSEQ


foo_rv_fetch_req_rsp_vseq m_foo_rv_fetch_req_rsp_vseq_h;

// Constraints
// Three Types of Constraint - valid, legal, typical
// Valid - need to be true or agent won't work correctly (things like positive values for delays)
// Legal - need to be true to obey protocol; can override to create protocol invalid transfers
// Typical - overridable constraint; use to set some reasonable bounds
constraint valid_base;
constraint legal_base {}
constraint typical_base {}

extern function new(string name = "");

extern virtual task pre_body();


extern virtual task body();
extern virtual task post_body();

// Helper Methods for Test Author


extern protected virtual task start_rv_fetch_req_rsp();
extern protected virtual task start_clock(int unsigned halfperiod = 'd1_111, bit init = 1'b0);
extern protected virtual task stop_clock(bit init = 1'b0);
extern protected virtual task reset();
extern protected virtual task monitor_irq();
extern protected virtual task irq_handler();
extern protected virtual task irq_handling(ref int unsigned stripe);
extern protected virtual task int_enable(int unsigned stripe, bit enable);
extern protected virtual task int_enable_all(bit enable);
extern protected virtual task int_mask(int unsigned stripe, bit mask);

gChips DV Google Confidential pg 72 / 93


Google Proprietary & Confidential

extern protected virtual task int_mask_all(bit mask);


extern protected virtual task int_test(int unsigned stripe, bit test);
extern protected virtual task int_test_strobe(int unsigned stripe);
extern protected virtual task int_isr_read(
int unsigned stripe,
bit isr_ovf,
output bit status
);
extern protected virtual task int_isr_clear(
int unsigned stripe,
bit isr_ovf,
bit clear = 1'b1 // W1C
);
extern protected virtual task int_isr_read_status(
int unsigned stripe,
bit isr_ovf,
ref irq_status_t irq_status
);
extern protected virtual task int_isr_clear_status(
int unsigned stripe,
bit isr_ovf,
ref irq_status_t irq_status
);
extern protected virtual task int_isr_check_status(
int unsigned stripe,
bit isr_ovf,
ref irq_status_t irq_status
);
extern protected virtual task build_regs();
extern protected virtual task config_dut();

extern protected virtual task test_cg_en(bit rndm = 0, bit enable = 0);

endclass: foo_test_base_vseq

//------------------------------------------------------------------------------
constraint foo_test_base_vseq::valid_base {
m_num_init_stripes <= FOO_STRIPES; // Max Number of initial stripes to launch
soft m_num_init_stripes > 0; // At least one stripe
}

//------------------------------------------------------------------------------
function foo_test_base_vseq::new(string name = "");
super.new( name );
m_test_seq_drain_time_us = 5; // default to 5us of drain time before EOT checks

m_int_enable_all = 1'b1; // default to enabling / unmasking all INTs


m_irq_handler_en = 1'b1;
foreach(m_irq_handler_stripe_en[stripe]) begin
m_irq_handler_stripe_en[stripe] = 1'b1; // default to handling IRQ

gChips DV Google Confidential pg 73 / 93


Google Proprietary & Confidential

end

// Default to Random Test CG Pin


m_test_cg_rndm = 1;
m_test_cg_pin = 0; // ignored in random
endfunction: new

//------------------------------------------------------------------------------
task foo_test_base_vseq::pre_body();
super.pre_body();

// Register Coverage Singleton


m_reg_cov_obj_h = foo_reg_cov_obj::create_singleton("m_reg_cov_obj_h");

// EOT Object
m_foo_eot_obj_h = foo_eot_obj::type_id::create("m_foo_eot_obj_h");
m_foo_eot_obj_h.init(FOO_STRIPES);

endtask: pre_body

//------------------------------------------------------------------------------
task foo_test_base_vseq::body();

super.body();

// Launch IRQ Monitoring


monitor_irq();

// Launch IRQ Handling / Checking


if (m_irq_handler_en) begin
irq_handler();
end

// Initialize Test CG Pin


test_cg_en(.rndm(m_test_cg_rndm), .enable(m_test_cg_pin));

// Start RV Fetch REQ/RSP Memory


start_rv_fetch_req_rsp();

// Start Clock
start_clock();

// Send Reset
reset();

// Enable / UnMask All Interrupts


if (m_int_enable_all) begin
int_enable_all();
int_mask_all();
end

gChips DV Google Confidential pg 74 / 93


Google Proprietary & Confidential

endtask: body

//------------------------------------------------------------------------------
task foo_test_base_vseq::post_body();
super.post_body();

delay_time(m_test_seq_drain_time_us, "us");

m_ral_model_h.eot_check();

if (m_foo_rv_fetch_req_rsp_vseq_h != null) begin


m_foo_rv_fetch_req_rsp_vseq_h.eot_check();
end

if (m_foo_mem_model_obj_h != null) begin


m_foo_mem_model_obj_h.eot_check();
end

endtask: post_body

//------------------------------------------------------------------------------
task foo_test_base_vseq::start_rv_fetch_req_rsp();
m_foo_rv_fetch_req_rsp_vseq_h = foo_rv_fetch_req_rsp_vseq::type_id::create(
"m_foo_rv_fetch_req_rsp_vseq_h",
p_sequencer
);
if (!m_foo_rv_fetch_req_rsp_vseq_h.randomize()) begin
`uvm_fatal(get_name(), "Randomize Failed")
end
fork
m_foo_rv_fetch_req_rsp_vseq_h.start(p_sequencer, this);
join_none

endtask: start_rv_fetch_req_rsp

//------------------------------------------------------------------------------
task foo_test_base_vseq::start_clock(int unsigned halfperiod = 'd1_111, bit init = 1'b0);
// NOTE: depends on clock timeunit being set in clock_cfg to picoseconds
clock_nominal_50duty_seq clock_nominal_50duty_seq_h;

clock_nominal_50duty_seq_h = clock_nominal_50duty_seq::type_id::create(
"clock_nominal_50duty_seq_h",
p_sequencer
);
if (!clock_nominal_50duty_seq_h.randomize() with {
m_init == local::init;
m_halfperiod_time == local::halfperiod;
}) begin
`uvm_fatal(get_name(), "Randomize Failed")

gChips DV Google Confidential pg 75 / 93


Google Proprietary & Confidential

end
clock_nominal_50duty_seq_h.start(p_sequencer.m_clock_sequencer_h, this);

endtask: start_clock

//------------------------------------------------------------------------------
task foo_test_base_vseq::stop_clock(bit init = 1'b0);
// NOTE: depends on clock timeunit being set in clock_cfg to picoseconds
clock_nominal_50duty_seq clock_nominal_50duty_seq_h;

clock_nominal_50duty_seq_h = clock_nominal_50duty_seq::type_id::create(
"clock_nominal_50duty_seq_h",
p_sequencer
);
if (!clock_nominal_50duty_seq_h.randomize() with {
m_init == local::init;
m_halfperiod_time == 'd0; // period of zero stops the clock
}) begin
`uvm_fatal(get_name(), "Randomize Failed")
end
clock_nominal_50duty_seq_h.start(p_sequencer.m_clock_sequencer_h, this);

endtask: stop_clock

//------------------------------------------------------------------------------
task foo_test_base_vseq::reset();
reset_nominal_seq reset_nominal_seq_h;
reset_nominal_seq_h = reset_nominal_seq::type_id::create("reset_nominal_seq_h", p_sequencer);
if (!reset_nominal_seq_h.randomize() with {
m_reset_kind == RESET_KIND_SEQUENCE;
m_postactive_cycles > 'd1;
}) begin
`uvm_fatal(get_name(), "Randomize Failed")
end
// Reset CSRs
m_ral_model_h.reset();
`uvm_info(
get_name(),
$sformatf("Reset\n%s",reset_nominal_seq_h.sprint()),
UVM_HIGH
)
// Send Reset
reset_nominal_seq_h.start(p_sequencer.m_reset_sequencer_h, this);
endtask: reset

//------------------------------------------------------------------------------
task foo_test_base_vseq::monitor_irq();

foo_irq_nominal_forever_seq_t foo_irq_nominal_forever_seq_h[];

gChips DV Google Confidential pg 76 / 93


Google Proprietary & Confidential

foo_irq_nominal_forever_seq_h = new[FOO_STRIPES];
foreach(foo_irq_nominal_forever_seq_h[stripe]) begin
foo_irq_nominal_forever_seq_h[stripe] = foo_irq_nominal_forever_seq_t::type_id::create(
$sformatf("foo_irq_nominal_forever_seq_h[%0d]", stripe),
p_sequencer
);
if (!foo_irq_nominal_forever_seq_h[stripe].randomize()) begin
`uvm_fatal(get_name(), "Randomize Failed")
end
fork
int stripe_auto = stripe;
begin
foo_irq_nominal_forever_seq_h[stripe_auto].start(
p_sequencer.m_foo_irq_sequencer_h[stripe_auto],
this
);
end
join_none
end

foreach(foo_irq_nominal_forever_seq_h[stripe]) begin
fork
int stripe_auto = stripe;
forever begin
@(foo_irq_nominal_forever_seq_h[stripe_auto].m_irq_event);
m_irq[stripe_auto] = foo_irq_nominal_forever_seq_h[stripe_auto].m_irq;
-> m_irq_event[stripe_auto];
end
join_none
end

endtask: monitor_irq

//------------------------------------------------------------------------------
task foo_test_base_vseq::irq_handler();

foreach(m_irq[stripe]) begin
if (m_irq_handler_stripe_en[stripe]) begin
fork
int unsigned stripe_auto = stripe;
forever begin
irq_handling(stripe_auto);
end
join_none
end
end

endtask: irq_handler

//------------------------------------------------------------------------------

gChips DV Google Confidential pg 77 / 93


Google Proprietary & Confidential

task foo_test_base_vseq::irq_handling(ref int unsigned stripe);


// Wait for IRQ
if (!m_irq[stripe]) begin
@(m_irq_event[stripe]);
`uvm_info(
get_name(),
$sformatf(
"irq_handling(): IRQ Event Stripe 'd%0d Frame 'd%0d: Value: 'd%0d",
stripe,
m_irq_handling_frame[stripe],
m_irq[stripe]
),
UVM_MEDIUM
)
end
else begin
`uvm_info(
get_name(),
$sformatf(
"irq_handling(): IRQ Still High Stripe 'd%0d Frame 'd%0d: Value: 'd%0d",
stripe,
m_irq_handling_frame[stripe],
m_irq[stripe]
),
UVM_MEDIUM
)
end

// Check and Clear Primary ISR


begin
bit isr_ovf;
isr_ovf = 1'b0; // Primary
while (m_irq[stripe]) begin
irq_status_t irq_status;
-> m_irq_triggered_event[stripe];
// Check Status
int_isr_check_status(
.stripe(stripe),
.isr_ovf(isr_ovf),
.irq_status(irq_status)
);
// Clear Interrupt
int_isr_clear_status(
.stripe(stripe),
.isr_ovf(isr_ovf),
.irq_status(irq_status)
);

m_foo_eot_obj_h.stripe_done(stripe);

gChips DV Google Confidential pg 78 / 93


Google Proprietary & Confidential

isr_ovf += 1; // Switch to Secondary / Primary ISR

end
end
endtask: irq_handling

//------------------------------------------------------------------------------
task foo_test_base_vseq::int_enable(
int unsigned stripe,
bit enable = 1
);
foo_csr_int_enable_seq csr_int_enable_seq_h;

csr_int_enable_seq_h = foo_csr_int_enable_seq::type_id::create(
"csr_int_enable_seq_h"
);
if (!csr_int_enable_seq_h.randomize() with {
m_stripe == stripe;
m_intr == enable;
}) begin
`uvm_fatal(get_name(), "Randomize Failed")
end
csr_int_enable_seq_h.start(p_sequencer.m_foo_csr_sequencer_h, this);

endtask: int_enable

//------------------------------------------------------------------------------
task foo_test_base_vseq::int_enable_all(
bit enable = 1
);
// Enable / UnMask All Interrupts
for (int unsigned stripe = 0; stripe < FOO_STRIPES; stripe += 1) begin
int_enable(stripe, enable);
end
endtask: int_enable_all

//------------------------------------------------------------------------------
task foo_test_base_vseq::int_mask(
int unsigned stripe,
bit mask = 1
);
foo_csr_int_mask_seq csr_int_mask_seq_h;

csr_int_mask_seq_h = foo_csr_int_mask_seq::type_id::create(
"csr_int_mask_seq_h"
);
if (!csr_int_mask_seq_h.randomize() with {
m_stripe == stripe;
m_intr == mask;
}) begin

gChips DV Google Confidential pg 79 / 93


Google Proprietary & Confidential

`uvm_fatal(get_name(), "Randomize Failed")


end
csr_int_mask_seq_h.start(p_sequencer.m_foo_csr_sequencer_h, this);

endtask: int_mask

//------------------------------------------------------------------------------
task foo_test_base_vseq::int_mask_all(
bit mask = 1
);
// Enable / UnMask All Interrupts
for (int unsigned stripe = 0; stripe < FOO_STRIPES; stripe += 1) begin
int_mask(stripe, mask);
end
endtask: int_mask_all

//------------------------------------------------------------------------------
task foo_test_base_vseq::int_test(
int unsigned stripe,
bit test
);
foo_csr_int_test_seq csr_int_test_seq_h;

csr_int_test_seq_h = foo_csr_int_test_seq::type_id::create(
"csr_int_test_seq_h"
);
if (!csr_int_test_seq_h.randomize() with {
m_stripe == stripe;
m_intr == test;
}) begin
`uvm_fatal(get_name(), "Randomize Failed")
end
csr_int_test_seq_h.start(p_sequencer.m_foo_csr_sequencer_h, this);

endtask: int_test

//------------------------------------------------------------------------------
task foo_test_base_vseq::int_test_strobe(int unsigned stripe);

int_test(stripe, 'd0);
int_test(stripe, 'd1);
int_test(stripe, 'd0);

endtask: int_test_strobe

//------------------------------------------------------------------------------
task foo_test_base_vseq::int_isr_read(
int unsigned stripe,
bit isr_ovf,
output bit status

gChips DV Google Confidential pg 80 / 93


Google Proprietary & Confidential

);

foo_csr_int_status_seq csr_int_status_seq_h;

csr_int_status_seq_h = foo_csr_int_status_seq::type_id::create(
"csr_int_status_seq_h"
);

if (!csr_int_status_seq_h.randomize() with {
m_isr_ovf == isr_ovf;
}) begin
`uvm_fatal(get_name(), "Randomize Failed")
end
csr_int_status_seq_h.start(p_sequencer.m_foo_csr_sequencer_h, this);

status = csr_int_status_seq_h.m_intr[stripe];

endtask: int_isr_read

//------------------------------------------------------------------------------
task foo_test_base_vseq::int_isr_clear(
int unsigned stripe,
bit isr_ovf,
bit clear = 1'b1 // W1C
);
foo_csr_int_clear_seq csr_int_clear_seq_h;

csr_int_clear_seq_h = foo_csr_int_clear_seq::type_id::create(
"csr_int_clear_seq_h"
);
if (!csr_int_clear_seq_h.randomize() with {
m_stripe == stripe;
m_isr_ovf == isr_ovf;
m_intr == clear;
}) begin
`uvm_fatal(get_name(), "Randomize Failed")
end
csr_int_clear_seq_h.start(p_sequencer.m_foo_csr_sequencer_h, this);

endtask: int_isr_clear

//------------------------------------------------------------------------------
task foo_test_base_vseq::build_regs();

// Randomize the Config -- if already constructed, use that reg config (don't randomize)
if (m_reg_foo_ctrl_obj_h == null) begin
m_reg_foo_ctrl_obj_h = foo_io_foo_reg_ctrl_obj::type_id::create("m_reg_foo_ctrl_obj_h");
if (!m_reg_foo_ctrl_obj_h.randomize()) begin
`uvm_fatal(get_name(), "Randomize Failed")
end

gChips DV Google Confidential pg 81 / 93


Google Proprietary & Confidential

end

// If not null - some derived class has pre-speced the stripe registers
if (m_reg_foo_stripes_obj_h == null) begin

m_reg_foo_stripes_obj_h = foo_reg_stripes_obj::type_id::create("m_reg_foo_stripes_obj_h");

// Announce Number of Init Stripes


`uvm_info(
get_name(),
$sformatf("build_regs(): Launching with 'd%0d Initial Stripes", m_num_init_stripes),
UVM_LOW
)

// Randomize adds a new random stripe


for (int unsigned ii = 0; ii < m_num_init_stripes; ii += 1) begin

if (!m_reg_foo_stripes_obj_h.randomize()) begin
`uvm_fatal(get_name(), "Randomize Failed")
end

m_foo_eot_obj_h.add(m_reg_foo_stripes_obj_h.m_new_config_h);

end
end

endtask: build_regs

//------------------------------------------------------------------------------
task foo_test_base_vseq::config_dut();
foo_csr_reg_config_seq csr_reg_config_seq_h;

csr_reg_config_seq_h = foo_csr_reg_config_seq::type_id::create(
"csr_reg_config_seq_h"
);

// Set the Registers to be Configured


csr_reg_config_seq_h.m_reg_foo_ctrl_obj_h = m_reg_foo_ctrl_obj_h;
csr_reg_config_seq_h.m_reg_foo_stripes_obj_h = m_reg_foo_stripes_obj_h;

// Set the Config Coverage Obj


csr_reg_config_seq_h.m_reg_cov_obj_h = m_reg_cov_obj_h;

// Program the DUT


csr_reg_config_seq_h.start(p_sequencer.m_foo_csr_sequencer_h, this);

endtask: config_dut

//------------------------------------------------------------------------------

gChips DV Google Confidential pg 82 / 93


Google Proprietary & Confidential

task foo_test_base_vseq::test_cg_en(bit rndm = 0, bit enable = 0);


foo_test_cg_seq_t foo_test_cg_seq_h;
foo_test_cg_seq_h = foo_test_cg_seq_t::type_id::create(
"foo_test_cg_seq_h",
p_sequencer
);
if (!foo_test_cg_seq_h.randomize() with {
(!rndm) -> {
m_payload_obj.m_payload.test_cg_en == enable;
}
}) begin
`uvm_fatal(get_name(), "Randomize Failed")
end
fork // fork so can launch method before starting the clock
foo_test_cg_seq_h.start(
p_sequencer.m_foo_test_cg_sequencer_h,
this
);
join_none
endtask: test_cg_en

Derived Test Virtual Sequence


The actual test sequences derive from the test base virtual sequence.
It is good practice to create families of test sequences that inherit from a common base class. In this way they
can be grouped together. Name the base class and derived classes such that the files end up being sorted
together when listed in the directory: foo_test_groupname_base_vseq with child tests
foo_test_groupname_*_vseq.

Like all virtual sequences - they are randomized before starting - so make use of rand members to make the test
different with each run.

The typical derived test virtual sequence follows the following structure (for a DUT named "foo"):
//--------------------------------------------------------------------------------------------------
// FOO Test Some Feature
//--------------------------------------------------------------------------------------------------
class foo_test_somefeature_vseq extends
foo_test_stripe_config_base_vseq;
`uvm_object_utils( foo_test_somefeature_vseq )

rand foo_reg_mode_t m_mode;


rand int unsigned m_feature;

constraint legal;
constraint typical;

extern function new(string name = "");

gChips DV Google Confidential pg 83 / 93


Google Proprietary & Confidential

extern virtual task body();

endclass: foo_test_somefeature_vseq

//------------------------------------------------------------------------------
constraint foo_test_somefeature_vseq::legal {
m_feature <= FOO_FEATURE_MAX;
}

//------------------------------------------------------------------------------
constraint foo_test_somefeature_vseq::typical {
soft m_feature == 16;
}

//------------------------------------------------------------------------------
function foo_test_somefeature_vseq::new(string name = "");
super.new( name );
// Defaults
m_feature = 16;
m_mode = FOO_SOME_MODE;
endfunction: new

//------------------------------------------------------------------------------
task foo_test_somefeature_vseq::body();

// Single Stripe
m_num_init_stripes = 'd1;

// setup the registers


m_reg_foo_ctrl_obj_h = foo_reg_foo_ctrl_obj::type_id::create("m_reg_foo_ctrl_obj_h");

// Setup the Configuration


if (!m_reg_foo_ctrl_obj_h.randomize() with {
m_reg_foo_ctrl_obj_h.m_feature == local::m_feature;
m_reg_foo_ctrl_obj_h.m_mode == local::m_mode;
...
}) begin
`uvm_fatal(get_name(), "Randomize Failed")
end

super.body(); // Reset the DUT, Launch the Config


build_regs(); // build the randomized registers
config_dut(); // configure the DUT - based on the regs

// Wait for end of test - no more active stripes


m_foo_eot_obj_h.wait_done();

endtask: body

gChips DV Google Confidential pg 84 / 93


Google Proprietary & Confidential

Test Directed Base Virtual Sequence


This sequence extends the test base virtual sequence and provides a collection of methods (mainly tasks) that
provide a shorthand to write directed testcases.
Similar to how we create test groups with named base classes, we separate the directed base from the base to
logically separate tests that are random from those that are directed / directed random with naming as:
foo_test_directed_base_vseq and derived tests foo_test_directed_somefeature_vseq.
Directed tests are those that are mostly non-rand. It's fine (and probably even better) to use rand variables in
directed tests.

The typical directed base test virtual sequence follows the following structure (for a DUT named "foo"):
//--------------------------------------------------------------------------------------------------
// FOO Directed Base Virtual Sequence
//--------------------------------------------------------------------------------------------------
class foo_test_directed_base_vseq extends foo_test_base_vseq;
`uvm_object_utils( foo_test_directed_base_vseq )

function new(string name = "");


super.new( name );
endfunction: new

extern virtual task body();

// Helper Methods
extern virtual task do_the_thing(input int unsigned id, output bit success);
extern virtual task undo_the_thing(int unsigned id);

endclass: foo_test_directed_base_vseq

//------------------------------------------------------------------------------
task foo_test_directed_base_vseq::body();
super.body(); // Reset the DUT, Launch the Clocks
endtask: body

//------------------------------------------------------------------------------
task foo_test_directed_base_vseq::do_the_thing(input int unsigned id, output bit success);
// Create, Randomize, Start the Thing Sequence
...
// Check The Thing
...
endtask: do_the_thing

//------------------------------------------------------------------------------
task foo_test_directed_base_vseq::undo_the_thing(int unsigned id);
// Create, Randomize, Start the Thing Sequence - clear the Thing
...
endtask: undo_the_thing

gChips DV Google Confidential pg 85 / 93


Google Proprietary & Confidential

Test Sequence List


The test sequence list is a compilation aid. It "tick includes" all of the test sequences that are available with this
environment.
The test sequence list should be named with a dut name prefix and '_vseq_list' suffix.

The test list follows the following structure (for a DUT named "foo"):
// Base
`include "foo_base_vseq.svh"

// Virtual Agents
`include "foo_mem_vseq.svh"
...

// Virtual Agent Overrides


`include "foo_mem_override_zerodelay_vseq.svh"
...

// Test Sequences
`include "foo_test_base_vseq.svh"
...
`include "foo_test_csr_access_vseq.svh"
...
`include "foo_test_something_vseq.svh"

// Directed Test Sequences


`include "foo_test_directed_base_vseq.svh"
`include "foo_test_directed_plusarg_vseq.svh"
...

Environment Package
The DUT environment package is the namespaced encapsulation of the entire environment.
In the package we import from other packages, to get access to types used in this package. We declare types
that are shared across the environment. And we include the files of the environment - to place the contained
classes in this package.
The package also needs to set a timeunit and precision.
The package should be named with a DUT name prefix and a '_pkg' suffix.

The package follows the following structure (for a DUT named "foo"):
`include "uvm_macros.svh"

//--------------------------------------------------------------------------------------------------
// FOO Package
//--------------------------------------------------------------------------------------------------

gChips DV Google Confidential pg 86 / 93


Google Proprietary & Confidential

package foo_pkg;

// Timescale - set to match tb module


timeunit 1ns;
timeprecision 1ps;

import uvm_pkg::*;

import ral_pkg::*;
import time_pkg::*;
import clock_pkg::*;
import reset_pkg::*;
import delay_pkg::*;
import activity_watchdog_pkg::*;

localparam int FOO_ADDR_WDTH = foo_rtl_pkg::FooAddrWidth;


localparam int FOO_DATA_WDTH = foo_rtl_pkg::FooDataWidth;
localparam int FOO_DATA_MASK_WDTH = foo_rtl_pkg::FooDataMaskWidth;
...

import foo_irq_pkg::*;
...
import foo_csr_pkg::*;
import foo_input_pkg::*;
import foo_output_pkg::*;
...

`include "foo_cfg.svh"
`include "foo_virtual_sequencer.svh"
`include "foo_scoreboard.svh"
`include "foo_env.svh"
`include "foo_vseq_list.svh"
`include "foo_test_list.svh"

endpackage: foo_pkg

Useful Agents / Objects


There are a handful of generic agents / objects that you may find useful - they are listed here. A full description is
beyond the scope of this document, but a list is provided to encourage your further investigation.

Clock Agent
A clock agent - interface level clock generator that can be started / stopped / frequency updated from a
sequence; like any standard UVM agent.

gChips DV Google Confidential pg 87 / 93


Google Proprietary & Confidential

Reset Agent
A reset agent - interface level reset generator agent that can be configured active high / low and any combination
of assert / deassert sync / async.
Allows driving reset from a sequence.

Delay Object
An object / interface pair that allows the generation of delay from a sequence through an object. Delay can be in
units of time or cycles. In an effort to be emulation friendly: cycle delays are implemented in the interface and
time delays are implemented using the uvm_delay macro.

Activity Watchdog Component


A configurable parameterized component that will wait for a configured watchdog time delay between
parameterized interface changes before generating a timeout error.
Interface is specialized to a struct of all of the 'watched' signals.
Component is configured with the delay time (in value and units) before a timeout occurs.

Time Package
A packaged collection of objects that enable measuring time in units of wall time, cpu time, and simulation time.
Useful for seeing how long your sim is running (wall time) and to determine your test's efficiency (sim time / wall
time).

IRQ Reactive Agent


A reactive driver that returns on receipt of an interrupt signal change.
Reactive agent that, once started, waits for an interrupt change and returns an item with the state of the interrupt
pin.
This agent can be typedef'd to create a reactive agent that waits on any single bit event signal.

Ready / Valid Active Request Agent


A type parameterized ready / valid active driver. The payload type of the data transferred when ready and valid
are high is passed to both the interface and agent as a parameter. This allows constraints to be written the
payload type's members (if the payload is a struct)
Active agent that drives, on receipt of an item, the valid signal with a payload and waits for ready from the DUT.

Ready / Valid Reactive Response Agent


A type parameterized ready / valid reactive driver. The payload type of the data transferred when ready and valid
are high is passed to both the interface and agent as a parameter.
Reactive agent that drives the read signal and waits for valid from the DUT - then captures the data transferred.

gChips DV Google Confidential pg 88 / 93


Google Proprietary & Confidential

'No Protocol' Agent


A type parameterized active driver. The payload type of the data transferred is passed to both the interface and
agent as a parameter. This allows constraints to be written the payload type's members (if the payload is a
struct.)
Active agent that drives, on receipt of an item, the data payload.

Directory Structure
The directory structure for a test environment is as follows (for a DUT called 'foo'):
➔ / path / to / verif / library

agents
● some_protocol
○ some_protocol_cfg.svh
○ some_protocol_item.svh
○ some_protocol_driver.svh
○ some_protocol_monitor.svh
○ some_protocol_sequencer.svh
○ some_protocol_agent.svh
○ some_protocol_synth_pkg.sv
○ some_protocol_pkg.sv
○ some_protocol_if.sv
○ sequences

some_protocol_nominal_seq.svh

some_protocol_something_seq.svh

some_protocol_something_else_seq.svh

some_protocol_seq_list.svh
● some_other_protocol
○ ...
➔ foo

agents
● foo_custom_protocol
○ foo_custom_protocol_cfg.svh
○ foo_custom_protocol_item.svh
○ foo_custom_protocol_driver.svh
○ foo_custom_protocol_monitor.svh
○ foo_custom_protocol_sequencer.svh
○ foo_custom_protocol_agent.svh
○ foo_custom_protocol_synth_pkg.sv
○ foo_custom_protocol_pkg.sv
○ foo_custom_protocol_if.sv
○ sequences

foo_custom_protocol_nominal_seq.svh

foo_custom_protocol_something_seq.svh

foo_custom_protocol_something_else_seq.svh

gChips DV Google Confidential pg 89 / 93


Google Proprietary & Confidential


foo_custom_protocol_seq_list.svh
● foo_some_protocol
○ sequences

foo_some_protocol_nominal_override_dothing_seq.svh

foo_some_protocol_seq_list.svh
○ foo_some_protocol_synth_pkg.sv
○ foo_some_protocol_pkg.sv
◆ sequences
● foo_base_vseq.svh
● ...
● foo_mem_vseq.svh
● ...
● foo_mem_override_zerodelay_vseq.svh
● ...
● foo_test_base_vseq.svh
● foo_test_csr_access_vseq.svh
● ...
● foo_test_something_vseq.svh
● ...
● foo_test_directed_base_vseq.svh
● foo_test_directed_plusarg_vseq.svh
● ...
● foo_test_seq_list.svh
◆ tests
● foo_test_base.svh
● foo_test_special.svh
● ...
● foo_test_list.svh
◆ foo_cfg.svh
◆ foo_virtual_sequencer.svh
◆ foo_scoreboard.svh
◆ foo_scoreboard_debug_if.sv
◆ foo_env.svh
◆ foo_synth_pkg.sv
◆ foo_pkg.sv

Considerations for Emulation / Testbench Acceleration


Emulation for testbench acceleration is a fantastic way to run large / long running test cases quickly. To run in the
emulator a few things need to happen. In emulation the code is split between emulator side (fast side) and
simulator side (slow side). A handshake between the simulator and emulator occurs each time information needs
to move from one to the other. So, for the best performance, everything that consumes cycles (and small delays)
should be made to run on the emulator side. A common place to split this in a UVM environment is as the
driver(and monitor) to interface boundary. To accomplish this we:
1. split the testbench top into a simulator side (dv_top) and a emulator side (tb_top)s

gChips DV Google Confidential pg 90 / 93


Google Proprietary & Confidential

a. emulator top: dut instance, interface instances - connected to dut, assertions


b. simulator top: virtual interface handles, uvm run_test (instances test instances
environment instances agents)
2. agent interfaces are changed to pull in the driver and monitor cycle consuming methods
Additionally, the emulator doesn't support as many language constructs as the simulator, so we are restricted to
a subset of the systemverilog language for the portions of code that run on the emulator.

UVM with Genesis


A minimal set of changes is required to build a UVM DV environment with the Genesis pre-processor flow.
Genesis requires additional file local file lists and perl calls in order to generate the final file list.
A traditional systemVerilog flow is made up of a file list with a mix of .sv files with +incdir+directory paths to the
`included svh files.
In genesis the files are converted from preprocessor files - with svp and svhp extensions to systemverilog files -
with sv and svh extensions respectively.
In genesis the final file list is generated from a combination of local file lists (called vpf files) and genesis perl
pre-processor generate commands found in the top of files that have `includes.
All genesis preprocessor files will typically include a header.svph file - that the preprocessor will expand into a
traditional copyright banner found at the top of the generated file.
Using the above described reset agent as an example we would create two vpf files. One vpf file found in the
sequences directory named: reset_sequences.vpf containing the list of files included in the reset_seq_list.svhp:
reset_nominal_seq.svhp
reset_assert_seq.svhp
reset_seq_list.svhp

The reset sequence list file will have this at the top - to tell genesis to generate and include the child files in the
final file list:
//; include("foo_header.svph");
/*; begin_multiline_perl

my $reset_nominal_seq = generate_base_include(
'reset_nominal_seq',
'dummy_reset_nominal_seq'
);
my $reset_assert_seq = generate_base_include(
'reset_assert_seq',
'dummy_reset_assert_seq'
);

end_multiline_perl ;*/

Similar to the sequences directory - the root agent directory "reset" will contain a reset.vpf file:
reset_if.svp
reset_cfg.svhp
reset_item.svhp

gChips DV Google Confidential pg 91 / 93


Google Proprietary & Confidential

reset_driver.svhp
reset_monitor.svhp
reset_sequencer.svhp
reset_agent.svhp
-inputlist sequences/reset_sequences.vpf
reset_pkg.svp

And the reset_pkg.svp file will include the following at the top, again, to tell genesis to generate and include the
child files in the final file list:
//; include("foo_header.svph");
/*; begin_multiline_perl
my $reset_cfg = generate_base_include(
'reset_cfg',
'dummy_reset_cfg'
);
my $reset_item = generate_base_include(
'reset_item',
'dummy_reset_item'
);
my $reset_driver = generate_base_include(
'reset_driver',
'dummy_reset_driver'
);
my $reset_monitor = generate_base_include(
'reset_monitor',
'dummy_reset_monitor'
);
my $reset_sequencer = generate_base_include(
'reset_sequencer',
'dummy_reset_sequencer'
);
my $reset_agent = generate_base_include(
'reset_agent',
'dummy_reset_agent'
);
my $reset_seq_list = generate_base_include(
'reset_seq_list',
'dummy_reset_seq_list'
);

end_multiline_perl ;*/

This examples holds throughout the verification environment with a couple of additional details.
When we reference files that aren't header files (interfaces and packages) we use 'generate_base' rather than
'generate_base_include' - since these files are compiled as part of the file list and not `included.
When we need to import a package into "this" we also need to import the corresponding vpf file into "this" vpf file.
Our foo environment would have a vpf that looks like this:
# RTL Package
../path/to/the/rtl/foo_rtl_pkg.svp

gChips DV Google Confidential pg 92 / 93


Google Proprietary & Confidential

# Agents
-inputlist ../path/to/the/agents/clock/clock.vpf
-inputlist ../path/to/the/agents/reset/reset.vpf
-inputlist ../path/to/the/agents/delay/delay.vpf
...

# DUT Specific Agents


-inputlist agents/foo_csr/foo_csr.vpf
-inputlist agents/foo_specific_protocol/foo_specific_protocol.vpf
-inputlist agents/foo_input/foo_input.vpf
...
-inputlist agents/foo_mem_model/foo_mem_model.vpf

# DMA Environment
foo_scoreboard_debug_if.svp
foo_cfg.svhp
foo_virtual_sequencer.svhp
foo_scoreboard.svhp
foo_env.svhp
foo_pkg.svp

# Virtual Sequences
-inputlist sequences/foo_sequences.vpf

# Tests
-inputlist tests/foo_tests.vpf

# Test Bench Top


foo_tb.svp

gChips DV Google Confidential pg 93 / 93

You might also like