Sequence-Driver-Sequencer communication in UVM

The communication between sequences, drivers, and sequencers is a fundamental aspect of UVM. It’s the core mechanism for generating and applying stimulus to the Design Under Verification (DUV). This communication flow ensures a well-coordinated and controlled verification process.

Key Components and Their Roles

  1. Sequence (uvm_sequence): Generates and controls the flow of transactions (sequence items).
  2. Sequencer (uvm_sequencer): Manages the execution of sequences and coordinates the flow of sequence items.
  3. Driver (uvm_driver): Drives the sequence items from the sequencer to the DUT through an interface.

 

Communication Flow Diagram

+-----------------------+
|       Sequence        |
|      (uvm_sequence)   |
+-----------------------+
          |
          v
+-----------------------+
|      Sequencer        |
|     (uvm_sequencer)   |
+-----------------------+
          |
          v
+-----------------------+
|        Driver         |
|      (uvm_driver)     |
+-----------------------+
          |
          v
+----------------------+
|        DUT           |
+----------------------+

 

  • Sequence: Generates transactions (sequence items).
  • Sequencer: Manages sequences and provides sequence items to the driver.
  • Driver: Drives the sequence items onto the DUV interface.

 

The Communication Flow:

The communication follows a specific protocol:

  1. Sequence Starts: A sequence is started on a particular sequencer using seq.start(sequencer).

  2. Request for Item (Sequence to Sequencer): The sequence calls start_item(item) to request that the sequencer make the item available to the driver. This call is blocking, meaning the sequence waits until the driver is ready to accept the item.

  3. Item Creation (Sequencer): The sequencer creates a new sequence item (transaction) using the factory (item_type::type_id::create()).

  4. Item Randomization (Sequence): The sequence randomizes the created item using item.randomize().

  5. Item Sending (Sequencer to Driver): The sequencer sends the randomized item to the driver through its seq_item_export using the put() method. The start_item() task within the sequence will return only after the item is sent to the driver.

  6. Item Reception (Driver): The driver retrieves the item from its seq_item_port using the blocking get_next_item(item) method. The driver waits here until an item is available.

  7. Driving (Driver to DUV): The driver drives the data from the received item onto the DUV interface.

  8. Item Completion (Driver to Sequence): After driving the transaction, the driver calls item_done() to signal completion to the sequence. The finish_item(item) task within the sequence will return after the driver calls item_done().

 

 

Diagram 1: Sequence, Sequencer, and Driver Interaction

+-----------+     start_item()     +-----------+     put()      +---------+     drive()     +-----+
| Sequence  |---------------------->| Sequencer |------------->| Driver  |------------->| DUV |
+-----------+      (Blocking)      +-----------+             +---------+             +-----+
| randomize()|                       | create()  |             | get_next_|             |     |
| finish_item()|<----------------------|           |             | item()   |             |     |
+-----------+      (Blocking)      +-----------+             +---------+             +-----+

  1.Sequence to Sequencer:

  • Start Sequence: The sequence generates sequence items and sends them to the sequencer.
  • Start Item: The start_item task is called to notify the sequencer that a new item is ready.
  • Randomization: The sequence item can be randomized before being sent to the sequencer.
  • Finish Item: The finish_item task is called after the item is processed.
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

  1. Sequencer to Driver:
    • Get Next Item: The driver calls get_next_item to retrieve the next sequence item from the sequencer.
    • Item Port Connection: The sequence item port of the driver is connected to the sequence item export of the sequencer.
    class my_driver extends uvm_driver#(my_transaction);
      `uvm_component_utils(my_driver)
    
      virtual function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        // Other build tasks
      endfunction
    
      task run_phase(uvm_phase phase);
        my_transaction tx;
        forever begin
          seq_item_port.get_next_item(tx); // Fetch transaction from sequencer
          drive(tx);                       // Drive the transaction to DUT
          seq_item_port.item_done();       // Indicate transaction completion
        end
      endtask
    
      virtual task drive(my_transaction tx);
        // Example driving logic
        // Drive transaction to DUT
      endtask
    endclass
    
  2. Driver to DUT:
    • Drive Transaction: The driver takes the sequence item and drives it to the DUT through the interface.
    • Synchronization: The driver ensures proper synchronization with the DUT’s clock or control signals.
    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
    

Example Code (Simplified):

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

  task body();
    my_transaction trans;
    trans = my_transaction::type_id::create("trans");
    assert trans.randomize();
    start_item(trans); // Blocking call - Sequence waits here
    finish_item(trans); // Blocking call - Sequence waits here
  endtask
endclass

// Driver
class my_driver extends uvm_driver#(my_transaction);
  `uvm_component_utils(my_driver)

  task run_phase(uvm_phase phase);
    my_transaction trans;
    forever begin
      seq_item_port.get_next_item(trans); // Blocking call - Driver waits here
      drive_transaction(trans);
      seq_item_port.item_done();
    end
  endtask

  // ... drive_transaction implementation ...
endclass

Diagram 2: Detailed Timing Diagram

Time -->
Sequence: | create | randomize | start_item (BLOCKING) -------------------------- finish_item (BLOCKING) |
                                     ^                                                              ^
                                     |                                                              |
Sequencer: |                         | put()                                                          |
                                     v                                                              v
Driver:   |                          | get_next_item (BLOCKING) - drive_transaction - item_done()       |

Key Points:

  • start_item() and get_next_item() are blocking calls, ensuring synchronization between the sequence and the driver.
  • The put() method on the sequencer’s export sends the item to the driver.
  • The item_done() method signals completion of the transaction to the sequence.
  • This handshaking mechanism ensures that transactions are processed in the correct order and that no data is lost.