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.

diagram
[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:

systemverilog
// [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
systemverilog
// [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
diagram
[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 field
  • rdata 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:

systemverilog
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
endclass

For 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:

systemverilog
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
diagram
[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 drive

Two-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:

systemverilog
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
diagram
[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 overlap
  • Pipelining 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:

systemverilog
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
endtask

Key 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.