Sei sulla pagina 1di 158

Advanced UVM

Architecting a UVM Testbench

Tom Fitzpatrick
Verification Evangelist

academy@mentor.com
www.verificationacademy.com
UVM Testbench - Architectural Design

For Each Interface:


• How does the interface work?
• What information is transferred?
• Transaction variants?
• Uni/bidirectional? Pipelined?
DUT
APB

SPI
I/F
For the Design: IRQ
• What does it do?
• What are the use cases?
• Which test cases are required?
• What type of stimulus scenarios are required?
• What represents correct behavior?
• What kind of functional coverage do I need?
UVC Structural Building Block

Analysis port: Send Detects transactions


transactions for checking on the interface

- Contains virtual UVC(agent)


interface handle
- Pass information Configuration One per
on how agent Object Monitor interface
should behave

Sequencer
DUT
seq_item
Driver
Sends stimulus
to Driver

Stimulus Converts seq_item


to pin wiggles
UVCs are Protocol-Specific

UVC(agent)
Configuration
Object Monitor
UVC(agent)

Configuration
Object Monitor

Sequencer
DUT
Driver Sequencer
Driver
UVCs are Protocol-Specific: The Agent
class dut_agent extends uvm_component;
`uvm_component_utils(dut_agent)
dut_agent_cfg m_cfg;
uvm_analysis_port #(dut_txn) ap;
dut_monitor m_monitor;
dut_driver m_driver;
uvm_sequencer #(dut_txn) m_seqr;

function void build_phase(uvm_phase phase);
super.build_phase(phase);
if(!uvm_config_db #(dut_agent_cfg)::get(this,“”,“config”,m_cfg))
`uvm_fatal(“Config fatal”,“Can’t get config”);
if(m_cfg.active == UVM_ACTIVE) begin
m_seqr = uvm_sequencer#(dut_txn)::type_id::create(“seqr”,this);
m_driver = dut_driver::type_id::create(“driver”,this);
end UVC(agent)

Configuration
endfunction Object Monitor

function void connect_phase(uvm_phase phase);


m_monitor.dut_if = m_cfg.bus_if; Sequencer
DUT
ap = m_monitor.ap; Driver
if(m_cfg.active == UVM_ACTIVE) begin
m_driver.seq_item_port.connect(m_seqr.seq_item_export);
m_driver.dut_if = m_cfg.bus_if;
end

endfunction
endclass
The Environment

UVC(agent)

Configuration
Object Monitor

Sequencer
DUT
Driver
The Environment
class my_env extends uvm_env;
`uvm_component_utils(my_env)

agent1 m_agent1;
agent2 m_agent2;
my_scoreboard m_scoreboard;
my_env_config m_cfg;

function new(string name = “my_env”, uvm_component parent = null);


super.new(name, parent);
endfunction

function void build_phase(uvm_phase phase);


if(!uvm_config_db #( my_env_config )::get( this , "",
“my_env_config" , m_cfg ) begin
`uvm_error("build_phase", "unable to get my_env_config")
end
if(m_cfg.has_agent1) begin
uvm_config_db #(agent1_config)::set( this , "m_agent1*",
"agent1_config", m_cfg.m_agent1_cfg );
m_agent1 = agent1::type_id::create("m_agent1", this);
DUT
end
if(m_cfg.has_agent2) begin
uvm_config_db #(agent2_config)::set( this , "m_agent2*",
"agent2_config", m_cfg.m_agent2_cfg );
m_agent2 = agent2::type_id::create("m_agent2", this);
end
if(m_cfg.has_my_scoreboard) begin
m_scoreboard = my_scoreboard::type_id::create("m_scoreboard", this);
end
endfunction:build_phase
The Environment
class my_env extends uvm_env;
`uvm_component_utils(my_env)

agent1 m_agent1;
agent2 m_agent2;
my_scoreboard m_scoreboard;
my_env_config m_cfg;

function new(string name = “my_env”, uvm_component parent = null);


super.new(name, parent);
endfunction

function void connect_phase( uvm_phase phase );


if(m_cfg.has_spi_scoreboard) begin
m_agent1.ap.connect(m_scoreboard.apb.analysis_export);
m_agent2.ap.connect(m_scoreboard.spi.analysis_export);
end
endfunction: connect_phase
endclass

DUT
The Base Test
class my_test_base extends uvm_test;
`uvm_component_utils(my_test_base)

my_env m_env;
my_env_config m_cfg;
my_agent1_config m_a1_cfg;
my_agent2_config m_a2_cfg;

function new(string name = “my_test_base”, uvm_component parent = null);


super.new(name, parent);
endfunction

function void build_phase( uvm_phase phase );


m_cfg = my_env_config::type_id::create(“m_env_cfg”);
// setup configuration for env and agents
uvm_config_db#(my_env_config)::set(this,"*", “my_env_config",
m_cfg);
m_env = my_env::type_id::create("m_env", this);
endfunction

endclass
DUT
The Actual Test
class my_test extends uvm_test_base;
`uvm_component_utils(my_test)

my_virt_seq m_vseq;

function new(string name = “my_test”, uvm_component parent = null);


super.new(name, parent);
endfunction

function void build_phase( uvm_phase phase );


super.build_phase(phase);
endfunction

task run_phase(uvm_phase phase);


m_vseq = my_virt_seq::type_id::create(“my virtual sequence”);
phase.raise_objection(this, “Starting virtual sequence”);
m_vseq.start();
phase.drop_objection(this, “Finished virtual sequence”);
endtask

endclass
DUT
A Word About Phasing
• UVM adds 12 new phases in parallel common
build
with run_phase connect

• Consensus is to use the new phases end_of_elab


start_of_sim uvm
to control stimulus pre_reset

reset
class my_phase_test extends uvm_test_base;
`uvm_component_utils(my_phase_test) post_reset
pre_config
task XXX_phase(uvm_phase phase); config
phase.raise_objection(this, “Starting Phase”);
// Start sequence(s) post_config
run
// begin-end / fork-join pre_main
phase.drop_objection(this, “Finished Phase”); main
endtask
post_main
endclass pre_shutdown

shutdown
• Drivers and monitors should just post_shutdown

use run_phase extract

check

• Don’t use phase domains or report

final
jumping
Architecture Summary
• Agents are protocol-specific
• Environments define the testbench topology
• Which agents and how many
• Other components
• Base Test instantiates env and handles
default configuration
• Extend the base test to define your test
• Tweek configuration and/or factory settings
• Start (virtual) sequence(s)
• Test handles phase objections
• Keep to basic phasing
Advanced UVM
Customization: Understanding
the Factory and Configuration

Tom Fitzpatrick
Verification Evangelist

academy@mentor.com
www.verificationacademy.com
Two Customization Mechanisms
• Factory
• Allows test to change the type of a desired component
or object
• Typically set up at start of simulation
• Configuration
• Allows parents to define properties for children
- Static (build-time) – Highest parent “wins”
- Dynamic (run_time) – Last set “wins”
• All UVM components get their own configuration
- Optionally use to configure their children
Create() vs. New()
• SystemVerilog objects must be constructed

new() hard-codes the type

create() returns a constructed


…without modifying the instance from the factory
instantiating code!

comp1

comp2

Factory lets you change the type


of the created component…
Registering with the Factory
• Objects are registered with the factory
via macro
• `uvm_object_utils(<type>)
• `uvm_component_utils(<type>)
‘type_id’ is a wrapper
created by the macro

No‘;’“;”
No
comp2
Registering with the Factory
• Static methods in wrapper

Override type returned by factory

get_type() returns
the type “handle”

returns a constructed instance


(no $cast needed)

comp2

• set_inst_override(<type>, “<instance>”); // too


Overriding a Type
Environments are components
Overriding a Type

New Desired type

All Instances
Overridden
Overriding an Instance

New Desired type

Instance Name

Instance Changed
Using Parameterized Types

Parameterized type
Using Parameterized Types
Tests are Components, too!
• run_test() creates the test from the factory

module top;
...

initial Register the test


begin: blk with the factory

...
run_test();
end

endmodule: top

Command line: vsim +UVM_TESTNAME=test


Tests are Components, too!
• Always call run_test() with null argument

module top;
...

initial
begin: blk
...
run_test();
end

endmodule: top

Command line: vsim +UVM_TESTNAME=test2


Use the Factory for Objects, too
UVM Configuration Database
• Implemented as resources
• uvm_config_db is a convenience layer
Usually, a component will get its
• Explicitly typed configuration and use that to
configure its children
• Tied to hierarchical scopes
test test
set e.a.d.x = 4 set e.y = 4
Path Value
env {test .e.a.d.x} 4 env
set a.d.x = 3 get y;
{test.e .a.d.x} 3 set a.y = y

