VIP Café

A place where the experts go to chat about verification IP.


In my previous blog post, I talked about guidelines to create reusable sequences. Continuing on this thread, here I am going to talk about virtual sequences and the virtual sequencer. Common questions I hear from users include: why do we need a virtual sequence? How can we use it effectively?

Most UVM testbenches are composed of reusable verification components, unless we are working on block-level verification of a simple protocol like MIPI-CSI. Consider a scenario of verifying a simple protocol; In this case, we can live with just one sequencer sending the stimulus to the driver. The top-level test will use this sequencer to process the sequences (as described in the previous blog post). Here we may not need 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 out testbench components, which have been used to verify these blocks. Let us consider a simple case where we are integrating two such blocks. Here, let us consider two sequencers driving these two blocks. From top-level test, we will need a way to control these two sequencers.

This can be achieved by using a virtual sequencer and virtual sequences. Other way of doing it is to call sequence’s start method explicitly from the top-level test by passing the sequencer to the start method.

I am going to explain this usage by taking an example, where USB host is integrated in an AXI environment. Let’s see how we can control USB sequencer and AXI sequencer from top-level test. For this particular test, I want to configure the AXI registers and then send USB transfers. For configuring AXI registers am using a sequence say axi_cfg_reg_sequence and for sending USB transfers am using the sequence (usb_complex_sequence) which I have used in the previous blog post. Below is an example where multiple sequencers are controlled without using a virtual sequence.

//Top-level test where multiple sequencers are controlled from the
//phase method.
class axi_cfg_usb_bulk_test extends uvm_test;

  //Sequences which needs to be exercised
  usb_reset_sequence    u_reset_seq;
  axi_reset_sequence    a_reset_seq;
  usb_complex_sequence   u_bulk_seq;
  axi_cfg_reg_sequence   a_cfg_reg_seq;

  function new (strint name=”axi_cfg_usb_bulk_test”,
                                            uvm_component parent=null);
  endfunction: new

  //Call the reset sequences in the reset_phase
  virtual task reset_phase (uvm_phase phase);
    //Executing sequences by calling the start method directly by passing the
    //corresponding sequencer

  virtual task main_phase (uvm_phase phase);
    //Executing sequences by calling the start method directly by passing the
    //corresponding sequencer
endclass: axi_cfg_usb_bulk_test

This is not the efficient way of controlling the sequencers as we are directly using the simple sequences inside the test and making it complex. By doing this, we cannot reuse these complex scenarios further to develop more complex scenarios; rather if we try to create a sequence and use this sequence in the test, then we can re-use these sequences in other tests (or sequences) as well. Also it will be easier to maintain and debug these sequences compared to creating entire scenario in the top-level test.

Having understood why we need virtual sequence and virtual sequencer, let’s see how this can be achieved by taking the same example shown above.

First thing we need to do is to create a virtual sequencer, note that virtual sequences can only associate with virtual sequencer (but not with non-virtual sequencer). Virtual sequencer is also derived from uvm_sequencer like any other non-virtual sequencer but is not attached to any driver. Virtual sequencer has references to the sequencers we are trying to control. These references are assigned from top environment to the non-virtual sequencers.

//Virtual sequencer having references to non-virtual sequencers
Class system_virtual_sequencer extends uvm_sequencer;
  //References to non-virtual sequencer
  usb_sequencer usb_seqr;
  axi_sequencer axi_seqr;

  function new (string name=”usb_ltssm_bulk_test”,
                                           uvm_component parent=null);
  endfunction: new


endclass: system_virtual_sequencer

//Top level environment, where virtual sequencer’s references
//are connected to non-virtual sequencers
class system_env extends uvm_env;
  //Agents where the non-virtual sequencers are present
  usb_host_agent  usb_host_agent_obj;
  axi_master_agent  axi_master_agent_obj;
  //Virtual sequencer
  system_virtual_sequencer sys_vir_seqr;


  function new (string name=”system_env”, uvm_component parent=null);
  endfunction: new

  function void connect_phase(uvm_phase phase);
    //Assigning the virtual sequencer’s references to non-virtual sequencers
    sys_vir_seqr.usb_seqr = usb_host_agent_obj.sequencer;
    sys_vir_seqr.axi_seqr = axi_master_agent_obj.sequencer;
  endfunction: connect_phase

endclass: system_virtual_sequencer

Now we have virtual sequencer with the references to our non-virtual sequencers, which we want to control, let’s see how we can control these non-virtual sequencers using virtual sequences.

Virtual sequences are same as any other sequence but it is associated to a virtual sequencer unlike non-virtual sequences, hence it needs to indicate which non- virtual sequencer it has to use to execute the underlying sequence. Also note that virtual sequence can only execute sequences or other virtual sequences but not the items. Use `uvm_do_on/`uvm_do_on_with to execute non-virtual sequences and `uvm_do/`uvm_do_with to execute other virtual sequences.

