UVM Sequencer

The UVM sequencer is a crucial component in a UVM testbench, responsible for generating and managing sequences of transactions (sequence items). It acts as the source of stimulus for the driver, orchestrating the flow of data to the Design Under Verification (DUV).

Diagram of UVM Sequencer

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

Defining a UVM Sequencer

1. Sequencer Class

A sequencer class is derived from uvm_sequencer. It manages the flow of sequence items from sequences to the driver.

class my_sequencer extends uvm_sequencer#(my_transaction);
  `uvm_component_utils(my_sequencer)

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

2. Using the Sequencer in an Agent

Sequencers are typically instantiated and connected within an agent.

class my_agent extends uvm_agent;
  `uvm_component_utils(my_agent)

  my_driver drv;
  my_sequencer seqr;

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

  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    drv = my_driver::type_id::create("drv", this);
    seqr = my_sequencer::type_id::create("seqr", this);
  endfunction

  virtual function void connect_phase(uvm_phase phase);
    super.connect_phase(phase);
    drv.seq_item_port.connect(seqr.seq_item_export);
  endfunction
endclass

Creating and Running Sequences

Sequences generate and send sequence items to the sequencer.

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

  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

Why Use a Dedicated Sequencer?

  • Centralized Sequence Management: Provides a single point for generating and controlling sequences.
  • Sequence Prioritization: Allows you to prioritize different sequences, controlling which transactions are sent to the driver first.
  • Sequence Randomization: Works in conjunction with sequences to randomize transaction data, creating diverse test scenarios.
  • Phasing and Synchronization: Helps synchronize the execution of sequences with the rest of the testbench.
  • Reusability: Sequencers can be reused across different tests and even different projects, as long as the transaction type remains the same.

 

Structure of a UVM Sequencer:

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

  1. `uvm_component_utils Macro: This macro is essential for registering the sequencer with the UVM factory and enabling other UVM features.
  2. seq_item_export: An export of type uvm_seq_item_pull_port used to provide sequence items to the driver.
  3. new() Constructor: The constructor is used to create an instance of the sequencer.  

Diagram 1: Sequencer in the Testbench Architecture

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

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_sequencer extends uvm_sequencer#(my_transaction);
  `uvm_component_utils(my_sequencer)

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

Diagram 2: Sequencer Class Structure

+-----------------+
| my_sequencer    |
+-----------------+
| `uvm_component_|
|  utils(my_seq)  |
| seq_item_export|
| new()           |
+-----------------+

How Sequences Interact with the Sequencer:

  1. A sequence is started on a specific sequencer using the seq.start(sequencer) method.
  2. The sequence then calls sequencer.get_next_item() to request a new sequence item.
  3. The sequencer creates a new sequence item (using the factory).
  4. The sequence randomizes the sequence item’s data members.
  5. The sequence sends the randomized sequence item to the driver using sequencer.seq_item_port.put().
  6. The driver receives the sequence item using seq_item_port.get_next_item().

Diagram 3: Sequence and Sequencer Interaction

+-----------+     get_next_item()    +-----------+     put()     +---------+
| Sequence  |---------------------->| Sequencer |------------->| Driver  |
+-----------+                       +-----------+             +---------+
| create()  |                       | create()  |             |         |
| randomize()|                       |           |             |         |
+-----------+                       +-----------+             +---------+

Example of Sequence Starting on a Sequencer:

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

  task body();
    my_transaction trans;
    repeat (10) begin // Generate 10 transactions
      trans = my_transaction::type_id::create("trans");
      assert trans.randomize();
      start_item(trans); // Send the transaction to the sequencer
      finish_item(trans);
    end
  endtask
endclass

// In a test's run_phase:
task run_phase(uvm_phase phase);
  my_sequence seq = new();
  phase.raise_objection(this);
  seq.start(env.agent.sequencer); // Start the sequence on the sequencer
  phase.drop_objection(this);
endtask

Key Takeaways:

  • The sequencer generates and manages sequences of transactions.
  • It uses a seq_item_export to provide sequence items to the driver.
  • Sequences are started on a specific sequencer using seq.start(sequencer).
  • The sequencer works in conjunction with sequences and drivers to generate stimulus for the DUV.
  • The start_item() and finish_item() tasks are used within the sequence body to send the transaction to the driver.