Skip to content

UVM

uvm_sequence_item

The transaction class you actually extend — the start_item/finish_item handshake, the sequencer link, late randomisation, and the ordered create→start→randomize→finish protocol.

UVM Base Classes · Module 4 · Page 4.5

The Engineering Problem

The data lineage is almost complete: uvm_object (data) → uvm_transaction (identity + timing) → and now uvm_sequence_item, the class you actually extend for transactions. The previous chapters explained what a transaction is; this one explains how it is generated and delivered — because a transaction that can't be produced by a sequence and handed to a driver is inert. uvm_sequence_item adds exactly that machinery.

What it adds over uvm_transaction is the sequence/sequencer handshake: a sequence creates an item, requests the sequencer's grant with start_item, randomises it, and sends it with finish_item; meanwhile a driver pulls it with get_next_item and signals completion with item_done (Module 3.4, the driver side). This handshake is a precise, blocking protocol with a specific order — and getting that order wrong (randomising before the grant, or forgetting to finish an item) produces real bugs: stimulus that hangs, or items that miss late randomisation. uvm_sequence_item is the layer that turns a timed, identifiable transaction into one a sequence can drive through a sequencer to a driver — and the class name you'll write at the top of nearly every transaction.

What is uvm_sequence_item, what does it add over uvm_transaction (the sequencer link and the start_item/finish_item handshake), and what is the precise, ordered protocol by which a sequence generates an item and a driver consumes it?

Motivation — why a transaction needs sequence machinery

The handshake uvm_sequence_item adds is what makes a transaction drivable, with three properties that matter:

  • It connects stimulus generation to the driver. A sequence produces intent (a randomised item); a driver consumes it and drives pins. The start_item/finish_itemget_next_item/item_done handshake is the bridge — without it, an item a sequence creates never reaches the driver. This is the mechanism by which the scenario layer (sequences) feeds the command layer (driver).
  • It enables late randomisation. Because you randomise between start_item and finish_item — i.e., after the sequencer grants — the item can be randomised with the most current state and through the sequence's mid-generation hooks. Randomising before the grant defeats this, producing items that ignore arbitration-time information.
  • It provides flow control and arbitration. start_item blocks until the sequencer grants, which is how the sequencer arbitrates among multiple sequences competing for one driver. The handshake is not just delivery; it's the point where the sequencer decides whose item goes next.
  • It supports request/response. A sequence can get a response back for an item (correlated by the transaction_id from uvm_transaction), enabling reactive sequences that adapt to the DUT's replies.

The motivation, in one line: a transaction is useless until a sequence can generate it and a driver can consume it, and uvm_sequence_item adds exactly that ordered, blocking, arbitrated handshake — which is why it's the class you extend for every transaction.

Mental Model

Hold uvm_sequence_item as a transaction with a delivery protocol:

A uvm_sequence_item is a transaction that knows how to be handed off — like a parcel that comes with a courier handshake. The sequence is the sender, the driver is the recipient, and the sequencer is the dispatch desk between them. To send a parcel, the sender first requests a slot at the dispatch desk (start_item — and waits until the desk grants one, because other senders may be queued). Once granted, the sender fills in the parcel (randomize — done now, at the last moment, so it reflects the latest information). Then the sender hands it over (finish_item) and waits until the recipient confirms delivery. On the other side, the recipient takes the next parcel (get_next_item), processes it (drives the pins), and signs for it (item_done), which is what unblocks the sender. The protocol is strictly ordered — request, fill, hand over — and every hand-over must be signed for, or the sender waits forever.

So when you write a sequence, the item is the parcel and the four calls are the courier protocol: start_item (request a slot), randomize (fill it in now), finish_item (hand it over and wait for the signature). The order and the pairing are the discipline; break either and delivery fails.

Visual Explanation — what uvm_sequence_item adds over uvm_transaction

uvm_sequence_item keeps everything from uvm_transaction (and uvm_object) and adds the sequence/sequencer handshake machinery on top — the final layer of the data lineage.