//virtual sequence for reset operation
class axi_usb_reset_virtual_sequence extends uvm_sequence;


  //non-virtual reset sequences
  usb_reset_sequence    u_reset_seq;
  axi_reset_sequence    a_reset_seq;

  function new (string name=” axi_usb_reset_virtual_sequence”,
                                  uvm_component parent=null);
  endfunction: new


  task body();
    //executingnon-virtual sequence on the corresponding
    //non-virtual sequencer using `uvm_do_on
    `uvm_do_on(a_reset_seq, p_sequencer.axi_seqr)
    `uvm_do_on(u_reset_seq, p_sequencer.usb_seqr)
  endtask: body

endclass: axi_usb_reset_virtual_sequence

//virtual sequence for doing axi register configuration
//followed by USB transfer
class axi_cfg_usb_bulk_virtual_sequence extends uvm_sequence;


  //Re-using the non-virtual sequences
  usb_complex_sequence   u_bulk_seq;
  axi_cfg_reg_sequence   a_cfg_reg_seq;

  function new (string name=” axi_cfg_usb_bulk_virtual_sequence”,
                                          uvm_component parent=null);
  endfunction: new

  task body();
    //executingnon-virtual sequence on the corresponding
    //non-virtual sequencer using `uvm_do_on
    `uvm_do_on(a_cfg_reg_seq, p_sequencer.axi_seqr)
    `uvm_do_on(u_bulk_seq, p_sequencer.usb_seqr)
  endtask: body

endclass: axi_cfg_usb_bulk_virtual_sequence

In the above virtual sequence, we are executing axi_cfg_reg_sequence and then usb_complex_sequence. Now having virtual sequence and virtual sequencer ready, let’s see how we can execute this virtual sequence from the top-level test.

//Top-level test where virtual sequence is set to virtual sequencer
class axi_cfg_usb_bulk_test extends uvm_test;
  virtual function void build_phase(uvm_phase phase );

    //Configuring variables in underlying sequences
    uvm_config_db#(int unsigned)::set(this,

    //Executing the virtual sequences in virtual sequencer’s
    //appropriate phase.
    //Executing reset virtual sequence in reset_phase
             "env.sys_vir_seqr.reset_phase", "default_sequence",

    //Executing the main virtual sequence in main_phase
                     "env.sys_vir_seqr.main_phase", "default_sequence",
  endfunction : build_phase

Until now we understood why and how we can use virtual sequences. We should also keep few things in mind while using virtual sequence and virtual sequencer to save a lot of debugging time.

1. While configuring the variables in the sequences (which are executed using virtual sequences) we have to use path thru virtual sequence. In above example, using the non-virtual sequencer path for setting the variables in the lower level sequence, will not work.

uvm_config_db#(int unsigned)::set(this,”env.usb_host_agent_obj.sequencer.u_bulk_sequence”,”sequence_length”,10);

Even though u_bulk_sequence is running on the usb_host_agent_obj.sequencer, this will not work because this sequence is created by the virtual sequence and hence hierarchal path should be from virtual sequence but not using non-virtual sequencer. So the right way of setting variables is using the virtual sequence path.

uvm_config_db#(int unsigned)::set(this,”env.sys_vir_seqr.axi_cfg_usb_bulk_virtual_sequence.u_bulk_sequence”,”sequence_length”,10);

This is also true for factory overrides. For example below factory override will not work for the same above reason.

set_inst_override_by_type(”env.usb_host_agent_obj.*”,usb_transfer_item::get_type(), cust_usb_transfer_item::get_type());

In the above example we are trying to change the underlying sequence item with a new derived type from top-level test. For doing this we need to use the virtual sequencer path.

set_inst_override_by_type(”env.sys_vir_seqr.*”,usb_transfer_item::get_type(), cust_usb_transfer_item::get_type());

Rule of thumb is:
• If the sequence is created by a virtual sequence directly or indirectly, then any hierarchical path in factory overrides or in configurations should use virtual sequencer’s hierarchical path.
• If the sequence is created by a non-virtual sequence, then any hierarchical path in factory overrides or configurations should use non-virtual sequencer’s hierarchical path.

2. Even though we have virtual sequencer to control multiple sequencers, in some tests, we may just need a single sequencer (for example USB sequencer alone). In such cases, we have to use the non-virtual sequencer’s hierarchical path directly (not the virtual sequencer’s reference path) for configuring the variables or factory overrides. Using the virtual sequencer’s reference path will not work as the hierarchy of non-virtual sequencer is incorrect.

uvm_config_db#(uvm_object_wrapper)::set(this, “env.sys_vir_seqr.usb_seqr.main_phase”, “default_sequence”, usb_complex_sequence::type_id::get());

