UVM Sequencer and Driver Communication:

In short: The driver and sequencer talk through a pair of matching ports: the driver has a seq_item_port, the sequencer has a seq_item_export, and connect_phase joins them. The driver pulls one item with get_next_item, drives it onto the pins, then calls item_done to release the sequence. That get, drive, done handshake is the heartbeat of every UVM driver, and getting the pairing right is what stops the two most common hangs in a testbench.

The link between the sequencer and the driver is where transaction-level stimulus becomes real pin activity. It is a small interface, just a port, an export, and a handful of methods, but the exact order you call those methods decides whether your testbench runs cleanly or hangs waiting forever. This guide covers the full handshake with runnable code, the difference between the two ways to pull items, how the return path for responses works, a method comparison table, and the mistakes that cause the classic sequence hang. We reuse one memory-bus example throughout.

A simple way to picture it

Picture a kitchen order rail, the metal bar where order slips get clipped. The sequence writes an order and clips it to the rail. The line cook (the driver) takes the next slip off the rail, cooks that dish (drives the pins), and only when the plate is finished does the cook mark the slip done so the next order can come forward. The cook handles one slip at a time and never grabs the next until the current dish is served. If the cook forgets to mark a slip done, the rail jams and no new orders move. The UVM handshake works exactly like that rail: get_next_item takes the slip, driving cooks it, and item_done clears it.

The two ends: port and export

The connection has two matching halves. The driver owns a seq_item_port, which is the requesting end. The built-in sequencer owns a seq_item_export, which is the providing end. In connect_phase you join them, and after that the driver can pull items that any sequence starts on that sequencer.

