A virtual sequence is a container to start multiple sequences on different sequencers in the environment. This virtual sequence is usually executed by a virtual sequencer which has handles to real sequencers. The need for a virtual sequence arises when you require different sequences to be run on different environments. For example, an SoC design might have multiple different interfaces that might need to be driven by a different set of sequences on individual sequencers. Hence the best way to start and control these different sequences would be from a virtual sequence. It becomes virtual because it is not associated with any particular data type.

virtual sequence

Use of a virtual sequencer


	class my_virtual_seq extends uvm_sequence;
		`uvm_object_utils (my_virtual_seq)
		`uvm_declare_p_sequencer (my_virtual_sequencer)
		
		function new (string name = "my_virtual_seq");
			super.new (name);
		endfunction
		
		apb_rd_wr_seq 	m_apb_rd_wr_seq;
		wb_reset_seq 	m_wb_reset_seq;
		pcie_gen_seq 	m_pcie_gen_seq;
		
		task pre_body();
			m_apb_rd_wr_seq = apb_rd_wr_seq::type_id::create ("m_apb_rd_wr_seq");
			m_wb_reset_seq  = wb_reset_seq::type_id::create ("m_wb_reset_seq");
			m_pcie_gen_seq  = pcie_gen_seq::type_id::create ("m_pcie_gen_seq");
		endtask
		
		task body();
			...
			m_apb_rd_wr_seq.start (p_sequencer.m_apb_seqr);
			fork
				m_wb_reset_seq.start (p_sequencer.m_wb_seqr);
				m_pcie_gen_seq.start (p_sequencer.m_pcie_seqr);
			join 
			...
		endtask
	endclass

Note the following from the example above.

  • my_virtual_seq is derived from uvm_sequence just like any other sequence
  • A handle called p_sequencer is created within the sequence via macro `uvm_declare_p_sequencer and assigned to be run with my_virtual_sequencer
  • Each sequence is started on its corresponding sequencer using the start() method
  • Each sequencer is referenced by p_sequencer handle which points to the virtual sequencer

Once a virtual sequence is defined, you can start this in your test as shown below.


	class my_test extends uvm_test;
		`uvm_component_utils (my_test)
		
		my_env 	m_env;
		
		...
		
		task run_phase (uvm_phase phase);
			my_virtual_seq m_vseq = my_virtual_seq::type_id::create ("m_vseq");
			phase.raise_objection (this);
			m_vseq.start (m_env.m_virtual_seqr);
			phase.drop_objection (this);
		endtask
	endclass

Without a virtual sequencer

You can also put handles to individual sequencers within the virtual sequence, and then would not need a virtual sequencer.


	class my_virtual_seq extends uvm_sequence;
		`uvm_object_utils (my_virtual_seq)
		...
		apb_sequencer 	m_apb_seqr;
		reg_sequencer 	m_reg_seqr;
		wb_sequencer 	m_wb_seqr;
		
		apb_rd_wr_seq 	m_apb_rd_wr_seq;
		wb_reset_seq 	m_wb_reset_seq;
		pcie_gen_seq 	m_pcie_gen_seq;
		
		virtual task pre_body();
			m_apb_rd_wr_seq = apb_rd_wr_seq::type_id::create ("m_apb_rd_wr_seq");
			...
			// Instantiate other sequences here
		endtask
		
		virtual task body ();
			m_apb_rd_wr_seq.start (m_apb_seqr);
			...
		endtask
	endclass

The handles to sequencers within the sequence needs to be assigned prior to starting this virtual sequence.


	class my_test extends uvm_test;
		...
		
		virtual task run_phase (uvm_phase phase);
			my_virtual_seq m_vseq = my_virtual_seq::type_id::create ("m_vseq");
			phase.raise_objection (this);
			
			// Assign all sequencer handles
			m_vseq.m_apb_seqr = m_env.m_apb_agent.m_apb_seqr;
			...
			
			m_vseq.start (null);
			phase.drop_objection (this);
		endtask
	endclass

If you would like, you can also split the creation and assignment of sequence into build_phase and connect_phase within my_test.