The sequence lifecycle
Know the order of a sequence's hooks and states, what kill and stop_sequences leave behind, and what happens to responses that nobody reads.
- Lesson
- U5.3
- Time
- 30 min
- Runs on
- Verilator with UVM
- Check
- 5 questions
A base sequence fetches its register model handle in pre_body(). Used on its own it works. Started from a virtual sequence as a child, with call_pre_post set to 0, it crashes on a null handle: pre_body never ran. The handle belonged in pre_start, which runs for every start.
| Time | Printed by | Hook | Why it ran |
|---|---|---|---|
| 10 ns | P | pre_start, pre_body | P is a root sequence; both run |
| 10 ns | K | pre_start | The child's pre_start always runs |
| 10 ns | P | pre_do(is_item=0), mid_do | P's hooks wrap the child it starts |
| 10 ns | K | pre_do(is_item=1), mid_do | K's own item, after the grant |
| 20 ns | K | post_do | K's item is done |
| 20 ns | P | post_do | P's hook closes the child it started |
| 20 ns | K | post_start | No post_body: P started K with call_pre_post = 0 |
| 20 ns | P | post_body, post_start | P ends normally |
The measured hook order when sequence P starts child K with call_pre_post = 0. K's pre_body and post_body never run.
start() runs a fixed script
call_pre_post = 0, and the driver takes 10 ns.- Root sequence set-up10 ns (illustrative)
The test starts P. Because P has no parent and
call_pre_postdefaults to 1,pre_startruns, thenpre_body, thenbody.More context
P starts at 10 ns because the test first ran R, which finished at 10 ns. Log lines
10 ns P pre_startand10 ns P pre_body.Follow the calls, values and waits
- call p.start(sqr): Test → Parent P
- Waiting: Test — in p.start until 20 ns
- Child set-up10 ns (illustrative)
P's body calls
k.start(sqr, this, -1, 0). K'spre_startruns; itspre_bodyis skipped because the fourth argument is 0. Then P's ownpre_do(0)andmid_dorun around the child.More context
The parent hooks run with is_item = 0: they wrap a sequence, not an item. They run after K's
pre_start, as the log shows.Follow the calls, values and waits
- call k.start(sqr, this, -1, 0): Parent P → Child K
- call pre_do(0), mid_do: Child K → P's item hooks
- Waiting: Parent P — in k.start
- K's item10 ns (illustrative)
K's body calls
start_item. After the grant, K'spre_do(1)runs, thenmid_do, and the item goes to the driver.More context
These are K's own item hooks, with is_item = 1, in K's own object.
mid_dois the last hook before the driver takes the item.Follow the calls, values and waits
- data K item 0: Child K → Sequencer
- data K item 0: Sequencer → Driver
- Waiting: Child K — finish_item waits for item_done
- Item done20 ns (illustrative)
The driver calls
item_done. K'spost_doruns andfinish_itemreturns. K's body ends: its state becomesUVM_ENDED.More context
The driver takes 10 ns per item. Log lines
20 ns K post_doand20 ns K finish_item 0 returned.Follow the calls, values and waits
- call item_done(): Driver → Sequencer
- Released: Child K
- Close the child, then the parent20 ns (illustrative)
P's
post_docloses the child; K skipspost_bodyand runspost_start. Then P runspost_bodyandpost_startand itsstartreturns to the test.More context
Code that every start needs belongs in pre_start and post_start; pre_body and post_body are conditional.
Follow the calls, values and waits
- call post_do: Child K → P's item hooks
- return k.start returns: Child K → Parent P
- return p.start returns: Parent P → Test
- Released: Parent P
- Released: Test
Read the diagram as text
- Test: Calls p.start(sqr)
- Parent P: pre_start, pre_body, then body
- Child K: pre_start only: no pre_body
- P's item hooks: pre_do(0), mid_do, post_do wrap K
- Sequencer: Grants K's item
- Driver: 10 ns per item
- Test → Parent P: start(sqr)
- Parent P → Child K: k.start(sqr, this, -1, 0)
- Child K → P's item hooks: calls P hooks
- Child K → Sequencer: start_item
- Sequencer → Driver: item
One start, eight hooks. The parent's three item hooks also wrap every child it starts.
Every item gets its own trip through pre_do, mid_do and post_do. In the example, R's log reads pre_do(is_item=1), mid_do, a 10 ns gap for the driver, post_do, then finish_item 0 returned. post_do is the first code in the sequence that runs after the driver has finished with the item.
The fourth argument of start is call_pre_post, default 1. A parent often passes 0 so that it does the set-up itself; older code that raised objections in pre_body relied on this too. The uvm_do macros pass 0 for every child they start (U5.4). The Cookbook advises avoiding pre_body and post_body altogether, because code that may or may not run is a trap.
All the hooks, their kind and their typical use
| Hook | Kind | When | Typical use |
|---|---|---|---|
pre_start / post_start | task | Always, around everything | Fetch configuration, raise and drop an objection |
pre_body / post_body | task | Only when call_pre_post is 1 | Set-up that a parent sequence would rather do itself |
pre_do(bit is_item) | task | For an item: after the grant, before randomization by the uvm_do macros. For a child sequence: after the child's pre_start and, if it runs, pre_body | Last-moment constraints on an item |
mid_do(item) | function | Just before the item is sent | Inspect or log the final item |
post_do(item) | function | After the driver's item_done | Read results, count, update credits (U5.2) |
do_kill | function | When the sequence is killed | Release resources the body held |
The hooks of uvm_sequence_base. All are virtual and empty by default.
States you can wait for
get_sequence_state(); arrows are the transitions start makes, labelled with what runs next. A new sequence is UVM_CREATED.- A normal run: R0–10 ns (illustrative)
With
call_pre_post = 1the state visits every box on the top row and back along the bottom toUVM_FINISHED.More context
R is the example's root sequence. Its body starts at 0 ns and its one item finishes at 10 ns; the four post states follow in that same time step. At 45 ns L's log reports R as
UVM_FINISHED.Follow the calls, values and waits
- data R's state: UVM_PRE_START → UVM_PRE_BODY
- data R's state: UVM_PRE_BODY → UVM_BODY
- data R's state: UVM_BODY → UVM_ENDED
- data R's state: UVM_ENDED → UVM_POST_BODY
- data R's state: UVM_POST_BODY → UVM_POST_START
- data R's state: UVM_POST_START → UVM_FINISHED
- A child started with call_pre_post = 0: K10–20 ns (illustrative)
The two body states are skipped:
UVM_PRE_STARTgoes straight toUVM_BODY, andUVM_ENDEDstraight toUVM_POST_START.More context
K's log has no
pre_bodyand nopost_bodyline. P'spost_doruns while K is inUVM_ENDED, before K'spost_start.Follow the calls, values and waits
- data K's state: UVM_PRE_START → UVM_BODY
- data K's state: UVM_BODY → UVM_ENDED
- data K's state: UVM_ENDED → UVM_POST_START
- data K's state: UVM_POST_START → UVM_FINISHED
- L reaches its body20 ns (illustrative)
L is started in a
fork. The other branch waits forUVM_BODYand returns when L's body begins.More context
Log line
20 ns L is in state UVM_BODY, printed bywait_for_sequence_state(UVM_BODY)returning.Follow the calls, values and waits
- data L's state: UVM_PRE_START → UVM_PRE_BODY
- data L's state: UVM_PRE_BODY → UVM_BODY
- Waiting: UVM_BODY — L's body: five items
- A kill45 ns (illustrative)
The example kills L while it is in
UVM_BODY. The state jumps toUVM_STOPPED;post_bodyandpost_startnever run and the state never becomesUVM_FINISHED.More context
A kill in any running state ends in UVM_STOPPED; the diagram shows the case the example measures. A test waiting for
UVM_FINISHEDalone would now wait forever.Follow the calls, values and waits
- data L's state: UVM_BODY → UVM_STOPPED
- Released: UVM_BODY
Read the diagram as text
- UVM_PRE_START: pre_start runs
- UVM_PRE_BODY: only if call_pre_post
- UVM_BODY: body runs
- UVM_ENDED: set as body returns
- UVM_POST_BODY: only if call_pre_post
- UVM_POST_START: post_start runs
- UVM_FINISHED: start returns
- UVM_STOPPED: kill(): start returns
- UVM_PRE_START → UVM_PRE_BODY: pre_body
- UVM_PRE_BODY → UVM_BODY: body
- UVM_BODY → UVM_ENDED: body returns
- UVM_ENDED → UVM_POST_BODY: parent post_do, post_body
- UVM_POST_BODY → UVM_POST_START: post_start
- UVM_POST_START → UVM_FINISHED: post_start returns
- UVM_BODY → UVM_STOPPED: kill()
- UVM_PRE_START → UVM_BODY: call_pre_post = 0
- UVM_ENDED → UVM_POST_START: call_pre_post = 0
UVM_ENDED comes before the parent's post_do and the post hooks. UVM_FINISHED and UVM_STOPPED are the two ends.
get_sequence_state() returns the current box. wait_for_sequence_state(mask) blocks until the state is one in the mask, which may be a bitwise or of several. The example starts L in a fork and, in the other branch, waits for UVM_BODY: it prints L is in state UVM_BODY at 20 ns, when L's body began. A test can wait for a background sequence to reach its main loop, or ask whether a sequence finished or was stopped, without a flag of its own.
Wait for both ends. A killed sequence never reaches UVM_FINISHED, so wait_for_sequence_state(UVM_FINISHED) blocks forever; measured here, it was still blocked 100 ns after the kill with the state UVM_STOPPED. wait_for_sequence_state(UVM_FINISHED | UVM_STOPPED) returns at once.
Stopping a sequence
- kill() on one sequence45 ns (illustrative)
kill()asks the sequencer to remove L's requests and locks, runsdo_kill, kills L's children and body, and setsUVM_STOPPED. L2 stays in the request FIFO.More context
Log lines
45 ns L do_killand45 ns L killed; state UVM_STOPPED. L2 has been in the driver since 40 ns; nothing tells the driver about the kill.Follow the calls, values and waits
- call kill(): Test → Sequence L
- Waiting: Driver — still working on L2
- The driver finishes quietly50 ns (illustrative)
The driver's
item_donefinds L2 in the FIFO and completes. Its response finds no sequence and is dropped with a warning.More context
The warning reads
UVM_WARNING @ 50: ... Dropping response for sequence 4, sequence not found. The run still ends with 0 errors.Follow the calls, values and waits
- call item_done(): Driver → Request FIFO
- Released: Driver
- Had the test called stop_sequences()
stop_sequences()kills every sequence on the sequencer and also flushes the request FIFO under the busy driver.More context
An alternative to the first two steps, not a continuation. The driver is not told; it keeps working on the item it already took.
Follow the calls, values and waits
- call stop_sequences(): Test → Arbitration queue
- call flush the FIFO: Arbitration queue → Request FIFO
- Waiting: Driver — still holds its item; the FIFO is empty
- The next item_done is fatal
The driver's next
item_donefinds nothing outstanding and ends the run with fatalSQRBADITMDN, as the second quiz question shows.More context
Measured in the quiz bench: stopping at 35 ns with the fourth item in the driver ends the run at 40 ns with fatal SQRBADITMDN.
Follow the calls, values and waits
- call item_done(): Driver → Request FIFO
- Released: Driver
Read the diagram as text
- Test: Calls the stop
- Sequence L: Body in UVM_BODY
- Arbitration queue: L's waiting requests and locks
- Request FIFO: Holds L2 until item_done
- Driver: Working on L2 until 50 ns
- Test → Sequence L: seq.kill()
- Test → Arbitration queue: sqr.stop_sequences()
- Sequence L → Arbitration queue: requests
- Request FIFO → Driver: L2
- Arbitration queue → Request FIFO: flush
A kill removes one sequence's queued work and leaves the driver's item alone. stop_sequences also empties the FIFO under a busy driver.
Neither call checks whether the driver is in the middle of an item, which is why the Cookbook says a started sequence should not be stopped at all. If you must stop, make sure the driver is idle first, or kill the one sequence and let the driver finish.
| After the call | seq.kill() | sqr.stop_sequences() |
|---|---|---|
| Sequence state | UVM_STOPPED, do_kill ran | Every sequence UVM_STOPPED |
| Queued requests and locks | Removed for this sequence and its children | Removed for all |
| Item the driver already holds | Stays in the request FIFO; the driver's item_done completes | Flushed; the driver's next item_done is a fatal SQRBADITMDN |
| Other sequences | Untouched | All killed |
For the driver shown here: get_next_item and item_done. The third row is the one that bites.
Killing is not undoing. The item in the driver still reaches the design, so a register it wrote stays written, and a scoreboard that predicted from the sequence still expects a result. post_start does not run, so an objection raised by hand in pre_start must be dropped in do_kill, or raised through set_automatic_phase_objection(1), which kill drops. kill also removes the sequence's queued requests, locks and grabs from every other sequencer it used, so a coordinator killed while holding a grab on another lane releases it. Anything else it changed, such as a configuration field or a register, stays changed.
The guard that kills an orphaned sequence
start() installs a guard on the process that called it. If that process dies without killing the sequence, the guard fires, the sequence reports warning SEQPRTZMB and calls kill() on itself. If the process that runs the body is terminated while start itself survives, for example by an errant disable fork, start reports warning SEQBDYZMB and kills the sequence. The sequencer has separate reports for queued requests whose process died, SEQREQZMB and SEQLCKZMB. Rely on these only for debugging.
Responses that nobody reads
A response travels back by sequence ID into that sequence's response queue (U4.2), and the queue belongs to the sequence object. Three lifecycle rules follow.
start clears it
Every
startempties the queue, so responses from an earlier run of the same object are gone.Eight, then silence
The queue holds 8 responses by default. Measured here: a sequence sends 12 items, the driver answers each, and the sequence reads only after the last; it gets 8, and the 4 overflows vanish without a report. With
set_response_queue_error_report_enabled(1)each overflow is aUVM_ERROR.After a kill, a warning
A response that arrives after its sequence was killed finds no sequence and is dropped with warning
[Sequencer] Dropping response ... sequence exited or has been killed. The example shows it at 50 ns, for L2.
Read responses as they arrive, or raise the depth with set_response_queue_depth(-1), or use use_response_handler(1) so the sequencer calls a function instead of queuing.
Try it
Three sequences on one driver that takes 10 ns per item and returns a response for each. R shows the full hook order for a root sequence. P starts K with call_pre_post = 0, so K has no pre_body or post_body, while P's pre_do(is_item=0), mid_do and post_do wrap the child. L is killed at 45 ns with its third item in the driver; do_kill runs, post_body does not, and the driver finishes the item at 50 ns. That item's response is dropped with the warning, because L is no longer registered.
Change k.start(m_sequencer, this, -1, 0) to k.start(m_sequencer, this) and K's pre_body and post_body reappear. Then replace l.kill() with sqr.stop_sequences() and predict the driver's next item_done before running it.
Transfer exercise: L raised an objection by hand in pre_start and drops it in post_start. Write the do_kill that keeps the test from hanging after the kill, and say which state a waiting test should accept.
--- R: one item, started from the test 0 ns R pre_start 0 ns R pre_body 0 ns R pre_do(is_item=1) 0 ns R mid_do 10 ns R post_do 10 ns R finish_item 0 returned 10 ns R post_body 10 ns R post_start --- P: starts child K with call_pre_post = 0 10 ns P pre_start 10 ns P pre_body 10 ns K pre_start 10 ns P pre_do(is_item=0) 10 ns P mid_do 10 ns K pre_do(is_item=1) 10 ns K mid_do 20 ns K post_do 20 ns K finish_item 0 returned 20 ns P post_do 20 ns K post_start 20 ns P post_body 20 ns P post_start --- L: five items, killed 25 ns into its body; state UVM_CREATED before start 20 ns L pre_start 20 ns L pre_body 20 ns L is in state UVM_BODY 40 ns L finish_item 1 returned 40 ns L pre_do(is_item=1) 40 ns L mid_do 45 ns L do_kill 45 ns L killed; state UVM_STOPPED 45 ns L start() returned; state UVM_STOPPED; R is UVM_FINISHED UVM_WARNING @ 50: uvm_test_top.sqr [Sequencer] Dropping response for sequence 4, sequence not found. Probable cause: sequence exited or has been killed UVM_ERROR : 0Check yourself
Remember
startrunspre_start,pre_body,body,post_body,post_start;call_pre_post = 0skips the two body hooks, so put what every start needs inpre_start.- Each item passes
pre_doafter the grant,mid_dojust before it is sent andpost_doafteritem_done; a parent's three hooks also wrap each child it starts, withis_item = 0. - The state ends in
UVM_FINISHEDor, after a kill,UVM_STOPPED; wait for both. kill()stops one sequence (do_kill, nopost_bodyorpost_start) and leaves the driver's item to finish;stop_sequences()also flushes the request FIFO, so a driver mid-item then hits a fatalitem_done.- A response queue holds 8 by default and drops overflows silently;
startclears it, and a response after a kill is dropped with a warning.
Practice
- A background sequence that leaves cleanly when killed Implement · Easy