UVM Driver: run_phase and the get_next_item Handshake

The UVM driver is the component that finally turns abstract stimulus into real pin activity. Everything above it in a UVM testbench deals in transactions: a sequence builds an object that says “read address 0x10” and hands it down. The driver is the one place that knows what a read actually looks like on the wires — which signals go high, on which clock edge, and for how many cycles. If you understand the driver, you understand the boundary between the object-oriented world of UVM and the RTL world of your DUT.

Note on running the code: the canonical examples below are written for a UVM-capable simulator (industry-standard simulation tools, VCS or industry-standard simulators, or the free EDA Playground in the browser). They depend on the UVM base-class library and will not compile on bare Icarus Verilog or Verilator, so they are shown as structure, not as runnable snippets. The one block marked as a plain-SystemVerilog illustration near the end uses no UVM library at all and does run on a free simulator.

Where the driver sits

In a typical agent you have a sequencer, a driver, and a monitor. The sequencer owns the stream of transactions produced by your sequences. The driver pulls one transaction at a time from the sequencer, drives it onto a virtual interface, and reports back when it is done. The monitor independently watches the same pins and reconstructs transactions for checking. The driver never randomizes anything and never decides what to send — it only decides how a given item appears on the bus. Keeping that responsibility narrow is what makes a UVM driver reusable across tests.

Extending uvm_driver

Your driver extends the parameterized base class uvm_driver #(REQ), where REQ is your sequence-item type. That parameter is important: it gives the driver a typed port, seq_item_port, that already knows how to talk to a sequencer carrying the same item type. You also register the class with the factory and grab a handle to the virtual interface in the build phase.