agent {test.e.a .d.x} 2 agent


get y; {test.e.a.d .x} 4 get y;
set d.x = 2; set d.y = y;
Highest Write Wins
driver driver
get x = 4 get y = 4
uvm_config_db
• Convenience layer on top of Resource db
uvm_config_db #(<type>)::set(this, “<inst>”,“<field>”,
value );

{this.get_full_name(),”.”,”<inst>”} must match

uvm_config_db #(<type>)::get(this, “<inst>”,“<field>”,


value );

• Similar functionality to set/get_config_*()


• No casting on get()
• Linked to component hierarchy
• Can mix set/get of any style
• As long as scope/context/inst_name matches
UVM Features – uvm_config_db
• For passing virtual interface handles:
ahb_if AHB(); // AHB Interface

initial begin
uvm_config_db #(virtual ahb_if)::set(null, “uvm_test_top”, “AHB”, AHB);

• For passing configuration objects:


ahb_agent_config ahb_cfg;
env_config env_cfg;

function void build_phase(uvm_phase);


ahb_cfg = ahb_agent_config::type_id::create(“ahb_cfg”);
if(!uvm_config_db #(virtual ahb_if)::get(this, “”, “AHB”, ahb_cfg.AHB))
begin
`uvm_error(…)
end
env_cfg = env_config::type_id::create(“env_cfg”);
env_cfg.ahb_cfg = ahb_cfg;
uvm_config_db #(env_config)::set(this, “*”, “config”, env_cfg);

Summary
• Use `uvm_object/component_utils macro to
register with the factory
• Always call <type>::type_id::create()
• Register tests with the factory
• Call run_test() with null argument
• Specify which test via the command line
• Use Config DB at build-time
• Components get their config before configuring
children
• Highest set wins
• Use Config DB at run_time
• Last set wins
• Up to the “getter” to decide when it’s legal
Advanced UVM
How TLM Works

Tom Fitzpatrick
Verification Evangelist

academy@mentor.com
www.verificationacademy.com
How TLM Works
• TLM is all about communication through method calls
• A TLM port specifies
the “API” to be used
• A TLM export supplies
the implementation of
class tr extends
the methods uvm_transaction;

• Connections are
Port
between ports/exports,
not components
Export
• Transactions are objects
• Ports & exports are
parameterized by the
transaction type being
communicated
How TLM Works
• TLM is all about communication through method calls
• A TLM port specifies
the “API” to be used
• A TLM export supplies
the implementation of
the methods
• Connections are
Port
between ports/exports,
not components
Export
• Transactions are objects
• Ports & exports are
parameterized by the
transaction type being
communicated
Hierarchical Connections
• Port-to-Export
Hierarchical Connections
• Port-to-Export
• port.connect(export);
Hierarchical Connections
• Port-to-Export
• port.connect(export);
• Port-to-Port
Hierarchical Connections
• Port-to-Export
• port.connect(export);
• Port-to-Port
• child.port.connect(
parent_port);
Hierarchical Connections
• Port-to-Export
• port.connect(export);
• Port-to-Port
• child.port.connect(
parent_port);
• Export-to-Export
Hierarchical Connections
• Port-to-Export
• port.connect(export);
• Port-to-Port
• child.port.connect(
parent_port);
• Export-to-Export
• parent_export.connect(
child.export);
Hierarchical Connections
• Port-to-Export
• port.connect(export);
• Port-to-Port
• child.port.connect(
parent_port);
• Export-to-Export
• parent_export.connect(
child.export);
• Last Export is actually
an ‘imp’
Analysis Communication
• Analysis ports support 1:many connections
• All write() functions
called in zero time
• Used by coverage
collectors and Analysis
Port
scoreboards
• uvm_subscriber has
built-in analysis_export
Analysis of Multiple Streams
• Choice 1: Use imp suffixes defined via macro
• Declare macros outside
of component
• Instantiate suffixed imps
• Implement write_SUFFIX
methods
• Write methods are
functions
• Can’t synchronize
between streams
Analysis of Multiple Streams
• Choice 2: Use embedded fifos
• Declare analysis exports
• Connect exports to fifos
• Run_phase must
actively pull from fifos
TLM Summary
• Every port must eventually connect to an
implementation (imp)
• You’ll mostly only use two port/export
connections
From To
analysis monitor.ap subscriber.analysis_export
sequencer/driver driver.seq_item_port sequencer.seq_item_export

• All TLM connections go from ‘origin’ to


‘destination’
• port.connect(export);
• child_port.connect(parent_port);
• parent_export.connect(child_export); // or imp
Advanced UVM
Modeling Transactions

Tom Fitzpatrick
Verification Evangelist

academy@mentor.com
www.verificationacademy.com
Separating Stimulus from the Testbench
• A key to reusability is to separate Behavior
from Structure
• Transactions (a.k.a. Sequence Items) are the
main communication vehicle across the
boundary

Behavior
Structure

DUT
Review: Sequences
• Decouple stimulus specification
from structural hierarchy
• Add/remove/modify stimulus
scenarios independent of testbench u1
• Simplify test writer API u1 s1
• Sequences define
transaction streams
• May start on any sequencer s3 s5

s2
• Sequences can call children
• Sequences & transactions s4

customizable via the factory


Designing a Sequence Item
class bus_item extends uvm_sequence_item; This is the
`uvm_object_utils(bus_item) “transaction”

rand int delay;


rand logic[31:0] addr; Make all “input”
rand op_code_enum op_code; properties rand
rand logic[31:0] data[];
string slave_name;
bit response;

function new(string name = "bus_item");


super.new(name);
endfunction
do_copy() Methods for
do_compare() standard
convert2string() operation
do_print()
do_record()
do_pack() Users call copy(),
do_unpack() compare()…

endclass: bus_item
Designing a Sequence Item: Methods
do_copy()
class bus_item extends uvm_sequence_item;
`uvm_object_utils(bus_item)

function void do_copy(uvm_object rhs);


bus_item rhs_;

Virtual method

do_copy()
do_compare()
convert2string()
do_print()
do_record()
do_pack()
endfunction: do_copy
do_unpack()

endclass: bus_item
Designing a Sequence Item: Methods
do_copy()
class bus_item extends uvm_sequence_item;
`uvm_object_utils(bus_item)

function void do_copy(uvm_object rhs);


bus_item rhs_; Make sure argument
is of correct type
if(!$cast(rhs_, rhs)) begin
uvm_report_error("do_copy:", "Cast failed");
return;
end

endfunction: do_copy

endclass: bus_item
Designing a Sequence Item: Methods
do_copy()
class bus_item extends uvm_sequence_item;
`uvm_object_utils(bus_item)

function void do_copy(uvm_object rhs);


bus_item rhs_;

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


uvm_report_error("do_copy:", "Cast failed");
return;
Chain the copy with
end parent classes
super.do_copy(rhs);

endfunction: do_copy

endclass: bus_item
Designing a Sequence Item: Methods
do_copy()
class bus_item extends uvm_sequence_item;
`uvm_object_utils(bus_item)

function void do_copy(uvm_object rhs);


bus_item rhs_;

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


uvm_report_error("do_copy:", "Cast failed");
return;
end
super.do_copy(rhs);
delay = rhs_.delay;
addr = rhs_.addr; Copy members of
op_code = rhs_.op_code; rhs to this
slave_name = rhs_.slave_name;
data = rhs_.data;
response = rhs_.response;
endfunction: do_copy

endclass: bus_item
Designing a Sequence Item: Methods
do_copy()
class bus_item extends uvm_sequence_item;
`uvm_object_utils(bus_item)

function void do_copy(uvm_object rhs);


bus_item rhs_;

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


uvm_report_error("do_copy:", "Cast failed");
return;
end
super.do_copy(rhs); USAGE:
delay = rhs_.delay; bus_item A, B; Deep copy
addr = rhs_.addr; A.copy(B);
op_code = rhs_.op_code; OR
slave_name = rhs_.slave_name;
$cast(A, B.clone());
data = rhs_.data;
response = rhs_.response;
endfunction: do_copy Clone returns a
uvm_object
endclass: bus_item
Designing a Sequence Item: Methods
do_copy()
class bus_item extends uvm_sequence_item; do_compare()
`uvm_object_utils(bus_item)

function bit do_compare(uvm_object rhs,


uvm_comparer comparer);
bus_item rhs_;
Policy class: don’t use
if(!$cast(rhs_, rhs)) begin
return 0;
end
0 = MISMATCH

endfunction: do_compare

endendclass: bus_item
Designing a Sequence Item: Methods
do_copy()
class bus_item extends uvm_sequence_item; do_compare()
`uvm_object_utils(bus_item)

function bit do_compare(uvm_object rhs,


uvm_comparer comparer);
bus_item rhs_;

if(!$cast(rhs_, rhs)) begin Chain the compare


return 0; with parent classes
end
return((super.do_compare(rhs, comparer) &&

endfunction: do_compare

endendclass: bus_item
Designing a Sequence Item: Methods
do_copy()
class bus_item extends uvm_sequence_item; do_compare()
`uvm_object_utils(bus_item)

function bit do_compare(uvm_object rhs,


uvm_comparer comparer);
bus_item rhs_;

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


return 0;
end
return((super.do_compare(rhs, comparer) &&
(delay == rhs_.delay) && Compare members
(addr == rhs.addr) && of rhs to this
(op_code == rhs_.op_code) &&
(slave_name == rhs_.slave_name) &&
(data == rhs_.data) && 1 = MATCH
(response == rhs_.response));
endfunction: do_compare

endendclass: bus_item
Designing a Sequence Item: Methods
do_copy()
class bus_item extends uvm_sequence_item; do_compare()
`uvm_object_utils(bus_item) convert2string()

function string convert2string();


string s;

endfunction: convert2string

endclass: bus_item
Designing a Sequence Item: Methods
do_copy()
class bus_item extends uvm_sequence_item; do_compare()
`uvm_object_utils(bus_item) convert2string()

function string convert2string();


string s;

s = super.convert2string();

endfunction: convert2string

endclass: bus_item
Designing a Sequence Item: Methods
do_copy()
class bus_item extends uvm_sequence_item; do_compare()
`uvm_object_utils(bus_item) convert2string()

function string convert2string();


string s;

s = super.convert2string();

$sformat(s,
"%s\n delay \t%0d\n addr \t%0h\n op_code \\
\t%s\n slave_name \t%s\n",
s, delay, addr, op_code.name(), slave_name);
Returns enum value
as a string

endfunction: convert2string

endclass: bus_item
Designing a Sequence Item: Methods
do_copy()
class bus_item extends uvm_sequence_item; do_compare()
`uvm_object_utils(bus_item) convert2string()

function string convert2string();


string s;

s = super.convert2string();
Iterate through
$sformat(s, array values
"%s\n delay \t%0d\n addr \t%0h\n op_code \\
\t%s\n slave_name \t%s\n",
s, delay, addr, op_code.name(), slave_name);
foreach(data[i]) begin
$sformat(s, "%s data[%0d] \t%0h\n", s, i, data[i]);
end

endfunction: convert2string

endclass: bus_item
Designing a Sequence Item: Methods
do_copy()
class bus_item extends uvm_sequence_item; do_compare()
`uvm_object_utils(bus_item) convert2string()

function string convert2string();


string s;

s = super.convert2string();

$sformat(s,
"%s\n delay \t%0d\n addr \t%0h\n op_code \\
\t%s\n slave_name \t%s\n",
s, delay, addr, op_code.name(), slave_name);
foreach(data[i]) begin
$sformat(s, "%s data[%0d] \t%0h\n", s, i, data[i]);
end
$sformat(s, "%s response \t%0b\n", s, response);
return s; USAGE:
endfunction bus_item A;
`uvm_info(“FOO”,A.convert2string(), UVM_NONE)
endclass: bus_item
Designing a Sequence Item: Methods
do_copy()
class bus_item extends uvm_sequence_item; do_compare()
`uvm_object_utils(bus_item) convert2string()
do_print()
function void do_print(uvm_printer printer);
printer.m_string = convert2string();
endfunction: do_print

USAGE:
bus_item A;
A.print();

endclass: bus_item
Designing a Sequence Item: Methods
do_copy()
class bus_item extends uvm_sequence_item; do_compare()
`uvm_object_utils(bus_item) convert2string()
do_print()
do_record()
function void do_record(uvm_recorder
recorder);
super.do_record(recorder);
Record inherited
data members

endfunction: do_record

endclass: bus_item
Designing a Sequence Item: Methods
do_copy()
class bus_item extends uvm_sequence_item; do_compare()
`uvm_object_utils(bus_item) convert2string()
do_print()
do_record()
function void do_record(uvm_recorder
recorder);
super.do_record(recorder);
`uvm_record_field("delay", delay)
`uvm_record_field("addr", addr)
`uvm_record_field("op_code", op_code.name())
`uvm_record_field("slave_name", slave_name)
Simulator-specific implementation
Questa uses $add_attribute

endfunction: do_record

endclass: bus_item
Designing a Sequence Item: Methods
do_copy()
class bus_item extends uvm_sequence_item; do_compare()
`uvm_object_utils(bus_item) convert2string()
do_print()
do_record()
function void do_record(uvm_recorder
recorder);
super.do_record(recorder);
`uvm_record_field("delay", delay)
`uvm_record_field("addr", addr)
`uvm_record_field("op_code", op_code.name())
`uvm_record_field("slave_name", slave_name)
foreach(data[i]) begin Iterate through array values
`uvm_record_field($sformatf("data[%0d]", i), data[i])
end
`uvm_record_field("response", response)
endfunction: do_record

USAGE:
uvm_config_db#(int)::set(this,”*”,”recording_detail”, UVM_FULL);

endclass: bus_item
Designing a Sequence Item: Methods
do_copy()
class bus_item extends uvm_sequence_item; do_compare()
`uvm_object_utils(bus_item) convert2string()
do_print()
do_record()
function void do_pack(uvm_packer packer); do_pack()
super.do_pack(packer); do_unpack()

endfunction: do_pack

function void do_unpack(uvm_packer packer);


super.do_unpack(packer);

endfunction: do_unpack

See the
Online Methodology Cookbook
for details

endclass: bus_item
Sequence Item Composition
bus_item my_bus_item
extends uvm_sequence_item; extends bus_item;
rand int delay; bit status;
rand logic[31:0] addr; logic[31:0] result;
rand op_code_enum op_code; my_bus_item
rand logic[31:0] data[]; extends bus_item;

Extension constraint c {addr >= 0 &&


addr < ‘h100;}
Composition

bus_item_pair
extends uvm_sequence_item;
rand bus_item a;
rand bus_item b;
Modeling Sequence Items
• Encapsulate the information needed to
process an operation
• Whatever that means for your application
• Helper functions
• do_copy() • do_print()
• do_compare() • do_record()
• convert2string() • do_pack()/do_unpack()
• Do not use `uvm_field* macros
• Decreases performance
• Hinders debug
• Use inheritance for similar transactions
• Use composition when needed
Summary: Rules for Sequence Items
• Define sequence items by specifying data
members only
• Do not override pre/mid/post_do
• Create items via their type_id
my_item::type_id::create(“tx”);
• Execute items using start_item()/finish_item()
Advanced UVM
The Proper Care and
Feeding of Sequences

Tom Fitzpatrick
Verification Evangelist

academy@mentor.com
www.verificationacademy.com
Separating Stimulus from the Testbench
• A key to reusability is to separate Behavior
from Structure

Behavior
Structure

DUT
Sequences
• Decouple stimulus specification
from structural hierarchy
• Add/remove/modify stimulus
scenarios independent of testbench u1
• Simplify test writer API u1 s1
• Sequences define
transaction streams
• May start on any sequencer s3 s5

s2
• Sequences can call children
• Sequences & transactions s4

customizable via the factory


Review: Sequence Basics
class my_seq extends uvm_sequence #(req_t);
`uvm_object_utils(uvm_seq) Transaction type
req_t req;
Default name in constructor
int num = 10;

function new(string name = “my_seq”);


super.new(name);
endfunction

task body();
uvm_config_db#(int)::get(this, “”, “num”, num);
req = req_t::type_id::create(“req”); Create request
for_int i = 0; i < num; i++) begin Initiate Driver Handshake
start_item(req);
if(!req.randomize()) begin Late Randomization
`uvm_error(“body”, “rand failure”) Note: begin-end around `uvm_<msg>
end
finish_item(req);
end Send transaction
endtask
endclass
Review: Sequence/Driver Handshake
my_seq1 driver

start_item(req); get_next_item(req);

finish_item(req);

item_done();

task body(); task run_phase(uvm_phase phase);


req = req_t::type_id::create(“req”); forever begin
for_int i = 0; i < num; i++) begin seq_item_port.get_next_item(req);
start_item(req); drive_item2bus(req);
if(!req.randomize()) begin seq_item_port.item_done();
`uvm_error(“body”, “rand failure”) end
end endtask
finish_item(req);
end
endtask
Review: Sequence/Driver Handshake
my_seq1 driver

start_item(req); get_next_item(req);

finish_item(req);

item_done();

get_response(rsp);
put_response(rsp);

task body(); task run_phase(uvm_phase phase);


req = req_t::type_id::create(“req”); forever begin
for_int i = 0; i < num; i++) begin seq_item_port.get_next_item(req);
start_item(req); drive_item2bus(req,rsp);
if(!req.randomize()) begin seq_item_port.item_done();
`uvm_error(“body”, “rand failure”) rsp.set_id_info(req);
end put_response(rsp);
finish_item(req); end
get_response(rsp); endtask
end
endtask
Review: Starting a Sequence

class test1 extends uvm_test;

`uvm_component_utils(test1)

my_env my_env_h;
...

task run_phase(uvm_phase phase);


read_modify_write seq;
seq = read_modify_write::type_id::create(“seq”);

Create sequence
via factory
Review: Starting a Sequence

class test1 extends uvm_test;

`uvm_component_utils(test1)

my_env my_env_h;
...

task run_phase(uvm_phase phase);


read_modify_write seq;
seq = read_modify_write::type_id::create(“seq”);
phase.raise_objection(this);
seq.start( my_env_h.my_agent_h.my_sequencer_h );
Always use start() to
execute a sequence
Review: Starting a Sequence

class test1 extends uvm_test;

`uvm_component_utils(test1)

my_env my_env_h;
...

task run_phase(uvm_phase phase);


read_modify_write seq;
seq = read_modify_write::type_id::create(“seq”);
phase.raise_objection(this);
seq.start( my_env_h.my_agent_h.my_sequencer_h );

Path to sequencer
Can Start Sequence from Environment too

class my_env extends uvm_env;

`uvm_component_utils(my_env)
Factory enables test to
my_agent my_agent_h; choose what default
... sequence to run

task run_phase(uvm_phase phase);


read_modify_write seq;
seq = read_modify_write::type_id::create(“seq”);
phase.raise_objection(this);
seq.start( my_agent_h.my_sequencer_h );

phase.drop_objection(this); Path to sequencer


endtask
Sequential Sequences

class my_test extends uvm_test;



init_seq iseq;
exec_seq eseq;
...
task run_phase(uvm_phase phase);
iseq = init_seq::type_id::create(“iseq”);
eseq = exec_seq::type_id::create(“eseq”);
phase.raise_objection(this);
iseq.start( my_agent_h.my_sequencer_h );
eseq.start( my_agent_h.my_sequencer_h );
start() is blocking

phase.drop_objection(this);
endtask
endclass
Parallel Sequences

class my_test extends uvm_test;



init_seq iseq;
exec_seq eseq;
...
task run_phase(uvm_phase phase);
iseq = init_seq::type_id::create(“iseq”);
eseq = exec_seq::type_id::create(“eseq”);
phase.raise_objection(this);
fork
iseq.start( my_agent_h.my_sequencer_h );
eseq.start( my_agent_h.my_sequencer_h );
join
Don’t use fork-join_none

phase.drop_objection(this);
endtask
endclass
Parallel Sequences

class my_test extends uvm_test;



init_seq iseq;
exec_seq eseq;
...
task run_phase(uvm_phase phase);
iseq = init_seq::type_id::create(“iseq”);
eseq = exec_seq::type_id::create(“eseq”);
phase.raise_objection(this);
fork
iseq.start( my_agent_h.my_sequencer_h, this, HIGH );
eseq.start( my_agent_h.my_sequencer_h, this, LOW );
join

phase.drop_objection(this);
endtask Optional
endclass priority
Hierarchical Sequences
Hierarchical Sequences

class test_seq extends uvm_sequence #(req,rsp);


`uvm_object_utils(test_seq)
init_seq iseq;
exec_seq eseq;

task body();
iseq = init_seq::type_id::create(“iseq”);


endtask
endclass
Hierarchical Sequences

class test_seq extends uvm_sequence #(req,rsp);


`uvm_object_utils(test_seq)
init_seq iseq;
exec_seq eseq;

task body();
iseq = init_seq::type_id::create(“iseq”);

iseq.start( m_sequencer, this );


endtask
endclass
Hierarchical Sequences

class test_seq extends uvm_sequence #(req,rsp);


`uvm_object_utils(test_seq)
init_seq iseq;
exec_seq eseq;

task body();
iseq = init_seq::type_id::create(“iseq”);

iseq.start( m_sequencer, this );


endtask Run on test_seq’s
sequencer
endclass
Hierarchical Sequences

class test_seq extends uvm_sequence #(req,rsp);


`uvm_object_utils(test_seq)
init_seq iseq;
exec_seq eseq;

task body();
iseq = init_seq::type_id::create(“iseq”);

iseq.start( m_sequencer, this );


Optional parent
… sequence specifier
endtask
endclass
Hierarchical Sequences

class test_seq extends uvm_sequence #(req,rsp);


`uvm_object_utils(test_seq)
init_seq iseq;
exec_seq eseq;

task body();
iseq = init_seq::type_id::create(“iseq”);
eseq = exec_seq::type_id::create(“eseq”);
iseq.start( m_sequencer, this );


endtask
endclass
Hierarchical Sequences

class test_seq extends uvm_sequence #(req,rsp);


`uvm_object_utils(test_seq)
init_seq iseq;
exec_seq eseq;

task body();
iseq = init_seq::type_id::create(“iseq”);
eseq = exec_seq::type_id::create(“eseq”);
iseq.start( m_sequencer, this );
eseq.start( m_sequencer, this );

endtask
endclass

“top.env.agent.sequencer.test_seq.eseq”
Summary: General Rules
• Start sequences using seq.start(sequencer)
• Use seq_item_port.get_next_item/item_done
in the driver
• Use try_next_item/item_done if driver must perform
idle cycles
• Use uvm_config_db#()::get() to configure
sequences
• Sequence and Driver must agree on response
path, if any
Advanced UVM
Layered Sequences

Tom Fitzpatrick
Verification Evangelist

academy@mentor.com
www.verificationacademy.com
Sequences & Sequencers
• Most sequences run on sequencers
• One sequencer per agent
• Env may define default sequence
• Can be overridden via factory Test

• Test defines other sequences


Env
• Sequences generate
items Sequencer

• Sequencer sends to driver Agent

Monitor Driver

DUT
Sequences & Sequencers
• Tests require coordinating multiple
sequences on multiple DUT interfaces

Test

Env

Sequencer Sequencer

Driver Driver

DUT
Virtual Sequences
typedef uvm_sequence #(uvm_sequence_item)
uvm_virtual_sequence; 2 1
tseq
Useful typedef B A
class myvseq_base extends uvm_virtual_sequence;
... Env
agent2 agent1
a_sequencer_t a_sequencer; Sqr
b_sequencer_t b_sequencer; Handles for target Drvr Drvr
sequencers
task body();
...
aseq.start( a_sequencer , this ); Start sequences on target
bseq.start( b_sequencer , this ); sequencers
endtask
endclass class my_test extends uvm_test;

my_seq vseq = my_seq::type_id::create("vseq");
Start vseq with
null sequencer vseq.a_sequencer = env.agent1.sequencer;
vseq.b_sequencer = env.agent2.sequencer;
vseq.start( null );
endclass
Virtual Sequence Initialization
class test_base extends uvm_test;
`uvm_component_utils(test_top_base) 2 1
tseq
B A
env_top m_env;
Env
agent2 agent1
function new(string name = "test_top_base", Sqr
uvm_component parent = null);
Drvr Drvr
super.new(name, parent);
endfunction

function void build_phase(uvm_phase phase);


m_env = env_top::type_id::create("m_env", this);
endfunction: build_phase

function void init_vseq(top_vseq_base vseq);


vseq.A = m_env.m_agent_1.m_sequencer; Method to initialize the
vseq.B = m_env.m_agent_2.m_sequencer; virtual sequence handles
endfunction: init_vseq

endclass: test_base
Extend base test Virtual Sequence in a Test
class init_vseq_test extends test_base;
`uvm_component_utils(init_vseq_test) 2 1
tseq
B A
function new(string name = "init_vseq_test",
uvm_component parent = null); Env
agent2 agent1
super.new(name, parent); Sqr
endfunction
Drvr Drvr

task run_phase(uvm_phase phase);


vseq_A_B vseq = vseq_A_B::type_id::create("vseq");

phase.raise_objection(this);
init_vseq(vseq);
vseq.start(null);
phase.drop_objection(this);
endtask: run_phase

endclass: init_vseq_test
Virtual Sequence in a Test
class vseq_A_B extends myvseq_base;
`uvm_object_utils(vseq_A_B) 2 1
tseq
B A
function new(string name = "vseq_A_B");
super.new(name); Env
agent2 agent1
endfunction Sqr
Drvr Drvr
task body();
a_seq a = a_seq::type_id::create("a");
b_seq b = b_seq::type_id::create("b");

fork
a.start(A);
b.start(B);
join
endtask: body

endclass: vseq_A_B
Layered Protocols
• Hierarchical Protocols (PCI Express, USB3.0,
MIPI LLI…)
• Transaction Layer
• Transport Layer
• Physical Layer
• Protocol-Independent
• Generic Layer (e.g. TLM2.0 GP)
• Specific Protocol (e.g. AMBA AHB)
• All require Sequence Items to be
deconstructed and reconstructed
high
• One-to-many
low
low

• Many-to-one high
low
high
Tests Start Sequences
• Want to execute sequences at the top layer
• Test starts sequence on sequencer
• Reuse as much as possible
• Protocol UVC on the bus
• Sequencers/monitors at higher layers

class prot_test extends uvm_test;


`uvm_component_utils(prot_test)

UVC(agent)
virtual task run_phase(uvm_phase phase);
my_seq = prot_seq::type_id::create(“my_seq”);
Monitor
my_seq.start(dut_agent.sequencer);

Sequencer
endtask DUT
prot Driver
endclass
Adding a Layer
• From “above,” the layer looks like a UVC
• Run sequence(s) on sequencer
• Monitor (analysis_port) to report activity
• Must be able to execute sequences on
intermediate layer
• Background traffic
• But how do we reuse the Protocol Agent?
class myL1_test
prot_test extends uvm_test;
`uvm_component_utils(prot_test)
`uvm_component_utils(myL1_test)
UVC(agent)

L1_mon Monitor
virtual task run_phase(uvm_phase phase);
my_seq =
prot_seq::type_id::create(“my_seq”);
myL1_seq::type_id::create(“my_seq”); L1_seqr Sequencer
DUT
L1 bkgr Driver
my_seq.start(dut_agent.sequencer);
my_seq.start(my_layer1_sequencer);

endtask
endclass This connection is the key
The Translation Sequence
Generates downstream item
class myXL_seq
extends uvm_sequence #(dut_txn); • Translation
`uvm_object_utils(myXL_seq)
Sequence runs on
uvm_sequencer #(L1_item) up_sequencer;
the lower
function new(string name=“”);
super.new(name); sequencer
endfunction
• Handles the layering
virtual task body();
L1_item l;
connection
Note Syntax
dut_txn d;
forever begin
• Converts items
up_sequencer.get_next_item(l); between layers
foreach(l.buf[i]) begin
d = dut_txn::type_id::create();
start_item(d); UVC(agent)
d.data = l.buf[i];
finish_item(d); Monitor
end
L1_seqr Sequencer
Sqr
up_sequencer.item_done();
DUT
end xl Driver
L1 D3
D2
D1
Sqr
endtask
endclass
The Analysis Path
Provides analysis_export
class D2L_monitor • Each layer uses
extends uvm_subscriber #(dut_txn);
`uvm_component_utils(D2L_monitor) a reconstruction
uvm_analysis_port#(L1_item) ap; monitor

• The “inverse” of
function new(string name, uvm_component parent);
super.new(name, parent); the translation
ap = new("ap",this);
endfunction sequence
function void write(dut_txn d); • Assembles high-
// reconstruction code omitted ...
ap.write( L1_out );
level items from
... lower-level items
endfunction
endclass UVC(agent)

L1
L1_mon D2
D1
D3
Monitor

Sequencer
function void connect_phase(uvm_phase phase); Driver DUT

dut_agent.ap.connect(d2l_mon.analysis_export);
endfunction
The Layered UVC
class D2L_layer extends uvm_subscriber #(dut_txn);
`uvm_component_utils(D2L_layer)

uvm_analysis_port#(L1_item) ap;
D2L_monitor m_mon;
uvm_sequencer#(dut_txn) m_seqr;
myXL_seq xlseq;
dut_agent d_agent;

function void build_phase(uvm_phase phase);
m_seqr = uvm_sequencer#(dut_txn)::type_id::create(“dut_seqr”, this);
m_mon = D2L_monitor::type_id::create(“m_mon”, this);
ap = new(“ap”, this);
...
endfunction

UVC(layer) UVC(agent)

endclass L1_mon Monitor

L1_seqr Sequencer
DUT
xl Driver
Sqr
The Layered UVC
class D2L_layer extends uvm_subscriber #(dut_txn);
`uvm_component_utils(D2L_layer)

uvm_analysis_port#(L1_item) ap;
D2L_monitor m_mon;
uvm_sequencer#(dut_txn) m_seqr;
myXL_seq xlseq;
dut_agent d_agent;

function void connect_phase(uvm_phase phase);
m_mon.ap.connect(ap);
analysis_export.connect(m_mon.analysis_export);
endfunction

virtual task run_phase(uvm_phase phase);


xlseq = myXL_seq::type_id::create(“xl_seq”);
xlseq.up_sequencer = m_seqr;
xlseq.start(d_agent.m_seqr); UVC(layer) UVC(agent)
endtask
endclass L1_mon Monitor

Lower sequencer accessed via UVC L1_seqr Sequencer


DUT
xl Driver
Sqr
The Layered UVC
• Connect components as usual
class layer_env extends uvm_env;
`uvm_component_utils(layer_env)

D2L_layer layer_agent;
dut_agent d_agent;

function void connect_phase(uvm_phase phase);


layer_agent.d_agent = d_agent;
d_agent.ap.connect(layer_agent.analysis_export);
endfunction
endclass

UVC2(layer) UVC(layer) UVC(agent)

L2_mon L1_mon Monitor

L2_seqr L1_seqr Sequencer


DUT
xl xl Driver
Sqr Sqr
The Multi-Layered UVC
• Encapsulate as many layers as needed
• Make intermediate analysis_ports available as
needed
• From “above” it just looks like a UVC

UVC2
UVC (layer)
(layer) UVC(layer) UVC(agent)

L2_mon L1_mon Monitor

L2_seqr L1_seqr Sequencer


DUT
xl xl Driver
Sqr Sqr
Multi-Layered UVC with Internal Agent
class D2L_layer extends uvm_subscriber #(dut_txn);
`uvm_component_utils(D2L_layer)


dut_agent d_agent;

function void build_phase(uvm_phase phase);
m_seqr = uvm_sequencer#(dut_txn)::type_id::create(“dut_seqr”, this);
m_mon = D2L_monitor::type_id::create(“m_mon”, this);
ap = new(“ap”, this);
d_agent = dut_agent::type_id::create(“d_agent”, this);
...
endfunction
endclass

UVC(layer)

UVC(layer) UVC(agent)

L2_mon L1_mon Monitor

L2_seqr L1_seqr Sequencer


DUT
xl xl Driver
Sqr Sqr
Layering Architecture
• A Layering UVC…
• Has a child sequencer for every non-leaf-level
• Creates and starts a translator sequence for every non-leaf-
level
- Translator sequence started on lower-layer sequencer
- Translator sequence points back to higher-layer sequencer
• Must have a handle to the leaf-level protocol agent
- Agent may be a child of the UVC or external
• May include a reconstruction monitor for each non-leaf-level
• Should create and connect an analysis_port for each monitor
• Will usually have a configuration object associated with it

UVC(layer) UVC(agent)

L2_mon L1_mon Monitor

L2_seqr L1_seqr Sequencer


DUT
xl xl Driver
Sqr Sqr
Advanced UVM
Writing and Managing Tests

Tom Fitzpatrick
Verification Evangelist

academy@mentor.com
www.verificationacademy.com
What is a Test?
• The environment is the “Testbench”
• Defines what components are
needed to verify the DUT
• Specifies defaults
Test
• The test’s job is to
Env Cov
“tweak” the testbench
• Configuration
• Factory overrides Sequencer

• Additional sequences Agent

Monitor Driver

DUT
Defaults in OVM

class my_env extends uvm_env; Env


int nslaves; Always call get_config
slave[0]
test_seq seq_h
my_slave slv_h[]; slave[1]
function void build_phase(uvm_phase phase);
if(!get_config_int(“nslaves”,nslaves))
nslaves = 2;
If no set_config, use default
slv_h = new[nslaves];
for(int i = 0; I < nslaves; i++) begin
$sformat(name, “slave[%0d]”,i);
slv_h[i] = my_slave::type_id::create(name, this);
end
endfunction
create() sets default type
task run_phase(uvm_phase phase);;
seq_h = my_seq::type_id::create(“my_seq”);
seq_h.start(slv_h[0]);
Start default sequence
endtask
endclass
Defaults in OVM

class my_env extends uvm_env; Env


int nslaves;
slave[0]
test_seq seq_h;
my_slave slv_h[]; slave[1]
function void build_phase(uvm_phase phase);
if(!get_config_int(“nslaves”,nslaves))
nslaves = 2;
else
assert({nslaves inside {2,8}})
else `uvm_error(“CFG”, “Illegal nslaves”);
slv_h = new[nlsaves];

… Check for legal values!


Use a Base Test to Set Defaults

class base_test extends uvm_test;


`uvm_component_utils(base_test);
my_env e;
test_seq tseq_h;
function void build_phase(uvm_phase phase);
e = my_env::type_id::create(“e”,this); Choose default environment
endfunction

function void end_of_elaboration_phase(uvm_phase phase);


tseq_h = test_seq::type_id::create(“test_seq”);
endfunction
Create default sequence
endclass
Extend Base Test to Create a Test

class my_test1 extends base_test;


`uvm_component_utils(my_test1);

Get defaults
function void build_phase(uvm_phase phase);
super.build_phase();
set_config_int(“e.nslaves”,4);
my_slave::type_id::set_inst_override( err_slave::get_type(),
Override slave[0] type “e.slave[0]” );|

test_seq::type_id::set_type_override( test1_seq::get_type());
endfunction Override default sequence type

endclass
ENV:
if(!uvm_config_db #(int)::get(this,“”,“nslaves”,nslaves))
nslaves = 2;
Setup and Invoke Test

module top;
...
dut_if dut_if1 ();

initial begin: blk


uvm_config_db#(dut_if)::set("*", "dut_if", dut_if1, 0);

+UVM_TESTNAME=“my_test1”
run_test();
end

endmodule: top
Complex Environment, Simple Test

Virtual Sequence
Test
embodies “the test”

Environment sets up Env


agents, etc Cov Score

The Test simply


chooses the virtual
sequence to run
Sqr Sqr
…and corresponding
coverage collectors and Cov Cov
scoreboard(s)

Mon Driver Mon Driver

DUT
Simple Test
+UVM_TESTNAME=“seq_test_by_type”

class seq_test_by_type extends uvm_test;

`uvm_component_utils(seq_test_by_type);
my_env e;
virt_seq vseq_h;

function void build_phase(uvm_phase phase);


e = my_env::type_id::create(“e”,this);
endfunction

function void end_of_elaboration_phase(uvm_phase phase);


vseq_h = virt_seq::type_id::create(“vseq”);
endfunction +uvm_set_type_override=virt_seq,my_virtseq

task run_phase(uvm_phase phase);


phase.raise_objection(this);
vseq_h.start(null); Start selected sequence
phase.drop_objection(this);
endtask
endclass
Extended Test
+UVM_TESTNAME=“cov_test”

class cov_test extends seq_test_by_type;

`uvm_component_utils(cov_test); Select Coverage to match


Virtual Sequence
function void build_phase(uvm_phase phase);;
super.build_phase(); Create environment
agent_cov::type_id::set_inst_override(ctrl_cov::get_type(),
“e.agent1.cov” );
my_score::type_id::set_type_override( foo_score::get_type());

endfunction
Select Scoreboard to
endclass match Virtual Sequence

+uvm_set_type_override=virt_seq,foo_virtseq
Extended Test

class cov_test extends seq_test_by_type;

`uvm_component_utils(cov_test);

function void build_phase(uvm_phase phase);;


super.build_phase();
agent_cov::type_id::set_inst_override(ctrl_cov::get_type(),
“e.agent1.cov” );
my_score::type_id::set_type_override( foo_score::get_type());
virt_seq::type_id::set_type_override(foo_virtseq::get_type());
endfunction

endclass
Explicit sequence type
override
Phase Objections
• Components or Sequences can raise or drop
objections
• Phase continues until all raised objections are
dropped
• Objection must be raised at beginning of the
phase
Objections are Hierarchical
• Objections are raised up the hierarchy

2
1

1 1
raise_objection()

1
Agent raise_objection()

Sequencer
raise_objection()

raise_objection()
Monitor

raise_objection()
Objections are Hierarchical
• Objections are raised up the hierarchy
• Objections are
dropped hierarchically too
• When a component’s 12
count = 0, 1 1
drop_objection()
wait for drain_time
1
to elapse Agent drop_objection()

Sequencer
drop_objection()

drop_objection()
Monitor

drop_objection()
Using Objections

class my_mon extends uvm_component;


...
task run_phase(uvm_phase phase);;
forever begin When transaction starts
wait(tx_start);
phase.raise_objection(this);
collect(tr);
ap.write(tr);
phase.drop_objection(this);
end
endtask What if my_mon is the only objector?
endclass
Test ends when all objections dropped
Recommended

class my_test extends uvm_test;


Manage phase
... objections from the test
task run;
phase.raise_objection(this);
vseq_h.start(null);
phase.drop_objection(this);
endtask
endclass
Requires that vseq_h eventually returns
Recommendation: Objecting in a Monitor

class my_mon extends uvm_component; Object at end of phase


...
function void phase_ready_to_end( uvm_phase phase );
if( !is_ok_to_end() ) begin
phase.raise_objection( this , "not done yet" );
fork begin
wait_for_ok_end();
phase.drop_objection( this , "ok to end phase" );
end
join_none
end
endfunction : phase_ready_to_end
endclass
Summary
• The environment is the “Testbench”
• Defines what components are
needed to verify the DUT
• Specifies defaults
Test
• The test’s job is to
Env Cov
“tweak” the testbench
• Configuration
• Factory overrides Sequencer

• Additional sequences Agent


• Callbacks
Monitor Driver
• The test’s other job is to
ensure the simulation ends
DUT
Summary

Test

Env
Cov Score

Sqr Sqr

Cov Cov

Mon Driver Mon Driver

DUT
Advanced UVM
Setting Up the Register Layer

Tom Fitzpatrick
Verification Evangelist

academy@mentor.com
www.verificationacademy.com
UVM Registers are Layered
• UVM Register Layer provides
protocol-independent
register-based layering

UVM Reg Layer UVC(agent)

Configuration
Predict Object Monitor

RegSeq Sequencer
DUT
Driver

Device specific Bus specific


cfg.write(0xDE); wr(0xAF, 0xDE);
UVM Register Use Models
• Stimulus Generation
• Firmware-level abstraction of stimulus:
- i.e. Set this bit in this register rather than write x to address y
• Stimulus reuse
- If the bus agent changes, the stimulus still works
• Front and Back Door access:
- Front door is via an agent
- Back door is directly to the hardware via the simulator database
• Configuration
• Register model reflects hardware programmable registers
• Set up desired configuration in register model then dump to
DUT
- Randomization with configuration constraints
• Analysis ‘Mirror’
• Current state of the register model matches the DUT
hardware
• Useful for scoreboards and functional coverage monitors
Registers, Blocks & Maps

31:14 13 12 11 10 9 8 7 6:0
Registers contain R R/W R/W R/W R/W R/W R/W R R/W
Reserved ASS IE LSB TxNeg RxNeg GoBsy Rsrv Char_Len
bits & fields
class csr_reg extends uvm_reg;
`uvm_object_utils(csr_reg)

uvm_reg_field reserved;
rand uvm_reg_field char_len;

function new(string name = “char_len");


super.new(name, 7, UVM_NO_COVERAGE);
endfunction

virtual function void build();


char_len = uvm_reg_field::type_id::create(“char_len");
char_len.configure(this, 7, 0, "RW", 0, 7'h7f, 1, 1, 1);
endfunction
endclass
Registers, Blocks & Maps
R R/W R/W R/W R/W R/W R/W R R/W
Reserved ASS IE LSB TxNeg RxNeg GoBsy Rsrv Char_Len

Address Map
points to Registers
R R/W R/W R/W R/W R/W R/W R R/W
Reserved ASS IE LSB TxNeg RxNeg GoBsy Rsrv Char_Len
Registers, Blocks & Maps
R R/W R/W R/W R/W R/W R/W R R/W
class spi_reg_block extends uvm_reg_block; Reserved ASS IE LSB TxNeg RxNeg GoBsy Rsrv Char_Len
`uvm_object_utils(spi_reg_block)
Address Map
rand csr_reg csr;
points to Registers
uvm_reg_map APB_map; // Block map
Register Block
function new(string name = "spi_reg_block");
super.new(name, UVM_NO_COVERAGE); contains Maps
endfunction
One Map per
virtual function void build();
csr = csr_reg::type_id::create("csr");
physical interface
csr.configure(this, null, "");
csr.build();
csr.add_hdl_path_slice("csr", 0, 7);
csr.add_hdl_path_slice(“csr_dff.q”, 0, 7, “GATES”);

APB_map = create_map("APB_map", 'h800, 4, UVM_LITTLE_ENDIAN);


APB_map.add_reg(csr, 32'h00000014, "RW");
add_hdl_path("DUT", "RTL"); DUT(RTL)
DUT(GATES)
add_hdl_path(“DUT”, “GATES”); 6 0
lock_model(); csr_dff csr
q
endfunction: build

endclass: spi_reg_block
Registers, Blocks & Maps
R R/W R/W R/W R/W R/W R/W R R/W
class soc_block extends uvm_reg_block; Reserved ASS IE LSB TxNeg RxNeg GoBsy Rsrv Char_Len
`uvm_object_utils(soc_block)
Address Map
spi_reg_blk spi_regs;
wsh_reg_blk wsh_regs; contains Registers
function new(string name = "soc_block"); Register Block
super.new(name, UVM_NO_COVERAGE);
endfunction contains Maps
virtual function void build(); One Map per
default_map = create_map("", 0, 1,
UVM_LITTLE_ENDIAN);
physical interface

spi_regs = spi_reg_blk::type_id::create( Blocks are


“spi_regs”,,get_full_name()); hierarchical
spi_regs.configure(this, “spi_regs”);
spi_regs.build();
default_map.add_submap(spi_regs.default_map, ‘h0000);

default_map.add_submap(wsh_regs.default_map, ‘h1000);
endfunction: build
endclass: spi_reg_block
The Register Map – uvm_reg_map
• Contains offsets for:
• Registers and Memories
• (Hierachical blocks)
• (Sub-maps)
• Also provides means to SQR

access registers
• Handle for target sequencer
SQR
• Handle for register layer adapter
• A block can have > 1 map
UVC(agent)

• AXI Master1 Monitor

• AXI Master2 (Fabric)


Sequencer
DUT
Driver
Setting Up the Register Map
class spi_env extends uvm_env;
`uvm_component_utils(spi_env)

function void build_phase(uvm_phase phase);


if(!uvm_config_db #(spi_env_config)::get(this, "", "spi_env_config",
m_cfg)) begin
`uvm_error("build_phase", "Failed to find spi_env_config")
end

endfunction:build_phase

function void connect_phase(uvm_phase phase);


if(m_cfg.m_apb_agent_cfg.active == UVM_ACTIVE) begin
reg2apb = reg2apb_adapter::type_id::create("reg2apb");
if(m_cfg.spi_rm.get_parent() == null) begin
SQR
m_cfg.spi_rm.APB_map.set_sequencer(
m_apb_agent.m_sequencer, reg2apb);
m_cfg.spi_rm.APB_map.set_auto_predict(0);//default
end
UVC(agent)
endfunction:connect_phase
endclass Monitor

Sequencer
DUT
Driver
How Do Register Accesses Work?
• When an explicit register access method is
called
• The register layer uses a generic register command:
- Address, Data, Read or Write
• This is then sent through a layering
to the target bus agent
• The layering has to convert: B

• Generic register requests to SQR

target bus sequence items


• This conversion takes UVC(agent)

Monitor
place in the adapter
RegSeq
• Extended from Sequencer
Driver B DUT

uvm_reg_adapter Reg
How Do Register Accesses Work?
• The predictor updates the value of the
register model
• Bus transaction (from monitor) converted back to Reg
transaction
• Write: Value that was written to DUT is reflected
• Read: Value that was read from DUT is reflected
• The predictor then writes the Reg

register transaction out its ap SQR

• Generic register requests to


target bus sequence items UVC(agent)
Predict
B
Monitor

RegSeq
Sequencer
DUT
Driver
Register Adapter Class Example
class reg2ahb_adapter extends uvm_reg_adapter;
`uvm_object_utils(reg2ahb_adapter) reg2bus() converts register
function new(string name = "reg2ahb_adapter");
operation to bus item
super.new(name); Note single access only
endfunction

virtual function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);


ahb_seq_item ahb = ahb_seq_item::type_id::create("ahb");
ahb.HWRITE = (rw.kind == UVM_READ) ? AHB_READ : AHB_WRITE;
ahb.HADDR = rw.addr; uvm_reg_bus_op
ahb.DATA = rw.data; is a struct
return ahb;
endfunction

virtual function void bus2reg(uvm_sequence_item bus_item,


ref uvm_reg_bus_op rw);
ahb_seq_item ahb;
if (!$cast(ahb, bus_item)) begin bus2reg() converts bus item
`uvm_fatal("NOT_AHB_TYPE",“Wrong type for bus_item")
end
to reg operation
rw.kind = (ahb.HWRITE == AHB_READ) ? UVM_READ : UVM_WRITE;
rw.addr = ahb.HADDR;
rw.data = ahb.DATA;
rw.status = ahb.status ? UVM_IS_OK : UVM_NOT_OK;
endfunction

endclass: reg2ahb_adapter UVM_HAS_X also legal


Register Adapter Class Example
class reg2ahb_adapter extends uvm_reg_adapter;
`uvm_object_utils(reg2ahb_adapter)
reg2bus() converts register
function new(string name = "reg2ahb_adapter"); operation to bus item
super.new(name); Note single access only
endfunction

virtual function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);


