u v m guide
u v m guide
&
UVM #2
(Combined):
UVM Fundamentals
Prepared By:
SemiColon; Digital Team; Amr El Batarny
Presenter:
SemiColon; Digital Team; Amr El Batarny
Session 6
(UVM #1):
Transitioning to UVM:
A New Perspective on
Verification Design
Prepared By:
SemiColon; Digital Team; Amr El Batarny
Presenter:
SemiColon; Digital Team; Amr El Batarny
Session 6, UVM-1
Outlines
E l Bata
▪ Introduction: From MonolithicrTestbenches to UVMr
m
▪ Step 0: Shifting Perspectives
n
▪ Example Design
y
▪ A Conventional Testbench for the TinyALU
▪ The Testbench File
▪ SystemVerilog Interfaces and Bus Functional Models
▪ Creating a Modular Testbench
▪ Review OOP
▪ Classes and Extension
▪ Polymorphism
▪ Static Methods and Variables
▪ Parameterized Class Definitions
Introduction to the Course: From Monolithic
Testbenches to UVM
▪ Refrences: E l Bata
r r
m
▪ The UVM Primer (by Ray Salemi)
n
▪ UVM User’s Guide (by Accelera)
y
▪ UVM Cookbook (by Siemens)
Introduction: From Monolithic Testbenches
to UVM
▪ Course Overview: E l Bata
r r
m
▪ This course takes you on a step-by-step journey from a basic, monolithic testbench
n
towards adopting the Universal Verification Methodology (UVM).
y
▪ Each step introduces concepts and practices that gradually build a modular,
scalable, and reusable verification environment.
n
modular design.
y
▪ Learn how to transition from viewing verification as code files to seeing it as
structured components and dynamic objects.
▪ Gradually build up the skills needed to implement and leverage UVM
effectively in your verification projects.
n
just files and code.
y
▪ Instead, see it as a collection of interconnected
components that build the verification structure.
n
▪ Enables a structured, maintainable approach that grows with the design, reducing complexity
A
and enhancing reusability.
y
Example Design: TinyALU
E l B a t
r synchronousareset.
▪ The ALU operates on the rising edge of the clock.
▪ The reset_n signal is an active-low,
▪ Operations can take anym number of cycles.
r
n
▪ When the start signal is active, the TinyALU:
y
▪ Reads operands from the A and B buses.
▪ Reads an operation from the op bus.
▪ Delivers the result based on the operation.
▪ The TinyALU raises the done signal when the operation is
complete.
▪ The TinyALU supports five operations: Operation Opcode
▪ NOP (No Operation) no_op 3’000
▪ ADD add_op 3’001
▪ AND and_op 3’010
▪ XOR
xor_op 3’011
▪ MULT (Multiply)
mul_op 3’100
unused 3’101 – 3’b111
Example Design TinyALU
E l Bata
▪ The user encodes the operation on the 3-bit op
r
bus when requesting a calculation.
r
m remain stable until
▪ The start signal must remain high, and the
operator and operands must
n
A
the TinyALU raises the done signal.
y
▪ The done signal remains high for only one clock
cycle.
▪ No done signal is raised for a NOP operation.
▪ For a NOP operation, the requester lowers the
start signal one cycle after raising it.
▪ The development of the TinyALU testbench
includes:
▪ Starting with a conventional testbench.
▪ Modifying the testbench in each chapter to make it UVM-
compliant.
▪ Discussing the advantages of using UVM as the testbench
evolves.
A Conventional Testbench for the TinyALU
The Testbench File
E l Bata
▪ The TinyALU testbench is storedr in a single simulation
r file.
m
▪ The file consists of four parts:
n
Generating Stimulus
A
▪
y
▪ Driving
▪ Self-checking
▪ Coverage
▪ The DUT is instantiated within the file.
▪ Signals are driven by the stimulus.
▪ Monitoring is done using self-checking and coverage always blocks.
The Testbench File
Testbench Variable Declarations
E l Bata
r r
m
module top;
n
typedef enum bit[2:0] {no_op = 3'b000,
A
add_op = 3'b001,
y
and_op = 3'b010,
xor_op = 3'b011,
mul_op = 3'b100,
rst_op = 3'b111} operation_t;
byte unsigned A;
byte unsigned B;
bit clk;
bit reset_n;
wire [2:0] op;
bit start;
wire done;
wire [15:0] result;
operation_t op_set;
assign op = op_set;
tinyalu DUT (.A, .B, .clk, .op, .reset_n, .start, .done, .result);
The Testbench File
The coverage Block
E l Bata
r r
m
covergroup op_cov;
n
coverpoint op_set {
y
bins single_cycle[] = {[add_op : xor_op], rst_op,no_op};
bins multi_cycle = {mul_op};
bins opn_rst[] = ([add_op:mul_op] => rst_op);
bins rst_opn[] = (rst_op => [add_op:mul_op]);
bins sngl_mul[] = ([add_op:xor_op],no_op => mul_op);
bins mul_sngl[] = (mul_op => [add_op:xor_op], no_op);
bins twoops[] = ([add_op:mul_op] [* 2]);
bins manymult = (mul_op [* 3:5]);
}
endgroup
The Testbench File
The tester Block
E l Bata
r r
m
initial begin : tester end
reset_n = 1'b0; rst_op: begin
n
@(negedge clk); reset_n = 1'b0;
y
@(negedge clk); start = 1'b0;
reset_n = 1'b1; @(negedge clk);
start = 1'b0; reset_n = 1'b1;
repeat (1000) begin end
@(negedge clk); default: begin
op_set = get_op(); wait(done);
A = get_data(); start = 1'b0;
B = get_data(); end
start = 1'b1; endcase // case (op_set)
case (op_set) // handle the start end
signal $stop;
no_op: begin end : tester
@(posedge clk);
start = 1'b0;
The Testbench File
The tester Block
E l Bata
r things we do inrthis block. We:
▪ Notice how many different
m
▪ Choose the number of operations
n
A
▪ Choose the data
y
▪ Manage the signal-level protocol.
E l Bata
Self-Checking with the Scoreboard Loop
always @(posedge done) beginr : scoreboard r
m
shortint predicted_result;
n
#1;
y
case (op_set)
add_op: predicted_result = A + B;
and_op: predicted_result = A & B;
xor_op: predicted_result = A ^ B;
mul_op: predicted_result = A * B;
endcase // case (op_set)
end : scoreboard
The Testbench File
Limitations
E l Bata
▪ Lack of Modularity: r r
m
▪ All functionality is crammed into a single file, making it hard to modify, debug, or reuse.
n
A
▪ Challenges with Monolithic Testbenches:
y
▪ As projects grow, monolithic testbenches become buggy and brittle, requiring
continuous patching, copying, or conditional compilation (#ifdefs).
▪ During critical project phases, the testbench may become so fragile that even minor
modifications risk breaking it, creating a maintenance nightmare.
▪ Long-Term Impact:
▪ A brittle, confusing testbench is impractical for future projects, causing inefficiencies.
n
A
▪ The BFM provides our first step towards modularization.
y
▪ It handles all the signal level stimulus issues so that the rest of our testbench can ignore these
issues (e.g. clock generation).
▪ The second thing we notice is that the scoreboard block, the coverage block, and the
tester block each define completely different pieces of functionality.
▪ By separating them, we'll be able to reuse portions of the testbench in other testbenches and
easily modify parts of the testbench such as the stimulus.
SystemVerilog Interfaces and Bus
Functional Models
E l Bata
▪ The tinyalu_bfm encapsulates all the signals, generates the clock, and provides
reset_alu() task r r
interface tinyalu_bfm;
m initial begin
n
import tinyalu_pkg::*; clk = 0;
y
forever begin
byte unsigned A; #10;
byte unsigned B; clk = ~clk;
bit clk; end
bit reset_n; end
wire [2:0] op;
bit start;
wire done; task reset_alu();
wire [15:0] result; reset_n = 1'b0;
operation_t op_set; @(negedge clk);
@(negedge clk);
assign op = op_set; reset_n = 1'b1;
start = 1'b0;
endtask : reset_alu
SystemVerilog Interfaces and Bus
Functional Models
E l Bata
▪ The tinyalu_bfm provides send_op() task
r r
m
task send_op(input byte iA, input byte iB,
input operation_t iop, output shortint
B = iB;
start = 1'b1;
n
A
alu_result); if (iop == no_op) begin
y
@(posedge clk);
op_set = iop; #1;
start = 1'b0;
if (iop == rst_op) begin end else begin
@(posedge clk); do
reset_n = 1'b0; @(negedge clk);
start = 1'b0; while (done == 0);
@(posedge clk); start = 1'b0;
#1; end
reset_n = 1'b1; end // else: !if(iop == rst_op)
end else begin
@(negedge clk); endtask : send_op
A = iA; endinterface : tinyalu_bfm
SystemVerilog Interfaces and Bus
Functional Models
E l Bata
r
Encapsulating this behavior has two benefits:
r
m
▪ We don't need to sprinkle our code with protocol-level behavior; code
n
that calls this task is simpler than code that would have to handle the
y
signals.
▪ We can modify all the protocol-level behavior in one place; a fix here
gets propagated throughout the design.
Now that we have a TinyALU BFM, we can modularize the rest of the
design.
Creating a Modular Testbench
E l Bata
r r
▪ The benefit of breaking the testbench into modules jumps out
m
immediately.
n
▪ The coder's intent blindingly obvious.
y
▪ We also know where to go if we want to fix or enhance any part of the
testbench.
▪ The testbench has four parts:
▪ tinyalu_bfm—A SystemVerilog interface that contains all the signal-level
behavior
▪ tester—A module that generates the test stimulus
▪ coverage—A module that provides functional coverage
▪ scoreboard—A module that checks the results
Creating a Modular Testbench
E l Bata
top module
r r
module top;
m
n
tinyalu_bfm bfm();
y
tester tester_i (bfm);
coverage coverage_i (bfm);
scoreboard scoreboard_i(bfm);
E l Bata
scoreboard module
r r
m
module scoreboard(tinyalu_bfm bfm);
import tinyalu_pkg::*;
n
A
y
always @(posedge bfm.done) begin
shortint predicted_result;
#1;
case (bfm.op_set)
add_op: predicted_result = bfm.A + bfm.B;
and_op: predicted_result = bfm.A & bfm.B;
xor_op: predicted_result = bfm.A ^ bfm.B;
mul_op: predicted_result = bfm.A * bfm.B;
endcase // case (op_set)
if ((bfm.op_set != no_op) && (bfm.op_set != rst_op))
if (predicted_result != bfm.result)
$error ("FAILED: A: %0h B: %0h op: %s result: %0h",
bfm.A, bfm.B, bfm.op_set.name(), bfm.result);
end
endmodule : scoreboard
Creating a Modular Testbench
E l Bata
r r
▪ The tester module is simpler now because it no longer has to
m
manage signal-level protocols. It calls the BFM tasks instead:
n
initial begin
y
byte unsigned iA;
byte unsigned iB;
operation_t op_set;
shortint result;
bfm.reset_alu();
repeat (1000) begin : random_loop
op_set = get_op();
iA = get_data();
iB = get_data();
bfm.send_op(iA, iB, op_set, result);
end : random_loop
$stop;
end // initial begin
endmodule : tester
Review OOP
E l Bata
▪ Why All This OOP Love?
r r
m
▪ Verification engineers love object-oriented programming for three
n
reasons:
y
▪ Code Reuse
▪ Code Maintainability
▪ Memory Management
▪ Let's look at each of these in turn.
Review OOP
E l Bata
Code Reuse
r r
m
▪ Programming objects contain data, tasks and functions that operate
n
on that data.
y
▪ Once you have a defined object, you don't need to worry about
what's inside it.
▪ You simply need to use it as the documentation tells you to use it.
“Reuse means that your testbench becomes more powerful with every
function you add. If you program your new functionality correctly, you
can use it to create more new functionality building upon your previous
work.”
Review OOP
E l Bata
Code Maintainability
r r
m
▪ Risks of Code Duplication:
n
▪ Copying code within your testbench creates potential maintenance issues.
y
▪ If you find a bug in one location, you must search and update it in all other
instances.
▪ Challenges with Copied Code:
▪ Copying someone else's code introduces further risks.
▪ If they fix a bug in their version without informing you, your testbench may
become out of sync.
Review OOP
E l Bata
Code Maintainability
r r
m
▪ Object-Oriented Programming (OOP) Benefits:
n
▪ OOP centralizes common code, reducing duplication and maintenance effort.
y
▪ Changes made to shared code automatically apply wherever it is accessed,
improving consistency.
▪ Maintainability:
▪ Well-written object-oriented code simplifies maintenance and enhances
collaboration.
Review OOP
E l Bata
Memory Management
r r
m
▪ Creating and passing objects allocates and shares memory within
n
the testbench.
y
▪ Traditional languages like C make this process complex and error-
prone.
▪ In OOP languages like SystemVerilog and Java, memory
management is automatic and seamless.
Review OOP
E l Bata
r r
▪ Defining a class
▪ Using a class
m
▪ The variable rectangle_h holds a handle to a rectangle (not for the object)
n
A
y
▪ We allocate the memory for the object when we call new()
"Instantiated an object of class rectangle and stored it in variable called rectangle_h."
class rectangle; module top_class;
int length; rectangle rectangle_h;
int width; initial begin
rectangle_h = new(.l(50), .w(20));
function new(int l, int w); $display("rectangle area: %0d",
length = l; rectangle_h.area());
width = w; end
endfunction endmodule
function int area();
return length * width;
endfunction
endclass
Review OOP
E l Bata
r
Good example for code reusability
r
m
▪ The previous is a simple example demonstrates how object-oriented
n
programming allows engineers to use each other's code without
y
needing to understand the implementation.
▪ The engineer using the rectangle object needs only to supply the
length and width upon creation and call the area() method.
▪ The thinking behind the object has been done by the person who
defined the class.
Classes and Extension
E l Bata
r r
▪ Creating a separate class for a square seems logical but is inefficient
m
since a square is essentially a rectangle with equal length and width.
n
▪ Duplicating rectangle code in a square class would lead to
y
maintenance challenges and code redundancy.
▪ OOP Solution:
▪ Inheritance: Allows you to extend an existing class (e.g., rectangle) to create a
more specific class (e.g., square).
▪ Shared Functionality: The square class inherits properties and methods from
rectangle but customizes or restricts them as needed.
Classes and Extension
E l Bata
▪ Subclasses can override r
▪ Overriding Methods:
r from the parent class.
▪ Here, the square classm
methods or data members
overrides the new() constructor to take one argument
n
(side length).
y
▪ The super Keyword:
▪ super calls methods or data members from the parent class.
▪ In square, super.new() calls the rectangle constructor with the side length as
both length and width. square_h = new(.side(50));
$display("square area: %0d", square_h.area());
class square extends rectangle;
function new(int side);
super.new(.l(side), .w(side)); // Set both length and width to 'side'
endfunction
endclass
Classes and Extension
E l Bata
▪ Benefits:
rand ensures consistency.
r
m
▪ Reduces code duplication
▪ Any changes made to the rectangle class automatically apply to square,
n
simplifying maintenance.
y
class square extends rectangle;
function new(int side);
super.new(.l(side), .w(side)); // Set both length and width to 'side'
endfunction
endclass
Polymorphism
E l Bata
r r
▪ Polymorphism springs from the fact that object-oriented class types
m
are derived from each other.
n
▪ A square is a rectangle is a parallelogram is a trapezoid is a polygon.
y
▪ It causes one to ask, "If I declare a variable of type polygon and I
instantiate a square object, can I store the square object in my
polygon variable?“
▪ The answer is yes, and that language behavior is called polymorphism.
Polymorphism
E l Bata
r r
▪ Let's examine polymorphism using a
simple example:
m
▪ UML diagrams allow us to design and
n
A
describe object-oriented class
y
hierarchies in a visual way.
▪ We have a base class called
transaction and two derived classes
that extend the base class:
read_transaction and
write_transaction.
▪ All transaction classes have a data
member, id, and one method:
display_type(). Transaction UML Diagram
E B a t a class
l read_transaction
r class read_transaction
r
transaction class
class transaction;
int id = -1; m extends transaction;
n
A
function new(int id_in);
y
function new(int id_in); super.new(id_in);
id = id_in; endfunction : new
endfunction : new
function void display_type();
function void display_type(); $display("This is a Read
$fatal(1, "Generic Transaction");
transactions have no specific endfunction : display_type
type.");
endfunction : display_type endclass : read_transaction
endclass : transaction
Polymorphism
E l Bata
r r
▪ This code raises a reasonable read_transaction class
question: "If I'm just calling the
m
super.new() constructor, why
class read_transaction extends transaction;
n
can't I simply inherit the
A
function new(int id_in);
y
constructor from my base class?“ super.new(id_in);
endfunction : new
▪ The answer is that our constructor
has an argument and function void display_type();
SystemVerilog requires that we $display("This is a Read
explicitly write code for Transaction");
endfunction : display_type
constructors that have arguments,
even if they just call the super endclass : read_transaction
constructor.
Polymorphism
E l Bata
r r
▪ Let's use these classes in a little program. We'll start with a conventional use model:
m
initial begin
read_transaction read_tr;
n
write_transaction write_tr;
y
transaction generic_tr;
read_tr = new(1);
read_tr.display_type();
$display("Transaction ID: %0d", read_tr.id);
write_tr = new(2);
write_tr.display_type();
$display("Transaction ID: %0d", write_tr.id);
E l Bata
r r
▪ But what if we stored read and write objects in the generic_tr variable?
m
generic_tr = read_tr;
generic_tr.display_type();
n
$display("Transaction ID: %0d", generic_tr.id);
y
generic_tr = write_tr;
generic_tr.display_type();
$display("Transaction ID: %0d", generic_tr.id);
▪ Here’s the output:
# ** Fatal: Generic transactions have no specific type.
▪ This fatal error is happening because the generic_tr variable is calling the
display_type() method defined in the transaction class.
▪ In a certain way, this makes sense. The variable is calling the method associated
with its class.
▪ But in another way this does not make sense. The generic_tr variable is holding a
lion object.
Polymorphism
E l Bata
▪ The virtual keyword impliesr r defined as "virtual" is really
Virtual Methods
n
A
different place.
y
class transaction;
int id = -1;
function new(int id_in);
id = id_in;
endfunction : new
E l Bata
r rwrite do not need the virtual
Virtual Methods
n
A
change.
y
▪ Here’s the output:
# This is a Read Transaction
# Transaction ID: 1
# This is a Write Transaction
# Transaction ID: 2
▪ The generic_tr.display_type() call returns different results based on the object
stored in the animal variable.
“That’s the concept of Polymorphism”
Polymorphism
E l Bata
Abstract Classes and Pure Virtual Functions
▪ The current transaction classr r
m
virtual class transaction;
forces users to override int id = -1;
n
display_type() by calling
y
$fatal, leading to long delays function new(int id_in);
before hitting an error in large id = id_in;
endfunction : new
designs. A better approach is
using abstract classes. pure virtual function void display_type();
▪ Abstract classes:
▪ Serve only as base classes and
endclass : transaction
cannot be instantiated. “You want to use my base class? That's fine.
▪ Allow methods to be defined as Here's the contract that describes what you
pure virtual, requiring overriding have to do."
in derived classes.
▪ Failing to override results in a
compilation error.
generic_tr = new(3);
# ** Fatal:(vsim—8250) Class allocator method 'new' called on Abstract Class."
Static Methods and Variables
E l Bata
r an object is allocated
r only after calling new().
Static Variables
m
▪ In class definitions, memory for
n
▪ However, adding the static keyword allocates memory for a data member when
y
the class is defined.
▪ Static members have a single shared copy across all instances, acting as a
controlled global variable.
class transaction_pool;
static transaction pool[$];
endclass : transaction_pool
▪ The static keyword in front of the declaration means that we can access the
variable without instantiating an object of this class.
▪ This variable has only one copy, and that copy gets allocated as soon as we run the
program. Now we can access the lion cage as many times as we want.
Static Methods and Variables
E l Bata
▪ We use the :: operator after r r its static variables.
Static Variables
n
initial begin ▪ Here’s the output
y
transaction t_h; # Transactions in pool:
# 0xA001
// Add transactions to the static pool # 0xA002
t_h = new read_transaction(1, "0xA001"); # 0xA003
transaction_pool::pool.push_back(t_h);
$display("Transactions in pool:");
foreach (transaction_pool::pool[i])
transaction_pool::pool[i].display_type();
end
Static Methods and Variables
E l Bata
r a raw static variabler is poor coding style. It
Static Variables
n
A
because we've exposed the guts.
y
▪ For example, you might not know how to use SystemVerilog queues, and
so you couldn't use the lion cage until you googled "SystemVerilog
queues."
▪ What we really want is a set of static methods that go along with our
static variables.
▪ These methods hide our implementation and make it easier for future
engineers to use our objects.
Static Methods and Variables
E l Bata
class transaction_storage; r r
Static Methods
n
protected static transaction storage[$];
y
static function void store_transaction(transaction t);
storage.push_back(t);
endfunction : store_transaction
static function void list_transactions();
$display("Stored Transactions:");
foreach (storage[i])
storage[i].display_info();
endfunction : list_transactions
endclass : transaction_storage
Static Methods and Variables
E l Bata
r r
Static Methods
▪ Protected Static Variable:
m
▪ Static storage is marked protected, blocking direct user access and ensuring
n
A
better modularity.
y
▪ Future implementation changes won't break external code.
E l Bata
r initial begin r
Static Methods
n
A
memory location.
y
t = new read_transaction(1, "Cache");
▪ We could also access the transaction_storage::store_transaction(t);
transaction_storage from
t = new write_transaction(2, "Memory");
anywhere in our program transaction_storage::store_transaction(t);
using the global reference to
the transaction_storage t = new read_transaction(3, "Register");
class. transaction_storage::store_transaction(t);
transaction_storage::list_transactions();
end
Parameterized Class Definitions
E l Bata
r r
▪ Previously, we created a static class to manage shared resources for
specific transactions.
m
▪ What if we needed to handle another type of transaction? Duplicating
n
A
and modifying the existing class for each new type would work but isn’t
y
scalable.
▪ Complex designs require handling various transactions, and copying
code for each type leads to inefficiency and bugs when making updates
(e.g., adding a process() method).
▪ Maintaining multiple copies of similar code becomes error-prone and
difficult to manage.
▪ SystemVerilog addresses this issue with parameterized classes, enabling
reusable structures for different transaction types.
▪ This feature is critical in SystemVerilog and widely used in the UVM to
build flexible, reusable verification components.
Parameterized Class Definitions
E l Bata
r r
▪ The most famous example of parameters is a memory.
m
module Memory #(awidth, dwidth) (
input wire [awidth-1:0] address,
n
inout wire [dwidth-1:0] data,
y
input we
);
initial $display("Address Width: %0d, Data Width: %0d", awidth, dwidth);
// Memory behavior implementation
endmodule : Memory
Parameterized Class Definitions
E l Bata
r r
▪ We're going to do the same thing to solve the transaction problem, but instead of
having the parameter be a number such as bus width, we're going to have it be a
type. m
n
A
class transaction_store #(type T);
y
protected static T store[$];
static function void store_transaction(T t);
store.push_back(t);
endfunction : store_transaction
m
write_transaction w_trans;
providing a
n
type when // Store read transactions
A
we use the
y
r_trans = new(1, "Read_A");
transaction_store #(read_transaction)::store_transaction(r_trans);
static r_trans = new(2, "Read_B");
methods. transaction_store #(read_transaction)::store_transaction(r_trans);
▪ The result is // Store write transactions
that we wind w_trans = new(101, "Write_X");
transaction_store #(write_transaction)::store_transaction(w_trans);
up with two w_trans = new(102, "Write_Y");
copies of the transaction_store #(write_transaction)::store_transaction(w_trans);
static queue,
// List stored transactions
one that $display("-- Read Transactions --");
stores reads transaction_store #(read_transaction)::list_transactions();
and another $display("-- Write Transactions --");
transaction_store #(write_transaction)::list_transactions();
that stores end
writes.
Parameterized Class Definitions
E l Bata
r r
▪ We see this separation when we look at the output from the two calls to
m
list_transactions():
n
# -- Read Transactions --
A
# Stored Transactions:
y
# Cache
# Memory
# Register
# -- Write Transactions –
# Stored Transactions:
# RAM
# SRAM
# DRAM
Session 7
(UVM #2):
UVM Essentials:
Factory, Phases, and
Testbench Design
Prepared By:
SemiColon; Digital Team; Amr El Batarny
Presenter:
SemiColon; Digital Team; Amr El Batarny
Session 7, UVM-2
Outlines
E l Bata
▪ The Factory Pattern r r
m
▪ An Object-Oriented Testbench
n
▪ UVM Building Blocks
y
▪ UVM Structure: The Big Picture
▪ UVM Phasing: From Build to Run
▪ Phase Descriptions
▪ Building a UVM Testbench
▪ UVM Messaging
▪ UVM Tests
▪ Understanding Objections
▪ UVM Environments
▪ Creating UVM Components with the UVM Factory
The Factory Pattern
▪ OOP Chess Analogy: E l Bata
r
▪ Step 1: Learn the "moves": r
m inheritance, static methods.
▪ Polymorphism, classes,
n
A
▪ Step 2: Combine them into "strategies":
y
▪ Design patterns (like chess combinations: pins, knight forks).
▪ Design Patterns:
▪ A shared vocabulary for OOP architects.
▪ Replace "programming tricks" with structured, reusable solutions.
▪ Today’s Focus:
▪ Factory Pattern: A foundational design pattern for flexible, scalable systems.
The Factory Pattern
Why Use the Factory Pattern?
E l Bata
r a limitation in the way
▪ The factory pattern addresses r we've been instantiating our
objects until this point. m
n
A
▪ Consider the way we instantiated the objects in our previous example:
y
// Store read transactions
read_tr = new(1, "Read_A");
transaction_store #(read_transaction)::store_transaction(read_tr);
read_tr = new(2, "Read_B");
transaction_store #(read_transaction)::store_transaction(read_tr);
▪ As you can see, we've made calls to new() in order to create all our objects.
The Factory Pattern
E
▪ Problem: Hardcoded Object Creationl Bata
r are rigidly embedded
▪ Objects instantiated via new() r in source code.
m
▪ We call this hardcoding
n
A
▪ Changing/adding objects (e.g., new transaction types) requires code
y
modification.
▪ Limitations of Hardcoding:
▪ Dynamic Data: Reading from files/random generation becomes impractical.
▪ Recompilation Hell: Every data change forces recompilation.
▪ Solution: Factory Pattern
▪ Decouples object creation from code, enabling dynamic, flexible instantiation.
The Factory Pattern
Creating a Transaction Factory
E l Bata
r r
m
class transaction_factory;
default :
n
static function transaction $fatal (1, {"No such transaction
y
make_transaction(string type, int addr, type: ", type});
int data = 0);
read_transaction read_t; endcase // case (type)
write_transaction write_t;
case (type) endfunction : make_transaction
"read" : begin
read_t = new(addr);
return read_t;
end
"write" : begin
write_t = new(addr, data);
return write_t;
end
The Factory Pattern
Using the Transaction Factory
E l Bata
r a static method, wer can use it from anywhere in the
m
▪ Since transaction_factory has
testbench.
n
A
▪ We’ll test our factory using an initial block:
y
initial begin
transaction generic_tr;
read_transaction read_tr;
write_transaction write_tr;
n
A
y
# Error: factory.sv(107): Field/method name ('priority') not in 'generic_tr'
▪ This only works if the target class is a child of the casted class. You cannot cast
read into write.
▪ We can combine both like this:
if (!$cast(read_tr, transaction_factory::make_transaction("read", 200, 55)))
$fatal(1, "Failed to cast transaction from factory to read_tr");
An Object-Oriented Testbench
E
▪ Why Use an Object-Oriented Testbench?l Bata
r r
▪ Traditional Verilog is sufficient for simple designs (e.g., TinyALU).
m
▪ It can even validate moderately complex designs (e.g., Wishbone to I2C).
n
▪ However, it fails for highly complex systems (e.g., Ethernet switches, multi-level cache
y
CPUs).
▪ Challenges in Verifying Complex Designs:
▪ Need for complex predictors and extensive coverage tools.
▪ Robust stimulus generators required for scalable testing (unit to system level).
▪ Large teams require a common verification framework.
▪ Reusability is essential—writing a new testbench for every project is inefficient.
▪ Why Object-Oriented Programming (OOP)?
▪ Encapsulation ensures modular and maintainable testbenches.
▪ Enforced reusability reduces redundancy and improves efficiency.
▪ Memory management supports handling complex verification scenarios.
An Object-Oriented Testbench
E l Bata
▪ Our first object-oriented testbench will contain one module and four classes:
r r
▪ top—The top-level
m
n
module that instantiates
y
the testbench class
▪ testbench—The top-level
class
▪ tester—Drives stimulus
▪ scoreboard—Checks that
the TinyALU is working
▪ coverage—Captures
functional coverage
information
An Object-Oriented Testbench
The Object-Based Top-Level Module
E l Bata
module top; r r
import tinyalu_pkg::*;
m
n
`include "tinyalu_macros.svh"
y
tinyalu DUT (.A(bfm.A), .B(bfm.B), .op(bfm.op),
.clk(bfm.clk), .reset_n(bfm.reset_n),
.start(bfm.start), .done(bfm.done), .result(bfm.result));
tinyalu_bfm bfm();
testbench testbench_h;
initial begin
testbench_h = new(bfm);
testbench_h.execute();
end
endmodule : top
An Object-Oriented Testbench
The testbench Class
E l Bata
class testbench; r r task execute();
m tester_h = new(bfm);
n
virtual tinyalu_bfm bfm; coverage_h = new(bfm);
A
scoreboard_h = new(bfm);
y
tester tester_h;
coverage coverage_h; fork
scoreboard scoreboard_h; tester_h.execute();
coverage_h.execute();
function new (virtual tinyalu_bfm b); scoreboard_h.execute();
bfm = b; join_none
endfunction : new endtask : execute
endclass : testbench
n
A
function new (virtual tinyalu_bfm b);
y
bfm = b;
endfunction : new
task execute();
shortint predicted_result;
forever begin : self_checker
@(posedge bfm.done)
#1;
case (bfm.op_set)
add_op: predicted_result = bfm.A + bfm.B;
and_op: predicted_result = bfm.A & bfm.B;
xor_op: predicted_result = bfm.A ^ bfm.B;
mul_op: predicted_result = bfm.A * bfm.B;
endcase // case (op_set)
...........
An Object-Oriented Testbench
The coverage Class
E l Bata
▪ In Modules: r r ...........
▪ Define covergroups.
m function new (virtual tinyalu_bfm b);
n
op_cov = new();
▪ Declare variables to hold covergroups
A
zeros_or_ones_on_ops = new();
y
(treating them as types). bfm = b;
▪ Variable declaration creates the endfunction : new
covergroup.
▪ Call sample() on the variable to collect task execute();
coverage. forever begin : sampling_block
▪ In Classes: @(negedge bfm.clk);
A = bfm.A;
▪ No need to declare variables for B = bfm.B;
covergroups. op_set = bfm.op_set;
▪ Must call a constructor to create the op_cov.sample();
covergroup. zeros_or_ones_on_ops.sample();
end : sampling_block
endtask : execute
An Object-Oriented Testbench
Putting It Together
E l Bata
▪ Top-Level Testbench Setup r r
m
▪ The top-level module instantiates the DUT and SystemVerilog interface as a
n
BFM.
y
▪ The BFM connects the DUT in the instantiation.
▪ Creating the Testbench Object
▪ The initial block creates a top-level testbench object.
▪ It passes a handle to the BFM as an argument to the new() method.
▪ The testbench object creates verification objects and passes them the BFM
handle.
▪ Launching Execution
▪ The top-level object launches verification objects.
▪ It calls execute() methods using a fork/join_none block, giving each object
its own thread.
UVM Building Blocks
The UVM is built on a robust
E l Bata
class hierarchy that organizes
r r
testbench elements into two
main branches: Components m
n
A
and Objects.
y
Components:
▪ A UVM testbench is
composed of component
objects extended from the
uvm_component base class.
▪ When a uvm_component
derived class object is
created, it becomes part of
the testbench hierarchy.
▪ It persists for the duration of
the simulation.
UVM Building Blocks
The UVM is built on a robust
E l Bata
class hierarchy that organizes
r r
m
testbench elements into two
main branches: Components
n
A
and Objects.
y
Objects:
▪ It contrasts with the
Component Branch of the
UVM class hierarchy.
▪ It involves transient objects.
▪ They are created, used and
destroyed (i.e. garbage
collected) once
dereferenced.
UVM Building Blocks
UVM Classes Diagram
E l Bata
r
▪ The UVM Class Library offers
r
m
base classes, utilities, and
macros for building modular
n
A
verification environments.
y
▪ Components are
hierarchically managed via
customizable phases
(initialize, run, complete),
extendable for projects.
▪ Details: UVM 1.2 Class
Reference.
UVM Building Blocks
UVM Classes Diagram
E l Bata
▪ We will focus in this course
r r
on those classes.
m
n
A
y
UVM Structure: The Big Picture
E l Bata
r
port export
r
Component Object
n
A
y
UVM Phasing: From Build to Run
E l Bata
▪ In order to have a consistent testbench execution flow,
r r
the UVM uses phases to order the major steps that
m
take place during simulation.
n
A
▪ There are three groups of phases, which are executed
y
in the following order:
1. Build phases - where the testbench is configured
and constructed
2. Run-time phases - where time is consumed in
running the testcase on the testbench
3. Clean up phases - where the results of the
testcase are collected and reported
E l Bata
▪ To start a UVM testbench, the run_test() method has
r r
to be called from the static part of the testbench. It is
m
usually called from within an initial block in the top
level module of the testbench.
n
A
▪ Calling run_test() constructs the UVM environment
y
root component and then initiates the UVM phasing.
▪ We will focus on the Build, Connect, and Run phases,
as they are the most frequently used and critical for
setting up and executing a UVM testbench.
UVM Phasing: From Build to Run
0. Initially: top
E l Bata
r r
m
n
A
y
UVM Phasing: From Build to Run
0. Initially: top
E l Bata
r
1. Build Phase (top-down): test 1.2. env
r
m
n
A
y
UVM Phasing: From Build to Run
0. Initially: top
E l Bata
r
1. Build Phase (top-down): test 1.2. env
r
env 1.3. sb, cvg, agt
n
A
y
UVM Phasing: From Build to Run
0. Initially: top
E l Bata
r r
1.4. sb_ep, cvg_ep,
sb
1. Build Phase (top-down): test 1.2. env env 1.3. sb, cvg, agt cvg
m
agt agt_ap, sqr, dvr, mon
n
A
y
UVM Phasing: From Build to Run
0. Initially: top
E l Bata
r r
1.4. sb_ep, cvg_ep, 1.5. mon_ap, sqr_ap,
sb mon
1. Build Phase (top-down): test 1.2. env env 1.3. sb, cvg, agt cvg sqr
m
agt agt_ap, sqr, dvr, mon dvr dvr_ep
n
A
y
UVM Phasing: From Build to Run
0. Initially: top
E l Bata
r r
1.3. sb_ep, cvg_ep, 1.4. mon_ap, sqr_ap,
sb mon
1. Build Phase (top-down): test 1.1. env env 1.2. sb, cvg, agt cvg sqr
m
agt agt_ap, sqr, dvr, mon dvr dvr_ep
2. Connect Phase (bottom-up): agt 2.1. mon_ap agt_ap, sqr_ap dvr_ep
n
A
y
UVM Phasing: From Build to Run
0. Initially: top
E l Bata
r r
1.3. sb_ep, cvg_ep, 1.4. mon_ap, sqr_ap,
sb mon
1. Build Phase (top-down): test 1.1. env env 1.2. sb, cvg, agt cvg sqr
m
agt agt_ap, sqr, dvr, mon dvr dvr_ep
2. Connect Phase (bottom-up): agt 2.1. mon_ap agt_ap, sqr_ap dvr_ep env 2.2. agt_ap sb_ep, cvg_ep
n
A
y
UVM Phasing: From Build to Run
l Bata
3. Run: Executes all Run tasks in parallel, each task runs in its own thread
E
“How do yourknow when to stop r
And this raises an important question:
the test?”
mKeep this question in mind
n
A
y
Phase Descriptions
Build Phases
E l B a t atestbench
▪ r
Purpose: Construct, configure, and connect the
r component
hierarchy.
mare functions that execute in zero simulation time.
n
▪ All build phase methods
y
▪ build Phase:
▪ Starts once the testbench root component is constructed.
▪ Constructs the hierarchy from the top down.
▪ Component construction is deferred for configuration by the parent layer.
▪ Components are indirectly constructed using the UVM factory.
▪ connect Phase:
▪ Establishes TLM connections between components or assigns handles to
resources.
▪ Occurs after the build phase and works from the bottom up.
Phase Descriptions
Build Phases
E l B a t ar
▪ end_of_elaboration Phase:r
simulation starts. m
▪ Makes final adjustments to structure, configuration, or connectivity before
n
A
▪ Assumes the testbench hierarchy and interconnections are in place and
y
executes from the bottom up.
Phase Descriptions
Run Time Phases
E l B a t
after the a
▪ r
Stimulus is generated and executed
r
build phases.
▪
m in parallel.
After the start_of_simulation phase, the run phase and phases from pre_reset
n
to post_shutdown execute
y
▪ start_of_simulation Phase
▪ A function phase before the time-consuming testbench operations begin.
▪ Used for displaying banners, testbench topology, and configuration info.
▪ Called in bottom-up order.
▪ run Phase
▪ Follows start_of_simulation.
▪ Used for generating stimulus and performing checks.
▪ Implemented as a task; all run_phase tasks run in parallel.
▪ Typically used by transactors like drivers and monitors.
Phase Descriptions
Run Time Phases
Parallel Run-Time Phases r E l B a t
(Executed in order,a
▪
r
concurrently with the run phase;
m
meant for tests and environment, not drivers/monitors)
n
▪ reset Phase:
A
▪ Handles DUT or interface-specific reset behavior.
y
▪ Generates reset signals and sets interfaces to default states.
▪ configure Phase:
▪ Programs the DUT and memories to prepare for the test.
▪ Sets signals to a state ready for the start of the test case.
n
▪ Implemented as functions,
A
▪ Operate from the bottom to the top of the component hierarchy.
y
▪ extract Phase:
▪ Retrieves and processes data from scoreboards and coverage monitors.
▪ Calculates statistics for reporting.
▪ Typically used by analysis components.
▪ check Phase:
▪ Verifies DUT behavior and detects errors during test execution.
▪ Usually used by analysis components.
Phase Descriptions
Clean Up Phases
E l B a t ar
▪ report Phase: r
m components.
▪ Displays or writes simulation results.
n
▪ Typically used by analysis
y
▪ final Phase:
▪ Completes any outstanding actions not handled by previous phases.
Building a UVM Testbench
The UVM testbench operates
E l Bata
through two interconnected
r r
domains:
m
n
A
1. Static World:
y
▪ Role: Initializes the testbench
and triggers UVM execution.
n
A
2. Dynamic World:
y
▪ Role: Manages the UVM
component lifecycle and
test execution.
E l Bata
▪ UVM messaging system provides message macros (e.g., `uvm_info).
▪ Why Use Messaging Macros:
r r
m
▪ Automatically embed file name and line number in messages for
debugging.
n
A
▪ For `uvm_info: Checks verbosity settings before printing (boosts
y
performance).
▪ Macro Syntax:
▪ Fatal: `uvm_fatal("message_id", "message_string")
▪ Error: `uvm_error("message_id", "message_string")
▪ Warning: `uvm_warning("message_id", "message_string")
▪ Info: `uvm_info("message_id", "message_string", uvm_verbosity)
▪ Parameters:
▪ message_id: Unique string to identify/control message behavior.
▪ message_string: Output text (supports formatting like $sformatf()).
▪ uvm_verbosity: Enumerated verbosity level (e.g., UVM_LOW, UVM_HIGH).
UVM Messaging
Message Verbosity
E l B a t a r as an enum within the UVM
▪ r
The UVM has six levels of verbosity which are defined
package:
m
n
Verbosity Level Effect on message
y
UVM_NONE It will always be printed regardless of the verbosity setting
UVM_LOW It will be printed if the verbosity is set to UVM_LOW, UVM_MEDIUM,
UVM_HIGH or UVM_FULL or UVM_DEBUG.
UVM_MEDIUM It will be printed is the verbosity is set to UVM_MEDIUM, UVM_HIGH or
UVM_FULL or UVM_DEBUG. This is the default level.
UVM_HIGH It will be printed if the verbosity is set to UVM_HIGH or UVM_FULL or
UVM_DEBUG.
UVM_FULL It will be printed if the verbosity is set to UVM_FULL or UVM_DEBUG.
UVM_DEBUG It will be printed if the verbosity is set to UVM_DEBUG.
UVM Messaging
E l B a t
r are shown in theatable
Message Verbosity
▪ Suggested uses of the settings
r below:
m
n
Verbosity Level Effect on message
y
UVM_NONE A message that will always be printed – e.g. Mandatory information such
as Copyright or design/testbench configuration.
UVM_LOW A message that should normally be printed, but could be filtered by a
UVM_NONE setting. – e.g. Higher level messaging, Assertion messaging
UVM_MEDIUM Debug level 1 – e.g. Phase has started/finished (Default verbosity level)
UVM_HIGH Debug level 2 – e.g. Detailed transaction messages
UVM_FULL Low level of detail debug messages
UVM_DEBUG Lower level of detail debug messages
UVM Tests
▪ Efficiency in Verification:
E l Bata
r r
▪ Verification teams need to run thousands of tests without recompiling each
time.
m
▪ Example: 1,000 tests at 5 minutes each equal 3.5 days of compile time—
n
A
clearly inefficient.
y
▪ Dynamic Testbench Concept:
▪ Compile the testbench once and run it with different arguments for various
tests.
▪ UVM supports this by allowing testbench creation through object classes and
dynamic instantiation.
▪ Current Challenge:
▪ The hardcoded TinyALU testbench requires rewriting and recompiling for
each new stimulus.
UVM Tests
▪ UVM Factory Usage:
E l Bata
r r
▪ UVM uses its factory to create almost any component dynamically.
m
▪ Enables launching different tests without recompiling the testbench.
▪ Example Command-Line Execution:
n
A
y
vsim testbench -coverage +UVM_TESTNAME=add_test
vsim testbench -coverage +UVM_TESTNAME=random_test
▪ Same compiled testbench runs multiple tests by passing different test names at
runtime.
▪ Implementation:
▪ Define test classes (e.g., add_test, random_test).
▪ UVM uses the UVM_TESTNAME string to:
1. Identify the test class.
2. Instantiate it via the factory.
3. Launch the simulation.
▪ Benefits:
▪ Reduces recompilation overhead.
▪ Simplifies test switching during verification.
UVM Tests
E l Bata
▪ Let’s start by creating the static part (top module)
r r
module top;
m
n
import uvm_pkg::*; We import uvm_pkg and its macros, which
1 include all UVM classes, methods, and objects.
y
`include "uvm_macros.svh"
import tinyalu_pkg::*;
`include "tinyalu_macros.svh"
tinyalu_bfm bfm();
tinyalu DUT (.A(bfm.A), .B(bfm.B), .op(bfm.op),
.clk(bfm.clk), .reset_n(bfm.reset_n),
.start(bfm.start), .done(bfm.done), .result(bfm.result));
endmodule : top
UVM Tests
E l Bata
▪ Previously, we passed the BFM handle via the constructor. UVM requires specific
r r
constructor arguments, so we use uvm_config_db instead.
module top;
m
n
import uvm_pkg::*;
y
`include "uvm_macros.svh"
import tinyalu_pkg::*;
`include "tinyalu_macros.svh"
tinyalu_bfm bfm();
tinyalu DUT (.A(bfm.A), .B(bfm.B), .op(bfm.op),
.clk(bfm.clk), .reset_n(bfm.reset_n),
.start(bfm.start), .done(bfm.done), .result(bfm.result));
initial begin
uvm_config_db #(virtual tinyalu_bfm)::set(null, "*", "bfm", bfm);
Store the BFM handle globally. The first two arguments (null,
end
2 "*") make the data globally available. The third names the
endmodule : top data, and the fourth is its value—the tinyalu_bfm handle.
UVM Tests
E l Bata
▪ The run_test() task uses +UVM_TESTNAME to instantiate tests. Passing a test name
r r
directly defeats the purpose of single compilation.
module top;
m
n
import uvm_pkg::*;
y
`include "uvm_macros.svh"
import tinyalu_pkg::*;
`include "tinyalu_macros.svh"
tinyalu_bfm bfm();
tinyalu DUT (.A(bfm.A), .B(bfm.B), .op(bfm.op),
.clk(bfm.clk), .reset_n(bfm.reset_n),
.start(bfm.start), .done(bfm.done), .result(bfm.result));
initial begin
uvm_config_db #(virtual tinyalu_bfm)::set(null, "*", "bfm", bfm);
run_test(); Run the test. The run_test() method gets a string from the
end
3 command line and uses the UVM factory to create a test
endmodule : top object of that class.
UVM Tests
E l Bata
The UVM developers did not want us to have to modify their source
r r
code, so they created a mechanism to solve the problem and
m
delivered it in the `uvm_component_utils macro.
n
A
y
class random_test extends uvm_test;
`uvm_component_utils(random_test);
The macro registers our random_test class with the factory.
virtual tinyalu_bfm bfm; 1 Now the factory can create random_test objects.
UVM Tests
E l B a t
new() Method Rules (Extending uvm_component):
a
1. Constructor Arguments:
r r in that order.
m
▪ Must be (string name, uvm_component parent)
2. Types:
n
A
▪ name is a string.
y
▪ parent is a uvm_component.
3. Call super.new():
▪ The first executable line in the constructor must be super.new(name, parent).
▪ UVM relies on this call for proper operation.
class random_test extends uvm_test;
`uvm_component_utils(random_test);
E l Bata
▪ Now, we can override the phases to build, connect and run the testbench.
r r
▪ For now, we only need to override the run_phase.
m
▪ The run_phase must have a single argument of type uvm_phase called phase.
n
task run_phase(uvm_phase phase);
A
3 Override the phases
y
phase.raise_objection(this);
#10;
`uvm_info("run_phase", "Entered the test’s run_phase", UVM_NONE)
phase.drop_objection(this);
endtask : run_phase
?
What about these
objection things ?
Understanding Objections
l B a
The test runs as long as some object in the t a
E
How do you know when to stop the test?
▪
r it. r
▪ m to raise an
testbench objects to finishing
UVM objects have the ability
n
task run_phase(uvm_phase phase);
A
objection to finishing the test.
y
▪ The test runs as long as one object has an phase.raise_objection(this);
objection to stopping it. .
▪ As the developer, you need to raise an .
objection before you start generating your .
stimulus and drop the objection when you're phase.drop_objection(this);
done.
▪ At that point you're telling the UVM, "It's fine endtask : run_phase
with me if you finish the test.“
▪ The test stops when all the objects have
dropped their objections.
UVM Environment
E
Separating Structure from Stimulus
l Bata
r r
▪ Problem:
m
▪ Combining testbench structure and stimulus generation in a single class
n
A
(e.g., random_test) leads to intractable code.
y
▪ Hardcoding structure (e.g., coverage, scoreboard) limits flexibility for different
test configurations.
▪ Solution:
▪ Separate Responsibilities:
▪ Structure: Define testbench components (e.g., agents, drivers, monitors)
in a uvm_env class.
▪ Stimulus: Define test-specific behavior (e.g., random_tester, add_tester)
in test classes.
UVM Environment
env Class Definition
E l Bata
r r
m
class env extends uvm_env;
`uvm_component_utils(env);
n
A
y
function new(string name = "env", uvm_component parent = null);
super.new(name, parent);
endfunction : new
phase.raise_objection(this);
#20;
`uvm_info("run_phase", "Entered the env’s run_phase", UVM_NONE)
phase.drop_objection(this);
endtask : run_phase
endclass
UVM Environment
Building env Object in the test
E l Bata
r r
m
class random_test extends uvm_test;
.
n
.
A
.
y
env env_h;
E l Bata
▪ Condition for the factory to create components and objects:
r r
▪ You add new classes to the factory by using the `uvm_component_utils() and
m
`uvm_object_utils() macros.
n
A
y
▪ The factory incantation uses the static member/method technique we
discussed earlier to deliver a uvm_component.