class alu_item extends uvm_sequence_item;
  rand bit [7:0] a;
  rand bit [7:0] b;
  rand bit       valid;

  `uvm_object_utils(alu_item)

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

class alu_driver extends uvm_driver #(alu_item);
  `uvm_component_utils(alu_driver)

  virtual alu_if vif;

  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction

  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    if (!uvm_config_db#(virtual alu_if)::get(this, "", "vif", vif))
      `uvm_fatal("DRV", "virtual interface not set for alu_driver")
  endfunction
endclass

Two details matter here. First, the driver asks for the virtual interface through uvm_config_db rather than hard-wiring it — the environment sets it once, and every driver instance picks it up by name. Second, a missing interface is a uvm_fatal, not a warning: a driver with no bus to drive is useless, so failing loudly at build time saves hours of confusion later.

The run_phase

All of the driver’s real work happens in run_phase, the one phase that consumes simulation time. The shape is almost always the same: an infinite loop that fetches an item, drives it, and signals completion. The loop never exits on its own — the test ends when objections drop elsewhere, and the driver is simply killed with its loop still waiting.

task alu_driver::run_phase(uvm_phase phase);
  // bring the bus to a known idle state first
  vif.valid <= 1'b0;

  forever begin
    alu_item req;

    // 1) ask the sequencer for the next transaction (blocks if none)
    seq_item_port.get_next_item(req);

    // 2) translate the transaction into pin activity
    drive_item(req);

    // 3) tell the sequencer this item is finished
    seq_item_port.item_done();
  end
endtask

Resetting the bus to idle before the loop is a small habit worth keeping. Without it, the very first clock edges see X on your control signals, and a monitor or assertion may fire before any real stimulus has started.

The get_next_item / item_done handshake

The two calls get_next_item and item_done form a strict pull handshake between the driver and the sequencer. Understanding it is the single most important part of learning the UVM driver, because almost every beginner bug lives here.

get_next_item(req) blocks. If a sequence is ready with a transaction, the call returns immediately with a handle to it. If nothing is pending, the driver’s run loop simply stops at that line and waits — no cycles are wasted, no polling happens. From the sequencer’s side, the item is now “in flight”: it has been handed to the driver but not yet acknowledged.

item_done() closes that handshake. Until you call it, the sequencer considers the current item unfinished and will not release the next one. The sequence that called finish_item is unblocked only when item_done runs. This is what keeps the driver and the sequence in lockstep.

CallWhat it does
get_next_item(req)Blocks until a transaction is available; returns a handle and marks it in flight.
try_next_item(req)Non-blocking form; returns a null handle immediately when nothing is pending.
item_done()Completes the handshake and frees the sequencer to offer the next item.
item_done(rsp)Same, but also passes a response object back to the sequence.

The rules that follow from this are simple but unforgiving. Every get_next_item must be matched by exactly one item_done. Call item_done twice and you corrupt the sequencer’s internal state; forget it and your sequence hangs forever, usually showing up as a simulation that runs to its timeout with no progress. If you need to send a response (a read’s returned data, for example), either pass it to item_done(rsp) or use the separate put_response path — but pick one convention per agent and stay with it.

There is also a second style, get followed by separate work, and a peek/get pattern for pipelined drivers. For a first driver, stick to the get_next_item/item_done pair: it is the clearest expression of “one item at a time”.

Driving the virtual interface

The drive_item task is where the transaction meets the clock. A virtual interface is just a handle to a real SystemVerilog interface instance that is wired to the DUT. Because the driver drives signals synchronously, every assignment is tied to a clock edge and uses non-blocking assignment to avoid races with the DUT and the monitor.

interface alu_if (input logic clk);
  logic [7:0] a;
  logic [7:0] b;
  logic       valid;
  logic [8:0] result;
endinterface

task alu_driver::drive_item(alu_item req);
  // wait for a clock edge, then present the stimulus
  @(posedge vif.clk);
  vif.a     <= req.a;
  vif.b     <= req.b;
  vif.valid <= req.valid;

  // hold for one cycle, then deassert the request
  @(posedge vif.clk);
  vif.valid <= 1'b0;

  `uvm_info("DRV",
    $sformatf("drove a=0x%0h b=0x%0h", req.a, req.b),
    UVM_HIGH)
endtask

A few points of good practice are baked into this snippet. Driving is gated on @(posedge vif.clk) so stimulus changes align with the protocol rather than at arbitrary times. Non-blocking assignment (<=) keeps the driver from fighting the sampling logic. And the uvm_info message at UVM_HIGH verbosity gives you a trace you can turn on only when debugging, instead of flooding every run. In a production driver you would replace the fixed two-cycle timing with the real handshake your protocol defines — waiting on a ready or ack signal, for instance.

A plain-SystemVerilog view of the pull loop

To see the driver’s run loop stripped of all UVM machinery, here is the same idea in plain SystemVerilog: a queue stands in for the sequencer’s item stream, and a method pulls one item, “drives” it (which costs time), and records that it is done. This block uses no UVM library, so it compiles and runs on a free simulator — watch how the loop processes exactly one item per pass, just like get_next_item/item_done.

module tb;
  // Plain-SystemVerilog model of a UVM driver's run loop:
  // pull an item, drive it (which costs time), mark it done.
  class packet;
    byte unsigned addr;
    byte unsigned data;
    function new(byte unsigned a, byte unsigned d);
      addr = a;
      data = d;
    endfunction
  endclass

  class driver;
    packet q[$];        // stands in for the sequencer's item stream
    int    driven = 0;

    function void push(packet p);
      q.push_back(p);
    endfunction

    // One pass = get_next_item -> drive -> item_done, repeated.
    task run();
      packet p;
      while (q.size() > 0) begin
        p = q.pop_front();                 // get_next_item()
        #10;                               // driving the bus costs time
        $display("t=%0t: drove addr=0x%02h data=0x%02h",
                 $time, p.addr, p.data);
        driven++;                          // item_done() bookkeeping
      end
    endtask
  endclass

  driver drv;

  initial begin
    drv = new();
    drv.push(new(8'h10, 8'hA0));
    drv.push(new(8'h11, 8'hA1));
    drv.push(new(8'h12, 8'hA2));
    drv.run();
    $display("t=%0t: driver finished, %0d items driven", $time, drv.driven);
    $finish;
  end
endmodule

t=10: drove addr=0x10 data=0xa0
t=20: drove addr=0x11 data=0xa1
t=30: drove addr=0x12 data=0xa2
t=30: driver finished, 3 items driven

The real UVM driver differs in one key way: instead of a finite queue it loops forever, and get_next_item blocks when the stream is empty rather than ending the loop. But the per-item rhythm — fetch, drive over time, acknowledge — is exactly the same.

Common mistakes to avoid

  • Forgetting item_done(), which hangs the sequence and shows up as a timeout with no obvious error.
  • Calling item_done() twice for one get_next_item, which desynchronizes the sequencer.
  • Putting driving logic in build_phase or connect_phase — only run_phase may consume time.
  • Using blocking assignment on interface signals, creating races with sampling.
  • Randomizing or deciding stimulus inside the driver, which breaks reuse and belongs in sequences.
  • Driving on an uninitialized virtual interface because the config_db set/get names did not match.

Keep the driver small, synchronous, and protocol-focused, and let the handshake carry the timing. Do that, and the same UVM driver will serve every sequence you ever point at it.

Quick check

Why must the driver’s main work live in run_phase and not build_phase?

Because run_phase is the phase that is allowed to consume simulation time (it is a task, not a function). Build and connect phases run in zero time to construct and wire the hierarchy, so clock-edge waits and pin driving cannot happen there.

What exactly does get_next_item do when no transaction is pending?

It blocks. The driver’s run loop stops at that line and waits, consuming no cycles, until a sequence makes an item available through the sequencer; then it returns a handle to that item.

What happens if you forget to call item_done()?

The sequencer treats the current item as still in flight and never releases the next one, and the sequence that called finish_item stays blocked. The simulation appears to stall and usually ends at its timeout.

Why use non-blocking assignment when driving the virtual interface?

Non-blocking assignment schedules the signal update in the NBA region, which keeps the driver’s writes from racing against the DUT’s logic and the monitor’s sampling on the same clock edge, giving deterministic behaviour.

Have questions about this topic?
Share your question in comments or talk to our mentor team for batch guidance.
▶ Try it in the ChipXpert Lab

Compile and simulate this in the browser — no install. Edit the code and re-run instantly.

Open in Verification Lab

Learn Verilog with mentors, labs and placement support

This free series is maintained by the ChipXpert training team in Hyderabad & Bengaluru. Go deeper with instructor-led courses on real EDA tools.

Explore VLSI Courses
Next batch starts 18th November 2026
Start your VLSI career with ChipXpert

Live online and classroom batches in Hyderabad & Bengaluru. Fill in your details and a counsellor will call you back.

  • Real EDA tools in your browser: industry-standard EDAReal EDA tools in your browser
  • Recorded sessions on the elearn portalRecorded sessions
  • Placement assistance: resume, mock interviewsPlacement assistance
  • Merit scholarship up to 60% · EMI optionsScholarship up to 60%
Hi! Ask me about courses, fees, batches or discounts. ×
BENGALURU
HYDERABAD