SideObjectOwnsRole
Requesteruvm_driverseq_item_portAsks for the next item and signals completion
Provideruvm_sequencerseq_item_exportDelivers items that sequences produce
class bus_agent extends uvm_agent;
  `uvm_component_utils(bus_agent)
  bus_sequencer sqr;
  bus_driver    drv;
  function new(string n, uvm_component p); super.new(n,p); endfunction

  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    sqr = bus_sequencer::type_id::create("sqr", this);
    drv = bus_driver   ::type_id::create("drv", this);
  endfunction

  function void connect_phase(uvm_phase phase);
    // join the driver port to the sequencer export
    drv.seq_item_port.connect(sqr.seq_item_export);
  endfunction
endclass

This one connect line is what makes everything else work. Forget it and the driver blocks on the very first get_next_item, because it is connected to nothing.

The handshake, step by step

The driver spends its whole life in a forever loop running three calls. Here is the canonical loop for our memory-bus driver, with a comment on each step.

Flow between a sequence and a driver: start_item, finish_item, get_next_item and item_done
The basic handshake: the sequence calls start_item then finish_item, while the driver calls get_next_item and, when finished, item_done.
task run_phase(uvm_phase phase);
  bus_item req;
  forever begin
    seq_item_port.get_next_item(req);   // 1) block until an item is ready
    drive(req);                         // 2) turn the item into pin activity
    seq_item_port.item_done();          // 3) release the sequence to send more
  end
endtask

task drive(bus_item req);
  @(posedge vif.clk);
  vif.addr  <= req.addr;
  vif.wdata <= req.data;
  vif.we    <= req.is_write;
  vif.valid <= 1'b1;
  @(posedge vif.clk);
  vif.valid <= 1'b0;
endtask

On the sequence side, start_item and finish_item are the matching pair. start_item asks the sequencer for permission to send (it blocks until the driver is ready for a new item), and finish_item hands the completed item across.

task body();
  bus_item req = bus_item::type_id::create("req");
  start_item(req);                 // wait for the sequencer/driver to be ready
  assert(req.randomize() with { addr==16'h100; is_write==1; });
  finish_item(req);                // send it; returns when driver calls item_done
endtask

The two sides interlock: the sequence blocks in finish_item until the driver calls item_done, and the driver blocks in get_next_item until the sequence calls finish_item. That mutual waiting is what keeps them in lockstep with no lost or duplicated items.

get_next_item versus try_next_item

There are two ways to pull an item, and the choice affects how the driver behaves when no stimulus is waiting.

MethodBehavior when no item is readyUse it when
get_next_itemBlocks until an item arrivesThe driver has nothing to do but wait for stimulus
try_next_itemReturns immediately with a null handleThe driver must keep the interface active (drive idle) even with no stimulus

Use try_next_item when the protocol needs the driver to keep toggling something, for example driving idle cycles or a heartbeat, rather than freezing the pins while it waits. Here is the idle-aware pattern:

task run_phase(uvm_phase phase);
  bus_item req;
  forever begin
    seq_item_port.try_next_item(req);
    if (req == null) begin
      drive_idle();                 // keep the bus alive
    end
    else begin
      drive(req);
      seq_item_port.item_done();    // only when we actually took an item
    end
  end
endtask

Note the important rule: you only call item_done when try_next_item actually returned an item. Calling it after a null return corrupts the handshake.

Returning a response to the sequence

Sometimes the sequence needs to know what happened, for example the read data that came back. There are two ways to send a response upstream. The lightweight way passes the same request object to item_done, since the driver can fill in fields on it. The formal way builds a separate response and uses put_response.

Response flow between sequence and driver using get, put and get_response on a separate port
The response path: the driver calls get, sends the response back with put on a separate port, which unblocks get_response in the sequence.
// lightweight: fill the request object and pass it back
task run_phase(uvm_phase phase);
  bus_item req;
  forever begin
    seq_item_port.get_next_item(req);
    drive(req);
    if (!req.is_write) req.data = vif.rdata;  // capture read data
    seq_item_port.item_done(req);             // return the updated item
  end
endtask
// on the sequence side, read the response back
task body();
  bus_item req = bus_item::type_id::create("req");
  start_item(req);
  assert(req.randomize() with { addr==16'h100; is_write==0; });
  finish_item(req);
  get_response(rsp);            // rsp.data now holds the read value
endtask

The full method map

These are all the calls involved, grouped by which side uses them, so you can see how a driver call pairs with a sequence call.

Driver sideSequence sideWhat happens
get_next_item(req)start_item(req) then finish_item(req)Driver receives the item the sequence sent
item_done()finish_item returnsSequence is released to build the next item
item_done(rsp)get_response(rsp)Driver returns a response the sequence reads
try_next_item(req)same start/finishNon-blocking pull; null if nothing is ready

What actually flows on the timeline

Follow one write from sequence to pins and back:

  1. The sequence calls start_item, which blocks until the driver is ready.
  2. The sequence randomizes the item and calls finish_item, handing it across.
  3. The driver, waiting in get_next_item, receives the item and returns from that call.
  4. The driver drives addr, wdata, we, and valid onto the pins over one or more clocks.
  5. The driver calls item_done, which unblocks the sequence finish_item call.
  6. The sequence loops and builds the next item, and the driver loops back to get_next_item.

Common mistakes to avoid

  • Forgetting item_done. The sequence blocks in finish_item forever and the whole test hangs. This is the number-one sequencer-driver bug.
  • Forgetting the connect line in connect_phase, so the driver port is joined to nothing and get_next_item blocks on the first call.
  • Calling item_done twice for one item, which desynchronizes the handshake and can drop the next item.
  • Calling item_done after try_next_item returned null. Only signal done when you actually took an item.
  • Doing a get_next_item then never driving, so time never advances and the item is never finished.
  • Trying to use get_response without the driver returning a response through item_done(rsp) or put_response.

Keep learning

This handshake sits at the bottom of the whole stack. See how the driver and sequencer fit the bigger picture in the UVM testbench architecture guide, learn how items are scheduled when several sequences compete in UVM sequence arbitration, and see the difference between the two sequencer handles in m_sequencer vs p_sequencer. Build the whole flow from scratch in how to build a UVM environment part 2, and find more on the UVM category page.

Frequently asked questions

Once the single handshake is clear, see how a virtual sequence coordinates several sequencers at once, and how the m_sequencer and p_sequencer handles let a sequence reach its sequencer.

What connects the driver to the sequencer?

The driver has a seq_item_port and the sequencer has a seq_item_export. You join them in connect_phase with drv.seq_item_port.connect(sqr.seq_item_export). After that line the driver can pull items from any sequence started on that sequencer.

Why does my sequence hang at finish_item?

Almost always because the driver never called item_done for that item. finish_item does not return until the driver signals the item is complete, so a missing or skipped item_done leaves the sequence waiting forever. Check that every path through your driver loop ends with item_done.

What is the difference between get_next_item and try_next_item?

get_next_item blocks until a sequence provides an item, so the driver simply waits when there is no stimulus. try_next_item returns immediately with a null handle when nothing is ready, which lets the driver keep the interface active by driving idle cycles instead of freezing.

How does the sequence get read data back from the driver?

The driver fills the response into the item and returns it with item_done(rsp), or builds a separate response and uses put_response. The sequence then reads it with get_response. For simple cases, reusing the same request object and calling item_done(req) is the lightest approach.

Do I call item_done after try_next_item returns null?

No. You only call item_done when you actually received an item. If try_next_item returned null, there is nothing to complete, and calling item_done anyway corrupts the handshake and can drop the next real item.

Can more than one sequence talk to one driver?

Yes. Several sequences can run on the same sequencer at once, and the sequencer arbitrates which item reaches the driver next. The driver does not know or care which sequence produced an item; it just pulls whatever the sequencer delivers.

Similar Posts