Part 5 · Sequences · Intermediate

Item vs Driver Boundary: What Belongs Where

Why sequence items carry transaction data only — not pin timing, virtual interfaces, or protocol state machines.

Why the boundary exists

UVM splits stimulus into intent (sequence + item) and execution (driver). The item is the serialized intent — a struct-like object the sequence fills and the driver reads. Pin-level timing, ready/valid handshakes, and clock alignment belong in the driver because they depend on the DUT's current state, not on what the test wants to send.

Violating this boundary is the most common mistake in junior UVM benches. Putting a virtual interface inside an item couples transaction data to a specific hierarchy level. Putting wait loops inside an item makes randomization and reuse impossible — the item becomes a mini-driver that cannot run in passive agents or scoreboard-only contexts.

The boundary also enables checking: monitors reconstruct items from pin samples. If your item class assumes driver-side state, the monitor cannot build the same type cleanly.

diagram
[ITEM] vs [DRV] — responsibility split

  ┌─────────────────────────────────────────────────────────────┐
  │ [ITEM] apb_item                                              │
  │   rand bit [31:0] addr, data                                 │
  │   rand bit        write                                      │
  │   // NO vif, NO @(posedge), NO pready loop                   │
  └──────────────────────────┬──────────────────────────────────┘
                             │ read-only during drive_apb()
                             ▼
  ┌─────────────────────────────────────────────────────────────┐
  │ [DRV] apb_driver.drive_apb(apb_item req)                     │
  │   @(vif.pclk);                                               │
  │   vif.paddr <= req.addr;    ← reads item fields              │
  │   vif.pwrite <= req.write;                                   │
  │   while (!vif.pready) @(vif.pclk);  ← timing HERE            │
  └─────────────────────────────────────────────────────────────┘

What a sequence item IS

A uvm_sequence_item extends uvm_object with hooks the library uses during start_item/finish_item. It carries the logical transaction: one APB beat, one AXI burst descriptor, one Ethernet frame header. The sequence randomizes or assigns fields; the driver interprets them on the bus.

systemverilog
class apb_item extends uvm_sequence_item;
  rand bit [31:0] addr;
  rand bit [31:0] data;
  rand bit        write;   // 1 = write, 0 = read
  bit [31:0]      rdata;   // filled by driver on reads — not rand

  `uvm_object_utils_begin(apb_item)
    `uvm_field_int(addr,  UVM_ALL_ON)
    `uvm_field_int(data,  UVM_ALL_ON)
    `uvm_field_int(write, UVM_ALL_ON)
    `uvm_field_int(rdata, UVM_ALL_ON)
  `uvm_object_utils_end

  function new(string name = "apb_item");
    super.new(name);
  endfunction
endclass
  • Extend uvm_sequence_item — not uvm_object — for sequencer linkage during handshake.

  • rand on fields the sequence should vary; plain bit for driver-filled response data.

  • One item = one logical transaction (one APB beat, not an entire burst stream).

  • Field macros register members for print/compare/copy — covered in the next lesson.

Why extend uvm_sequence_item instead of uvm_object

  • uvm_sequence_item adds sequencer association hooks used by start_item/finish_item.

  • Drivers typed with uvm_driver #(T) expect T extends uvm_sequence_item.

  • Parallel hierarchy: uvm_sequence for procedure, uvm_sequence_item for payload.


What NOT to put in items

The following belong in the driver, agent config, or interface — never in the item class:

diagram
[ITEM] anti-patterns — keep these OUT

   virtual apb_if vif;            driver owns interface handle
   task drive_beat();             that is driver code
   int unsigned wait_cycles;      timing policy, not payload
   semaphore bus_lock;            resource arbitration in agent
   uvm_event beat_done;           synchronization in driver/seq

   addr, data, write, strb, id   protocol payload
   rand constraints on legality  legal transaction space

A practical test: could a monitor reconstruct this object from sampled pins alone? If the item requires driver-internal state to make sense, the design is wrong.