uvm_sequence_item layers the sequencer link and start_item/finish_item handshake over uvm_transaction's identity and timing and uvm_object's data toolkitWhat uvm_sequence_item adds over uvm_transactionWhat uvm_sequence_item adds over uvm_transactionstart_item / finish_item handshakethe sequence-side protocol to request a grant and deliver the item to the driverthe sequence-side protocol to request a grant and deliver the item to the driverSequencer + parent-sequence linkknows the sequencer it runs on and the sequence that created it; supports request/responseknows the sequencer it runs on and the sequence that created it; supports request/responseuvm_transaction (inherited)transaction_id (identity) + begin_tr/end_tr (timing/recording)transaction_id (identity) + begin_tr/end_tr (timing/recording)uvm_object (inherited)the data toolkit — copy, compare, print, packthe data toolkit — copy, compare, print, pack
Figure 1 — uvm_sequence_item = uvm_transaction + sequence/sequencer machinery. Inherited: the uvm_object data toolkit and uvm_transaction's identity and timing. Added: a link to the sequencer it runs on and its parent sequence, and the start_item/finish_item handshake that delivers it to a driver. This is the class you actually extend for transactions, because it has the machinery to be generated by a sequence.

The two added layers are what make an item drivable. The sequencer/parent-sequence link is the item's connection to where it runs — it knows its sequencer (so it can be arbitrated and delivered) and its parent sequence (so responses can be routed back). The start_item/finish_item handshake is the sequence-side half of the delivery protocol — the calls a sequence makes to request a grant and hand the item to the driver. Everything below (greyed) is inherited: the data toolkit from uvm_object, the identity and timing from uvm_transaction. So uvm_sequence_item is the complete transaction — data + identity + timing + the machinery to be generated and delivered — which is precisely why it's the class you extend for a transaction rather than any of its bases.

RTL / Simulation Perspective — generating an item in a sequence

The handshake is concrete in a sequence's body(): create the item, start_item to get the grant, randomise, finish_item to send. The item class itself is a thin extension of uvm_sequence_item.

uvm_sequence_item — the class you extend; generated via start_item/finish_item
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class bus_item extends uvm_sequence_item;       // the transaction class you ACTUALLY extend
  rand bit [31:0] addr, data;
  rand bit        write;
  `uvm_object_utils(bus_item)
  function new(string name = "bus_item"); super.new(name); endfunction
endclass
 
// In a SEQUENCE's body(): the ordered, blocking handshake
class bus_seq extends uvm_sequence#(bus_item);
  `uvm_object_utils(bus_seq)
  task body();
    bus_item req = bus_item::type_id::create("req");
    start_item(req);                 // 1. request the sequencer's grant — BLOCKS until granted
    assert(req.randomize());         // 2. randomise AFTER the grant (late randomisation)
    finish_item(req);                // 3. send to the driver — BLOCKS until the driver's item_done()
  endtask
endclass
// DRIVER side (Module 3.4): get_next_item(req) → drive on pins → item_done()

