UVM
Sequence Items
What uvm_sequence_item adds beyond a plain transaction — the identity (sequence_id/transaction_id) and sequencer connectivity that let a sequence generate it, a driver consume it, and a response match back via set_id_info.
Transaction Modeling · Module 8 · Page 8.2
The Engineering Problem
The previous chapter modeled a transaction as a data object — the logical "what" of one operation (Module 8.1). But a transaction that's only data can be analyzed, compared, and recorded; it can't yet travel the stimulus path. To be generated by a sequence, routed through a sequencer, and delivered to a driver — and to have its response matched back to the sequence that asked for it — a transaction needs more than fields. It needs an identity and a connection to the sequence machinery. That is exactly what uvm_sequence_item adds.
uvm_sequence_item extends uvm_transaction with the two things the stimulus path requires: identity — a sequence_id and transaction_id that record which sequence produced the item and which item it is — and connectivity — the hooks that let a sequence hand the item to a sequencer (start_item/finish_item) and a driver pull it (get_next_item/item_done). The identity is what makes the path bidirectional: when a driver returns a response, those IDs (copied onto the response with set_id_info) let the sequencer route it back to the originating sequence, so a sequence that sent a read gets its read's data, not someone else's. This chapter is that addition: what a sequence item is beyond a transaction, the identity it carries, the generate/consume handshake it flows through, and why forgetting to copy its IDs onto a response is the bug that silently breaks request/response matching.
What does
uvm_sequence_itemadd beyond a plain transaction — the identity (sequence_id/transaction_id) and the sequencer connectivity — and how do those let a sequence generate an item, a driver consume it, and a response be matched back to its request?
Motivation — why a transaction needs identity and connectivity
The jump from "data object" to "sequence item" exists because the stimulus path imposes requirements that pure data can't meet:
- The path needs to route items, so items need a source identity. A sequencer may arbitrate items from many sequences onto one driver. To track where each item came from — and to send its response back — each item must carry which sequence produced it (
sequence_id). Without that, the path could deliver items but never answer them correctly. - Responses need to match requests, so items need a per-item identity. A sequence may have several items outstanding. When responses come back, each must reach the right originating call. The
transaction_iddistinguishes items within a sequence, so a response can be matched to the specific request it answers. - A sequence and a driver never reference each other, so the item needs connectivity hooks. They communicate only through the sequencer via a TLM handshake.
uvm_sequence_itemprovides the bookkeeping the handshake needs (the sequencer handle, the parent-sequence link), so the item can be handed off and pulled without the two ends knowing each other. - Bidirectional traffic is the real reason identity matters. If stimulus were one-way (fire and forget), an item would need little beyond its fields. It's the response — the driver returning data the sequence is waiting for — that makes identity essential:
set_id_infocopies the request's IDs onto the response so it routes home. Identity is what makes request/response work. - Everything from the transaction still holds. A sequence item is a transaction (and a
uvm_object): it keeps its logical fields and its data services (copy/compare/print, Module 8.4). The identity and connectivity are added on top — a sequence item is "transaction plus the means to travel and be answered."
The motivation, in one line: the stimulus path is a routed, bidirectional channel shared across sequences, so the items flowing through it need an identity (to be routed and answered) and connectivity (to be handed off) — which is precisely what uvm_sequence_item adds to a plain transaction.
Mental Model
Hold a sequence item as a parcel with a tracking label, not just contents:
A plain transaction is the parcel's contents; a sequence item is that parcel with a shipping label and tracking number attached, so the courier network can route it and match its delivery confirmation back to the sender. The contents are the logical fields (the operation, address, data) — the same whether or not the parcel ever ships. The shipping label is what
uvm_sequence_itemadds: a sender ID (sequence_id— which sequence sent it) and a tracking number (transaction_id— which specific parcel). With that label, the courier system (sequencer → driver) can pick the parcel up from the sender (start_item/finish_item), carry it to the destination (get_next_item), and — crucially — send a delivery confirmation back to the right sender (the response, routed by the IDs). The one rule the destination must follow: when it writes the delivery confirmation, it must copy the tracking number from the original parcel onto the confirmation (set_id_info), or the confirmation can't be matched to the shipment and the sender is left waiting. Contents make the parcel meaningful; the label makes it shippable and answerable.
So choosing uvm_sequence_item as your transaction's base is attaching the shipping label: the same logical contents, plus the identity that lets the item travel the stimulus network and have its response come home. The set-the-IDs-on-the-response rule is the one piece of etiquette the destination owes the system.
Visual Explanation — the class stack: what each level adds
A sequence item's capabilities come from its inheritance: each base class in the stack adds one layer, and uvm_sequence_item is the layer that adds stimulus-path identity and connectivity.
The stack shows exactly where a sequence item's powers come from. At the base, uvm_object provides the data services — copy, compare, print, clone — that every data object needs (Module 8.4); this is what lets a scoreboard copy and compare items. uvm_transaction adds transaction-level identity used for timing and recording (begin/end times, a transaction handle for the recorder). uvm_sequence_item is the layer this chapter is about: it adds the stimulus-path identity — sequence_id (which sequence) and transaction_id (which item) — and the connectivity (the sequencer handle and parent-sequence link) that let the item be generated by a sequence, routed through a sequencer, consumed by a driver, and answered. Finally, your item (my_item) adds the logical fields — the "what" from Module 8.1. The practical reading of the stack is a decision rule: a transaction that only needs to be analyzed, compared, or recorded (a monitor's output to a scoreboard) can extend uvm_transaction or even just uvm_object; but a transaction that must be generated by a sequence and driven — the stimulus — must extend uvm_sequence_item, because only that layer carries the identity and connectivity the stimulus path requires. In practice almost every stimulus transaction extends uvm_sequence_item, getting all four layers at once.
RTL / Simulation Perspective — the generate/consume API
A sequence item is defined like any transaction (Module 8.1) but extends uvm_sequence_item; the new surface is the API on each end — how a sequence generates the item and how a driver consumes it and answers.
class bus_item extends uvm_sequence_item; // sequence_item, not plain transaction
rand bit [31:0] addr;
rand bit [31:0] data;
rand bit is_read;
bit [31:0] rdata; // filled by the response
`uvm_object_utils(bus_item)
function new(string name="bus_item"); super.new(name); endfunction
endclass
// ── SEQUENCE side: generate the item (start_item → randomize → finish_item) ──
task my_seq::body();
bus_item req = bus_item::type_id::create("req");
start_item(req); // request a grant from the sequencer (blocks)
assert(req.randomize() with { is_read == 1; addr == 'h40; });
finish_item(req); // send to the driver (blocks until item_done)
get_response(rsp); // retrieve the matched response (by IDs)
endtask
// ── DRIVER side: consume the item and ANSWER it ──
task my_driver::run_phase(uvm_phase phase);
forever begin
seq_item_port.get_next_item(req); // pull the next item from the sequencer
drive_on_pins(req); // translate to pin activity (the 'how', Module 8.1)
rsp = bus_item::type_id::create("rsp");
rsp.set_id_info(req); // ← copy req's IDs onto rsp so it routes BACK
rsp.rdata = sampled_read_data;
seq_item_port.item_done(rsp); // signal completion + return the response
end
endtaskThe class itself is just a transaction whose base is uvm_sequence_item. The new material is the API on both ends. On the sequence side, generation is a three-step handshake: start_item(req) asks the sequencer for permission to send (it blocks until the sequencer grants this sequence arbitration), then the item is randomized (now, between start and finish, so constraints and late values apply), then finish_item(req) hands it to the driver and blocks until the driver signals done; get_response(rsp) then retrieves the matched response. On the driver side, consumption is the TLM pull: seq_item_port.get_next_item(req) pulls the next item from the sequencer, the driver translates it to pin activity (the "how", Module 8.1), and item_done(rsp) signals completion and returns a response. The one line that makes the response work is rsp.set_id_info(req): it copies the request's sequence_id and transaction_id onto the response, so when the response travels back, the sequencer can match it to the originating sequence and its get_response returns it. Omit that line and the response has no identity — it can't be matched home (the DebugLab). Everything else — the fields, the randomization — is transaction modeling; the identity copy is the sequence-item-specific obligation.
Verification Perspective — identity makes request/response work
The defining capability uvm_sequence_item adds is matchable identity. A one-way path wouldn't need it; the moment a driver returns a response, identity is what routes that response back to the sequence that asked — and set_id_info is how the identity is carried onto the response.
The figure traces the round trip that identity enables. The sequence sends a request carrying its sequence_id (which sequence) and transaction_id (which item). The driver pulls it, drives it, and builds a response — and here is the pivotal step: it calls set_id_info(req) to copy the request's IDs onto the response. Now the response carries the same identity as the request it answers. When the driver returns it (via item_done(rsp) or put_response), it travels back through the sequencer, which reads those IDs and routes the response to the originating sequence — so that sequence's get_response returns this response, matched to its request. The identity is the whole mechanism: it's how the sequencer knows, among possibly many sequences and many outstanding items, which sequence is waiting for this particular answer. The reason set_id_info is a manual step (not automatic) is that the driver creates a new response object, which starts with no identity; the driver must explicitly stamp it with the request's identity for the routing to work. Skip that stamp and the response is an orphan — correct data, but no return address — so the sequencer cannot match it, and the sequence's get_response blocks or the framework flags a mismatch. Request/response in UVM is identity matching, and set_id_info is the line that performs it.
Runtime / Execution Flow — the generate/consume handshake
At run time, a sequence item flows through a blocking handshake between the sequence and the driver, brokered by the sequencer. The sequence's start_item/finish_item and the driver's get_next_item/item_done interlock to pass one item and return one response.
The handshake is a blocking interlock that passes exactly one item and one response. The sequence calls start_item and blocks until the sequencer grants this sequence arbitration (when many sequences share a sequencer, this is where arbitration happens). Once granted, the sequence randomizes the item — deliberately after start_item, so the values reflect the latest state — and calls finish_item, which sends the item to the driver and blocks until the driver is done. On the driver side, get_next_item returns the item that finish_item sent; the driver drives it on the pins, builds a response (stamping it with set_id_info), and calls item_done(rsp), which signals completion: that unblocks the sequence's finish_item, and the response routes back so a later get_response retrieves it. The crucial property is that the sequence and the driver never reference each other — the sequencer brokers the entire exchange, and the sequence item is the unit passed across the TLM port between them. This is why the same driver works with any sequence and the same sequence with any compatible driver: their only contract is the item type and this handshake. (The uvm_do family of macros wraps the create/start/randomize/finish steps into one call for the common case — but it's the same handshake underneath.) The detailed timing of this round trip — what blocks when, and the full lifecycle — is the subject of the next chapter; here the point is the interface: the item is generated by one end, consumed by the other, and answered back.
Waveform Perspective — request out, response back, matched by ID
The request/response round trip is the behavior identity enables. The waveform shows an item driven on the pins and its response returning, matched to the request by the IDs the driver copied with set_id_info.
A request is driven and its response returns, matched to the request by ID
11 cyclesThe waveform makes the identity-driven round trip concrete. At the start, req_sent marks the sequence sending its item via finish_item — the item carries its identity, shown as ID7 on the id_match bus. The driver then drives the item (drv_active over several cycles — the pin-level "how" from Module 8.1). On completion, item_done pulses with a response, and because the driver called set_id_info(req), that response also carries ID7 — the id_match bus stays ID7 across both directions. That shared identity is what lets the sequencer, at the next step, route the response back to the originating sequence (rsp_matched), where get_response retrieves it. The single most important thing the waveform shows is the continuity of the ID from request to response: the same ID7 on the way out and the way back is precisely what makes the match possible. Had the driver not copied the IDs, the response would carry no matching identity — the id_match continuity would break — and the sequencer would have no way to route it back, leaving the sequence's get_response waiting. Request out, driven, response back, matched by ID: that round trip, enabled by the sequence item's identity, is what uvm_sequence_item exists to support.
DebugLab — the response that never came back
A sequence that hung on get_response because the driver never stamped the response's ID
A sequence sent a read request and called get_response(rsp) to retrieve the read data. It hung — get_response never returned — even though the driver clearly drove the read and built a response with the correct data. In a variant, instead of hanging, the framework issued a "response queue overflow" or a sequence-id mismatch warning, and the wrong sequence appeared to receive the response. The request side worked perfectly; only the response failed to come home.
The driver built a fresh response object but never copied the request's IDs onto it (set_id_info was missing), so the response had no identity to route it back:
sequence: finish_item(req); // req carries sequence_id + transaction_id (ID7)
get_response(rsp); // waits for the response matching ID7
driver: get_next_item(req);
rsp = bus_item::type_id::create("rsp"); // NEW object → IDs default (no identity)
rsp.rdata = sampled_data;
// ✗ MISSING: rsp.set_id_info(req); // never copied req's IDs onto rsp
item_done(rsp); // response returned, but with NO matching identity
result: sequencer cannot match rsp to the sequence waiting on ID7
→ get_response blocks forever (no match) / or mismatch warning / routed wrong
fix: stamp the response with the request's identity before returning it:
rsp = bus_item::type_id::create("rsp");
rsp.set_id_info(req); // ← copy sequence_id + transaction_id from req
rsp.rdata = sampled_data;
item_done(rsp); // now routes back to the originating sequenceThe response path is identity-driven, and the driver broke the identity link. When a sequence sends a request, the item carries a sequence_id and transaction_id (here, ID7) that say which sequence and which item it is; get_response waits for a response with that identity. But the driver created a brand-new response object, which starts with default (no matching) IDs, and never called set_id_info(req) to copy the request's identity onto it. So when item_done(rsp) returned the response, the sequencer had no way to match it to the sequence waiting on ID7 — the response was an orphan with correct data but no return address. Depending on the version and configuration, the result is get_response blocking forever (no match ever arrives), a "response queue overflow" warning (unmatched responses pile up), or the response being mis-routed. The fix is the one line the response path requires: rsp.set_id_info(req) before returning the response, which copies the sequence_id and transaction_id from the request so the response carries the identity that routes it home. (If a driver reuses the request object as the response instead of creating a new one, the IDs are already present — but the moment it builds a separate response, it owes the set_id_info stamp.)
The tell is a request that works but a response that never matches. Diagnose response-routing bugs:
- Confirm the request side is fine. The driver received and drove the item — so the problem is the return path, not generation. A hung
get_response(or a response-queue warning) points at response identity. - Check for
set_id_infoon the response. Find where the driver builds the response and confirm it callsrsp.set_id_info(req)beforeitem_done/put_response. A new response object without that stamp is the bug. - Check whether the response is a new object. If the driver creates a fresh
rsp, it needsset_id_info; if it reusesreq, the IDs are already there. A newly-created, unstamped response is the classic case. - Confirm the IDs match end to end. If available, print the request's and response's
get_sequence_id()/get_transaction_id(); a mismatch (or default on the response) confirms the missing stamp.
Stamp every response with its request's identity:
- Call
set_id_info(req)whenever you build a separate response. Make it the reflexive first thing after creating a response object, before setting its data — so the response always carries the request's identity. - Decide deliberately: new response vs reuse the request. If you don't need a distinct response object, returning the (modified) request preserves the IDs automatically; if you build a new one, you owe
set_id_info. - Only call
get_responseif the driver actually returns one. Aget_responsewith no responding driver hangs regardless; pair response-returning drivers with response-consuming sequences, and skipget_responsefor fire-and-forget items. - Watch for response-queue warnings. A "response queue overflow" usually means responses are returning unmatched (often a missing
set_id_info) or never consumed — treat it as a routing/identity signal, not noise.
The one-sentence lesson: a response is routed back to its originating sequence by the request's identity, so a driver that builds a new response object must copy the request's IDs onto it with set_id_info(req) before returning it — omit that stamp and the response is an orphan, leaving get_response to hang (or the framework to warn about unmatched responses).
Common Mistakes
- Forgetting
set_id_infoon a new response. A freshly-created response has no matching identity; withoutrsp.set_id_info(req)it can't be routed back, andget_responsehangs. Stamp every separate response. - Using a plain
uvm_transactionfor stimulus. A transaction that must be generated and driven must extenduvm_sequence_item; a plain transaction lacks the identity and connectivity to travel the stimulus path. - Randomizing before
start_item. Randomize betweenstart_itemandfinish_item, so the item reflects the latest state and arbitration order; randomizing earlier can use stale context. - Calling
get_responsewhen no response is returned. It blocks waiting for a response the driver never sends; only retrieve responses for items the driver actually answers. - Confusing the item with the channel. The sequence item is the data passed across the handshake;
start_item/finish_item/get_next_item/item_doneare the protocol. Modeling fields belongs in the item, not the handshake. - Mismatched item types across the port. The sequence, sequencer, and driver must agree on the sequence-item type parameter; a mismatch breaks the handshake at elaboration.
Senior Design Review Notes
Interview Insights
It adds the identity and connectivity a transaction needs to travel the stimulus path. uvm_sequence_item extends uvm_transaction, and on top of the transaction's timing/recording identity it adds a sequence_id and transaction_id — recording which sequence produced the item and which specific item it is — plus the hooks (a sequencer handle, a link to the parent sequence) that let the item flow through the sequence-to-sequencer-to-driver chain. Those additions are what make the stimulus path work in both directions. The identity lets a sequencer route items from many sequences onto one driver and, crucially, route each response back to the sequence that originated the request — the sequence_id says which sequence, the transaction_id distinguishes items within it. The connectivity lets a sequence hand the item to a sequencer with start_item/finish_item and a driver pull it with get_next_item/item_done, even though the sequence and driver never reference each other. Everything from the transaction still holds — a sequence item is still a uvm_object with copy/compare/print and still a transaction with logical fields — the identity and connectivity are layered on. The practical consequence is the decision rule: a transaction that only needs to be analyzed, compared, or recorded (like a monitor's output) can extend uvm_transaction, but a transaction that must be generated by a sequence and driven must extend uvm_sequence_item, because only that layer carries what the stimulus path requires.
set_id_info copies a request item's identity — its sequence_id and transaction_id — onto a response item, so the response can be routed back to the sequence that originated the request. It's necessary because the response path is identity-driven and a driver typically builds a brand-new response object. When a sequence sends a request, the item carries IDs that say which sequence and which item it is, and the sequence's get_response waits for a response with that identity. But a newly created response object starts with default IDs — no matching identity — so if the driver returns it as-is, the sequencer has no way to know which waiting sequence it belongs to. Calling rsp.set_id_info(req) stamps the response with the request's IDs, giving it the return address it needs; then when the driver returns it with item_done(rsp) or put_response, the sequencer matches those IDs to the originating sequence and its get_response returns the response. If you omit set_id_info, the response is an orphan with correct data but no matching identity: get_response blocks forever waiting for a match that never comes, or the framework warns about a response-queue overflow, or the response is mis-routed. The one exception is if the driver reuses the request object as the response rather than creating a new one — then the IDs are already present and no copy is needed. But the moment a driver builds a separate response, it owes the set_id_info stamp, which is why it's the canonical sequence-item response bug.
Exercises
- Place the stamp. Given a driver that creates a new
rsp, sets its data, and callsitem_done(rsp), insert the one missing line and state what symptom its absence produces. - Choose the base. For (a) a transaction a sequence randomizes and drives, and (b) a transaction a monitor reconstructs for the scoreboard, choose
uvm_sequence_itemvsuvm_transaction/uvm_objectand justify each. - Order the handshake. List the four calls (
start_item,finish_item,get_next_item,item_done) in execution order across the sequence and driver, and say which two block and on what. - Trace the identity. Explain how a response gets matched to its request, naming the IDs involved and the call that copies them.
Summary
- A
uvm_sequence_itemis a transaction that can travel the stimulus path: it extendsuvm_transactionwith identity (sequence_id,transaction_id) and connectivity (sequencer hooks) — while remaining auvm_objectwith all its data services and a transaction with its logical fields. - A sequence generates the item with the handshake
start_item→ randomize →finish_item, and a driver consumes it withget_next_item→ drive →item_done— brokered by the sequencer, so the two ends never reference each other and the item is the unit passed between them. - Identity makes request/response work: when a driver builds a response, it copies the request's IDs onto it with
set_id_info(req), so the sequencer routes the response back to the originating sequence and itsget_responseretrieves it. - The canonical bug is a missing
set_id_info: a new response object without the request's identity is an orphan — correct data, no return address — soget_responsehangs or the framework warns about unmatched responses. - The durable rule of thumb: make stimulus transactions
uvm_sequence_items (for the identity and connectivity), generate them with start/finish and consume them with get_next/item_done, and whenever a driver builds a separate response, stamp it withset_id_info(req)first — because the response is routed home by the request's identity, and an unstamped response never arrives.
Next — Transaction Lifecycle: this chapter covered the interface by which an item is generated and consumed; the next traces its full lifecycle — the temporal journey of one transaction from creation, through randomization, the blocking handshake, driving, completion, and response — and exactly what blocks when along the way.