ahb_seq_item ahb = ahb_seq_item::type_id::create("ahb");
ahb.HWRITE = (rw.kind == UVM_READ) ? AHB_READ : AHB_WRITE;
ahb.HADDR = rw.addr;
bus2reg() converts bus item
ahb.DATA = rw.data;
return ahb; to reg operation
endfunction virtual function void bus2reg(uvm_sequence_item bus_item,
ref uvm_reg_bus_op rw);
ahb_seq_item ahb;
if (!$cast(ahb, bus_item)) begin
`uvm_fatal("NOT_AHB_TYPE",“Wrong type for bus_item")
return;
end
rw.kind = (ahb.HWRITE == AHB_READ) ? UVM_READ : UVM_WRITE;
rw.addr = ahb.HADDR;
rw.data = ahb.DATA;
rw.status = ahb.status ? UVM_IS_OK : UVM_NOT_OK;
endfunction
endclass: reg2ahb_adapter
Register Model Testbench Integration
class spi_env extends uvm_env;
Register adapter specific to
apb_agent m_apb_agent; bus agent
spi_env_config m_cfg;
Predictor is a parameterized
// Register layering adapter:
reg2apb_adapter reg2apb; uvm base class
// Register predictor:
uvm_reg_predictor #(apb_seq_item) apb2reg_predictor;

function void spi_env::connect_phase(uvm_phase phase);


if(m_cfg.ss_rm == null) begin
`UVM_FATAL(“spi_env”, “No Register Model found in m_cfg”)
end else begin
reg2apb = reg2apb_adapter::type_id::create("reg2apb");
// Register sequencer layering part:
m_cfg.ss_rm.TOP_map.set_sequencer(m_apb_agent.m_sequencer, reg2apb);
// Set the predictor map:
apb2reg_predictor.map = m_cfg.ss_rm.TOP_map;
Predictor is integrated
// Set the predictor adapter:
apb2reg_predictor.adapter = reg2apb; during the connect phase
// Connect the predictor to the bus agent monitor analysis port
m_apb_agent.ap.connect(apb2reg_predictor.bus_in);
end
endfunction: connect_phase
Stimulus Reuse (Bridge Example)
• SPI master is integrated inside an AHB
peripheral block
• Host bus sequences can be reused as is
• Testbench structure changes
AHB to APB
Bridge
SPI Master
SPI Host Bus
Sequence AHB
APB
Bus Agent
APB SPI