The order is the protocol, and each step is deliberate. create makes a fresh item (an object). start_item(req) asks the sequencer to grant this sequence the right to send — and blocks until it does, which is where the sequencer arbitrates if several sequences compete. randomize happens after the grant, so the item is filled in with the latest information (late randomisation) and through the sequence's generation hooks. finish_item(req) delivers the item to the driver and blocks until the driver calls item_done, providing flow control (the sequence paces to the driver). On the other side, the driver's get_next_item/item_done (Module 3.4) is the matching half. The shorthand `uvm_do(req) macro wraps create-start-randomize-finish, but knowing the explicit four steps is what lets you control randomisation and debug the handshake.

Verification Perspective — the two-sided handshake

The full handshake has two halves that meet at the sequencer: the sequence side (start_item/finish_item) and the driver side (get_next_item/item_done). Seeing them together explains the blocking points and the arbitration.

Sequence side (start_item, finish_item) and driver side (get_next_item, item_done) meeting at the sequencerrequest grant(blocks)send item (blocks)deliver granted itemitem_done unblocksstart_item (seq)request the grantfinish_item (seq)send; wait for item_doneSequencerarbitrates + routesget_next_item (drv)take the granted itemitem_done (drv)signal completion12
Figure 2 — the two-sided sequence-item handshake, meeting at the sequencer. Sequence side: start_item requests a grant (blocks), then finish_item sends the item (blocks until item_done). Driver side: get_next_item retrieves the granted item, the driver drives it, and item_done signals completion (which unblocks finish_item). The sequencer sits between them, arbitrating among competing sequences and routing the granted item to the driver. The blocking calls provide flow control and arbitration, not just delivery.

The two-sided view explains why the calls block. start_item blocks because the sequencer must grant — and if several sequences are running on one sequencer, the sequencer arbitrates among their start_item requests, so blocking here is where arbitration happens. finish_item blocks until the driver's item_done, which is flow control: the sequence cannot run ahead of the driver, so stimulus is naturally paced to how fast the driver can drive. The driver's get_next_item retrieves whichever item the sequencer granted, and its item_done is the signal that releases the sequence's finish_item. This is why the handshake is a protocol, not a function call: the two sides rendezvous at the sequencer, with arbitration on the way in and flow control on the way out — which is exactly what makes one driver serve many sequences in a controlled order.

Runtime / Execution Flow — the ordered item lifecycle

A single item's lifecycle in a sequence is a strict four-step order, and the placement of randomise in that order is what enables late randomisation — getting it wrong is a common, subtle bug.

Item lifecycle: create, start_item requests grant, randomise after grant, finish_item sends and waitscreate → start_item → randomise → finish_itemcreate → start_item → randomise → finish_item1create the itema fresh sequence item (an object) via the factory — not yetrandomised or sent.2start_item — request grantask the sequencer for the right to send; BLOCKS until granted(arbitration point).3randomise — after the grantlate randomisation: fill the item now, reflecting current state andsequence hooks.4finish_item — send + waitdeliver to the driver; BLOCKS until item_done (flow control). Itemnow driven.
Figure 3 — an item's ordered lifecycle in a sequence. Create the item; start_item to request the sequencer's grant (blocking); randomise after the grant (late randomisation, reflecting the latest state and the sequence's hooks); finish_item to send it and block until the driver completes. The order matters: randomising before start_item bypasses late randomisation and arbitration-time information. Create → start_item → randomise → finish_item.

The order is not cosmetic. Randomising at step 3 — after start_item grants — is late randomisation: the item is filled in at the last moment, so it can reflect information that only became available at grant time (the state of the DUT, the responses to prior items in a reactive sequence, the sequence's mid_do/pre_do hooks). If you instead randomise before start_item, the item is locked in before the sequencer arbitrates and before those hooks run, so it ignores arbitration-time information and bypasses the generation callbacks — a subtle bug where items carry stale or unintended values, especially in reactive or arbitrated scenarios (the DebugLab). And step 4, finish_item, must always follow a start_item — every requested item must be finished, or the sequence and driver deadlock. Create, start, randomise, finish: the order encodes late randomisation and the pairing encodes flow control.

Waveform Perspective — the handshake in time

The blocking handshake has a clear timing signature: start_item requests, the sequencer grants, the item is randomised and sent, the driver drives it, and item_done completes the rendezvous — releasing the sequence.

The sequence-item handshake in time — request, grant, drive, done

10 cycles
The sequence-item handshake in time — request, grant, drive, donestart_item(req): the sequence requests the sequencer's grant (blocks until granted)start_item(req): the s…granted → randomise the item → finish_item sends it to the drivergranted → randomise th…driver drives, then item_done(): finish_item unblocks, the handshake completesdriver drives, then it…clksi_reqgntvaliddata000000A0A1A200000000donet0t1t2t3t4t5t6t7t8t9
Figure 4 — start_item requests the sequencer's grant (si_req); the sequencer grants (gnt); the item is randomised and finish_item sends it; the driver drives it over several cycles (valid/data); item_done (done) signals completion, which unblocks the sequence's finish_item. The blocking points — start_item waiting for the grant, finish_item waiting for item_done — are where arbitration and flow control happen. This rendezvous is the sequence-item handshake.

The sequence of si_reqgnt → drive → done is the handshake's rendezvous on a timeline. Notice the gaps: between start_item (cycle 1) and the grant (cycle 2) the sequence is blocked, waiting for the sequencer — that's the arbitration window, where the sequencer decides whose item goes next. Between finish_item (after grant) and item_done (cycle 6) the sequence is blocked again, waiting for the driver — that's flow control, pacing the sequence to the driver. The randomisation happens in the brief moment after the grant and before the drive, which is what makes it late. Reading this trace is reading the protocol: request, grant, fill, hand over, drive, sign for — with the sequence blocked at exactly the two points where the sequencer and driver, respectively, hold control.

DebugLab — items randomised before the grant

A reactive sequence whose items ignored the latest state — randomised too early

Symptom

A sequence was meant to react to the DUT — each item's address was supposed to depend on the response to the previous item, and a mid_do hook was supposed to tweak fields at send time. Instead, the items came out with values that ignored both: the reactive addressing didn't track the responses, and the mid_do adjustments never appeared. The randomisation ran (items had random values), but it ignored everything that should have shaped it.

Root cause

The item was randomised before start_item, not between start_item and finish_item. So it was filled in before the sequencer granted — before the latest state was available and before the sequence's generation hooks ran:

why early randomisation ignores the latest state
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
what was written:   req.randomize();  start_item(req);  finish_item(req);   // randomise FIRST
correct order:      start_item(req);  req.randomize();  finish_item(req);   // randomise AFTER grant
consequence:        randomised before the grant → before mid_do/late hooks → before the
                    response to the prior item was available → values ignore current state
result:             reactive addressing doesn't track responses; mid_do tweaks never apply
fix:                move randomize() to AFTER start_item (late randomisation)

start_item is the point at which the sequencer grants and the sequence's send-time machinery engages; randomising before it locks the item's values in too early, bypassing the late-randomisation window. The randomisation worked — it just happened at the wrong moment, so it couldn't see what it was supposed to react to.

Diagnosis

The tell is items that are random but ignore reactive constraints or send-time hooks — a randomisation-timing bug. Diagnose by checking the order:

  1. Confirm randomize() is between start_item and finish_item. If it's before start_item, the item is randomised before the grant and before the sequence's hooks — late randomisation is bypassed. The order must be create → start_item → randomize → finish_item.
  2. Check whether reactive data was available yet. If an item should depend on a prior response or current state, that information is only present after the grant; randomising earlier can't use it. The symptom is "reactive logic ignored."
  3. Look for missing mid_do/pre_do effects. These hooks run as part of the start_item/finish_item flow; if their effects don't appear, the item was likely randomised outside that window.
Prevention

Follow the item protocol order, and randomise late:

  1. Always create → start_item → randomize → finish_item. Randomise after the grant so the item reflects the latest state and the sequence's send-time hooks. Never randomise before start_item.
  2. Use the `uvm_do family when you don't need custom timing. These macros perform create-start-randomize-finish in the correct order, so the randomisation is late by construction; drop to explicit calls only when you need to control the steps.
  3. For reactive sequences, get the response before randomising the next item. Pair request/response (by transaction_id) and randomise the next item after the prior response is in hand, inside the start/finish window.

