Virtual sequences and virtual sequencers

Understanding UVM Sequences: A Two-Part Guide

From Basic Sequences to Virtual Sequences Implementation

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.

Part 1: Understanding Basic Sequences and Sequencers

The first part of our journey focuses on the foundational elements of UVM sequences, covering:

  • Fundamentals: Understanding what a Sequence, Sequencer and Sequence items are, their UVM hierarchy and their critical role in the UVM testbench architecture.
  • Sequence Operation: Detailed exploration of how Sequences work, including their execution flow and basic implementation.
  • Sequence-Sequencer handles: Understanding the differences and applications of Sequencers m_sequencer and p_sequencer.
  • Integrated view and conclusion: A resume of what is described in the previous sections and what is expected for the second part.

Part 2: Advanced Virtual Sequencing

The second part delves into virtual sequences and their implementation, exploring:

  • Virtual Sequences and Virtual Sequencers: In-depth coverage of Virtual Sequences and Virtual Sequencers, understanding their purpose and architecture
  • Multi-Driver Control: Techniques for coordinating stimulus generation across multiple Drivers simultaneously
  • Implementation Details: Practical guide to implementing virtual Sequencers in your verification environment
  • Sequence Initialization: Best practices for starting and managing virtual Sequences effectively
  • Overview: Overall conclusion and final thoughts.

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?

Sequence fundamentals

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:

  • Declarations
  • New Function
  • Body method
  • Other Functions, Tasks, etc

An example of a basic Sequence this is shown in the next code:

Example 1

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.

Sequencer fundamentals

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.

Example 2

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.

Sequence item fundamentals

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: 

  • Unidireccional non-pipelinedsome text
    • In a unidirectional setup, sequence items flow in one direction from the Sequencer to the Driver, and then from the Driver to the DUT
  • Bidireccional non-pipelinedsome text
    • In bidirectional transmission, there is a two-way communication between the Sequencer (through the Driver) and the DUT. The Sequence item not only drives data or commands to the DUT but also expects a response back
  • Pipelinedsome text
    • Pipelined transmission is more complex and involves multiple Sequence items being processed in parallel stages. In this model, the Driver handles multiple transactions concurrently, often overlapping them in time.
  • Out-of-Order pipelinedsome text
    • This model provides support for advanced features like out-of-order transactions, where response phases need not be in an order with the request phases. Depending on the availability of the response data (driven from the corresponding slave) can be responded back in a pipelined fashion.

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.

Hierarchical UVM

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:

  • uvm_object: the base class for non-phase-related UVM elements, providing essential object functionalities like creation, comparison, and factory support. it does not interact with the UVM phase mechanism (e.g., no run_phase, build_phase methods).
  • uvm_transaction: Extends uvm_object, adding transaction-specific functionality for data packets (transactions) used across testbench components but still doesn't engage with UVM phases.
  • uvm_sequence_item: A subclass of uvm_transaction, representing individual items in Sequences. This class offers features for packing, unpacking, and randomization, enabling easy data manipulation.
  • uvm_sequence: A parameterized class that extends uvm_transaction, built to manage specific types of uvm_sequence_item. It controls the Sequence item flow and interactions within the testbench.
  • uvm_component: Represents testbench structure elements like drivers and monitors, supporting UVM phases such as build, connect, and run.
  • uvm_sequencer: An extension of uvm_component, acting as an interface between Sequences and Drivers to coordinate stimuli generation and processing.

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

Sequence operation

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:

Example 3

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:. 

  1. The transaction item is created in the Sequence with the handle.
  2. The start_item(req) is called.

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.

  1. The sequence or transaction is randomized.

All randomisable fields of the Sequence are randomized in this part.

  1. The finish_item(req) is called.

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:

  • UVM_SEQ_ARB_FIFO (default): Processes Sequences in the order they are received, using a First-In, First-Out approach.
  • UVM_SEQ_ARB_WEIGHTED: Gives priority to Sequences with higher weights, followed by random selection among sequences with the same weight.
  • UVM_SEQ_ARB_RANDOM: Selects Sequences purely at random without considering any priority, even for partially completed Sequences.
  • UVM_SEQ_ARB_STRICT_RANDOM: Random selection, but with a higher likelihood for Sequences with greater weights.
  • UVM_SEQ_ARB_STRICT_FIFO: Gives priority to higher-weighted Sequences, followed by processing in the order they were received (FIFO) for Sequences with the same weight.
  • UVM_SEQ_ARB_USER: Allows the user to define a custom arbitration scheme by overriding the user_priority_arbitration() method.

Sequence-Sequencer handles

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:

Example 4

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:

  • It is declared (The macro declares the p_sequencer handle).
  • It is set (The p_sequencer is assigned the specific Sequencer type).
  • It is checked when a new virtual sequence is created, and it correctly points to the Virtual Sequencer running the Virtual Sequence ($cast()).
Example 5

Below we see an example of using this Virtual Sequencer as shown on figure 4 on a sequence.

Example 6

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.

Integrated view and conclusion

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