Part 5 · Sequences · Intermediate

Driver get_next_item / item_done and drive_apb

Driver run_phase forever loop, get_next_item blocking, drive_apb pin conversion, and item_done pairing rules.

Driver role in the pull model

The uvm_driver is the only component that touches the virtual interface. Its run_phase loops forever on get_next_item, converts each item to pin activity, then calls item_done. The driver sets the pace — sequences wait on the driver's schedule, not the reverse.

get_next_item and item_done must be strictly paired: one item_done per get_next_item. Calling get_next_item twice without item_done corrupts sequencer internal state and produces unpredictable hangs.

diagram
[DRV] driver responsibilities

  PULL   get_next_item(req)     ← request next [ITEM] when ready
  DRIVE  drive_apb(req)         ← convert [ITEM] to pin wiggles
  RELEASE item_done()           ← unblock [SEQ] finish_item
  REPEAT forever in run_phase

Complete apb_driver

systemverilog
class apb_driver extends uvm_driver #(apb_item);
  `uvm_component_utils(apb_driver)

  virtual apb_if vif;

  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    if (!uvm_config_db#(virtual apb_if)::get(this, "", "vif", vif))
      `uvm_fatal("NOVIF", "virtual apb_if not found")
  endfunction

  task run_phase(uvm_phase phase);
    apb_item req;

    // Initialize bus to idle
    vif.psel   <= 0;
    vif.penable <= 0;

    forever begin
      // Pull next item — blocks when sequencer has nothing pending
      seq_item_port.get_next_item(req);
      `uvm_info("DRV", $sformatf("got item addr=0x%08x w=%0b",
                                  req.addr, req.write), UVM_MEDIUM)

      // Convert [ITEM] to pin activity
      drive_apb(req);

      // MUST call before next get_next_item — releases sequence
      seq_item_port.item_done();
      `uvm_info("DRV", "item_done", UVM_HIGH)
    end
  endtask

  task drive_apb(apb_item req);
    @(posedge vif.pclk);
    vif.paddr   <= req.addr;
    vif.pwrite  <= req.write;
    vif.pwdata  <= req.data;
    vif.psel    <= 1'b1;
    vif.penable <= 1'b0;
    @(posedge vif.pclk);
    vif.penable <= 1'b1;
    while (!vif.pready) @(posedge vif.pclk);
    if (!req.write)
      req.rdata = vif.prdata;   // capture read data into [ITEM]
    @(posedge vif.pclk);
    vif.psel    <= 0;
    vif.penable <= 0;
  endtask
endclass
  • build_phase gets vif from config_db — fatal if missing.

  • run_phase forever loop — standard for all protocol drivers.

  • drive_apb reads req fields — never randomizes in driver.

  • Read path fills req.rdata before item_done — sequence sees it after finish_item.


get_next_item blocking

get_next_item blocks when no sequence has a pending start_item. This is normal at time zero before the test calls seq.start(), or between sequences. It is a hang only if the sequence never calls start_item — debug the test/sequence side first.

diagram
[DRV] get_next_item block scenarios

  NORMAL BLOCK:  driver ready, no sequence started yet
                 test calls seq.start()  sequence start_item  unblock

  HANG:          sequence at finish_item forever
                 driver never got item OR driver stuck in drive_apb
                 check item_done and vif.pready

  NORMAL BLOCK:  sequence between items, driver already called item_done
                 next get_next_item waits for next start_item
diagram
[SEQ] [DRV] paired timeline

  get_next_item(req)  ◄── driver blocks until start_item+finish_item path ready
  drive_apb(req)      ◄── [DRV] active window — reads [ITEM]
  item_done()         ◄── MUST happen — unblocks finish_item
  get_next_item(req)  ◄── safe to pull next

drive_apb walkthrough — pin by pin

APB setup/access/wait/idle phases mapped to item fields. All timing lives here — not in apb_item:

diagram
[DRV] APB write addr=0x4000 data=0xDEAD — pin timeline

  Cycle 0 (SETUP):  psel=1 penable=0  paddr=0x4000  pwrite=1  pwdata=0xDEAD
  Cycle 1 (ACCESS): psel=1 penable=1  wait pready
  Cycle N:          pready=1  beat complete
  Cycle N+1 (IDLE): psel=0 penenable=0

  [ITEM] req unchanged throughout — driver reads once at start

Separate drive tasks per direction

  • drive_write(apb_item req) and drive_read(apb_item req) — clarity over one mega-task.

  • Branch on req.write in drive_apb for simple APB — fine for learning.

  • Complex protocols: drive_aw, drive_w, drive_b as separate tasks.


item_done pairing rules

Violating item_done pairing corrupts the sequencer. These rules are absolute:

systemverilog
// CORRECT — paired
seq_item_port.get_next_item(req);
drive_apb(req);
seq_item_port.item_done();

// WRONG — double get without item_done
seq_item_port.get_next_item(req_a);
seq_item_port.get_next_item(req_b);  // SEQUENCER CORRUPT

// WRONG — item_done before drive completes
seq_item_port.get_next_item(req);
seq_item_port.item_done();  // sequence may randomize next item
drive_apb(req);             // race — req fields may change mid-drive
diagram
[UVM] item_done timing

  CORRECT:  get  drive (complete)  item_done
  WRONG:    get  item_done  drive     (sequence races ahead)
  WRONG:    get  get                   (sequencer corrupt)
  WRONG:    drive  forget item_done    (sequence hangs forever)

Connect phase — seq_item_port wiring

The agent connect_phase connects sequencer export to driver import. Without this connection, get_next_item blocks forever regardless of sequence activity:

systemverilog
class apb_agent extends uvm_agent;
  apb_driver   drv;
  apb_sequencer sqr;
  apb_monitor  mon;

  function void connect_phase(uvm_phase phase);
    super.connect_phase(phase);
    if (is_active == UVM_ACTIVE)
      drv.seq_item_port.connect(sqr.seq_item_export);
  endfunction
endclass
diagram
[UVM] agent wiring

  [SEQ] SEQUENCER.seq_item_export
           │
           │ connect_phase
           ▼
  [DRV] DRIVER.seq_item_port

  Broken connect  get_next_item hangs with active sequence

Key takeaways

  • Driver run_phase: forever get_next_item → drive → item_done.

  • One item_done per get_next_item — no exceptions.

  • item_done AFTER drive completes — not before.

  • Read data: fill req.rdata in driver, pass via item_done(req).

Common pitfalls

  • Missing item_done() — #1 sequence hang cause.

  • item_done before drive completes — sequence races ahead of pins.

  • Double get_next_item — sequencer state corruption.

  • Null vif — driver may hang in drive_apb before any item_done.