The one-sentence lesson: randomise an item between start_item and finish_item, not before — late randomisation after the grant is what lets the item reflect the current state and the sequence's hooks, and randomising too early silently ignores both.

Common Mistakes

  • Randomising before start_item. Late randomisation requires randomising after the grant (between start_item and finish_item); doing it earlier bypasses reactive constraints and send-time hooks, so items ignore current state. Order: create → start_item → randomize → finish_item.
  • Forgetting finish_item (or start_item). Every start_item must be followed by a finish_item; an unmatched start_item leaves the item undelivered and the sequence/driver deadlocked. Pair them, or use the `uvm_do macros.
  • Extending uvm_transaction instead of uvm_sequence_item for stimulus. A transaction you generate from a sequence needs the sequence-item machinery; extend uvm_sequence_item (not uvm_transaction or uvm_object) so it can be sent via start_item/finish_item.
  • Modifying the item after finish_item. Once finished, the driver is processing the item; mutating it (or reusing the same handle for the next iteration without a fresh create) corrupts what the driver and scoreboard see. Create a new item per iteration.
  • Confusing the sequence and driver sides. start_item/finish_item are the sequence side; get_next_item/item_done are the driver side. They are two halves of one handshake that meet at the sequencer — don't call a driver method from a sequence or vice versa.
  • Driving the item from the sequence. A sequence produces items; it does not touch pins. The driver drives. Putting pin activity in a sequence is a layer violation (Module 3.6).

Senior Design Review Notes

Interview Insights

uvm_sequence_item is the uvm_transaction subtype that adds the machinery for a transaction to be generated by a sequence and delivered to a driver, and it's the class you actually extend for transactions in a UVM environment. Over uvm_transaction (which gives identity and timing) and uvm_object (which gives the data toolkit), it adds a link to the sequencer it runs on and its parent sequence, plus the start_item/finish_item handshake used to send it. You extend it — rather than uvm_transaction or uvm_object directly — because virtually every transaction is produced by a sequence: the sequence creates the item, calls start_item to get the sequencer's grant, randomises it, and calls finish_item to deliver it to the driver. Without the sequence-item machinery, a transaction you create couldn't participate in that handshake, so it could never be generated and driven. So uvm_sequence_item is the complete transaction class — data plus identity plus timing plus the generation/delivery protocol — which is why it's the one you name at the top of your transaction definitions.

Exercises

  1. Order the handshake. Write the four sequence-side steps to generate and send an item, in the correct order, and mark which two calls block and on what.
  2. Fix the randomisation. A sequence does req.randomize(); start_item(req); finish_item(req); and its reactive addressing ignores prior responses. State the bug and the corrected order, and explain what "late randomisation" gives you.
  3. Two sides. Match each call to the sequence side or the driver side: start_item, get_next_item, finish_item, item_done — and explain where the two sides meet and what blocks at each.
  4. Pick the base. For a transaction generated by a sequence, state which class you extend and why, naming what uvm_sequence_item adds over uvm_transaction that makes the generation possible.

Summary

  • uvm_sequence_item is the uvm_transaction subtype you actually extend for transactions — it adds the sequencer/parent-sequence link and the start_item/finish_item handshake on top of inherited identity, timing, and the data toolkit.
  • The sequence-side protocol is ordered and blocking: create → start_item (request grant — blocks, arbitration point) randomize (late, after the grant) finish_item (send — blocks until item_done, flow control). The driver side is get_next_item → drive → item_done.
  • The two sides rendezvous at the sequencer: start_item blocks for the grant (where the sequencer arbitrates among sequences), finish_item blocks for item_done (pacing the sequence to the driver). The handshake is arbitration + flow control, not a plain call.
  • Two disciplines: randomise between start and finish (late randomisation, so the item reflects current state and the sequence's hooks — randomising too early silently ignores them), and pair every start_item with a finish_item (or the sequence and driver deadlock).
  • The durable rule of thumb: extend uvm_sequence_item for transactions, generate them with create → start_item → randomize → finish_item, randomise after the grant, and always finish what you start — the handshake is the courier protocol that carries a transaction from a sequence to a driver.

Next — uvm_object vs uvm_component: you've now seen both halves of the class library in depth — the data lineage (uvm_objectuvm_transactionuvm_sequence_item) and the structural base (uvm_component). The next chapter consolidates the distinction: a definitive side-by-side of object versus component, and the rules for choosing between them.