Another DUT
Another DUT
APB ANI
Another DUT
APB ANI
APB ANI
UVM Register Package Generation

Optional Blocks & Block Maps

Register Definitions
Customer Example
Early in project:
335 Registers  11,500 lines

Final project:
1,000 Registers  35,000+ lines
of Register Package code
16
Register Assistant* Overview
• Central, Scalable & Extensible
Register/Memory Datamodel
• Enables easy specification of registers
• Manages register changes
• Eliminates hand coding & resultant mistakes
• Completely customizable
• Automatically Generates
Register Outputs
• UVM and OVM
• Synthesizable RTL
• Documentation
• Extensive roadmap Supports the entire design team
* Included with Certe Testbench Studio
Register Documentation Generation
• Communicate the register layer to all team
members
• Final documents auto-generated
• Customizable content & style
Summary
Template
Generated

SQR

UVC(agent)
Predict Generated by
Monitor Register
Assistant
RegSeq
Sequencer DUT
Driver
Advanced UVM
Register-Based Testing

Tom Fitzpatrick
Verification Evangelist

academy@mentor.com
www.verificationacademy.com
Register-Based Analysis Components
• The register model mirror is used by analysis
components
• Scoreboards to check current DUT configuration
- Where this may affect the checking algorithm
• Functional coverage monitors
- What is the configuration at a triggered sample?
• Analysis components use the register model
passively:
• get()
• Backdoor read() or peek() accesses
• Alternatively they look up the register model
values using
• uvm_reg_field.value
• uvm_reg.value – aggregate of field values
Register Scoreboard Guidelines
• Scoreboard needs a handle to the register
model
• Scoreboard accesses register values
• via handle to register (spi_rm.ctrl.ie.value)
• or calling register.get() (spi_rm.ctrl.ie.get())
• Scoreboard checks DUT register contents
• Compare observed data vs.
register contents
• Compare DUT contents vs.
expected
- via peek access to DUT
• Use predict() to set mirrored
value
- Use mirror() to read & check
- Via scoreboard or sequence
Scoreboard Checking
• Actual vs. register contents
rdata = spi_rm.rxtx_reg.get();
if(rdata != mosi_data)
error = 1;

