
virtual task start ( uvm_sequencer_base sequencer,
uvm_sequence_base parent_sequence = null,
int this_priority = -1,
bit call_pre_post = 1 );
Note that you have to always pass the handle to a sequencer which should execute this sequence, whereas the other arguments are optional. parent_sequence is often referred to the sequence from which the current one was called. this_priority specifies a priority for the sequence (by default it assumes priority of the parent sequence), and can be ignored for now. call_pre_post will call the pre_body()
and post_body()
of the current sequence. Here's an example of how the start()
function has to be used, and what happens in the background.
seq.randomize (...); // optional
seq.start (m_sequencer, null, , 1);
// The following methods will be called in start()
seq.pre_start(); (task)
seq.pre_body(); (task) if call_pre_post == 1
parent_seq.pre_do() (task) if parent_seq != null
parent_seq.mid_do(this) (func) if parent_seq != null
seq.body() (task) your code
parent_seq.post_do(this) (func) if parent_seq != null
seq.post_body() (task) if call_pre_post == 1
sub_seq.post_start() (task)

Simple sequence flow
To understand the sequence flow better, let us construct a simple sequence with a print statement in each of the above mentioned methods. First we'll try to understand how call_pre_post
argument affects the output.
// Let's just declare a base sequence from which we can have a child sequence
// This will help to prove execution order of pre_do, mid_do and post_do tasks
class base_seq extends uvm_sequence;
// Factory registration and new function is assumed to be written next
...
// For every method during the body() execution flow, we'll simply add a display statement to track
// how each method in this sequence is getting executed
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
Within our test case, we'll call the start
method of our sequence base_seq. Note that the default value of call_pre_post
argument of the start
method is 1 and hence the pre_body
and post_body
tasks of that sequence will be called.
// Define a base test that will launch our base sequence
class base_test extends uvm_test;
...
// Instantiate the base_seq and start it on default sequencer
virtual task run_phase (uvm_phase phase);
base_seq bs = base_seq::type_id::create ("bs", this);
bs.start (null); // Default value of call_pre_post is 1
endtask
endclass
As expected, the sequence executes the pre_body
and post_body
tasks in addition to the body
method.
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 ---
Now, let's supply 0 to call_pre_post
argument and see that pre_body
and post_body
methods are not executed.
class base_test extends uvm_test;
...
// Call the same sequence with call_pre_post argument set to 0
// This will show the difference in result compared to previous example
virtual task run_phase (uvm_phase phase);
base_seq bs = base_seq::type_id::create ("bs", this);
bs.start (null, null, 0, 0); // call_pre_post is given 0
endtask
endclass
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 ---
Takeaway: if call_pre_post
is 1, then pre_body
and post_body
methods of the started sequence will be executed.
Inherited sequence flow
The second argument to start
method is parent which might cause a confusion regarding the term parent used in inheritance versus the parent sequence that starts the main sequence. To try out an example, we'll create a child sequence of base_seq with a print statement in each of its methods and spawn that from the test case.
// The child sequence will have its own pre_body task to see pre_body() method of which parent is called
class child1_seq extends base_seq;
// Again, to track the message lets just print this into the log
virtual task pre_body();
`uvm_info (get_type_name(), "pre_body()", UVM_LOW)
endtask
// Definition of all other methods like base class should come here
endclass
class base_test extends uvm_test;
...
// Start the child sequence with the default value of call_pre_post
virtual task run_phase (uvm_phase phase);
child1_seq cs = child1_seq::type_id::create ("cs", this);
cs.start (null); // Default value of call_pre_post is 1
endtask
endclass
In this case also, we get a similar result as before where pre_body
and post_body
methods are invoked because of the default value of call_pre_post
argument in the start method. So it is clear that parent argument simply specifies the parent sequence from where the main sequence is started.
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 ---
Spawned sequence flow
Here, we'll start another sequence called child2_seq from the body
method in child1_seq with rest of the code being the same. This example will demonstrate how the parent argument in start
method can be used.
class child2_seq extends base_seq;
...
virtual task pre_body();
`uvm_info (get_type_name(), "pre_body()", UVM_LOW)
endtask
// Definition of all other methods like base class should come here
endclass
class child1_seq extends base_seq;
...
virtual task body();
child2_seq cs = child2_seq::type_id::create ("cs");
cs.start (null); // parent arg is by default null
endtask
...
endclass
It can be seen that child2_seq also executed its own methods after which child1_seq execution is resumed.
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 ---
Now we'll supply the pointer to child1_seq as an argument to the parent
of the start method in child2_seq.
class child1_seq extends base_seq;
...
virtual task body();
child2_seq cs = child2_seq::type_id::create ("cs");
cs.start (null, this); // Give handle of current seq as parent to child2
endtask
...
endclass
Now it can be seen that pre_do
, mid_do
and post_do
methods of the parent sequence child1_seq has been called.
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 ---