Above configuration will not work, as non-virtual sequencer (usb_seqr/usb_host_agent_obj.sequencer) is actually created in the agent, so the parent for this sequencer is agent but not the virtual sequencer, though the reference is in virtual sequencer. Hence we should not use virtual sequencer path when trying to set variables in the actual sequencer, instead we have to use the hierarchical path through the agent (actual parent to the sequencer).

uvm_config_db#(uvm_object_wrapper)::set(this, “env.usb_host_agent_obj.sequencer.main_phase”, “default_sequence”, usb_complex_sequence::type_id::get());

3. Whenever we are using virtual sequencer and want to control non-virtual sequencers from virtual sequencer, make sure to set the default_sequence in all the actual sequencers to null.

uvm_config_db#(uvm_object_wrapper)::set(this, “env.usb_host_agent_obj.sequencer.main_phase”, “default_sequence”, null);
uvm_config_db#(uvm_object_wrapper)::set(this, “env.axi_master_agent_obj.sequencer.main_phase”, “default_sequence”, null);

This is important because if there is any default_sequence set, then our non-virtual sequencer will be running both the default_sequence and the sequence from the virtual sequence. To control non-virtual sequencers solely from virtual sequencer, we need to set the default_sequence of the non-virtual sequencers as null.

I hope you find this post useful for understanding virtual sequences and save debugging time with the guidelines outlined. I am sure there will be other guidelines while using virtual sequences, which we learn the harder way debugging complex environments; please share any such guidelines with me.

Share and Enjoy:
  • Print
  • Facebook
  • Twitter
  • Google Bookmarks
Category : VIP Design

6 Responses to “Virtual Sequences in UVM: Why? How?”

martinborja July 16, 2013

Hello VIP Central,

This is a really helpful post as I am starting to learn how to use virtual sequencers. I have a slightly different usage in mind though that I would like to seek some advice from you guys. The testbench I have in mind is using not only non-virtual sequencers but also has a virtual sequencer controlling a medium-sized agent, which contains three non-virtual sequencers. My question is can I still use the same method discussed above now that I introduced a non-virtual sequencer to be controlled by the top-level virtual sequencer?

I am currently working on this and am still trying to make it work. Maybe I can get some useful advice from you guys here at VIP central.

Martin Borja

    martinborja July 16, 2013

    “My question is can I still use the same method discussed above now that I introduced a non-virtual sequencer to be controlled by the top-level virtual sequencer? ”

    Please let me rephrase:
    “My question is can I still use the same method discussed above now that I introduced a virtual sequencer to be controlled by the top-level virtual sequencer? “

      Hari Balisetty July 17, 2013

      Hi Martin,

      If I understand your question, you are having a virtual sequencers which are controlled by another top level virtual sequencer.
      Yes you can use the same method discussed in the article for this hierarchy, only difference you have is a virtual sequencer on top of the virtual sequencers.
      This is possible when you are using a VIP from a third party, which has a virtual sequencer to control different sequencers it has.
      Now in your top level design you might have other blocks having virtual sequencers.
      In such scenarios you will end up having a virtual sequencer controlling these virtual sequencers.
      One thing you should keep in mind is that, you run a sequence on a physical sequencer.
      Hope this answers your question.

      Hari Vinodh

martinborja July 30, 2013

To solve the raise_objection problem, add it to the one that is initiated as “default_sequence” in the test. There is no need to trigger start_phase for the physical sequence.


martinborja July 26, 2013

Hello VIP Central,

In the case of having a top virtual sequencer controlling another virtual sequencer with several sub sequencers.
In which sequence do you put this:

if(starting_phase != null)

top virtual, sub-virtual, or lowest physical?
I tried to put it in the lowest level, which is the physical sequence but it is unable to raise the objection precisely because it fails the
starting_phase != null condition.
I also tried to put it in the top level but it only delays the end of the test and does nothing.

Please let me know if you have any similar experiences, problems and solutions. It will be much appreciated.


Hari Balisetty July 26, 2013

Hi Martin,

I would suggest you to have this code, for every physical sequence’s pre_start and post_start methods.
This is because this sequence might be used as a top level sequence or the sub sequence.

The starting_phase is set automatically to those sequences which are started as a default sequence.
If this is a not a default sequence then it is expected that starting_phase is not set for this sequence.
In such cases you can explicitly set starting_phase to the appropriate phase before calling start method (or the uvm_do macros).
This way starting_phase will be set and the objection will be raised in this phase.

Hope this explains why you are not able to raise_objection from the physical sequence.

Coming to the case where you put this in top level sequence, this should also work
because post_start of top level sequence should be called only after completing the body and post_start methods
of the sub sequence, that way having the raise_objection code in the top level sequence should also work.


You must be logged in to post a comment.