• DUT contents vs. expected


spi_rm.rxtx_reg.peek(status, pdata);
if(pdata != miso_data)
error = 1;
assert(spi_rm.rxtx_reg.predict(miso_data));
Functional Coverage Monitors
• The register model has built-in functional
coverage
UVM_NO_COVERAGE (0) - None
UVM_CVR_REG_BITS (1) - Individual register bits
UVM_CVR_ADDR_MAP (2) - Individual register and memory
addresses
UVM_CVR_FIELD_VALS (4) - Field values
UVM_CVR_ALL (-1) - All coverage models

• A custom functional coverage monitor lets


you sample based on significant events
• Interrupts
• Writes to certain ‘trigger’ registers
• Register Assistant generates an ‘intelligent’
register access covergroup
Currently limited to
• included in the register package UVM_CVR_ADDR_MAP
Functional Coverage Monitor Example
class spi_reg_functional_coverage extends
uvm_subscriber #(apb_seq_item);
`uvm_component_utils(spi_reg_functional_coverage)
spi_reg_block spi_rm;

covergroup combination_cov;
option.per_instance = 1;
ASS: coverpoint spi_rm.ctrl_reg.ass.value[0];
IE: coverpoint spi_rm.ctrl_reg.ie.value[0];
LSB: coverpoint spi_rm.ctrl_reg.lsb.value[0];
TX_NEG: coverpoint spi_rm.ctrl_reg.tx_neg.value[0];
RX_NEG: coverpoint spi_rm.ctrl_reg.rx_neg.value[0];
// Suspect character lengths - there may be more ....
CHAR_LEN: coverpoint spi_rm.ctrl_reg.char_len.value[6:0] {
bins LENGTH[] = {0, 1, [31:33], [63:65], [95:97], 126, 127};
}
CLK_DIV: coverpoint spi_rm.divider_reg.ratio.value[15:0] {
bins RATIO[] = {16'h0, 16'h1, 16'h2, 16'h4, 16'h8,
16'h10, 16'h20, 16'h40, 16'h80};
}
COMB_CROSS: cross ASS, IE, LSB, TX_NEG, RX_NEG, CHAR_LEN, CLK_DIV;
endgroup: combination_cov Covergroup checks that all interesting SPI
Master configurations have been checked.
Functional Coverage Monitor Example
class spi_reg_functional_coverage extends
uvm_subscriber #(apb_seq_item);
`uvm_component_utils(spi_reg_functional_coverage)
spi_reg_block spi_rm;

