Advantages of virtual sequences versus tasks

I’ve always read how virtual sequences can coordinate multiple sequences to be run over multiple sequencers, but for some reason in my organization we have started off writing tasks to be run in the testcase’s run_phase, where all the coordination is done.

So our typical testcase would look like:


///////////////////////////////////////////////////////////////////////////////
// Run phase: the actual testcase
task utc_application_default::run_phase(uvm_phase phase);
    /////////////////////////////////////////////////////////////////////////////
    // Create sequences
    my_sequence my_loop_seq;

    my_loop_seq = my_sequence::type_id::create("my_loop_seq");

    /////////////////////////////////////////////////////////////////////////////
    // UVM Settings
    uvm_top.finish_on_completion = 1;

    // Raise an objection until the end of the TB
    phase.raise_objection(this);

    // The cfg_vseq is a dut configuration sequence instantiated in the base class the testcase extends
    cfg_vseq.set_config();

    // Yet another globally defined task
    default_input_stimuli();

    /////////////////////////////////////////////////////////////////////////////
    // Power up circuit
    pwr_up_seq.start(env.power_agt.power_sqr);

    void'(my_loop_seq.randomize() with {
        my_loop_seq.infinite_loop == 0;
        my_loop_seq.count == 1000;
        my_loop_seq.id_is_random == 0;
        my_loop_seq.default_id == 1;
    });

    `WAIT_MS(3);
    my_loop_seq.start(env.if_agt.sequencer);

    /////////////////////////////////////////////////////////////////////////////
    phase.drop_objection(this);
endtask // run_phase


As it could be seen the testcase itself orchestrates waits, sequence’s randomization and so forth.

I have the impression this approach, while working, is not leveraging the UVM framework completely, but I have not enough experience to provide a solid argument. Can someone chime in and openly provide some critics to this method w.r.t. using virtual sequences to do the exact same thing?

In reply to abs:

I guess you made this post inspired by my post, The best way to start/select sequence(s) in a test class.

One drawback of manual sequence start in run_phase of test class that I think is that you need to rewrite that sequence start part per test. In case of virtual sequence way, you can use the same way for every test meaning you can put that virtual sequence start part in base test class. You need to do factory override of sequences in build_phase of test class per test, though…

In reply to serizawa:

In reply to abs:
I guess you made this post inspired by my post, The best way to start/select sequence(s) in a test class.

I wasn’t aware of that post. Thanks for pointing it out.

One drawback of manual sequence start in run_phase of test class that I think is that you need to rewrite that sequence start part per test.

Actually this is not necessarily the case. For instance, in the example reported in the OP the ‘start part’ can be a simple task defined in the parent test class and available to all derived classes, avoiding the need to rewrite it for each and all of them.

So I still don’t see the great benefit to coordinate through virtual sequences w.r.t. classical tasks defined in the testcase class.

In reply to abs:

The code you are posting does not do things like a virtual sequence is doing. It is fine what you are doing.

In reply to chr_sue:

The intent is clearly to coordinate stimuli, isn’t it what the virtual sequences are meant for?
Do you have any argument to use virtual sequences instead?
What would be the pro/cons w.r.t. the OP example then?

In reply to abs:

It is not visible how you are coordinating the function of different agents using tasks.

In reply to chr_sue:

In reply to abs:
It is not visible how you are coordinating the function of different agents using tasks.

Well, this section of the code might be what I call ‘coordination’:


    /////////////////////////////////////////////////////////////////////////////
    // Power up circuit
    pwr_up_seq.start(env.power_agt.power_sqr);
 
    //...
 
    `WAIT_MS(3);
    my_loop_seq.start(env.if_agt.sequencer);
 
    /////////////////////////////////////////////////////////////////////////////
    phase.drop_objection(this);

where the two sequences are run on two different sequencers with some timing between them.
Maybe I’m using the term ‘coordination’ in the wrong way, what I mean by it is the ability to orchestrate different sequences of ‘transactions’ onto different interfaces of my DUT, in order to create the required scenario.

Obviously one disadvantage of using the above mentioned approach is that you cannot easily reuse the testcase in the event of a different scenario. While using the virtual sequence(r) we could simply extend the base class and override the build_phase() to select a different virtual sequence, in the above methodology you would need to copy the testcase and change the run_phase itself, maybe also adding some extra ‘utility tasks’ defined in the base class.

Does this argument hold? Any other argument in favor of using virtual sequences? Maybe the real question would be: what can be done with virtual sequences(ers) that cannot be done with the approach described in the OP?

In reply to abs:

I found an article Virtual Sequences in UVM: Why, How?

However, I don’t agree with the following part in the article at all.

This is not the most 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.

Obviously, having virtual sequencer(s) is making the environment more complex and we can reuse each sequence to create more complex senarios.

On the other hand, I understand the following part.

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.

So, my advocacy of virtual sequence use is to make that virtual sequence reusable for another virtual sequences. This is true only when the virtual sequence is complex like the sequences inside that virtual sequences have complex interactions.

E.g.)


task virtual_sequence1::body()
  fork
    begin
      seq1.start(seqr1);
      @seq2.flagB;
      seq1.param1 = seq2.cnt1*4;
      repeat(2) seq1.start(seq1);
    end

    begin
      @seq1.flagA;
      seq2.start(seqr2);
      @seq1.flagC;
      seq2.val = seq1.cnt2+seq1.cnt1; 
      @seq1.flagC;
      seq2.start(seqr2)    
    end
  join
endtask


task virtual_sequence2::body()
  v_seq1.start(null);
  fork
    seq1.start(seqr1);
    seq2.start(seqr2);
  join
  v_seq1.start(null);
endtask

In other words, virtual sequence is totally senseless if no complex sequence interaction is considered like simple parallel or serial sequences run.
I.e.)


fork
  seq1.start(seqr1);
  seq2.start(seqr2);
join


seq1.start(seqr1);
seq2.start(seqr2);

This is my conclusion.