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. sequence_library 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
arrange sequences in multiple ways

Sequences can do operations on sequence items, or kick-off new sub-subsequences:

  1. Execute using the start() method of a sequence or `uvm_do macros
  2. 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.

sub-sequences and sequence items In this page, we'll try to execute a sequence item using the start_item/finish_item task.In order to create a user-defined sequence :
  1. Derive from uvm_sequence base class with a specified data object type
  2. Register the sequence with the factory using `uvm_object_utils
  3. Set the default sequencer that should execute this sequence
  4. 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"); (name);

   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);

   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);
      `uvm_info (get_type_name (), $sformatf ("Sequence %s is over", this.get_name()), UVM_MEDIUM)
   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);
Note the following from the example above:
  1. base_sequence has been derived from uvm_sequence with a data object type my_data
  2. This sequence is specified to execute with my_sequencer using the macro `uvm_declare_p_sequencer
  3. Main task body() contains the code to drive the stimulus to the driver.
  4. There are two additional tasks pre_body() and post_body() that can be included (but optional) to perform some task before and after executing the body()
  5. 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.
  6. The data object will be randomized and sent to the driver via start_item and finish_item

sequencer operates on a sequence

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); (name, parent);


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);
                                          "default_sequence", base_sequence::type_id::get());


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))

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);

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); (name, parent);
      `uvm_update_sequence_lib_and_item (my_data)

   `uvm_sequencer_utils (my_sequencer)

class base_sequence extends uvm_sequence #(my_data);
	`uvm_sequence_utils (base_sequence, my_sequencer)
 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               -     @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/ @ 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@@base_sequence [BASE_SEQ] Optional code can be placed here in pre_body()
UVM_INFO ./tb/ @ 390000: uvm_test_top.m_top_env.m_seqr0@@base_sequence [BASE_SEQ] Starting body of base_sequence
UVM_INFO ./tb/ @ 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/ @ 410000: uvm_test_top.m_top_env.m_drv0 [my_driver] Waiting for data from sequencer
UVM_INFO ./tb/ @ 410000: uvm_test_top.m_top_env.m_seqr0@@base_sequence [base_sequence] Sequence base_sequence is over
UVM_INFO ./tb/ @ 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/ @ 410000: uvm_test_top.m_top_env.m_drv0 [my_driver] Finished DUT simulation

--- UVM Report catcher Summary ---