Use of sequence macros give the added advantage of inline constraints, however you lose the ability to control the invocation of pre_body and post_body methods in the executed sequence. It reduces the number of lines in our code by creating the item, randomizing it and automatically calling the required tasks to start the given sequence or sequence item.

In a previous article, we learned that all `uvm_do macros ultimately call the code defined in `uvm_do_on_pri_with. So, let's see how that is structured within UVM to get better clarity behind the process.

`define uvm_do_on_pri_with(SEQ_OR_ITEM, SEQR, PRIORITY, CONSTRAINTS)     
  uvm_sequence_base __seq;                                               
  `uvm_create_on(SEQ_OR_ITEM, SEQR)                                      
  if (!$cast(__seq,SEQ_OR_ITEM)) start_item(SEQ_OR_ITEM, PRIORITY);      
  if ((__seq == null || !__seq.do_not_randomize) && !SEQ_OR_ITEM.randomize() with CONSTRAINTS ) begin 
    `uvm_warning("RNDFLD", "Randomization failed in uvm_do_with action") 
  if (!$cast(__seq,SEQ_OR_ITEM)) finish_item(SEQ_OR_ITEM, PRIORITY);     
  else __seq.start(SEQR, this, PRIORITY, 0);                             

If the object passed to this macro is a sequence item, then it creates the item and starts executing the item with start_item method call. The item will be randomized and end with finish_item method call. If the object __seq is not a sequence item, then start method of the sequence is called.

Note that call_pre_post field of the start method is set to 0 which means pre_body and post_body methods of a sequence will never be called when using these sequence macros. Execution flow is otherwise similar to when sequence is executed by the start method.

flow of sequences as shown in example


Let's take the same code we saw while using start(), and now call `uvm_do_pri_with instead. Below is the base_sequence from which three child sequences (seq1, seq2, seq3) are derived.

class base_sequence extends uvm_sequence #(my_data);
   `uvm_object_utils (base_sequence)
   `uvm_declare_p_sequencer (my_sequencer)

   function new (string name = "base_sequence"); (name);

   virtual function void mid_do (uvm_sequence_item this_item);
      `uvm_info (get_type_name (), "Executing mid_do", UVM_MEDIUM)

   virtual task pre_do (bit is_item);
      `uvm_info (get_type_name (), "Executing pre_do", UVM_MEDIUM)

   virtual function void post_do (uvm_sequence_item this_item);
      `uvm_info (get_type_name (), "Executing post_do", UVM_MEDIUM)

   virtual task body ();
      starting_phase.raise_objection (this);
      `uvm_info ("BASE_SEQ", $sformatf ("Starting body of %s", this.get_name()), UVM_MEDIUM)
      `uvm_info ("BASE_SEQ", $sformatf ("Sequence %s is over", this.get_name()), UVM_MEDIUM)
      starting_phase.drop_objection (this);

In seq1 below, notice that we have used `uvm_do_pri_with macro with empty constraints and no priority. We expect this to call the stat() method of seq2, and skip the pre_body( ) and post_body( ) tasks.

