UVM Sequence
In the last article we learnt to implement sequence item. Continuing further in our UVM track, in this article we will explore UVM sequences, their purpose, and how to define and use them effectively.
A UVM sequence is a fundamental element in UVM testbenches that generates transactions for drivers. Sequence creates instances of UVM sequence item class and sends it as a stimulus to driver.
What is UVM sequence?
A UVM sequence is a class derived from uvm_sequence
base class. It is responsible for creation of new sequence item objects and maintains the order in which the transactions or packets are sent to the driver.
Transactions or packets here are referred to the objects of UVM sequence item class. This is a general term used in TLM (transaction level modelling) test bench.
Sequences allow us to generate complex stimulus as multiple sequences can be orchestrated together to generate varieties of transactions.
Key Characteristics of UVM Sequences
UVM sequences have below characteristics:
- Transaction Control: Determines the flow of transactions in the testbench.
- Reusability: Can be reused across multiple drivers and test scenarios.
- Customizability: Allows for custom behavior and dynamic transaction generation.
Defining UVM sequence
Defining a UVM sequence is similar to UVM sequence item.
1. Defining class
We extend uvm_sequence
base class to define the UVM sequence class.
class my_sequence extends uvm_sequence#(my_seq_item);
Do note that the
uvm_sequence
base class is a parameterized class and we need to pass theuvm_sequence_item
class which will be used in the sequence.
2. Register using UVM factory
Just like UVM sequence item, UVM sequence also needs to be registered to UVM factory using the uvm_object_utils
macro.
`uvm_object_utils(my_sequence)
3. Constructor definition
Constructor definition is same as uvm_sequence_item
.
function new(string name = "my_sequence");
super.new(name);
// additional code goes here
endfunction`
Defining the body of sequence
At this point we are done with the basic definition of the UVM sequence class. Now, we need to define the sequence's behavior and the transaction flow. UVM sequence provides set of methods, which acts a entry point of the sequence class and defines the flow. These methods are unique to class extended from uvm_sequence
base class.
body() method
This is the main method where all the transaction flow is defined. Inside this, a sequence item is created, randomized and sent to the driver.
Before a new sequence item is randomized and sent to driver, we need to ensure that the driver is not busy. Until the driver is busy, sequence does not get a grant and waits for the grant. UVM sequencer is the component responsible for issuing grant to UVM sequence. We will learning more about sequencer later.
Various Callback methods
UVM sequence base class provides various other callback methods, which can be used to perform activities before and after main body execution. All methods are called sequentially starting with pre_start()
.
Method | Description | |
---|---|---|
task pre_start() | this is the entry point of the UVM sequence clas | |
task pre_body() | called only if call_pre_post is set to 1 and sequence is started with start | |
task pre_do() | called on the parent sequence only when parent_sequence is not null | |
task body() | always called and is the main method where transaction flow is defined | |
task post_do() | called on the parent sequence only when parent_sequence is not null | |
task post_body() | called only if call_pre_post is set to 1 and sequence is started with start | |
task post_start() | this is the last method to be called and used for housekeeping activites | |
All the above methods are task
because they can consume time. Also, all of these methods are callback methods and hence called automatically when sequence is started.
The above methods should not be called directly.
Implementing transaction flow
Inside body method, we need to create objects of uvm_sequence_item
known as transactions and pass them to the driver. To achieve this, UVM provides various methods. Lets see them step by step
1. Create item
First we need to create object of uvm_sequence_item
. This is known as transaction or packet and holds the stimulus that will be sent to the driver. This can be done either through create_item
method or using the create
method of UVM.
my_trans = my_seq_item::type_id::create("my_trans") //preferred method
// or
my_trans = create_item(my_seq_item)
Creating a new object for each packet/stimulus should be carefully thought off as in big test benches, we would generate thousands of stimulus. As each object takes memory, it can lead to higher memory consumption while running the TB. If possible the objects can be re-used.
2. Generate stimulus
Once transaction is created, we need to assign values to the various fields present inside uvm_sequence_item
. The values can either be assigned manually for directed scenarios or randomized for random cases.
Generation of stimulus can be before grant or after grant has been given. When stimulus is generated before grant it is known as early generation and when generated after grant, it is known as late generation.
3. Waiting for grant
As sequence can send a stimulus only when driver is free, thus we need to wait for the grant. UVM has a in-built mechanism for this and can be initiated by calling start_item()
. After this method is called, UVM waits for the grant and till then blocks further execution. Thus, start_time()
is blocking method and may consume time.
4. Sending packet
After calling the start_item()
, we need to call finish_item()
. Packet is sent when finish_item
is called. There should be no delay below these two calls to ensure that the packet is sent immediately when grant is issued.
Implementation example
virtual task body();
my_trans = my_seq_item::type_id::create("my_trans")
repeat(10) begin
start_item(my_trans)
if(!my_trans.randomize())
`uvm_error("SEQ", "transaction randomization failed!")
finish_item(my_trans)
end
endtask
In above example, we have re-used the same object to create multiple stimulus.
Starting a sequence
After the sequence is defined, we need to execute it from a top level component so that the sequence starts sending transaction flow.
- Create a object of sequence class in a top level component
- Use
start()
method call on the sequence object to start the sequence.
start()
The start()
methods take following arguments:
sequencer
- the sequencer which is associated with this sequence. All the arbitration is done by the associated sequencer.parent_sequence
- parent sequence of the sequence.this_priority
- sets the priority of the sequence. If sequencer is associated with multiple sequence.call_pre_post
- if set to 1 callspre_do
,post_do
methods.
Functional signature of start()
method:
virtual task start(uvm_sequencer_base sequencer, uvm_sequence_base parent_sequence = null, int this_priority = -1, bit call_pre_post = 1)
Example
In last article we created sequence item , namedtransaction
for our Test bench. We will continue working on the same test bench and create sequences for our TB.
`include "uvm_macros.svh"
import uvm_pkg::*;
class random_seq extends uvm_sequence#(transaction);
`uvm_object_utils(random_seq)
transaction trans;
int no_rnd_test_cases;
function new(string name = "rnd_seq");
super.new(name);
endfunction //new()
task pre_start();
// below variable can be fetched from config DB which helps
// in better scability. We will see that in future articles
no_rnd_test_cases = 200;
endtask //pre_start()
task body();
trans = transaction::type_id::create("trans");
for (int i=0; i < no_rnd_test_cases; i++) begin
start_item(trans);
if(!trans.randomize())
`uvm_fatal(get_type_name(), "Randomization failed")
trans.isRandom = 1;
finish_item(trans);
`uvm_info("RND_SEQ", "Sent packet", UVM_HIGH)
end
endtask //body()
endclass //random_seq extends uvm_sequence
Observations
- We have used
pre_start()
to initialize some of the variables needed in the sequence no_rnd_test_cases
is used to generate the expected number of transactions. This variable is hardcoded but can be fetched usingconfigDB
which increases scalability and flexibility.- Late generation methods is used to generate transactions.
Conclusion
This article outlines the essentials of UVM sequences, including their definition, utilities, and usage. Sequences are vital for creating reusable and scalable transaction flows in UVM testbenches. In the next article, we will explore layered sequences and advanced sequence control mechanisms.