Sequence Execution & Macros
The Sequencer & Sequence covered the mechanics every sequence in this section has used since: start_item()/finish_item(), the get_next_item()/item_done() handshake on the driver side, and starting a sequence with seq.start(sequencer). This page covers two things that build directly on top of that foundation: everything start() actually accepts beyond just a sequencer, and the `uvm_do macro family — a shorthand that collapses the create-randomize-send pattern every sequence in this section has hand-written into a single line.
start()'s full signature
Every call to start() so far has used just one argument — the sequencer — but the full signature has three more, all defaulted:
task start(uvm_sequencer_base sequencer,
uvm_sequence_base parent_sequence = null,
int this_priority = -1,
bit call_pre_post = 1);
parent_sequence— when one sequence starts another from inside its ownbody()(a sequence generating sub-sequences of stimulus), passing itself as the parent means the parent'spre_do()/mid_do()/post_do()hooks wrap the child's execution. Leftnull(the default), a sequence runs as its own independent top-level sequence, exactly as every sequence in this section has so far.this_priority— passed through to the sequencer's arbitration when more than one sequence is competing for the same driver;-1(the default) means "no explicit priority."call_pre_post— when1(the default),pre_body()/post_body()run aroundbody(), in addition tobody()itself. Setting it to0skips them, running onlybody()— this is exactly what the`uvm_domacros do internally for a sequence argument (as opposed to a plain item), since a sub-sequence invoked via a macro isn't meant to be treated as its own independent top-level unit.
The `uvm_do macro family
Every sequence in this section has written the same three-step pattern by hand: create() the item, start_item()/randomize/finish_item(). The `uvm_do family collapses that into one line:
task body();
repeat (4) `uvm_do(req)
endtask
This one line does exactly what The Sequencer & Sequence's hand-written loop did: creates a new item via the factory, randomizes it, and sends it through start_item()/finish_item(). req here is a handle UVM's base uvm_sequence #(REQ, RSP) class already declares for you, matching the sequence's own parameterized type — no separate local variable declaration needed, unlike the hand-written version.
The full family, in increasing order of what each variant adds:
| Macro | Adds |
|---|---|
`uvm_create(item) | Factory-creates item only — no randomization, no send |
`uvm_send(item) | Sends an already-created item — no randomization, no creation |
`uvm_do(item) | Create + randomize + send, in one call |
`uvm_do_with(item, {constraints}) | Same as `uvm_do, with inline randomization constraints |
`uvm_do_pri(item, priority) | Same as `uvm_do, with an explicit sequencer priority |
`uvm_do_on(item, sequencer) | Same as `uvm_do, targeting a specific sequencer instead of the default one |
Every longer-named variant ( `uvm_do_on_pri_with, and so on) is a combination of the same three axes — which sequencer, what priority, what constraints — collapsing to the plain `uvm_do form when none of those need to be specified. `uvm_create/ `uvm_send exist as the decomposed building blocks for cases that need creation and sending as two separate steps — most commonly, when a field needs to be set by hand between creating an item and sending it, something `uvm_do itself doesn't leave room for.
One more variant sits between those two extremes: `uvm_rand_send(item). Where `uvm_send sends an already-created item with no randomization at all, `uvm_rand_send randomizes and sends an already-created item — it skips only the creation step. That makes it the right choice when a handle was created once (via `uvm_create) and needs a fresh set of random values on a later send, without allocating a new object each time. `uvm_rand_send_with(item, {constraints}) is its inline-constraint counterpart, the same way `uvm_do_with relates to `uvm_do.
fifo_txn item;
`uvm_create(item)
item.op = fifo_txn::WRITE; // set a field directly, bypassing randomize() for this one
`uvm_send(item)
`uvm_do re-randomizes every time it's calledCalling `uvm_do repeatedly on the same already-created object re-randomizes it on every call — it never skips randomization just because an object happens to already exist. If a field was deliberately set by hand and needs to survive unrandomized, use `uvm_send (which never randomizes at all) rather than calling `uvm_do again on the same handle.
do_not_randomizeIf an item's do_not_randomize bit is set, `uvm_do/ `uvm_do_with still attempt randomization internally — the attempt doesn't error out, but produces an "RNDFLD" warning in the log rather than halting simulation. A test that looks like it's silently ignoring constraints is worth checking for this warning specifically before assuming the constraint logic itself is wrong.
What's next
Every sequence so far has targeted exactly one sequencer. The next page covers coordinating stimulus across several sequencers at once — virtual sequences and virtual sequencers.