UVM Sequence

UVM sequences are the core of stimulus generation in a UVM testbench. They define the order and content of transactions that are sent to the driver, which in turn drives the design under verification (DUV). Sequences provide a powerful mechanism for creating complex and reusable test scenarios.

Diagram of UVM Sequences

+-----------------------+
|   UVM Environment     |
|                       |
|  +------------------+ |
|  |     Agent        | |
|  | +--------------+ | |
|  | |  Driver      | | |
|  | +--------------+ | |
|  +------------------+ |
|                       |
|  +------------------+ |
|  |   Sequencer     |  |
|  +------------------+ |
|         ^             |
|         |             |
|  +--------------+     |
|  |   Sequence   |     |
|  +--------------+     |
|         ^             |
|         |             |
|  +--------------+     |
|  | Sequence Item|     |
|  +--------------+     |
+-----------------------+
          |
          v
+----------------------+
|      DUT             |
+----------------------+

Why Use Sequences?

  • Organization: Sequences organize transactions into logical units, making tests easier to understand and maintain.
  • Randomization: Sequences can randomize transaction data, allowing for comprehensive coverage of different input scenarios.
  • Control Flow: Sequences can control the flow of transactions, including loops, conditional branching, and synchronization with other sequences.
  • Reusability: Sequences can be reused in different tests, saving development time and effort.
  • Abstraction: Sequences operate at a higher level of abstraction than drivers, focusing on the “what” (what transactions to send) rather than the “how” (how to drive the interface).

 

Structure of a UVM Sequence:

A UVM sequence is a class that extends uvm_sequence. It typically contains the following key elements:

  1. `uvm_object_utilsĀ  Macro: This macro is essential for registering the sequence with the UVM factory.
  2. body() Task: This task defines the sequence’s main behavior, including creating, randomizing, and sending sequence items to the driver.
  3. Sequence Item Declarations: Declarations of the sequence item types that the sequence will use.

Diagram 1: Sequence in the Testbench Architecture

+-----------+     +-----------+     +---------+     +-----+
| Sequencer |---->| Sequence  |---->| Driver  |---->| DUV |
+-----------+     +-----------+     +---------+     +-----+
| get_next_ |     | create()  |     | drive() |     |     |
| item()    |     | randomize()|     |         |     |     |
+-----------+     +-----------+     +---------+     +-----+
                  ^
                  | Sequence Item

Defining a UVM Sequence

1. Sequence Class

A sequence class is derived from uvm_sequence. It implements the main functionality to generate and send sequence items to the driver.

class my_sequence extends uvm_sequence#(my_transaction);
  `uvm_object_utils(my_sequence)

  function new(string name = "my_sequence");
    super.new(name);
  endfunction

  task body();
    my_transaction tx;
    tx = my_transaction::type_id::create("tx");
    start_item(tx);
    if (!tx.randomize())
      `uvm_fatal("RAND_ERR", "Randomization failed");
    finish_item(tx);
  endtask
endclass

2. Creating and Starting Sequences

Sequences are typically created and started within the test class or environment.

class my_test extends uvm_test;
  `uvm_component_utils(my_test)

  my_env env;

  function new(string name = "my_test", uvm_component parent = null);
    super.new(name, parent);
  endfunction

  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    env = my_env::type_id::create("env", this);
  endfunction

  task run_phase(uvm_phase phase);
    phase.raise_objection(this);
    // Start the sequence
    env.m_sequencer.start(my_sequence::type_id::create("my_seq"));
    phase.drop_objection(this);
  endtask
endclass

Example Code:

`include "uvm_macros.svh"

class my_transaction extends uvm_sequence_item; // (From previous examples)
  rand bit [7:0] address;
  rand bit [31:0] data;
  rand bit write_enable;
  `uvm_object_utils(my_transaction)
  // ... (rest of the transaction class)
endclass

class my_sequence extends uvm_sequence#(my_transaction);
  `uvm_object_utils(my_sequence)

  function new(string name = "my_sequence", uvm_component parent = null);
    super.new(name, parent);
  endfunction

  task body();
    my_transaction req;

    repeat (10) begin // Repeat the sequence 10 times
      req = my_transaction::type_id::create("req"); // Create a transaction
      if (!req.randomize()) begin // Randomize the transaction
        `uvm_error("SEQ_RAND_FAIL", "Failed to randomize transaction");
      end
      start_item(req); // Send the transaction to the driver
      finish_item(req); // Signal transaction completion
    end
  endtask
endclass

Diagram 2: Sequence Class Structure

+-----------------+
| my_sequence     |
+-----------------+
| `uvm_object_utils|
| body()          |
| ...             |
+-----------------+

Starting Sequences:

Sequences are started on a sequencer using the start() method.

my_sequence seq = new();
seq.start(env.agent.sequencer); // Start the sequence on the agent's sequencer

Sequence Item Methods:

  • start_item(item): Sends a sequence item to the driver.
  • finish_item(item): Signals the completion of a sequence item.
  • get_response(rsp): Retrieves a response from the driver (if applicable).

Sequence Control Flow:

  • repeat(): Repeats a block of code a specified number of times.
  • if-else: Conditional branching.
  • fork-join: Parallel execution of multiple blocks of code.
  • wait_for(): Waits for a specific event.

Example with fork-join:

task body();
  fork
    begin
      my_sequence seq1 = new();
      seq1.start(m_sequencer);
    end
    begin
      my_other_sequence seq2 = new();
      seq2.start(m_sequencer);
    end
  join
endtask