Controlloing seq_item using virtual sequence and sequencer

Hi All,

How to control the seq_items execution on different sequencer using virtual sequence and sequencer. For examples if we have have two sequences seq1 and seq2, seq1 have seq_item a, b,and c, seq2 have seq_items x, y, and z. Now if i want to run following sequence of items:
a, x, y, b, z, c, then how to control this execution using virtual sequencer and sequences.

And as we can run different sequence on different sequencer, then how virtual sequencer and sequence are helpfull??
Please reply.

Thanks,
Rahul Kumar

In reply to Rahul Kumar khokher:

Hi Rahul,
Do you want EXACTLY the sequence of items you mentioned (a, x, y, b, z, c), or do you just want seq1 and seq2’s items to be randomly interleaved?
-Doug

In reply to Doug Smith:

Hi Doug,

Thanks for reply, Please let me know both of them(a,x,y,b,z,c and items to be randomly interleaved) and please explain the advantage of virtual sequence and sequencer with an example if possible.

Thinks,
Rahul Kumar

Hi Rahul,

The idea in UVM is that a sequencer/driver combination will be tied to a specific design interface (usually encapsulated in an agent), which is why we parameterize our sequencer and driver (and monitor’s analysis port) to a transaction of a specific bus protocol. Then when we write our sequences, they generate transaction items for that specific bus protocol.

But in a fullchip/SoC testbench, we need some mechanism to coordinate between the different interfaces–that’s where the virtual sequencer/sequences come in. You can think of a virtual sequencer as a testbench controller, coordinating the traffic between all the different bus protocols. It’s called “virtual” (a terrible term, by the way, since the keyword “virtual” is used to mean 3 completely different things in SystemVerilog) because it doesn’t generate transactions (sequence items)—it just tells other sequencers what to do.

Typically, you only need one virtual sequencer, but nothing stops you from having as many as you want. Truth be told, you can even run virtual sequences WITHOUT a virtual sequencer if you like, but there are some advantages to using a virtual sequencer. First, virtual sequencers arbitrate between multiple sequences, and this allows you specify sequence priorities or grab exclusive access to specific bus interfaces. Second, we can use a virtual sequencer to hold references to our bus-specific sequencers, which allows us to write our sequences in a generic way so that they don’t need hard-coded hierarchical references or any knowledge of how the testbench is constructed (which makes them portable between test benches). Third, we can use a virtual sequencer’s hierarchical path to query hierarchical information from the config database from inside our sequences.

Now, to answer your first question about generating the stimulus. There is not one correct way to generate the stimulus you’re looking for. It really depends on what you prefer, and it depends on where you put the randomization.

The easiest way to generate stimulus is as follows. First, create two simple sequences that generate a single sequence item. For example,

class seq1 extends uvm_sequence_item#(trans_type1);
    `uvm_object_utils(seq1)

    // Constructor

    task body();
         `uvm_do(req)
    endtask
endclass

Create a similar class for seq2. (Note, Verification Academy does not recommend the use of sequence macros, but I’m using them here to simplify the code). Then create a virtual sequence that invokes the two sequences in the way you specified above:

class virt_seq extends uvm_sequence;
   `uvm_object_utils(virt_seq)

   // Constructor

   task body();
      seq1  s1;
      seq2  s2;

      s1 = seq1::type_id::create("s1");
      s2 = seq1::type_id::create("s2");

      // Now randomize your sequences to generate the stimulus you want
      `uvm_do_on_with(s1, seqr1, { /* Constraints for "a" transaction */ })
      `uvm_do_on_with(s2, seqr2, { /* Constraints for "x" transaction */ })
      `uvm_do_on_with(s2, seqr2, { /* Constraints for "y" transaction */ })
      `uvm_do_on_with(s1, seqr1, { /* Constraints for "b" transaction */ })
      `uvm_do_on_with(s2, seqr2, { /* Constraints for "z" transaction */ })
      `uvm_do_on_with(s1, seqr1, { /* Constraints for "c" transaction */ })
   endtask
endclass

This virtual sequence will generate the sequence a, x, y, b, z, c. If you want these transactions to be randomly interleaved, then you could place everything inside of a fork … join (the Verilog standard does not guarantee the order of thread execution):

task body();
      ...
   fork
      `uvm_do_on_with(s1, seqr1, { /* Constraints for "a" transaction */ })
      `uvm_do_on_with(s2, seqr2, { /* Constraints for "x" transaction */ })
      `uvm_do_on_with(s2, seqr2, { /* Constraints for "y" transaction */ })
      `uvm_do_on_with(s1, seqr1, { /* Constraints for "b" transaction */ })
      `uvm_do_on_with(s2, seqr2, { /* Constraints for "z" transaction */ })
      `uvm_do_on_with(s1, seqr1, { /* Constraints for "c" transaction */ })
   join
endtask

But chances are that this won’t be so very random so you may prefer to put this inside of a randcase or randsequence.

Alternatively, you could define your sequences to generate 3 transactions at a time. Sequence 1 could generate sequentially or randomly a, b, c, and sequence 2 generate x, y, z. But coordinating between these transaction objects becomes a lot tricker because the sequencers are running independently of each other. For that, you would need to some kind of synchronization between the two sequences. You could do this with a uvm_event or uvm_barrier, but that means that your sequences have to be aware of each other. Instead, you could pass a reference to your sequence item to the virtual sequencer (maybe using a uvm_event?) and then start/stop the sequencers based on the order you want the traffic, or you could have your sequences use a global barrier and make decisions at that point. But by far, it’s easiest to use the simple approach shown above.

I hope that helps!
-Doug

Hi,

I had a question related to virtual sequence, i.e. writing constraints for 2 different variables of 2 different sequences and randomizing them.
for ex :In the above example we have 2 sequences i.e. seq1(which has variable a int type) and seq2(which has variable x int type).
Now I want to write a constraint in virtual sequence that a must not be equal to x, and these two bits should be randomized for every clock cycle.
I tried doing the following which gives me only 1 randomized value.

rand int a_sub;
rand int x_sub;

constraint y {a_sub != b_sub;}

task body();
starting_phase.raise_objection(this);
repeat(10)
begin
fork
uvm_do_on_with(seq1, p_sequencer.wr_seqr,{seq1.a == a_sub;}) uvm_do_on_with(seq2, p_sequencer.rd_seqr,{seq2.x == x_sub;})
join
end
starting_phase.drop_objection(this);
endtask
endclass

This generates only single value i.e a = 25 and x = 26
Can anybody help me in this?

Thanks
Sathya