Sequences are made up of several data items, which may form an interesting scenario. For example, you can have a sequence that performs register read/writes to all the registers within the design, a sequence to perform reset, or another one to apply some stimulus to the DUT. So, you'll end up having a number of different sequences that perform different tasks to verify different aspects of the design. Remember that a sequence item refers to a packet of data, while a sequence is just a container for an arrangement of items/sub-sequences. Now, you have a few options :
- Use existing sequences to drive stimulus to the DUT individually
- Combine existing sequences to create new ones - perform reset sequence followed by register read/writes followed by FSM state change sequence
- Pull random sequences from the sequence library and execute them on the DUT

Sequences can do operations on sequence items, or kick-off new sub-subsequences:
- Execute using the
start()
method of a sequence or`uvm_do
macros - Execute sequence items via
start_item/finish_item
or `uvm_do macros
`uvm_do
macros will identify if the argument is a sequence or sequence_item and will call start()
or start_item()
accordingly.

- Derive from
uvm_sequence
base class with a specified data object type - Register the sequence with the factory using
`uvm_object_utils
- Set the default sequencer that should execute this sequence
- Code the main stimulus inside the
body()
task
Let's look at an example next.
class base_sequence extends uvm_sequence #(my_data);
`uvm_object_utils (base_sequence)
`uvm_declare_p_sequencer (my_sequencer)
my_data data_obj;
int unsigned n_times = 1;
function new (string name = "base_sequence");
super.new (name);
endfunction
virtual task pre_body ();
`uvm_info ("BASE_SEQ", $sformatf ("Optional code can be placed here in pre_body()"), UVM_MEDIUM)
if (starting_phase != null)
starting_phase.raise_objection (this);
endtask
virtual task body ();
`uvm_info ("BASE_SEQ", $sformatf ("Starting body of %s", this.get_name()), UVM_MEDIUM)
data_obj = my_data::type_id::create ("data_obj");
repeat (n_times) begin
start_item (data_obj);
assert (data_obj.randomize ());
finish_item (data_obj);
end
`uvm_info (get_type_name (), $sformatf ("Sequence %s is over", this.get_name()), UVM_MEDIUM)
endtask
virtual task post_body ();
`uvm_info ("BASE_SEQ", $sformatf ("Optional code can be placed here in post_body()"), UVM_MEDIUM)
if (starting_phase != null)
starting_phase.drop_objection (this);
endtask
endclass
Note the following from the example above:base_sequence
has been derived fromuvm_sequence
with a data object typemy_data
- This sequence is specified to execute with
my_sequencer
using the macro`uvm_declare_p_sequencer
- Main task
body()
contains the code to drive the stimulus to the driver. - There are two additional tasks
pre_body()
andpost_body()
that can be included (but optional) to perform some task before and after executing thebody()
- When a sequence has started, always raise a flag/objection to let the testbench know that it's still active. This flag/objection should be lowered when the sequence finishes so that testbench can finish the simulation and exit gracefully. If the flag is not raised, then there are chances that some other component in the testbench call a simulation exit and the sequence will be exited abruptly. You may also place it in the
body()
task. - The data object will be randomized and sent to the driver via
start_item
andfinish_item

Let's create a simple sequencer to execute the sequence above.
class my_sequencer extends uvm_sequencer #(my_data);
`uvm_component_utils (my_sequencer)
function new (string name, uvm_component parent);
super.new (name, parent);
endfunction
endclass
Remember to set default sequence to the base sequence.
class base_test extends uvm_test;
...
function void start_of_simulation_phase (uvm_phase phase);
super.start_of_simulation_phase (phase);
uvm_config_db#(uvm_object_wrapper)::set(this,"m_top_env.m_seqr0.main_phase",
"default_sequence", base_sequence::type_id::get());
endfunction
...
endclass
Alternatives
Use uvm_sequencer
If you don't want to create a new sequencer class, and instead prefer to use uvm_sequencer
, you can do so by substituting the appropriate lines. This will create a sequencer of type uvm_sequencer
that can operate on data my_data
instead of the user-defined custom sequencer from example above.
class base_sequence extends uvm_sequence #(my_data);
...
`uvm_declare_p_sequencer (uvm_sequencer #(my_data))
...
endclass
class my_env extends uvm_env;
...
uvm_sequencer #(my_data) m_seqr0;
...
virtual function void build_phase (uvm_phase phase);
...
m_seqr0 = uvm_sequencer#(my_data)::type_id::create ("m_seqr0", this);
endfunction
endclass
Use uvm_sequence_utils
Another way to accomplish the same task is to use the macro `uvm_sequence_utils
for the sequence and `uvm_sequencer_utils
for the sequencer while registering with the factory. Remember to call the `uvm_update_sequence_lib_and_item
macro to update the database. This macro may be deprecated in the future.
class my_sequencer extends uvm_sequencer #(my_data);
function new (string name, uvm_component parent);
super.new (name, parent);
`uvm_update_sequence_lib_and_item (my_data)
endfunction
`uvm_sequencer_utils (my_sequencer)
endclass
class base_sequence extends uvm_sequence #(my_data);
`uvm_sequence_utils (base_sequence, my_sequencer)
...
endclass
---------------------------------------------------------------- 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 - @2631 m_top_env my_env - @208 m_drv0 my_driver - @202 rsp_port uvm_analysis_port - @2830 seq_item_port uvm_seq_item_pull_port - @2779 m_seqr0 my_sequencer - @2730 rsp_export uvm_analysis_export - @2920 seq_item_export uvm_seq_item_pull_imp - @3468 arbitration_queue array 0 - lock_queue array 0 - num_last_reqs integral 32 'd1 num_last_rsps integral 32 'd1 ------------------------------------------------------------ UVM_INFO ./tb/my_pkg.sv(98) @ 0: uvm_test_top.m_top_env.m_drv0 [my_driver] Applying initial reset UVM_INFO ./tb/my_pkg.sv(102) @ 390000: uvm_test_top.m_top_env.m_drv0 [my_driver] DUT is now out of reset UVM_INFO ./tb/my_pkg.sv(109) @ 390000: uvm_test_top.m_top_env.m_drv0 [my_driver] Waiting for data from sequencer UVM_INFO ./tb/my_pkg.sv(149) @ 390000: uvm_test_top.m_top_env.m_seqr0@@base_sequence [BASE_SEQ] Optional code can be placed here in pre_body() UVM_INFO ./tb/my_pkg.sv(155) @ 390000: uvm_test_top.m_top_env.m_seqr0@@base_sequence [BASE_SEQ] Starting body of base_sequence UVM_INFO ./tb/my_pkg.sv(122) @ 410000: uvm_test_top.m_top_env.m_drv0 [DRV] Driving data item across DUT interface data_obj: (my_data@3581) { data: 'hed addr: 'h5 begin_time: 390000 depth: 'd2 parent sequence (name): base_sequence parent sequence (full name): uvm_test_top.m_top_env.m_seqr0.base_sequence sequencer: uvm_test_top.m_top_env.m_seqr0 } UVM_INFO ./tb/my_pkg.sv(109) @ 410000: uvm_test_top.m_top_env.m_drv0 [my_driver] Waiting for data from sequencer UVM_INFO ./tb/my_pkg.sv(163) @ 410000: uvm_test_top.m_top_env.m_seqr0@@base_sequence [base_sequence] Sequence base_sequence is over UVM_INFO ./tb/my_pkg.sv(168) @ 410000: uvm_test_top.m_top_env.m_seqr0@@base_sequence [BASE_SEQ] Optional code can be placed here in post_body() UVM_INFO ./tb/my_pkg.sv(128) @ 410000: uvm_test_top.m_top_env.m_drv0 [my_driver] Finished DUT simulation --- UVM Report catcher Summary ---