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.
[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.
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
endclassExtend 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:
[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 spaceA 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.
[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 validclass 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
endclassSequence 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.
[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// 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);
endfunctionDesign 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.