Part 5 · Sequences · Intermediate
Pipelining & Read Responses via item_done(req)
try_next_item sketch for pipelined drives, read rdata return path, and item_done with response argument.
Beyond single in-flight items
The default handshake assumes one in-flight item per driver–sequencer pair. Some protocols accept a new request before the previous completes — pipelined FIFO interfaces, overlapping AXI AW and W channels, or dual-buffered masters. The driver may need to hold multiple items or use try_next_item to peek without blocking.
Even without pipelining, read transactions require a response path: the driver captures rdata from pins and returns it to the sequence via item_done(req). The sequence reads req.rdata after finish_item returns.
[DRV] single vs pipelined
SINGLE (default APB): get → drive → item_done → get
PIPELINED: get A → fork drive A → get B → drive B → item_done A ...
READ RESPONSE: drive read → req.rdata = prdata → item_done(req)Read response path — APB read walkthrough
For reads, the driver samples prdata during the access phase and writes it into the item before item_done. The sequence sees rdata only after finish_item:
// [DRV] in drive_apb — read branch
if (!req.write) begin
req.rdata = vif.prdata;
`uvm_info("DRV", $sformatf("read rdata=0x%08x", req.rdata), UVM_MEDIUM)
end
seq_item_port.item_done(req); // pass req back with rdata filled// [SEQ] apb_rd_seq — consume rdata after finish_item
task body();
apb_item req;
repeat (5) begin
req = apb_item::type_id::create("req");
start_item(req);
assert(req.randomize() with { write == 0; addr == 32'h4000; });
finish_item(req); // blocks until driver item_done(req)
`uvm_info("SEQ", $sformatf("read rdata=0x%08x", req.rdata), UVM_LOW)
check_rdata(req.rdata); // scoreboard or inline check
end
endtask[SEQ] [DRV] [ITEM] read response timeline
[SEQ] finish_item(req) BLOCKS
[DRV] drive_apb: wait pready, req.rdata = vif.prdata
[DRV] item_done(req) ← response embedded in [ITEM]
[SEQ] finish_item RETURNS req.rdata valid here
[ITEM] same req object — now carries response fieldrdata is plain bit in item — driver writes, sequence reads.
item_done(req) vs item_done() — equivalent when no separate rsp object.
Do not read rdata before finish_item — driver has not sampled yet.
Separate response item pattern
Advanced drivers use uvm_sequence_item response objects — req and rsp as separate handles. uvm_driver #(REQ,RSP) supports item_done(rsp) with a distinct rsp object:
class apb_driver extends uvm_driver #(apb_item, apb_item);
task run_phase(uvm_phase phase);
apb_item req, rsp;
forever begin
seq_item_port.get_next_item(req);
rsp = apb_item::type_id::create("rsp");
rsp.copy(req);
drive_apb(req, rsp); // rsp gets rdata
seq_item_port.item_done(rsp);
end
endtask
endclassFor simple APB, mutating req in place is standard. Separate rsp matters when the request object must remain immutable for logging.
try_next_item — non-blocking peek
try_next_item(req) returns 0 immediately if no item is pending — unlike get_next_item which blocks. Use it when the driver must service the bus (e.g., drain responses) before accepting a new request:
task run_phase(uvm_phase phase);
apb_item req;
forever begin
// Wait until an item is available — non-blocking poll loop
do begin
@(posedge vif.pclk);
end while (!seq_item_port.try_next_item(req));
drive_apb(req);
seq_item_port.item_done();
end
endtask[DRV] try_next_item vs get_next_item
get_next_item: blocks until item pending — standard
try_next_item: returns 0/1 immediately — poll when driver must stay reactive
try without get: peek only — still need item_done after driveTwo-item pipeline sketch
A minimal pipeline overlaps drive of item A with fetch of item B. Every get still needs item_done — ordering must match:
apb_item req_a, req_b;
// Item A — start drive without blocking on completion
seq_item_port.get_next_item(req_a);
fork
drive_apb(req_a);
join_none
seq_item_port.item_done(); // release seq A — drive may still run in fork
// Item B
seq_item_port.get_next_item(req_b);
drive_apb(req_b);
seq_item_port.item_done();
wait fork; // ensure req_a drive completed[STIM] pipeline hazard warning
Early item_done while drive still running:
[SEQ] finish_item returns → randomizes NEXT item
[DRV] still reading OLD req fields in fork → RACE
Safe pipeline requires:
separate req objects OR
item_done only after drive truly complete OR
explicit req_a/req_b handles — never reuse during overlapPipelining is advanced — default single in-flight is correct for APB.
Early item_done + concurrent drive = req field race.
wait fork before next get if forked drive shares req handle.
Response checking in sequence
Sequences can inline-check read data or send to scoreboard via analysis port. Clone before queue if storing:
task body();
apb_item req, snap;
start_item(req);
assert(req.randomize() with { write == 0; addr == STATUS_REG; });
finish_item(req);
if (req.rdata[0] !== 1'b1)
`uvm_error("STATUS", $sformatf("ready bit not set: 0x%08x", req.rdata))
snap = apb_item::type_id::create("snap");
snap.copy(req);
rsp_ap.write(snap); // to scoreboard — cloned
endtaskKey takeaways
Read rdata: driver fills req.rdata, calls item_done(req), sequence reads after finish_item.
Default: one in-flight item — pipelining requires careful req lifetime management.
try_next_item for reactive drivers; get_next_item for standard pull.
Common pitfalls
Reading rdata before finish_item — driver has not sampled pins.
item_done before read sample — sequence sees stale rdata.
Pipeline with single req handle — race between forked drive and next randomize.
Pipelining without understanding item_done pairing — sequencer corruption.