In modern verification environments, UVM sequences serve as the backbone of stimulus generation. This comprehensive guide is divided into two parts, taking you from fundamental concepts to advanced implementation techniques.
The first part of our journey focuses on the foundational elements of UVM sequences, covering:
The second part delves into virtual sequences and their implementation, exploring:
This structured approach ensures readers first master the fundamentals before tackling more complex virtual sequencing concepts. Whether you're building your first UVM testbench or looking to optimize your verification environment, this guide provides the knowledge needed to effectively implement and manage UVM Sequences.
Let's begin our exploration with the fundamental question: What exactly is a Sequencer in UVM?
A Sequence is a class extended from uvm_sequence that creates, randomizes, and transmits stimuli to a Sequencer, which then transmits them to the Driver.This makes tests more efficient and easy to write, providing abstraction and reuse.
In depth, to simplify the transmission of Sequence items, it's important to consider the role of both Sequences and Sequencers in the context of the DUT (Device Under Test) stimulation. A Sequence, which is executed within a Sequencer, generates transactions (Sequence items) to the Driver that are sent through the Sequencer agent mechanism. These transactions are translated by the Driver into pin-level interactions for use with the DUT.
Subsequently, a diagram illustrates the transmission of Sequence items
Figure 1
The basic structure of a Sequence is divided into:
An example of a basic Sequence this is shown in the next code:
The demo1_seq_base class is a base class that extends from the uvm_sequence class, which is parameterized by the type of data to be generated. In this case, the uvm_sequence class is parameterized by the demo1_seq_item type, meaning that demo1_seq_base is designed to work with sequences of demo1_seq_item objects. The demo1_seq_base class provides a common structure and functionality for generating and handling these sequences, while leaving the specific implementation details to be defined in derived classes. This promotes code reuse and modularity in the design of UVM sequences.
As previously stated, the Sequences are executed in the Sequencers that extend from uvm_sequencers. This component is parameterized by the Sequence item to be used, and serves the purpose of controlling the Sequence item request and response flow (req_fifo, rsp_fifo) between the Sequence and the Driver as shown on figure 4.
The following code illustrates the fundamental syntax for using a sequencer derived from uvm_sequencer.
When a Sequence is started, it is associated with a Sequencer. The “m_sequencer” handle contains the reference to the Sequencer on which the Sequence is running. The behavior of m_sequencer will be explained in the Sequence-Sequencer handle section.
A Sequence item is an object that encapsulates all the necessary data to generate transactions to the Device Under Test (DUT). This data typically includes constraints, control information, configuration parameters, and other relevant details that dictate the behavior.
Sequence items are also transient objects, which means, they are created, used, and then can be discarded. Both Sequences and Sequence items are objects. Since they are objects, they can be randomized, which is a very useful property.
They also have several virtual methods that help us to implement common functions such as copy, clone, compare, print, etc.
The handling of Sequence items is closely tied to the way they are transmitted by the Driver. The transmission can generally be categorized into four main types:
Sequence items don't have a parent-child hierarchy in the same sense that UVM components do. Instead, they are typically managed and operated upon by the Sequences and Sequencers within a UVM testbench. Their lifecycle is strictly controlled by the Sequences that create and use them.
In UVM, classes are organized into hierarchies that establish a structural basis for the development of flexible and reusable verification environments. The before mentioned hierarchies consist in the following classes:
Each class layer provides a unique role in organizing data flow and communication, forming the foundation for developing virtual classes that further abstract functionality in the UVM environment.
Figure 2
Like was shown on Sequence fundamentals, a Sequence like other UVM classes is divided into: declarations, new, body and others. For their operation the body method is a fundamental part as it determines the behavior of a Sequence. This method can generate transactions, start other Sequences and/or write registers. Also manages the generation of Sequence items that are sent to the Driver.
Figure 3
The following code illustrates a potential format for a body function declaration:
Therefore there is a handshake going between the Sequences running on the Sequencer then generating transactions on the body, and the run phase method of the Driver that is calling down transactions as shown in the next steps:.
This function notifies the Driver that this particular Sequence is ready to send a request. Also, the start_item(req) becomes blocked until the Driver enables it, signaling that it is ready to receive a transaction. When it returns from that function (because the Driver is ready to receive it through get_next_item()), it proceeds to step 3, which is to randomize the Sequence item.
All randomisable fields of the Sequence are randomized in this part.
In this case, the finish_item (req) finally sends the packet (sequence item) to the Driver. The Driver receives it and starts doing his part, such as moving the pins of the DUT. Meanwhile, the finish_item becomes blocked and waits for the Driver to finish its process by calling item_done(), a function that belongs to the Driver.
Once the Driver executes item_done(), the communication build between the Driver and Sequencer for this particular seq item ends.
Figure 4
The interaction between the Sequencer and the Driver using req_fifo and rsp_fifo will not be explained here because it is not the goal of this blog.
As was shown in the previous Figure 4 the Sequencer will handle the sending arbitration between the Driver and the Sequences. If there's only one Sequence, it's very straightforward, but in the case of multiple Sequences there will be required an “arbitration”.
This is a built-in mechanism of the sequencer that can be called on the sequence through the set_arbitration(arb_type) line and decides which sequence gets the permission to send its transaction item to the driver.
Following are the six Sequence Arbitration mechanism:
Each Sequence has a handle called m_sequencer to a Sequencer (which runs this Sequence). After the Sequence starts in the Sequencer, the m_sequencer handle is set to the Sequencer that was passed to it in the start method as shown in the next code:
The p_sequencer handle is a pointer to which the m_sequencer is assigned, this (the p_sequencer handle) is assigned the type of Sequencer to execute in the Sequence and is more accessible in its use than the m_sequencer.
We use the p_sequencer inside our Sequences to access the methods and properties of the Sequencers defined by the user.
As opposed to the m_sequencer handle, the p_sequencer handle is not automatic, which means that it has to be declared using the uvm_declare_p_sequencer() macro. This macro and the p_sequencer are used to access the properties and behaviors of the Sequencer inside the Sequence.
In UVM, while the p_sequencer handle is not mandatory, its use can greatly simplify the process of working with Sequences, particularly when dealing with custom or virtual Sequencers. When the macro is used, it automatically performs the following:
Below we see an example of using this Virtual Sequencer as shown on figure 4 on a sequence.
In part 2 of this blog, the Sequencer implementation of the Virtual Sequencer (vsequencer) will be explained in more detail.
In conclusion, the macro automates critical steps such as declaration, type assignment, and type casting, ensuring that your Sequences can interact with their Sequencers in a robust and reliable manner. This automation is particularly beneficial in complex UVM environments, where Sequences may need to interact with Virtual Sequencers.
In the initial section of this two-part document, we examine the basics of a Sequence and how to interact with the Sequencers and Sequence items that are essentials for UVM verification. In a modern verification where the coordination and management of multiple transactions across multiple interfaces is required, the use of virtual Sequences and virtual Sequencers is not only recommended, but practically essential. The following section will present an overview of Virtual Sequences and Sequencers, and how they differ from normal Sequences and Sequencers.
Diaz Miguez Lucas and Doctorovich Juan
Design Verification Engineers at Emtech S.A.
Thanks, Gabriel Duga, Edgar Solera, Rubén Rodríguez and Marcelo Pouso for their valuable feedback and insightful reviews.
—-------------------------------------------------------------------------------------------------
Any Comments or questions, please feel free to contact us: info@emtech.com.ar