UVM Driver

The muscular arm of the testbench. It pulls transactions from the sequencer and drives them to the DUT signals.

The Protocol Translator

What Does a Driver Actually Do?

The Simple Version: The Driver is your testbench's "hands." It takes test instructions and physically applies them to your design's signals.

Think of it like: A pianist. The Sequencer gives it sheet music (transactions), and the Driver presses the actual keys (signals) at the right time.

A UVM Driver is the "Muscle" of the agent. Its job is to take high-level test instructions (called transactions) from the Sequencer and convert them into actual signal changes on your design's pins.

New to these terms?
  • Transaction: A high-level test action (e.g., "write data 0x42 to address 0x100")
  • Sequencer: The component that generates test transactions
  • DUT: Design Under Test—the hardware you're verifying
  • Pins/Signals: The actual wires connecting to your design

1. How the Driver Gets Transactions

The Driver and Sequencer work together like a relay race. Here's how they coordinate:

  • seq_item_port.get_next_item(req): This is a blocking call. The driver sleeps here until a sequence provides a new transaction.
  • seq_item_port.item_done(): This is a non-blocking signal back to the sequencer. It unblocks the finish_item() call in the sequence.

Driving Signals via Virtual Interfaces

Drivers do not access DUT signals directly. They use a Virtual Interface (VIF). This layer of abstraction allows your UVM component to remain pure SystemVerilog while interacting with the static world of Verilog modules.

Implementation: Standard Driver Loop

class axi_driver extends uvm_driver #(axi_item);
    `uvm_component_utils(axi_driver)
    virtual axi_if vif;
    task run_phase(uvm_phase phase);
        // Reset the interface
        vif.valid <= 0;
        forever begin
            // Get the transaction
            seq_item_port.get_next_item(req);
            // DRIVE logic
            @(posedge vif.clk);
            vif.addr  <= req.addr;
            vif.data  <= req.data;
            vif.valid <= 1;
            // Wait for DUT acknowledgment
            wait(vif.ready == 1);
            @(posedge vif.clk);
            vif.valid <= 0;
            // Complete handshake
            seq_item_port.item_done();
        end
    endtask
endclass
                            

Advanced: Pipelined Handshaking

Modern protocols (like AXI or PCIe) are pipelined. This means a driver might start a new request before the previous response has finished.

The Pipelining Pattern:

Instead of a simple loop, you use fork...join_none to handle the "Data Phase" and "Address Phase" in parallel.

Conceptual Pipelined Drive

task run_phase(uvm_phase phase);
    forever begin
        seq_item_port.get_next_item(req);
        // 1. Driving Phase (Blocking)
        drive_address_phase(req); 
        // 2. Data Phase (Non-blocking / Background)
        fork
            automatic axi_item req_copy = req;
            drive_data_phase(req_copy);
        join_none
        // 3. Immediately ask for next item while data drives
        seq_item_port.item_done();
    end
endtask
                            

This allows the sequencer to generate the next address while the hardware is still busy with the current data phase, maximizing bus throughput.