UVM Virtual Sequence - vnrjoshi/UVM-Virtual-Sequence GitHub Wiki
UVM Virtual Sequence
A virtual sequencer is used in the stimulus generation process to allow a single sequence to control activity via several agents.
- It is not attached to any driver.
- Does not process items itself.
- Has references to multiple sequencers in an agent environment.
Virtual sequence invokes sequences only on virtual sequencer
- Can also work as stand alone.
Virtual sequence is usually executed on the virtual sequencer which has handles to real sequencers. It is recommended to use a virtual sequencer if you have multiple agents and stimulus coordination is required.
- Why are the virtual_sequence and virtual_sequencer named virtual?
System Verilog has virtual methods, virtual interfaces, and virtual classes. “virtual” keyword is common in all of them. But, virtual_sequence and virtual_sequencer do not require any virtual keyword. UVM does not have uvm_virtual_sequence and uvm_virtual_sequencer as base classes. A virtual sequence is derived from uvm_sequence
. A virtual_sequencer is derived from uvm_sequencer
as a base class.
Virtual sequencer controls other sequencers. It is not attached to any driver and can not process any sequence_items too. Hence, it is named virtual.
- Difference between a virtual sequence and a virtual sequencer:
- Why do we need a virtual sequence?
Most UVM test benches are composed of reusable verification components unless we are working on block-level verification of a simple protocol. Let us consider a scenario of verifying a simple protocol; In this case, we can use just one sequencer to send the stimulus to the driver.
The top-level test will use this sequencer to process the sequences. Here we may not need a virtual sequence (or a virtual sequencer).But when we are trying to integrate this IP into our SOC (or top-level block), we surely want to consider reusing our testbench components, which have been used to verify these blocks.
Let us consider a simple case where we are integrating two such blocks: two sequencers driving these two blocks. From the top-level test, we will need a way to control these two sequencers.
So, this can be achieved by using a virtual sequencer and virtual sequences. Another way of doing it is to call the sequence’s start method explicitly from the top-level test by passing the sequencer to the start method.
Since now we need to make use of the virtual sequence and virtual sequencer, there will be some changes in the environment and test structure. Earlier the environment used to contain agents now along with agents it contains additional component like virtual sequencer.
Basically virtual sequencer is a container that contains pointers for all the physical sequencers, so as shown in the above diagram in our environment there are two physical sequencers.
Even in the test also we have a new object called virtual sequence, earlier test used to contain direct instances for sequence, but now test contains virtual sequence, virtual sequence contains several sequences and virtual sequencer.
We are going to declare the instances of sequence along with instances of physical sequencer handles, so since above show diagram contains two sequencer, even our virtual sequence will also contain handles for those two sequencers along with that the virtual sequence will also contain virtual sequencer.
-
Examples:
//Virtual sequencer class virtual_sequencer extends uvm_sequencer#(uvm_sequence_item); `uvm_component_utils(virtual_sequencer) uvm_sequencer#(transaction) seq1_h; uvm_sequencer#(transaction) seq2_h; function new(string name="virtual_sequencer",uvm_component parent=null); super.new(name,parent); endfunction endclass
Virtual_sequencer class is declared by extending the uvm_sequencer
. Sequencer handles are also declared inside it. Virtual Sequencer is the part of the environment. Virtual sequencer contains the handles of the target sequencers i.e. & which are physically located inside the Agents. These sequencers handles assignment will be done during connect phase of the environment class.
Now lets see the implementation of the virtual sequence. Virtual sequence will be extended by uvm_sequence.
//Virtual sequence
class virtual_sequence extends uvm_sequence#(uvm_sequence_item);
`uvm_object_utils(virtual_sequence)
virtual_sequencer v_seqr_h;
function new(input string name="virtual_sequence");
super.new(name);
endfunction
uvm_sequencer#(transaction) seqr1_h;
uvm_sequencer#(transaction) seqr2_h;
sequence1 s1;
sequence2 s2;
task body();
if(!$cast(v_seqr_h,m_sequencer))
`uvm_error(get_full_name(),"Virtual sequencer pointer casting failed")
seqr1_h = v_seqr_h.seq1_h;
seqr2_h = v_seqr_h.seq2_h;
s1=sequence1::type_id::create("s1");
s2=sequence2::type_id::create("s2");
repeat(2) begin
s1.start(seqr1_h);
s2.start(seqr2_h);
end
endtask
endclass
Virtual sequence is located outside the environment class & it is created in the run_phase() method of the test. The virtual sequence is designed to run on the virtual sequencer & virtual sequence also gets the handles of the sequencers from the virtual sequencer.
Now lets see the code for the environment class which instantiates virtual sequencer as well as the agents.
//Environment class
class environment extends uvm_env;
`uvm_component_utils(environment)
agent a1,a2;
virtual_sequencer v_seqr_h;
function new(string name="environment",uvm_component parent=null);
super.new(name,parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
a1=agent::type_id::create("a1",this);
a2=agent::type_id::create("a2",this);
v_seqr_h=virtual_sequencer::type_id::create("v_seqr_h",this);
endfunction
virtual function void connect_phase(uvm_phase phase);
v_seqr_h.seq1_h=a1.s_h;
v_seqr_h.seq2_h=a2.s_h;
endfunction
endclass
In the above environment class i.e. “environment", virtual sequencer is instantiated & built along with Agents i.e. a1
& a2
. Sequencer handles are also assigned in the connect_phase(). Usage of a flexible & handy feature of UVM i.e. m_sequencer
is being shown which by default points to the UVM sequencer derived from the uvm_sequencer
.
Let's see how the virtual sequence is started on the virtual sequencer from the test
//Test class
class test extends uvm_test;
`uvm_component_utils(test)
environment env;
virtual_sequence v_seq_h;
function new(string name="test",uvm_component parent=null);
super.new(name,parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
env = environment::type_id::create("env",this);
v_seq_h=virtual_sequence::type_id::create("v_seq_h");
endfunction
virtual task run_phase(uvm_phase phase);
phase.raise_objection(this);
v_seq_h.start(env.v_seqr_h);
phase.drop_objection(this);
endtask
endclass
In the test class i.e. “test”, both environment & virtual sequence i.e. “environment” & “virtual sequence” are instantiated and created. Finally virtual sequence is started on the virtual sequencer which exists inside the Environment.
So that’s how, the virtual sequence can be implemented using in UVM.
- Output: