The first step in verifying a RTL design is defining what kind of data should be sent to the DUT. While the driver deals with signal activities at the bit level, it doesn’t make sense to keep this level of abstraction as we move away from the DUT, so the concept of transaction was created. A transaction is a class object, usually extended fromuvm_transaction oruvm_sequence_itemclasses, which includes the information needed to model the communication between two or more components.
Transactions are the smallest data transfers that can be executed in a verification model.
They can include variables, constraints and even methods for operating on themselves.
Due to their high abstraction level, they aren’t aware of the communication protocol
between the components, so they can be reused and extended for different kind of tests if
correctly programmed.
An example of a transaction could be an object that would model the communication bus
of a master-slave topology. It could include two variables: the address of the device and
the data to be transmitted to that device. The transaction would randomize these two
variables and the verification environment would make sure that the variables would
assume all possible and valid values to cover all combinations.
In order to drive a stimulus into the DUT, a driver component converts transactions into
pin wiggles, while a monitor component performs the reverse operation, converting pin
wiggles into transactions.
After a basic transaction has been specified, the verification environment will need to
generate a collection of them and get them ready to be sent to the driver. This is a job for
the sequence. Sequences are an ordered collection of transactions, they shape
transactions to our needs and generate as many as we want. This means if we want to
test just a specific set of addresses in a master-slave communication topology, we could
restrict the randomization to that set of values instead of wasting simulation time in invalid
values.
Sequences are extended from uvm_sequence and their main job is generating multiple
transactions. After generating those transactions, there is another class that takes them to
the driver: the sequencer. The code for the sequencer is usually very simple and in simple
environments, the default class from UVM is enough to cover most of the cases.
Due to their high abstraction level, they aren’t aware of the communication protocol
between the components, so they can be reused and extended for different kind of tests if
correctly programmed.
An example of a transaction could be an object that would model the communication bus
of a master-slave topology. It could include two variables: the address of the device and
the data to be transmitted to that device. The transaction would randomize these two
variables and the verification environment would make sure that the variables would
assume all possible and valid values to cover all combinations.
In order to drive a stimulus into the DUT, a driver component converts transactions into
pin wiggles, while a monitor component performs the reverse operation, converting pin
wiggles into transactions.
After a basic transaction has been specified, the verification environment will need to
generate a collection of them and get them ready to be sent to the driver. This is a job for
the sequence. Sequences are an ordered collection of transactions, they shape
transactions to our needs and generate as many as we want. This means if we want to
test just a specific set of addresses in a master-slave communication topology, we could
restrict the randomization to that set of values instead of wasting simulation time in invalid
values.
Sequences are extended from uvm_sequence and their main job is generating multiple
transactions. After generating those transactions, there is another class that takes them to
the driver: the sequencer. The code for the sequencer is usually very simple and in simple
environments, the default class from UVM is enough to cover most of the cases.
A representation of this operation is shown in Figure 4.1
Figure 4.1 - Relation between a sequence, asequencer and a driver
The sequence englobes a group oftransactions and the sequencer takes a transaction
from the sequence and takes it to thedriver.
To test our DUT we are going to define asimple transaction, extended
from uvm_sequence_item. It will include thefollowing variables:
rand bit[1:0] ina
rand bit[1:0] inb
bit[2:0] out
The variables ina and inb are going to berandom values to be driven to the inputs of the
DUT and the variable out is going to storethe result. The code for the transaction is
represented in Code 4.1.
class simpleadder_transaction extendsuvm_sequence_item;
rand bit[1:0] ina;
rand bit[1:0] inb;
bit[2:0] out;
function new(string name = "");
super.new(name);
endfunction: new
`uvm_object_utils_begin(simpleadder_transaction)
`uvm_field_int(ina, UVM_ALL_ON)
`uvm_field_int(inb, UVM_ALL_ON)
`uvm_field_int(out, UVM_ALL_ON)
`uvm_object_utils_end
endclass: simpleadder_transaction
Code 4.1 – Transaction for the simpleadder
An explanation of the code will follow:
Lines 2 and 3 declare the variables forboth inputs. The rand keyword asks the
compiler to generate and store randomvalues in these variables.
Lines 6 to 8 include the typical classconstructor.
Lines 10 to 14 include the typical UVMmacros.
These few lines of code define theinformation that is going to be exchanged between the
DUT and the testbench.
To demonstrate the reuse capabilities ofUVM, let’s imagine a situation where we would
want to test a similar adder with a thirdinput, a port named inc.
Instead of rewriting a differenttransaction to include a variable for this port, it would be
easier just to extend the previous class tosupport the new input.
It’s possible to see an example in Code5.2.
class simpleadder_transaction_3inputsextends simpleadder_transaction;
rand bit[1:0] inc;
function new(string name = "");
super.new(name);
endfunction: new
`uvm_object_utils_begin(simpleadder_transaction_3inputs)
`uvm_field_int(inc, UVM_ALL_ON)
`uvm_object_utils_end
endclass: simpleadder_transaction_3inputs
Code 5.2 – Extension of the previoustransaction
As a result of the classsimpleadder_transaction_3inputs being an extension
of simpleadder_transaction, we didn’t needto declare again the other variables. While in
small examples, like this one, this mightnot look like something useful, for bigger
verification environments, it might save alot of work.
Sequence
Now that we have a transaction, the nextstep is to create a sequence.
The code for the sequencer can be found inCode 5.3.
class simpleadder_sequence extendsuvm_sequence#(simpleadder_transaction);
`uvm_object_utils(simpleadder_sequence)
function new(string name = "");
super.new(name);
endfunction: new
task body();
simpleadder_transaction sa_tx;
repeat(15) begin
sa_tx = simpleadder_transaction::type_id::create(...
start_item(sa_tx);
assert(sa_tx.randomize());
finish_item(sa_tx);
end
endtask: body
endclass: simpleadder_sequence
Code 5.3 - Code for the sequencer
An explanation of the code will follow:
Line 8 starts the task body(), which isthe main task of a sequence
Line 11 starts a cycle in order togenerate 15 transactions
Line 12 initializes a blank transaction
Line 14 is a call that blocks until thedriver accesses the transaction being created
Line 15 triggers the rand keyword of thetransaction and randomizes the variables
of the transaction to be sent to the driver
Line 16 is another blocking call whichblocks until the driver has completed the
operation for the current transaction
Sequencer
The only thing missing is the sequencer.The sequence will be extended from the
classuvm_sequencer and it will beresponsible for sending the sequences to the driver.
The sequencer gets extended fromuvm_sequencer. The code can be seen on Code 5.4.
1 typedefuvm_sequencer#(simpleadder_transaction) simpleadder_seque
ncer;
Code 5.4 – Extension of the previoustransaction
The code for the sequencer is very simple,this line will tell UVM to create a basic
sequencer with the default API because wedon’t need to add anything else.
So, right now our environment has thefollowing structure
Figure 4.2 – State of the verification environment after the sequencer