Driver-Sequencer Handshake

The core mechanism that powers UVM stimulus generation. Understanding the interaction between `get_next_item` and `item_done` is critical for building correct drivers.

The Demand-Pull Handshake

In UVM, stimulus is not "pushed" to the driver. Instead, the driver pulls data when its physical interface is ready. This Demand-Pull handshake is the most critical synchronization point in a testbench, ensuring that zero-time sequences stay perfectly aligned with time-consuming RTL cycles.

The Port-Export Binding:

  • Driver: Owns the seq_item_port (Initiator).
  • Sequencer: Owns the seq_item_export (Target).
  • Handshake: A bidirectional method exchange that moves the item handle.

The Standard Handshake

The most common pattern uses get_next_item() and item_done(). This is a Serial Handshake: the driver processes one item at a time.

Protocol: Synchronous Driver

task run_phase(uvm_phase phase);
    forever begin
        // 1. Request: Blocks until a sequence provides data
        seq_item_port.get_next_item(req);
        // 2. Execution: Drive physical pins (consumes time)
        drive_to_pins(req);
        // 3. Acknowledge: Unblocks finish_item() in the sequence
        seq_item_port.item_done();
    end
endtask
                            

Advanced: The Pipelined Handshake

High-performance protocols (like AXI or NVMe) often have separate Address and Data phases. A standard handshake would be too slow here. Instead, we use get() and put() for pipelining.

Pipelining Strategy:

The driver can call get() to take an item and immediately start the next get() in a parallel thread, without waiting for the first transaction to finish on the pins. This allows multiple transactions to be "in-flight" simultaneously.

Implementation: Pipelined Driver

task run_phase(uvm_phase phase);
    fork
        // Thread 1: Keep grabbing items
        forever begin
            seq_item_port.get(req); // Non-handshaking pull
            pipeline_queue.push_back(req);
        end
        // Thread 2: Process the queue
        forever begin
            wait(pipeline_queue.size() > 0);
            drive_transfer(pipeline_queue.pop_front());
        end
    join
endtask
                            

Common Pitfalls

  • The Hanging Test: Forgetting to call item_done(). The sequencer will never release the sequence, and simulation will hang at the end of the first transaction.
  • ID Corruptions: When sending responses back via item_done(rsp), always call rsp.set_id_info(req) to ensure the response finds its way back to the correct sequence in a multi-sequence environment.

get_next_item() vs try_next_item() vs get()

This is one of the most frequently asked interview questions. Understanding when to use each method is critical for building robust drivers.

Method Blocking? Returns null? Needs item_done()? Use Case
get_next_item() Yes No Yes Standard synchronous handshake
try_next_item() No Yes (if empty) Yes (if not null) Polling, idle state drivers
get() Yes No No (implicit) Pipelined drivers
Example: Non-Blocking Polling Driver

task run_phase(uvm_phase phase);
    forever begin
        // Non-blocking: Check if sequence has data
        seq_item_port.try_next_item(req);
        if (req != null) begin
            // Got an item, drive it
            drive_to_pins(req);
            seq_item_port.item_done();
        end else begin
            // No item available, drive idle/default
            drive_idle();
        end
        @(posedge vif.clk);
    end
endtask
                            

Common Mistake

If you use try_next_item() and get a non-null item, you MUST still call item_done(). Forgetting this will hang your simulation.