UVM
finish_item()
The second half of the item handshake — finish_item closes the randomization window, delivers the finalized item to the driver, and blocks until the driver signals item_done, pacing the sequence to the driver.
Sequences · Module 9 · Page 9.4
The Engineering Problem
The previous chapter covered start_item — winning the grant and opening the randomization window (Module 9.3). This chapter is the call that closes that window and completes the handshake: finish_item. Where start_item blocks on the grant, finish_item blocks on the driver — it delivers the finalized item and then waits until the driver has driven it. Misunderstand that and you either mutate an item the driver has already taken (so your change never reaches the pins), or you stare at a sequence frozen at finish_item because the driver never signalled completion.
finish_item(item) is the second half of the two-phase handshake. It does three things in order: it closes the randomization window (the item is now final), it delivers the item to the driver (satisfying the driver's get_next_item), and it blocks until the driver calls item_done. So what finish_item waits on is the driver's completion — the time-consuming driving from Module 8.3 — which makes finish_item the point where the sequence is paced by the driver (back-pressure). It's also the point of no return: once finish_item has handed the item over, the item belongs to the driver, and changing it afterward can't affect what's driven. This chapter is finish_item: what it sends, what it blocks on, why it's the point of no return, and why a finish_item that never returns means the driver never said item_done.
What does
finish_itemdo — how does it close the window, deliver the item to the driver, and block untilitem_done— why is it the point of no return and the back-pressure point, and why does a hungfinish_itemmean the driver never completed the item?
Motivation — why "finish" is a separate, blocking call
finish_item is a distinct, blocking call (rather than a fire-and-forget send) because delivering the item and waiting for it to be driven are both essential, and in that order:
- The item must be delivered after it's finalized, so "finish" closes the window.
start_itemopened the window to randomize (Module 9.3); something has to close it and mark the item final.finish_itemis that boundary — after it, the item is the driver's, so it must come after the randomization. - The sequence must wait for the driver, so "finish" blocks. A sequence that fired items without waiting would race ahead of a driver that hasn't consumed them. Blocking until
item_donemeans the sequence advances in lockstep with the driver — one item fully driven before the next is prepared (Module 8.3). - Blocking on
item_doneis the back-pressure mechanism. Because the driver callsitem_doneonly when it (and the DUT behind it) has finished,finish_item's wait is governed by the design's pace. This is how stimulus is automatically paced to the DUT, with no manual flow control (Module 8.3). - It's the point of no return, which keeps the model coherent. Once the item is delivered, the driver reads its values; allowing later changes to matter would make "what was driven" ambiguous. Making
finish_itema hard handoff means the item driven is exactly the item as it was atfinish_item. - It's the response rendezvous point. If the driver returns a response with
item_done(rsp), it becomes available afterfinish_item(retrieved viaget_response, Module 8.2) — sofinish_itemcompleting is also when a request's answer has been produced.
The motivation, in one line: finish_item must deliver the finalized item and then wait for the driver to drive it, so it's a separate blocking call — closing the window, handing the item over (a point of no return), and suspending the sequence until item_done, which is exactly what paces the sequence to the driver.
Mental Model
Hold finish_item as submitting the form and waiting at the counter until it's processed:
finish_itemis handing your completed form to the clerk and waiting until they've fully processed it before you do anything else — and once it's in their hands, you can't change it. In the previous step you filled in the form on your turn (start_item+ randomize).finish_itemis submitting it: you slide it across the counter (deliver the item to the driver), and now it's theirs — you can't reach over and edit it, and any scribble you make on your copy afterward changes nothing about what they process (the point of no return). Then you wait at the counter (finish_itemblocks) until the clerk has finished processing it and stamps it done (item_done) — and only then do you step away to handle your next item. The waiting matters: you can't fire off ten forms and walk away; the clerk processes one at a time at their pace, so a slow clerk slows you down (back-pressure). And if the clerk takes your form but never stamps it done — gets distracted, drops the step — you wait at the counter forever (finish_itemhangs becauseitem_donenever comes).
So finish_item is submit-and-wait-until-done: hand over the finalized item, accept that it's now the driver's, and stay blocked until the driver stamps it complete. The wait is the pacing; the handoff is the point of no return.
Visual Explanation — closing the window, sending, waiting
The defining picture is finish_item's three ordered actions: close the window, deliver to the driver, block until item_done — the mirror of start_item's "open."
The figure shows finish_item as the closing mirror of start_item's opening. Step 1, it closes the randomization window: whatever the item was set to in the window (Module 9.3) is now final — finish_item is the boundary past which the item can't be meaningfully changed. Step 2, it delivers the item to the driver, satisfying the driver's pending get_next_item — and this is the point of no return, because the item now belongs to the driver, which will read its values to drive the pins. Step 3, finish_item blocks: the sequence is suspended while the driver drives the item over clock cycles (the time-consuming "how", Module 8.1), and the length of this block is the driver's driving time — which makes it the back-pressure point where the driver (and the DUT behind it) paces the sequence. Step 4, when the driver calls item_done, finish_item unblocks and returns to the sequence, which can now prepare its next item (and retrieve any response, Module 8.2). The shape to carry is the symmetry with start_item: start_item opens (request grant → window), finish_item closes (deliver → wait for done) — together they bracket the item, with the randomization safely in between and the driver's work in the finish_item block.
RTL / Simulation Perspective — finish_item in code
finish_item is one call that pairs with start_item, and the surrounding callbacks (mid_do, post_do) mark the send and completion points.
task my_seq::body();
bus_item req = bus_item::type_id::create("req");
start_item(req); // ① grant + open window (Module 9.3)
assert(req.randomize()); // ② finalize the item in the window
finish_item(req); // ③ CLOSE window, DELIVER to driver, BLOCK until item_done
// (mid_do called before send; post_do after item_done)
// ── finish_item has returned → the driver has driven the item ──
get_response(rsp); // optional: retrieve the response (Module 8.2)
endtask
// ── DRIVER side: get_next_item is satisfied by finish_item; item_done unblocks it ──
task my_driver::run_phase(uvm_phase phase);
forever begin
seq_item_port.get_next_item(req); // receives the item finish_item delivered
drive_on_pins(req); // CONSUMES TIME — this is finish_item's block (Module 8.3)
seq_item_port.item_done(); // ← signals completion → unblocks the sequence's finish_item
end
endtaskThe idiom is the closing half of the pair. After start_item and the randomization (Module 9.3), finish_item(req) does the three actions in one call: it closes the window (the item is now final), delivers the item to the driver (satisfying the driver's seq_item_port.get_next_item), and blocks until the driver calls item_done. The line after finish_item does not execute until the driver is done — so finish_item's return is the signal that the item has been driven. On the driver side, get_next_item(req) receives exactly the item finish_item delivered, drive_on_pins(req) is the time-consuming work that constitutes finish_item's block (Module 8.3), and item_done() is what unblocks the sequence's finish_item. The two sides interlock precisely: finish_item↔get_next_item for the handoff, item_done↔finish_item-return for the completion. Around finish_item, UVM calls the mid_do callback (after finalize, before the send) and post_do (after item_done) — hooks for per-item logic at the send and completion points. And if the driver returns a response (item_done(rsp)), it's available after finish_item via get_response (Module 8.2). The shape: finish_item is the blocking "deliver and wait" call, and the driver's item_done is what releases it.
Verification Perspective — the finish_item ↔ item_done interlock
finish_item's blocking is one side of a precise interlock with the driver: the sequence's finish_item and the driver's get_next_item/item_done wait on each other, which is what synchronizes them.
The interlock is the heart of finish_item's behavior. There are two synchronization points between the sequence and the driver. The first is delivery: the sequence's finish_item hands the item over, satisfying the driver's get_next_item — so the driver, which was blocked waiting for an item (Module 8.3), now has one. The second is completion: after the driver drives the item (the time-consuming work), it calls item_done, which unblocks the sequence's finish_item. Between these two points, the sequence is suspended inside finish_item while the driver works — neither side runs ahead of the other. This mutual waiting is exactly what makes the sequence and driver advance in lockstep: the driver can't get a second item until the sequence delivers one (it's blocked in get_next_item), and the sequence can't deliver a second item until the driver finishes the first (it's blocked in finish_item). The consequence is the back-pressure of Module 8.3: because finish_item waits for item_done, and item_done comes only as fast as the driver (and DUT) allow, the sequence's rate is set by the driver's rate. So finish_item isn't just "send" — it's the sequence's half of a two-point interlock that paces it to the driver, and understanding both points (delivery and completion) is what lets you reason about where a stalled handshake is stuck.
Runtime / Execution Flow — the point of no return and pacing
At run time, finish_item marks the transition from "the sequence's item" to "the driver's item," and its block is the entire driving span — so it's both the point of no return and the pacing point.
The runtime view ties finish_item's two famous properties to its mechanics. Before finish_item (step 1), the item is the sequence's: it's still in the window, finalizable, owned by the sequence. At finish_item (step 2), ownership transfers to the driver — the driver reads the item's values to drive the pins — and this is the point of no return: a change to the item after finish_item is delivered can't affect what's driven, because the driver already has (and is reading) the item as it was at delivery. This is why you must finalize before finish_item (Module 9.3's window) and why mutating afterward is a bug. During finish_item's block (step 3), the sequence is suspended for the entire driving span, so its progress is gated by the driver — the pacing/back-pressure point: one item advances per DUT-paced driving cycle, and a slow DUT naturally slows the sequence (Module 8.3). After item_done (step 4), finish_item returns and the sequence prepares the next item. So the two properties are one mechanism viewed two ways: the handoff makes it the point of no return, and the wait for item_done makes it the pacing point. Both follow from finish_item being a blocking delivery — which is exactly why it's a separate call from start_item, and why the line after it runs only once the driver is done.
Waveform Perspective — finish_item's block is the driving time
finish_item's defining behavior — blocking for the whole driving span — is clearest on a timeline: the call returns only when the driver, after driving across many cycles, signals item_done.
finish_item blocks for the entire driving span, until item_done
12 cyclesThe waveform shows finish_item's block is the driving time. From the moment of delivery, fi_wait is high: the sequence is blocked inside finish_item, having handed the item to the driver. The drv_active trace and the data beats (D0–D4) show the driver driving the item over several cycles — and this entire span is finish_item's block, because the sequence can't proceed until the driver is done. When the driver completes and pulses item_done, fi_wait falls: finish_item returns, and the sequence advances to its next item. So the length of finish_item equals the length of the driving — which is precisely the back-pressure relationship: the sequence is paced by how long the driver (and DUT) take. Compare this with start_item (Module 9.3), whose block was the grant wait; together, start_item waits for the grant, then finish_item waits for the drive, so the full handshake's time is grant-wait plus drive-time, with the small randomization window in between. The diagnostic reading mirrors start_item's: if fi_wait never falls, finish_item is stuck waiting for an item_done that isn't coming — the driver took the item but never signalled completion (the DebugLab). The timeline makes finish_item's nature concrete: a blocking delivery whose duration is the driver's driving time.
DebugLab — the finish_item that never returned
A sequence frozen at finish_item because the driver skipped item_done
A sequence drove fine for a while, then stalled: it produced one item and then a message right after its finish_item(req) never printed. The simulation made no further progress on that agent — no more transactions — and eventually hit a timeout. There was no error; the sequence was simply suspended inside finish_item, waiting. The driver appeared to have received the item (it drove something), but the sequence never got control back.
The driver called get_next_item but, on a particular path, returned to the top of its loop without calling item_done — so the item was never completed, and the sequence's finish_item waited for an item_done that never came:
driver run loop:
get_next_item(req); // receives the item finish_item delivered
if (req.is_read && special) begin
handle_special(req);
continue; // ✗ early continue — item_done() NEVER called for this item
end
drive_on_pins(req);
item_done(); // only reached on the normal path
sequence:
finish_item(req); // delivered the item, now BLOCKS waiting for item_done...
// → item_done never comes → finish_item never returns
result: the sequence is suspended at finish_item indefinitely (no error — a completion hang)
and the driver's next get_next_item may also error ("get_next_item called twice
without item_done") because the prior item was never completed
fix — pair every get_next_item with item_done on EVERY path:
get_next_item(req);
if (req.is_read && special) begin handle_special(req); item_done(); continue; end // ✓ complete it
drive_on_pins(req);
item_done(); // ✓ normal pathThis is the completion-side mirror of the previous chapter's grant hang. finish_item delivers the item and then blocks until the driver calls item_done (Figure 2) — that's its defining wait. The driver, however, hit an early continue on a special path and skipped item_done, so the item it took was never marked complete. The sequence's finish_item is therefore waiting for a completion signal that will never arrive, and it hangs with no error (a completion hang, not a crash). There's often a second symptom: when the driver loops back and calls get_next_item again without having completed the previous item, UVM flags "get_next_item called twice without item_done" — a strong hint pointing straight at the missing completion. The fix is the driver-side pairing discipline: every get_next_item must be matched by exactly one item_done, on every path — including special-case, error, and early-continue paths. The general lesson is diagnostic and symmetric with start_item: a start_item that never returns means the grant isn't coming (Module 9.3); a finish_item that never returns means item_done isn't coming — the driver took the item but never completed it. The wait is in finish_item; the cause is the driver's missing item_done.
The tell is a finish_item that never returns, often with a "get_next_item twice" warning. Diagnose completion hangs:
- Confirm the hang is at
finish_item. A message right afterfinish_itemthat never prints localizes it to the completion wait — distinct from astart_item(grant) hang. - Check the driver pairs
get_next_itemwithitem_done. Trace every path between the driver'sget_next_itemand the loop's next iteration; any path missingitem_doneis the bug. - Look for the "get_next_item called twice" warning. It means a prior item was never completed — a direct pointer to the missing
item_done. - Distinguish from a
start_itemhang. If the hang is atstart_item, it's the grant (Module 9.3); atfinish_item, it'sitem_done. Confirm which call is stuck.
Complete every item, exactly once, on every path:
- Pair
get_next_itemwithitem_doneon all paths. Including special-case, error, and early-exit paths — the moment you callget_next_item, ensure exactly oneitem_donefollows however the code leaves. - Don't mutate the item after
finish_item. Past delivery the item is the driver's (point of no return); set everything the driver needs beforefinish_item, not after. - Match
get_responseto a responding driver. If the sequence callsget_response, the driver must return a response (item_done(rsp), withset_id_info, Module 8.2); otherwise skipget_response. - Treat a hung
finish_itemas a driver-completion problem. The wait is in the sequence, but the fix is in the driver'sitem_donepairing — look there.
The one-sentence lesson: finish_item delivers the item and blocks until the driver calls item_done, so a finish_item that never returns means the driver took the item but skipped item_done (often on a special-case or early-exit path) — pair every get_next_item with exactly one item_done on every path, because the sequence's completion wait depends entirely on the driver signalling done.
Common Mistakes
- Mutating the item after
finish_item. Past delivery the item belongs to the driver; later changes don't reach the pins. Finalize everything beforefinish_item(in the window, Module 9.3). - A driver path that skips
item_done. The sequence'sfinish_itemhangs waiting for a completion that never comes; pair everyget_next_itemwith exactly oneitem_doneon all paths. - Expecting
finish_itemnot to block. It blocks for the entire driving time — the sequence is suspended untilitem_done. Treat it as a synchronization point, not a fire-and-forget send. finish_itemwithout a precedingstart_item. The handshake must be opened first;finish_itemalone has no granted item to send.- Calling
get_responsewhen the driver returns none. It blocks waiting for a response that never comes; only retrieve responses for items the driver answers (Module 8.2). - Confusing a
finish_itemhang with astart_itemhang. Astart_itemhang is the grant (Module 9.3); afinish_itemhang isitem_done. Identify which call is stuck.
Senior Design Review Notes
Interview Insights
finish_item is the second half of the two-phase item handshake. It does three things in order: it closes the randomization window that start_item opened, so the item is now final; it delivers the item to the driver, satisfying the driver's get_next_item; and it blocks until the driver calls item_done. So what finish_item blocks on is the driver's completion — the time-consuming driving of the item over clock cycles. That makes finish_item's duration equal to the driving time, which is the back-pressure point: because the driver calls item_done only when it and the DUT have finished, the sequence's rate is paced by the driver. It's also the point of no return: once finish_item delivers the item, the item belongs to the driver, which reads its values to drive the pins, so changing the item after finish_item can't affect what's driven. The line after finish_item doesn't execute until item_done arrives, so finish_item returning is the signal that the item has been driven, and any response is then available via get_response. The relationship with start_item is symmetric: start_item blocks on the grant, finish_item blocks on item_done, with the randomization window in between, so the full handshake time is grant-wait plus drive-time. When a finish_item never returns, the driver took the item but never called item_done — the wait is in the sequence, but the cause is the driver's missing completion.
Exercises
- Order the calls. List the sequence-side and driver-side calls for one item in execution order (
start_item, randomize,finish_item,get_next_item, drive,item_done), and mark which two sequence-side calls block and on what. - Fix the hang. A driver
continues on a special case beforeitem_done. Describe the symptom and rewrite the path sofinish_itemreturns. - Explain the boundary. A sequence changes a field after
finish_itemand the change doesn't appear on the pins. Explain why, naming the property offinish_item. - Reason about pacing. Explain why a sequence slows down when the DUT inserts wait states, referencing what
finish_itemblocks on.
Summary
finish_item(item)is the second half of the handshake: it closes the randomization window (the item is now final), delivers the item to the driver (satisfyingget_next_item), and blocks until the driver callsitem_done.- It blocks on the driver's completion — the time-consuming driving — so
finish_item's duration is the driving time, making it the back-pressure point where the sequence is paced by the driver (and the DUT behind it). - It's the point of no return: once delivered, the item belongs to the driver, which reads its values to drive — so a change after
finish_itemcan't reach the pins (finalize before, in the window of Module 9.3). finish_itemand the driver interlock at two points — delivery (finish_item→get_next_item) and completion (item_done→finish_itemreturns) — so neither runs ahead, advancing the sequence and driver in lockstep.- The durable rule of thumb: finalize the item before
finish_item, thenfinish_itemto deliver and wait foritem_done— and whenfinish_itemnever returns, the driver took the item but skippeditem_done(pair everyget_next_itemwith exactly oneitem_done, on every path), becausefinish_item's wait is entirely the driver's completion signal.
Next — Sequence Control: with the item handshake (start_item/finish_item) understood, the next chapter steps up to controlling sequences themselves — running them in order or in parallel, nesting sub-sequences, and the macros and methods that orchestrate stimulus above the single-item level.