Sequences and the item handshake
Write a sequence that creates, randomizes and sends items, and know exactly where the sequence and the driver wait for each other.
- Lesson
- U4.1
- Time
- 25 min
- Runs on
- Verilator with UVM
- Check
- 5 questions
A new driver passes a one-item smoke test, then a ten-item test never finishes. The log shows the first item driven again and again, an error saying get_next_item was called twice, and finally UVM's timeout. The driver loop never called item_done, so the sequence is still waiting inside finish_item for item 1.
| Time | Sequence | Driver |
|---|---|---|
| 0 ns | Calls start_item, is granted, calls finish_item and waits | Calls get_next_item and receives item 1 |
| 10 ns | Waiting in finish_item | Drives item 1, then loops back without item_done |
| 20 ns | Waiting in finish_item | Calls get_next_item again: an error, and item 1 comes back |
| Every cycle after | Waiting in finish_item | Drives item 1 again, until the timeout |
The driver never completed the handshake for item 1, so neither side could move on.
A sequence is a recipe for items
In the layered bench, a generator made transactions and put them in a mailbox. UVM splits that job in two. A sequence is an object that decides which items to send. A sequencer is a component that passes them to the driver one at a time.
The item is a class derived from uvm_sequence_item. Its rand fields are the values a test may randomize, and its constraints keep them legal. The example item has one field, x, constrained to the range 16 to 31.
A sequence extends uvm_sequence, parameterized with the item type it sends. Its work lives in one task, body(). The test runs it with seq.start(sqr), which calls body() and returns when body() ends. So start blocks, and the test raises its objection around it.
A sequence can also start other sequences inside its own body(), so long tests are built from small, tested pieces. U10 uses this for virtual sequences, which coordinate several sequencers at once.
Four steps send one item
incr_item it = incr_item::type_id::create("it"); // 1. create
start_item(it); // 2. wait for a grant
if (!it.randomize()) // 3. fill in the values
`uvm_error("RAND", "randomize failed")
finish_item(it); // 4. send it, wait for item_doneCreate the item through the factory, as U2 showed, so a test can override its type. Start with start_item(it), which asks the sequencer for permission to send. It returns when the sequencer grants this sequence the driver.
Fill in the item, usually with randomize(). Check the result: a failed randomization returns 0 and leaves the old values in place, and nothing else warns you. Finish with finish_item(it), which hands the item to the driver. It returns only when the driver says it has finished with that item.
Spend no simulation time between start_item and finish_item. The driver is already waiting for this item, so any delay there stalls the bus.
The handshake, call by call
The driver pulls items; the sequence never pushes them. The driver's loop calls seq_item_port.get_next_item(req), which blocks until an item arrives. Later it calls seq_item_port.item_done() to say it has finished. These two calls pair with the sequence's two calls.
| Step | Sequence | Sequencer | Driver |
|---|---|---|---|
| 1 | start_item(it): waits for a grant | Queues the request | Busy, or not asking yet |
| 2 | Still waiting | Arbitrates and grants the sequence | Calls get_next_item(req) and waits |
| 3 | start_item returns; randomize() | - | Still waiting |
| 4 | finish_item(it): waits for done | Passes the item handle on | get_next_item returns req, the same object |
| 5 | Still waiting | - | Drives the pins; time passes |
| 6 | finish_item returns | Releases the sequence | Calls item_done() |
| 7 | Next start_item | - | Next get_next_item |
One item through the handshake, as a sequence diagram read top to bottom. Simulation time passes only in step 5.
Read the table as two waits on each side. The sequence waits in start_item until the driver asks for an item, and in finish_item until the driver says it is done. The driver waits in get_next_item until a sequence has an item ready. Nobody polls; each side blocks until the other side acts.
Step 4 matters in the next lesson. The driver receives the very object the sequence created, not a copy. Anything the driver writes into req is visible to the sequence once finish_item returns.
Reading the handshake in a log
The example prints one line at each of these calls. Its driver keeps the bus idle for two clock cycles after time 0, then serves three items, two cycles each.
The sequence asks at 0 ns but is granted at 20 ns, when the driver first calls get_next_item.
- The sequence asks first0 ns (illustrative)
The sequence calls
start_itemand waits. The driver is keeping the bus idle for two falling edges and has not asked for anything.More context
Log line
0 ns sequence start_item: waiting for a grant. The request is queued in the sequencer; nothing can be granted yet.Follow the calls, values and waits
- call start_item(it): incr_seq.body → Sequencer
- Waiting: incr_seq.body — in start_item: no driver asking
- Waiting: Driver — idle until 20 ns
- The driver asks: grant20 ns (illustrative)
The driver calls
get_next_item. Now a driver is asking, so the sequencer grants the waiting sequence andstart_itemreturns.More context
The grant waited 20 ns for the driver, not for arbitration: with one sequence there is no one to choose between.
Follow the calls, values and waits
- call get_next_item(req): Driver → Sequencer
- return grant: Sequencer → incr_seq.body
- Waiting: Driver — in get_next_item
- Released: incr_seq.body
- Released: Driver
- Randomize late, then hand over20 ns (illustrative)
The sequence randomizes x=29 and calls
finish_item. The sequencer passes that same object to the driver, andget_next_itemreturns it.More context
Log lines
20 ns sequence granted; x=29; finish_itemand20 ns driver got x=29. One handle travels:reqin the driver isitin the sequence.Follow the calls, values and waits
- call finish_item(it): incr_seq.body → Sequencer
- data item 1: x=29: incr_seq.body → Sequencer
- data item 1: x=29: Sequencer → Driver
- Waiting: incr_seq.body — in finish_item until item_done
- Released: Driver
- Drive the pins30 ns (illustrative)
At the next falling edge the driver sets
validandx. Simulation time passes only here, while the sequence waits.More context
The trace shows valid=1 at 30 ns. The driver holds the item until the following falling edge, at 40 ns.
Follow the calls, values and waits
- data valid=1, x=29: Driver → Incrementer
- Done: finish_item returns40 ns (illustrative)
The driver reads y=30, clears
validand callsitem_done.finish_itemreturns in the same time step.More context
Log lines
40 ns driver design answered y=30; item_doneand40 ns sequence finish_item returned. A driver that skipped item_done would leave the sequence here for good.Follow the calls, values and waits
- data y=30: Incrementer → Driver
- call item_done(): Driver → Sequencer
- return finish_item returns: Sequencer → incr_seq.body
- Released: incr_seq.body
- Item 2 in the same time step40 ns (illustrative)
The driver asks again at once, and the sequence's next
start_itemis granted at 40 ns. In steady state the handshake costs no simulation time.More context
Item 2 (x=27) is answered at 60 ns and item 3 (x=21) at 80 ns, each through the same handshake. The last
finish_itemreturns at 80 ns.Follow the calls, values and waits
- call get_next_item(req): Driver → Sequencer
- call start_item(it): incr_seq.body → Sequencer
- return grant: Sequencer → incr_seq.body
Read the diagram as text
- incr_seq.body: start_item, randomize, finish_item
- Sequencer: Grants a waiting sequence to an asking driver
- Driver: get_next_item, drive, item_done
- Incrementer: y = x + 1
- incr_seq.body → Sequencer: start_item / finish_item
- Driver → Sequencer: get_next_item / item_done
- Driver → Incrementer: valid, x
- Incrementer → Driver: y
The driver pulls; the sequence never pushes. Each side blocks until the other side acts.
Two details stand out. First, the sequence called start_item at 0 ns but was granted at 20 ns, the moment the driver first asked. A grant goes only to a driver that is asking.
Second, finish_item for item 1 returned at 40 ns, in the same time step as item_done. The sequence was granted item 2 at once, because the driver asked again immediately. In steady state the handshake costs no simulation time at all.
The same two waits tell you where to look when a test hangs. A sequence stuck in finish_item points at the driver: it took the item but never called item_done. A sequence stuck in start_item means no driver is asking, because the driver is blocked elsewhere, was never connected to this sequencer, or has not started its loop.
Randomize after the grant
You could randomize before start_item, and the item would go out unchanged. Randomizing after the grant is called late randomization, and it has a real advantage. The values are chosen at the moment the driver is ready for them, so constraints can use the current state of the test.
Picture a sequence that writes to a FIFO and reads a fill-level counter kept by the scoreboard. Randomized late, its next item can be constrained to a write only while there is room. Randomized early, the item would reflect a fill level that may be several cycles old.
Inline constraints narrow one call without changing the class, as in it.randomize() with { x < 20; }. The class constraints still apply, so here x falls between 16 and 19. Ask for x < 10 instead and the two constraints conflict: randomize() returns 0 and x keeps its old value, which is why the example checks the result.
Breaking the handshake
Every get_next_item needs exactly one item_done. Miss one, and the hook's hang follows. In uvm-core, a second get_next_item before item_done reports an error and hands back the same item again. The driver then repeats that item while the sequence waits forever.
Call item_done twice and the second call finds no item outstanding, which is a fatal error. A loop with several exits, such as one that skips some items with continue, is where a missing item_done usually hides.
Skips item_done on one path
seq_item_port.get_next_item(req);
if (req.x == 0) continue; // leaves the item open
drive(req);
seq_item_port.item_done();
The first zero value leaves its item open. The next get_next_item returns the same item, and the sequence hangs.
Completes every item
seq_item_port.get_next_item(req);
if (req.x != 0) drive(req);
seq_item_port.item_done();
Every path reaches item_done, so the sequence always moves on.
Two other driver calls change the pairing. get(req) fetches the item and completes the request handshake; it does not wait for pin activity or a response. try_next_item(req) returns null if no item becomes available during its delta-cycle check. A null result needs no item_done; lesson U4.3 uses this to drive idle cycles.
Try it
The log has one line per handshake call. Match it against the trace above: the sequence waits for its first grant until 20 ns, and each finish_item returns in the same time step as the driver's item_done.
Delete the repeat (2) line in the driver and predict the new time of the first grant. Then move item_done to just after get_next_item and watch finish_item return before the item is driven.
0 ns sequence start_item: waiting for a grant 20 ns driver get_next_item: waiting for an item 20 ns sequence granted; x=29; finish_item 20 ns driver got x=29 40 ns driver design answered y=30; item_done 40 ns driver get_next_item: waiting for an item 40 ns sequence finish_item returned 40 ns sequence start_item: waiting for a grant 40 ns sequence granted; x=27; finish_item 40 ns driver got x=27 60 ns driver design answered y=28; item_done 60 ns driver get_next_item: waiting for an item 60 ns sequence finish_item returned 60 ns sequence start_item: waiting for a grant 60 ns sequence granted; x=21; finish_item 60 ns driver got x=21 80 ns driver design answered y=22; item_done 80 ns driver get_next_item: waiting for an item 80 ns sequence finish_item returned UVM_ERROR : 0Check yourself
Remember
- A sequence decides what to send in
body(); the sequencer passes its items to the driver one at a time. - Each item takes four steps: create,
start_item, randomize,finish_item. start_itemwaits for a grant, which comes when the driver asks;finish_itemwaits for the driver'sitem_done.- The driver receives the same object the sequence created, not a copy.
- Every
get_next_itemneeds exactly oneitem_done, on every path through the driver loop.