class seq1 extends base_sequence;
   `uvm_object_utils (seq1)

   seq2 m_seq2;
   virtual task pre_start ();
      `uvm_info (get_type_name (), "Executing pre_start()", UVM_MEDIUM)

   function new (string name = "seq1"); (name);

   virtual task body ();
      starting_phase.raise_objection (this);
      m_seq2 = seq2::type_id::create ("m_seq2");
      `uvm_info ("SEQ1", "Starting seq1", UVM_MEDIUM)
      `uvm_do_pri_with (m_seq2, ,{})
      `uvm_info ("SEQ1", "Ending seq1", UVM_MEDIUM)
      starting_phase.drop_objection (this);


In seq2, we kick off another sequence (seq3) using the same macro.

class seq2 extends base_sequence;
   `uvm_object_utils (seq2)

   seq3 m_seq3;

   function new (string name = "seq2"); (name);

   virtual task pre_body ();
      `uvm_info (get_type_name(), "Executing pre_body", UVM_MEDIUM)

   virtual task body ();
      m_seq3 = seq3::type_id::create ("m_seq3");
      `uvm_info ("SEQ2", "Starting seq2", UVM_MEDIUM)
      `uvm_do_pri_with (m_seq3, ,{})
      `uvm_info ("SEQ2", "Ending seq2", UVM_MEDIUM)

   virtual task post_body ();
      `uvm_info (get_type_name(), "Executing post_body", UVM_MEDIUM)


In our final sequence, we don't do anything.

class seq3 extends base_sequence;
   `uvm_object_utils (seq3)

   function new (string name = "seq3"); (name);

   virtual task pre_body ();
      `uvm_info (get_type_name(), "Executing pre_body", UVM_MEDIUM)

   virtual task body ();
      `uvm_info ("SEQ3", "Starting seq3", UVM_MEDIUM)
      `uvm_info ("SEQ3", "Ending seq3", UVM_MEDIUM)

   virtual task post_body ();
      `uvm_info (get_type_name(), "Executing post_body", UVM_MEDIUM)

   virtual task post_start ();
      `uvm_info (get_type_name (), "Executing post_start", UVM_MEDIUM)

The simulation results might look a bit fuzzy. The important thing to note is that the pre_do/mid_do methods are called before the body( ) task is called. Let's do a simple dry run.

- SEQ1 : pre_start()  
- go to the body() of SEQ1
- Display "[SEQ1] Starting seq1"
- Start SEQ2                                // there's nothing in pre_start() and pre_body() is not called
- Call parent's (SEQ1) pre_do
- Display "[seq1] Executing pre_do"
- Call parent's (SEQ1) mid_do
- Display "[seq1] Executing mid_do"
- Call body() of SEQ2 
- Display "[SEQ2] Starting seq2"
- Start SEQ3 
 Simulation Log
CDNS-UVM-1.1d (14.10-s013)
(C) 2007-2013 Mentor Graphics Corporation
(C) 2007-2013 Cadence Design Systems, Inc.
(C) 2006-2013 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
UVM_INFO @ 0: reporter [RNTST] Running test base_test...
UVM_INFO @ 0: reporter [UVMTOP] UVM testbench topology:
Name                     Type                    Size  Value
uvm_test_top             base_test               -     @2644
  m_top_env              my_env                  -     @214
    m_drv0               my_driver               -     @208
      rsp_port           uvm_analysis_port       -     @2843
      seq_item_port      uvm_seq_item_pull_port  -     @2792
    m_seqr0              my_sequencer            -     @2743
      rsp_export         uvm_analysis_export     -     @2933
      seq_item_export    uvm_seq_item_pull_imp   -     @3481
      arbitration_queue  array                   0     -
      lock_queue         array                   0     -
      num_last_reqs      integral                32    'd1
      num_last_rsps      integral                32    'd1

UVM_INFO ./tb/ @ 0: uvm_test_top.m_top_env.m_drv0 [my_driver] Applying initial reset
UVM_INFO ./tb/ @ 390000: uvm_test_top.m_top_env.m_drv0 [my_driver] DUT is now out of reset
UVM_INFO ./tb/ @ 390000: uvm_test_top.m_top_env.m_drv0 [my_driver] Waiting for data from sequencer
UVM_INFO ./tb/ @ 390000: uvm_test_top.m_top_env.m_seqr0@@seq1 [seq1] Executing pre_start()
UVM_INFO ./tb/ @ 390000: uvm_test_top.m_top_env.m_seqr0@@seq1 [SEQ1] Starting seq1
UVM_INFO ./tb/ @ 400000: uvm_test_top.m_top_env.m_seqr0@@seq1 [seq1] Executing pre_do
UVM_INFO ./tb/ @ 400000: uvm_test_top.m_top_env.m_seqr0@@seq1 [seq1] Executing mid_do
UVM_INFO ./tb/ @ 400000: uvm_test_top.m_top_env.m_seqr0@@seq1.m_seq2 [SEQ2] Starting seq2
UVM_INFO ./tb/ @ 410000: uvm_test_top.m_top_env.m_seqr0@@seq1.m_seq2 [seq2] Executing pre_do
UVM_INFO ./tb/ @ 410000: uvm_test_top.m_top_env.m_seqr0@@seq1.m_seq2 [seq2] Executing mid_do
UVM_INFO ./tb/ @ 410000: uvm_test_top.m_top_env.m_seqr0@@seq1.m_seq2.m_seq3 [SEQ3] Starting seq3
UVM_INFO ./tb/ @ 420000: uvm_test_top.m_top_env.m_seqr0@@seq1.m_seq2.m_seq3 [SEQ3] Ending seq3
UVM_INFO ./tb/ @ 420000: uvm_test_top.m_top_env.m_seqr0@@seq1.m_seq2 [seq2] Executing post_do
UVM_INFO ./tb/ @ 420000: uvm_test_top.m_top_env.m_seqr0@@seq1.m_seq2.m_seq3 [seq3] Executing post_start
UVM_INFO ./tb/ @ 420000: uvm_test_top.m_top_env.m_seqr0@@seq1.m_seq2 [SEQ2] Ending seq2
UVM_INFO ./tb/ @ 420000: uvm_test_top.m_top_env.m_seqr0@@seq1 [seq1] Executing post_do
UVM_INFO ./tb/ @ 420000: uvm_test_top.m_top_env.m_seqr0@@seq1 [SEQ1] Ending seq1
UVM_INFO ./tb/ @ 420000: uvm_test_top.m_top_env.m_drv0 [my_driver] Finished DUT simulation

--- UVM Report catcher Summary ---