Every stimulus pattern in a UVM testbench, from a single register write to a full traffic scenario, runs because something called start() on a sequence. Knowing exactly what start() does, and which hooks it calls, is what lets you build layered sequences, add setup and cleanup code, and control priority when several sequences compete for one driver.

A UVM sequence is executed by calling its start() task with the sequencer it should run on. start() then calls a fixed series of hooks: pre_start(), optionally pre_body(), the sequence's own body(), optionally post_body(), and finally post_start(). If a parent sequence is named, the parent's pre_do(), mid_do() and post_do() are called around the child's body() as well.

8 min read | Intermediate Level

What You'll Learn

  • Call start() with the right sequencer, parent, priority and call_pre_post arguments
  • Predict the order in which start() calls each hook
  • Explain what call_pre_post controls
  • Use the parent argument when one sequence starts another

Syntax


virtual task start (uvm_sequencer_base sequencer,
                    uvm_sequence_base  parent_sequence = null,
                    int                this_priority   = -1,
                    bit                call_pre_post   = 1);
  • sequencer - the sequencer the sequence runs on. A sequence that sends items must be given a sequencer. A virtual sequence, which only starts other sequences, may be started with null.
  • parent_sequence - the sequence that is starting this one, if any. When given, the parent's pre_do(), mid_do() and post_do() hooks are called during this sequence's execution.
  • this_priority - the arbitration priority. The default of -1 means "use the parent's priority", or 100 for a sequence with no parent. Higher numbers mean higher priority.
  • call_pre_post - when 1 (the default), pre_body() and post_body() are called around body().

start() is a task and returns only when the sequence has finished. A typical call from a test looks like this:


seq = my_seq::type_id::create("seq");
if (!seq.randomize()) `uvm_error("TEST", "Randomization failed")   // optional
seq.start(m_env.m_agent.m_sequencer);

The Order of Hooks

pre_start() and post_start() always run. pre_body() and post_body() run only when call_pre_post is 1. The parent's pre_do(), mid_do() and post_do() run only when a parent sequence was passed in, and they are called on the parent, not on the sequence being started.

Example: call_pre_post

This base sequence prints a message from each hook so the execution order shows up in the log.


class base_seq extends uvm_sequence;
  // Rest of the code (factory registration and constructor)

  virtual task pre_body();
    `uvm_info(get_type_name(), "pre_body()", UVM_LOW)
  endtask

  virtual task pre_do(bit is_item);
    `uvm_info(get_type_name(), "pre_do()", UVM_LOW)
  endtask

  virtual function void mid_do(uvm_sequence_item this_item);
    `uvm_info(get_type_name(), "mid_do()", UVM_LOW)
  endfunction

  virtual task body();
    `uvm_info(get_type_name(), "body()", UVM_LOW)
  endtask

  virtual function void post_do(uvm_sequence_item this_item);
    `uvm_info(get_type_name(), "post_do()", UVM_LOW)
  endfunction

  virtual task post_body();
    `uvm_info(get_type_name(), "post_body()", UVM_LOW)
  endtask
endclass

The test starts it with the default arguments. No sequencer is needed because this sequence sends no items.


class base_test extends uvm_test;
  // Rest of the code

  virtual task run_phase(uvm_phase phase);
    base_seq bs = base_seq::type_id::create("bs", this);
    bs.start(null);                 // call_pre_post defaults to 1
  endtask
endclass
Output
UVM_INFO @ 0: reporter [RNTST] Running test base_test...
UVM_INFO ./tb/tb_top.sv(19) @ 0: reporter@@bs [base_seq] pre_body()
UVM_INFO ./tb/tb_top.sv(31) @ 0: reporter@@bs [base_seq] body()
UVM_INFO ./tb/tb_top.sv(39) @ 0: reporter@@bs [base_seq] post_body()

--- UVM Report catcher Summary ---

The prefix reporter@@bs shows the sequence ran with no sequencer. pre_do(), mid_do() and post_do() do not appear, because there is no parent. Starting it with call_pre_post set to 0 removes pre_body() and post_body() as well:


bs.start(null, null, 0, 0);         // no parent, priority 0, call_pre_post = 0
Output
UVM_INFO @ 0: reporter [RNTST] Running test base_test...
UVM_INFO ./tb/tb_top.sv(31) @ 0: reporter@@bs [base_seq] body()

--- UVM Report catcher Summary ---

Inheritance Is Not the Parent Argument

The word "parent" is easy to confuse with class inheritance. If child1_seq extends base_seq and the test starts it with cs.start(null), there is still no parent sequence, so its pre_do(), mid_do() and post_do() are never called. The parent argument refers only to the sequence that starts another one at run time.

Example: One Sequence Starting Another

Now child1_seq starts child2_seq from its body(). Both extend base_seq and print from every hook. First, the child is started without a parent:


class child1_seq extends base_seq;
  // Rest of the code (registration, constructor, other hooks as in base_seq)

  virtual task body();
    child2_seq cs2 = child2_seq::type_id::create("cs2");
    `uvm_info(get_type_name(), "body()", UVM_LOW)
    cs2.start(null);                // parent argument left at its default, null
  endtask
