UVM Sequence Item
We have till now seen the classes which makes the base of all other classes in UVM. From this article we will start learning about the UVM classes which we use everyday. We will start from sequence item and move to upper level objects/components slowly.
In the world of UVM-based testbenches, UVM sequence items are fundamental building blocks used to represent the transactions exchanged between testbench components and the DUT. This article explores what UVM sequence items are, how to define and use them, and the utilities they offer.
What is a UVM Sequence Item?
UVM sequence item can be considered as a container for the transaction-level data which other components of the test bench like, driver, monitor, checkers, etc. can process. These are also known as transactions and are vital part of transaction-level modelling.
A UVM sequence item is a class derived from the uvm_sequence_item
UVM base class.
Defining a UVM Sequence Item
Let's see how to define a sequence item.
1. Define the class
First, we need to define the class by extending uvm_sequence_item
class.
class my_seq_item extends uvm_sequence_item;
2. Register with UVM factory
As discussed on earlier articles, we need to register our class with UVM factory. As sequence item is derived from uvm_object
, we use uvm_object_utils
macro to register the class with the UVM factory.
`uvm_object_utils(my_seq_item)
3. Constructor Definition
The constructor of uvm_sequence_item
is same as that of UVM object. It takes one input name
function new(string name = "my_seq_item");
super.new(name);
endfunction
4. Transaction fields
As sequence item is wrapper for all the signals, that needs to be used by other components, thus we need to add all the variables that represent the transaction data.
bit [7:0] addr;
bit [31:0] data;
With this we are done with the basic definition of the UVM sequence item. But there is one more thing we need to take care of. UVM sequence item represents transaction and may encapsulate lot of signals as variable. In many areas of code, we might need to make a copy of sequence item object or print the values of all the variable inside sequence item objects or compare the fields in the scoreboard.
To tackle this, UVM provides in-built mechanism which helps perform these tasks easily. To utilize these mechanism, we need to expose the variables using UVM field macros.
5. UVM field macros
UVM field macros expose the transaction fields to built-in methods like copy
, compare
and print
, etc. These macros simplify handling sequence items in a testbench.
`uvm_object_utils_begin(my_seq_item)
`uvm_field_int(addr, UVM_ALL_ON)
`uvm_field_int(data, UVM_ALL_ON)
`uvm_object_utils_end
Do note that UVM field macros saves time and effort by exposing field to various utility functions but at the same time it can cause performance issues. This is because the field macros gets replaces with lot of codes and if there lots of fields, then the compiled code is very inflated.
The entire definition of uvm_sequence_item
looks like below.
class my_seq_item extends uvm_sequence_item;
`uvm_object_utils_begin(my_seq_item)
`uvm_field_int(addr, UVM_ALL_ON)
`uvm_field_int(data, UVM_ALL_ON)
`uvm_object_utils_end
// Transaction fields
bit [7:0] addr;
bit [31:0] data;
// Constructor
function new(string name = "my_seq_item");
super.new(name);
endfunction
endclass
Built-in Methods and callback methods
UVM sequence items has lot of built-in methods to help manage transactions easily for various activities. Only the variables exposed through field macros are available inside the in-built functions. When using field macros we can expose the variables either to all in-built method or few methods or just one method.
This done using various field macros flag provided by UVM.
Flag | Description |
---|---|
UVM_DEFAULT | All field operations turned on |
UVM_COPY | Field will participate in copy |
UVM_COMPARE | Field will participate in compare |
UVM_PRINT | Field will participate in print |
UVM_PACK | Field will participate in pack/unpack |
UVM_NOCOPY | Field will not participate in copy |
UVM_NOCOMPARE | Field will not participate in compare |
UVM_NOPRINT | Field will not participate in print |
UVM_NORECORD | Field will not participate in record |
UVM_NOPACK | Field will not participate in pack/unpack |
UVM_DEEP | Object field will be deep copied |
UVM_SHALLOW | Object field will be shallow copied |
UVM_REFERENCE | Object field will copied by reference |
UVM_READONLY | Object field will NOT be automatically configured |
Above FLAGS can be bitwise ORed to use multiple flags at a time.
Callback methods
UVM provides various callback methods methods which are called automatically when in-built functions are executed. These methods can be used to override the functionality of the in-built functions.
In-built function | Callback method |
---|---|
copy() | do_copy(uvm_object rhs) |
compare() | do_compare(uvm_object rhs,uvm_comparer comparer) |
print() | do_print(uvm_printer printer) |
pack() | do_pack (uvm_packer packer) |
unpack() | do_unpack (uvm_packer packer) |
record() | do_record(uvm_recorder recorder) |
convert2string() | NA |
convert2string()
is a special in-built function which has no implementation. To use this, we need to implement this method in our code. This method is used to convert a transaction object into string and helpful to print the object details in a single line.
Example
We will learn various UVM components by creating a actual and simple test bench for a 8-bit ALU (arithmetic logic unit). Slowly, with each article, we will develop different components of the test bench.
The ALU has following port signals:
Signal | Width | I/O | Description |
---|---|---|---|
OPA | 8-bit | I | First Operand for operation |
OPB | 8-bit | I | Second Operand for operation |
cin | 1-bit | I | Carry In bit |
mode | 1-bit | I | Mode of operation 0 - Arithmetic 1 - Logical |
cmd | 4-bit | I | The operation that needs to be performed by ALU For mode 0, valid cmd is 0 to 9 For mode 1, valid cmd is 0 to 13 |
res | 9-bit | O | result of the operation |
cout | 1-bit | O | carry out bit |
err | 1-bit | O | Indicates that error has happened |
oflow | 1-bit | O | Indicates overflow in subtraction operation |
e | 1-bit | O | Is 1 when both operands are equal |
g | 1-bit | O | is 1 when OPA > OPB |
l | 1-bit | O | is 1 when OPA < OPB |
Let us start with creating the uvm_sequence_item
class with name transaction
.
import uvm_pkg::*;
class transaction extends uvm_sequence_item;
`uvm_object_utils(transaction)
// Inputs to DUT
rand logic [7:0] OPA, OPB;
rand logic cin, mode, ce;
rand logic [3:0] cmd;
constraint cmd_val {
if (mode == 1)
cmd inside { [0:9] };
else
cmd inside { [0:13] };
};
constraint ce_dist { ce dist { 1 := 9, 0 := 1 }; }
// Ouput from DUT
logic [2:0] egl;
logic [8:0] res;
logic cout, err, oflow;
static bit [7:0] trans_ID; // Transaction ID
static bit isRandom; // Control bit
function new(string name = "trans");
super.new(name);
init_val();
endfunction //new()
function void pre_randomize();
trans_ID++;
endfunction
// Helper functions
function void init_val();
OPA = 'z;
OPB = 'z;
cin = 'z;
cmd = 'z;
ce = 'z;
//r_bit = 'z;
mode = 'z;
egl = 'z;
res = 'z;
cout = 'z;
err = 'z;
oflow = 'z;
endfunction
function void do_print(uvm_printer printer);
printer.print_field("Transaction ID: %0d", trans_ID);
printer.print_field("OPA", OPA, 8, UVM_UNSIGNED);
printer.print_field("OPB", OPB, 8, UVM_UNSIGNED);
printer.print_field("Mode", mode, 1, UVM_BIN);
printer.print_field("CMD", cmd, 4, UVM_BIN);
printer.print_field("CE", ce, 1, UVM_BIN);
printer.print_field("Result", res, 9, UVM_UNSIGNED);
printer.print_field("cout", cout, 1, UVM_BIN);
printer.print_field("err", err, 1, UVM_BIN);
printer.print_field("oflow", oflow, 1, UVM_BIN);
printer.print_field("EGL", egl, 3, UVM_BIN);
endfunction
function string convert2string();
string s;
s = $sformatf("Packet ID: %0d", trans_ID);
s = $sformatf("%s\nInput: OPA = %b, OPB = %b, cin = %b, cmd = %b, mode = %b, ce = %b, ",
s, OPA, OPB, cin, cmd, mode, ce);
s = $sformatf("%s\nOuput: result = %b, cout = %b, err = %b, oflow = %b, EGL = %b",
s, res, cout, err, oflow, egl);
return s;
endfunction
function bit do_compare(uvm_object rhs, uvm_comparer comparer);
transaction _rhs;
bit cmp;
if(!$cast(_rhs, rhs))
`uvm_fatal(get_type_name(), "object passed is not compatible with Transaction");
cmp = this.egl === _rhs.egl & this.res === _rhs.res & cout === _rhs.cout
& err === _rhs.err & oflow === _rhs.oflow;
return cmp;
endfunction
function void do_copy(uvm_object rhs);
transaction _rhs;
if(!$cast(_rhs, rhs))
`uvm_fatal(get_type_name(), "rhs is not compatible with this class");
OPA = _rhs.OPA;
OPB = _rhs.OPB;
cin = _rhs.cin;
cmd = _rhs.cmd;
ce = _rhs.ce;
mode = _rhs.mode;
egl = _rhs.egl;
res = _rhs.res;
cout = _rhs.cout;
err = _rhs.err;
oflow = _rhs.oflow;
endfunction
endclass //transaction extends uvm_sequence_item
Observations
- In above code, we have used callback methods to use the utility functions. Using the field macros
uvm_field_int
will help perform same functionality without implementing any callback methods. - We call a custom method
init_val
in the object constructor to initialize variables. - Implemented
convert2string()
method to covert the object into a string. - All the variables which are input to the design have been declared as
rand
so that they can be randomized in each object oftransaction
class. - Used
pre_randomize()
method to incrementtrans_ID
. We are not using constructor to increment this variable as we can create new objects which are copy of other objects and does not account for a newtrans_ID
. This is a general randomization concept from SV. trans_id
is a static variable, therefore we can increment the value of this variable across any object.
Conclusion
This article outlines the essentials of uvm_sequence_item
including its definition, usage and declaration of variables inside sequence item. Sequence items form the backbone of transaction-based verification in UVM.
In the next, article we will about UVM sequences and how they are used to interact with sequence items.