function void write(T t);


// Sample the combination covergroup when go_bsy is true
if(address == 5'h10) begin
if(wnr) begin
if(t.data[8] == 1) begin
combination_cov.sample(); // TX started
end
end
end Covergroup sampled when a transfer starts,
endfunction: write could be on an interrupt to indicate that a
transfer has completed
Coding Guideline
• Always wrap a covergroup in a uvm_object
wrapper
class covergroup_wrapper extends uvm_object; Wrapper class can be
`uvm_object_utils(covergroup_wrapper) overridden from the factory

covergroup cg (string name) with function sample(my_reg reg, bit is_read);


option.name = name;
PARITY: coverpoint reg.parity {
bins parity_on = {1'b1}; bins parity_off = {1'b0};}
ALL_OPTIONS: cross CHAR_LEN, PARITY;
endgroup: cg

function new(string name = "covergroup_wrapper");


super.new(name);
cg = new(); Covergroup can be (conditionally)
endfunction constructed at any time
function void sample();
cg.sample();
endfunction: sample

endclass: covergroup_wrapper
Monitor with Wrapped Covergroup
class spi_reg_functional_coverage extends
uvm_subscriber #(apb_seq_item);
`uvm_component_utils(spi_reg_functional_coverage)
spi_reg_block spi_rm;

combination_cov_wrapper cg;

function new(string name = "covergroup_wrapper");


super.new(name);
cg = new();
endfunction

function void write(T t);


// Sample the combination covergroup when go_bsy is true
if(address == 5'h10) begin
if(wnr) begin
if(t.data[8] == 1) begin
cg.sample();
end Covergroup checks that all interesting SPI
end Master configurations have been checked.
end
Modelling Memory
• The register model provides access to
memory regions
• mem.read()/write() to location x in memory y
• The memory location address offset is calculated:
mem.read(status, offset_addr, data, ….)
mem.write(status, offset_addr, data, ….)

• The model does not shadow memory regions


• DUT memories are usually modelled separately
• Maintaining a memory shadow is expensive
• No set()/get() functions available
• Memory accesses can support bursts
• mem.read_burst()
• mem.write_burst()
Example Memory Based Sequence
class mem_1_test_seq extends mem_ss_base_seq;
`uvm_object_utils(mem_1_test_seq)
Buffers for the addresses
uvm_reg_addr_t addr_array[10]; and the write data
uvm_reg_data_t data_array[10];
Write loop Randomize data
task body;
super.body(); Constrain address to be
for(int i = 0; i < 10; i++) begin within the memory range
assert(this.randomize() with {addr <= mem_ss_rm.mem_1.get_size();});
mem_ss_rm.mem_1.write(status, addr, data, .parent(this));
addr_array[i] = addr; Write to randomized addresses
data_array[i] = data;
end
Read loop Read back from
for(int i = 0; i < 10; i++) begin stored addresses
mem_ss_rm.mem_1.read(status, addr_array[i], data, .parent(this));
if(data_array[i][31:0] != data[31:0]) begin
`uvm_error("mem_1_test", Check against stored data
$sformatf("Memory access error: expected %0h,
actual %0h", data_array[i][31:0], data[31:0]))
end
end
endtask: body
endclass: mem_1_test_seq
Memory Built-In Sequences
Sequence Name Description
uvm_mem_single_walk_seq Walking ones bit test for a single memory
uvm_mem_walk_seq Walking ones bit test for all memories within a
block
uvm_mem_single_access_seq Checks front and back door accesses to a single
memory
uvm_mem_access_seq Checks front and back door accesses to all
memories in a block
uvm_mem_shared_access_seq Where a memory is present in several maps,
checks that all mapped access combinations work

Aggregated Sequences:
Sequence Name Description
uvm_reg_mem_shared_access_seq Runs reg_access_seq and
mem_access_seq on all the registers and
memories available in that block
uvm_reg_mem_built_in_seq Runs all of the built in sequences on a block
Register Summary
• Register Block contains
• Register model • Address Map
- Fields • Sub-blocks
• Register analysis components have register
block pointer
• Access via get() or backdoor read/peek
• Use model.reg.value directly
• Wrap covergroups to increase flexibility
• Use built-in test sequences for sanity
checking
• Registers and Memories

Potrebbero piacerti anche