APB write walkthrough — boundary in action

Follow one APB write from sequence randomization through driver execution. Notice where each layer touches the item — and where pin timing lives.

diagram
[STIM]  [SEQ]  [ITEM]  [DRV]  APB write addr=0x4000 data=0xDEAD

  Step 1 [SEQ] apb_wr_seq.body()
    req = apb_item::type_id::create("req");
    start_item(req);
    req.randomize() with { write==1; addr==32'h4000; data==32'hDEAD; };
    finish_item(req);                    ← blocks here

  Step 2 [SEQ] SEQUENCER forwards req to driver via get_next_item

  Step 3 [DRV] apb_driver.run_phase
    get_next_item(req);                  ← receives same object handle
    vif.paddr  <= req.addr;   // 0x4000  ← reads [ITEM] fields only
    vif.pwdata <= req.data;   // 0xDEAD
    vif.pwrite <= req.write;  // 1
    @(posedge vif.pclk);
    vif.psel <= 1;
    while (!vif.pready) @(posedge vif.pclk);  ← [DRV] timing, not in item
    vif.psel <= 0;
    seq_item_port.item_done();           ← unblocks finish_item

  Step 4 [SEQ] sequence resumes after finish_item — req fields still valid
systemverilog
class apb_wr_seq extends uvm_sequence #(apb_item);
  `uvm_declare_p_sequencer(apb_sequencer)
  rand int unsigned num_trans = 1;
  `uvm_object_utils(apb_wr_seq)

  task body();
    apb_item req;
    repeat (num_trans) begin
      req = apb_item::type_id::create("req");
      start_item(req);
      if (!req.randomize() with { write == 1; })
        `uvm_fatal("RAND", "apb_item randomize failed")
      finish_item(req);
    end
  endtask
endclass
  • Sequence never touches vif — only creates and randomizes req.

  • Driver never randomizes — only reads req and drives pins.

  • finish_item blocks until item_done — sequence cannot mutate req during drive.


Monitor symmetry — why clean items matter

The monitor samples pins and builds an apb_item (or bus_txn) for the scoreboard. If the driver-side item contained timing state, the monitor could not produce a matching type. Clean items make monitor and driver agree on the transaction representation.

diagram
[UVM] monitor rebuilds [ITEM] from pins — same class, different path

  DRIVER path:  seq  req (randomized)  drive_apb(req)  pins
  MONITOR path: pins  sample at PREADY  build apb_item  analysis port

  Both produce apb_item { addr, data, write, rdata }
  Scoreboard compares monitor item vs expected — NOT driver item
systemverilog
// Monitor — builds item from sampled pins, no driver state needed
function void sample_apb();
  apb_item txn = apb_item::type_id::create("txn");
  txn.addr  = vif.paddr;
  txn.data  = vif.pwdata;
  txn.write = vif.pwrite;
  txn.rdata = vif.prdata;
  ap.write(txn);
endfunction

Design rules for sequence items

  • Keep virtual interface handles OUT of items — breaks portability and reuse.

  • Keep timing (delays, clock cycles) OUT of items — that is driver policy.

  • One item = one logical transaction (one APB beat, one AXI burst descriptor).

  • Use rand on every field you want variation; use constraints for legality, not tests.

  • Provide post_randomize() only when derived fields must be computed after randomize.

  • Response fields (rdata) are plain bit — driver fills them, sequence reads after finish_item.

Key takeaways

  • Items carry WHAT; drivers carry HOW — the most important UVM stimulus rule.

  • If a monitor cannot reconstruct the item from pins, the item has driver leakage.

  • uvm_sequence_item is the only payload type the driver seq_item_port accepts.

Common pitfalls

  • Putting virtual interface handles inside items — breaks agent reuse across hierarchies.

  • Embedding wait loops in items — makes randomization and passive agents impossible.

  • One mega-item for an entire burst stream — use a sequence loop instead.

  • Mutating req after finish_item until driver returns item_done — race with driver reads.