Start a sequence in UVM

In Universal Verification Methodology (UVM), sequences are used to generate transactions that drive stimulus to the DUT (Device Under Test). However, understanding how to start a sequence and manage its communication with the sequencer and driver is critical to implementing an efficient and reusable UVM testbench.

The sequence, sequencer, and driver are the primary components involved in transaction generation and delivery. Sequences must be associated with a sequencer so they can send transactions. This is done when starting the sequence. The sequencer is most probably the m_sequencer which have reference to the sequencer on which sequence is running.

Example:

axi_sequence seq;  
seq = axi_sequence::type_id::create("seq");  // Create the sequence  
seq.start(env.agent.axi_seqr);  // Start the sequence on the sequencer

Here, env.agent.axi_seqr is a reference to the sequencer.

communication between sequence, sequencer, and driver:

The flow of communication between sequence, sequencer, and driver is as follows:

  1. create()
  2. wait_for_grant()
  3. Randomizing a sequence item
  4. send_request(req)
  5. wait_for_item_done()
  6. get_response(rsp)

1. create() Method

The create() method is used to dynamically instantiate objects such as sequences and sequence items. This is useful in cases where the exact object to be created is determined at runtime, enabling flexibility and reusability.

Example:

axi_transaction req;
req = axi_transaction::type_id::create("req");  // Dynamically create a transaction

This ensures that the axi_transaction object is properly registered with the UVM factory, making it compatible with UVM’s factory override mechanism.

2. wait_for_grant() Method

The wait_for_grant() method ensures that the sequence has been granted access to the sequencer before it starts generating transactions. This is particularly important when multiple sequences are running on the same sequencer, as the sequencer arbitrates between them.

Example:

wait_for_grant();

This method blocks the sequence until the sequencer grants it permission to proceed.

3. Randomizing a Sequence Item

Randomization allows you to generate diverse stimulus by applying constraints to transaction fields. The randomize() method is used to randomize the fields of a sequence item based on its defined constraints.

Example:

if (!req.randomize())  
    `uvm_error(get_name(), "Transaction randomization failed")

This ensures that the transaction (req) is generated with randomized field values that meet the constraints defined in the transaction class.

4. send_request(req) Method

The send_request(req) method is used to send a transaction request from the sequence to the sequencer. This method initiates the transaction and allows the sequencer to pass the transaction to the driver for execution.

Example:

send_request(req);  // Send the transaction request to the sequencer

5. wait_for_item_done() Method

After sending a transaction, the sequence must wait for the driver to complete its processing of the transaction. The wait_for_item_done() method ensures that the sequence does not proceed until the sequencer receives acknowledgment from the driver.

Example:

wait_for_item_done();  // Wait until the transaction is processed

6. get_response(rsp) Method

The get_response(rsp) method is used to retrieve a response from the driver, if applicable. This is particularly useful in scenarios where the DUT generates a response (e.g., read operations).

Example:

axi_transaction rsp;
get_response(rsp);  // Retrieve the response transaction

This allows the sequence to process or verify the response from the DUT. Steps 5 and 6 are optional.

Example: Complete Flow Using These Methods

Let’s look at a practical example to demonstrate how these methods work together. This example uses an AXI transaction to show a typical read/write operation.

AXI Transaction Class:
class axi_transaction extends uvm_sequence_item;
  `uvm_object_utils(axi_transaction)

  rand bit [31:0] addr;
  rand bit [31:0] data;
  rand bit        write;  // 1 for write, 0 for read

  constraint addr_c { addr < 32'hFFFF; }  
  constraint data_c { data != 0; }

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

  function string convert2string();
    return $sformatf("ADDR: %0h, DATA: %0h, WRITE: %0b", addr, data, write);
  endfunction
endclass
AXI Sequence Class:
class axi_sequence extends uvm_sequence #(axi_transaction);
  `uvm_object_utils(axi_sequence)

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

  virtual task body();
    axi_transaction req, rsp;

    // Wait for sequencer grant
    wait_for_grant();

    // Create and randomize a transaction
    req = axi_transaction::type_id::create("req");
    if (!req.randomize())  
      `uvm_error(get_name(), "Transaction randomization failed")

    `uvm_info(get_name(), $sformatf("Generated transaction: %s", req.convert2string()), UVM_HIGH)

    // Send the transaction request
    send_request(req);

    // Wait for the driver to complete the transaction
    wait_for_item_done();

    // Get the response from the driver (for read operations)
    get_response(rsp);
    `uvm_info(get_name(), $sformatf("Received response: %s", rsp.convert2string()), UVM_HIGH)
  endtask
endclass

Key Takeaways

  • The create() method allows for dynamic object creation.
  • The wait_for_grant() method ensures proper arbitration in multi-sequence environments.
  • Randomization is essential for generating diverse stimulus.
  • The send_request() and wait_for_item_done() methods coordinate the flow of transactions between the sequence, sequencer, and driver.
  • The get_response() method retrieves responses for scenarios like read operations.