Skip to main content

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 own body() (a sequence generating sub-sequences of stimulus), passing itself as the parent means the parent's pre_do()/mid_do()/post_do() hooks wrap the child's execution. Left null (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 — when 1 (the default), pre_body()/post_body() run around body(), in addition to body() itself. Setting it to 0 skips them, running only body() — this is exactly what the `uvm_do macros 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:

MacroAdds
`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 called

Calling `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.

A quieter gotcha: do_not_randomize

If 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.