endclass
Output
UVM_INFO @ 0: reporter [RNTST] Running test child1_test...
UVM_INFO ./tb/tb_top.sv(52) @ 0: reporter@@cs [child1_seq] pre_body()
UVM_INFO ./tb/tb_top.sv(65) @ 0: reporter@@cs [child1_seq] body()
UVM_INFO ./tb/tb_top.sv(87) @ 0: reporter@@cs2 [child2_seq] pre_body()
UVM_INFO ./tb/tb_top.sv(99) @ 0: reporter@@cs2 [child2_seq] body()
UVM_INFO ./tb/tb_top.sv(107) @ 0: reporter@@cs2 [child2_seq] post_body()
UVM_INFO ./tb/tb_top.sv(76) @ 0: reporter@@cs [child1_seq] post_body()

--- UVM Report catcher Summary ---

cs2 runs completely inside cs's body(), and cs resumes afterwards. Its name in the log is just cs2, because without a parent it is a root sequence. Passing this as the parent changes both of those things:


cs2.start(null, this);              // child1_seq is now cs2's parent sequence
Output
UVM_INFO @ 0: reporter [RNTST] Running test child1_test...
UVM_INFO ./tb/tb_top.sv(52) @ 0: reporter@@cs [child1_seq] pre_body()
UVM_INFO ./tb/tb_top.sv(65) @ 0: reporter@@cs [child1_seq] body()
UVM_INFO ./tb/tb_top.sv(87) @ 0: reporter@@cs.cs2 [child2_seq] pre_body()
UVM_INFO ./tb/tb_top.sv(56) @ 0: reporter@@cs [child1_seq] pre_do()
UVM_INFO ./tb/tb_top.sv(60) @ 0: reporter@@cs [child1_seq] mid_do()
UVM_INFO ./tb/tb_top.sv(99) @ 0: reporter@@cs.cs2 [child2_seq] body()
UVM_INFO ./tb/tb_top.sv(72) @ 0: reporter@@cs [child1_seq] post_do()
UVM_INFO ./tb/tb_top.sv(107) @ 0: reporter@@cs.cs2 [child2_seq] post_body()
UVM_INFO ./tb/tb_top.sv(76) @ 0: reporter@@cs [child1_seq] post_body()

--- UVM Report catcher Summary ---

Three things changed. The child's name is now cs.cs2, showing its place under the parent. The parent's pre_do() and mid_do() run after the child's pre_body() and before its body(), and the parent's post_do() runs right after the child's body(). That matches the order in the diagram exactly.

Quick Check

Q: A parent sequence wants to count how many child sequences it has started. Which of its own hooks is a good place for the counter?

Show Answer

mid_do() or post_do(). Both run on the parent once per child started with the parent passed in, and they receive the child as an argument. They do not run for children started without a parent, so the parent argument must be given.

Priority and Arbitration

The this_priority argument matters only when two or more sequences run on the same sequencer at the same time and both want to send an item. The sequencer then has to pick one, and how it picks is set by its arbitration mode, chosen with set_arbitration().

The default mode, UVM_SEQ_ARB_FIFO, grants requests in the order they arrived and ignores priority completely. To make priority count, switch to a mode such as UVM_SEQ_ARB_STRICT_FIFO, which always serves the highest-priority request first, or UVM_SEQ_ARB_WEIGHTED, which picks randomly with priority as the weight.


m_env.m_agent.m_sequencer.set_arbitration(UVM_SEQ_ARB_STRICT_FIFO);
fork
  bg_seq.start(m_env.m_agent.m_sequencer, null, 100);    // background traffic
  irq_seq.start(m_env.m_agent.m_sequencer, null, 500);   // wins whenever both wait
join

Here irq_seq gets the driver whenever both sequences are waiting, while bg_seq fills the gaps. Without the set_arbitration() call, the two priorities would have no effect.

Where to Use Each Hook

  • pre_start() and post_start() always run, which makes them the place for code that must happen whichever way the sequence is started.
  • pre_body() and post_body() suit setup and cleanup that should run only for top-level starts, since the `uvm_do macros start child sequences with call_pre_post set to 0.
  • pre_do(), mid_do() and post_do() let a parent observe or adjust each item or child sequence it runs, for example to record statistics.

Common Mistakes

Mistake #1: Starting an item-sending sequence with a null sequencer

A sequence whose body() calls start_item() needs a sequencer to send items through. Starting it with null works only for virtual sequences that start other sequences on real sequencers.

Mistake #2: Expecting pre_body() to run under `uvm_do

Sequences started with the `uvm_do family run with call_pre_post set to 0, so their pre_body() and post_body() are silently skipped. Put code that must always run in pre_start() and post_start().

Mistake #3: Calling start() without an objection

Starting a sequence from run_phase does not keep the phase alive by itself. Raise an objection before start() and drop it after, as described in UVM Objection.

Summary

In this article, you learned:

  • start() runs a sequence on a sequencer and returns when body() and all hooks have finished
  • pre_start() and post_start() always run, while pre_body() and post_body() depend on call_pre_post
  • Passing a parent sequence makes the parent's pre_do(), mid_do() and post_do() run around the child's body()
  • The default priority is the parent's, or 100 for a root sequence
  • The parent argument is about run-time nesting, not class inheritance
Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5