Skip to content

05. UVM Stimulus Generation

muneebullashariff edited this page Jan 4, 2024 · 8 revisions

A sequence/stimulus is an object where transactions are going to be generated. Sequences are made up of several data items with different scenarios. Sequences can do operations on sequence items and also create sub-sequences.

A sequence generates a series of sequence_items and sends it to the driver via the sequencer.

Advantage of using sequences

We can develop multiple test cases without having knowledge of the design's protocol. We need to think about various test scenarios based on which we can define sequences that can be used to generate the stimulus based on the constraints that we are writing.

S.No. Topic
1. Sequence Generation
2. `uvm_do sequence macros
3. Sequence Start()
4. Macros for pre-existing items
5. UVM Virtual Sequence
6. UVM Sequence Library
7. UVM Sequence Arbitration

1. Sequence Creation

The sequence is written by extending from uvm_sequence. uvm_sequence is derived from an uvm_sequence_item. A sequence is parameterized with the type of sequence_item, this defines the type of the item sequence that will send to the driver.

uvm_generation_

                  Figure.1 - Sequence Generation 

Sequence item flow

As we know, the sequencer is the mediator between the sequence and the driver for communication.
The below table shows the flow of sequence and driver.

S.No. Sequence Driver
1. Wait for the request from the driver Send the request to get the sequence item.
2. Generate the transaction(sequence item) Wait for the sequence item.
3. Gives the sequence item to the driver Drives the sequence item to DUT.
4. Wait for acknowledgement or response Send the acknowledgement or response

Explanation

UVM Sequence Driver Handshake

For sequence, there are three predefined methods are there and these are-
1.start_item(req)-
This predefined method is used for waiting for the request and once the request generates, it unblocks and will generate the transaction. It is not mandatory to create the item.
Without creating the item, we can use a predefined method(start_item(req)).
If we want to create_item (a user-defined) request.

Syntax
req = **_seq_item::type_id::create(“req”);
This will create and initialize* a sequence_item or sequence*initialize – initialized to communicate with the specified sequencer.

2.req.randomize
For generating the transaction req. randomize()method is used. It generates the transaction and sends it to the driver for acknowledgment.

3.finish_item(req)
This method gives the sequence to the driver and waits for the acknowledgement.

Apart from these three, few predefined methods are there and these are-

wait_for_grant()
This method call is blocking, execution will be blocked until the method returns.

  1. This method issues a request to the current sequencer.
  2. The sequencer grants on getting get_next_item() request from the driver.

send_request(req,re-randomize)
re-randomize = 0 or re-randomize = 1;

Send the requested item to the sequencer, which will forward it to the driver. If the re-randomize the bit is set, the item will be randomized before being sent to the driver.

wait_for_item_done()
This call is optional. This task will block until the driver calls item_done or put.

get_current_item()
Returns the requested item currently being executed by the sequencer. If the sequencer is not currently executing an item, this method will return null.

get_response(rsp)
This receives the response from the driver.

Example

task body();  
start_item(); 
req.randomize();
finish_item(req); 
endtask;`  

body() is the main method used to write the sequences while pre_body() and post_body() are useful callbacks to be used if required.

Example
Here, in this example, we will get the proper structure for the complete uvm code.

Before jumping to the sequence-related code example we need to understand about sequence_item class.

The below code gives us a clear picture of sequence_item class.

Code Snap

    class transaction extends uvm_sequence_item;      
    rand bit [6:0] a;    
    rand bit [3:0] b;    
    rand  bit [3:0] y;    
    `uvm_object_utils_begin(transaction)   
    `uvm_field_int(a,UVM_DEC)   
    `uvm_field_int(b,UVM_HEX)   
    `uvm_field_int(y,UVM_ALL_ON)   
    `uvm_object_utils_end    
     function new(input string name="transaction");   
       super.new(name);   
     endfunction   

     endclass : transaction   

Explaination
Here, we are creating a transaction class by extending the uvm_sequence item. In this class, we are declaring the properties. Using the `uvm_object_utils_begin() register the class "transaction".Inside this, using uvm_fielde_*(variable, flag) as per uvm code guidance and at last, use uvm_object_utils_end to end the operation. After that using function new constructor for the "transaction" class.

Now after the sequence item class next step is sequence. The below code gives an idea about the sequence -

Code Snap

      class sequence1 extends uvm_sequence#(transaction);   
        `uvm_object_utils(sequence1)    

        function new(input string name="sequence1");     
        super.new(name);     
        endfunction      
       transaction trans;   
       virtual task pre_body();   
       `uvm_info("SEQ1","Pre-Body",UVM_MEDIUM)   
       endtask   

       virtual task body();   

      `uvm_info("SEQ1","Body",UVM_MEDIUM)   

       `uvm_info("SEQ1","Sequence item is created", UVM_NONE)   
       trans= transaction::type_id::create("trans");   
       `uvm_info("SEQ1","Waiting for the Grant from Driver", UVM_NONE)   
        wait_for_grant();   
        `uvm_info("SEQ1","Grant received now randomizing the data", UVM_NONE)   
        assert(trans.randomize());   
        trans.print();   
       `uvm_info("SEQ1","Randomization done and now sent a request to the driver", UVM_NONE)   
   
       send_request(trans);   
       `uvm_info("SEQ1","Waiting for item done response from driver",UVM_NONE)   
        wait_for_item_done();   
        `uvm_info("SEQ1","SEQ ended",UVM_NONE)   
         endtask   

         virtual task post_body();   
         `uvm_info("SEQ1","Post-Body",UVM_NONE)   
         endtask   

        endclass   

Explanation

As we know the uvm_sequence class is a parameterised class. Here, we are creating a class "sequence1" by extending the uvm_sequence class and passing a parameter "transaction" which is our extended class of uvm_sequence_item where we ate creating data transactions. As these are object classes, we need to register it using `uvm_object_utils(). Function new for creating a memory block for the class (same as system Verilog). We can define the predefined methods inside the body() method which is already present in the uvm_sequence class. Here, we are using the object "trans" to the transaction class and creating it using the create() method. Firstly the sequence is waiting for the driver's response using the wait_for_grant() method. Once the grant is received it will randomize the transactions using randomize() function. Then it will create a send request to the driver using the send_request() method. Once the request is received wait_for_item() method will be executed.


For drivers, there are mainly below methods -

1.get_next_item(req)
This method is used to send the request to get the sequence item and then wait for the sequence items. After the execution of the finish_item(req), get_item(req) will be unblocked and have the transaction information.

2. User-defined task
Using that transaction information to drive to the DUT and driving the information to DUT is completely protocol dependent which means the UVM developer cannot come up with predefined methods. So, the driver of the data can be achieved by user-defined tasks. Once the stimulus has been driven to the DUT, if the DUT is giving any response, then that response is sent back to the sequence.

3.item_done()
That response is sent back by using the item_done() to the sequence.

Example

task run_phase(uvm_phase phase);
`get_next_item(req);
//user-defined task;
item_done();  
endtask

Execution of the body method only finishes after the execution of the driver methods.

Code Snap
The below example gives more clarity about the working of the driver.
Here, we are using the run_phase because the driver is a component and we can make use of phase to write the executable logic.

Code Snap

    class driver extends uvm_driver#(transaction);   

    `uvm_component_utils(driver)   

    transaction t;   

    function new(input string name="driver",uvm_component parent = null);   
       super.new(name,parent);   
    endfunction   

     virtual function void build_phase(uvm_phase phase);   
     super.build_phase(phase);   
     t=transaction::type_id::create("t");   
     endfunction   

     virtual task run_phase(uvm_phase phase);   
     forever begin   
     seq_item_port.get_next_item(t);   
         /////////////////   
     seq_item_port.item_done();   
     end   
     endtask   

     endclass :driver   

Explanation

As we know same as uvm_sequence class, the uvm_sequence driver is also a parameterised class. Here, we are creating the driver class by extending the uvm_driver class. Here, we are using the build phase for building the transaction in this class. Then run phase is using the predefined methods because the run phase is used for time-consuming operations. seq_item_port.get_next_item(t) is used to send a request to the sequence for getting the transaction. Once the item is sent and the operation is done seq_item_port.item_done() will executed.

Now, after sequence item , we need to create agent class.

Code Snap

   class agent extends uvm_agent;   

     `uvm_component_utils(agent)   
     driver d;   
     uvm_sequencer #(transaction) seqr;   

      function new(string name="agent",uvm_component parent=null);   
      super.new(name,parent);   
      endfunction   

      virtual function void build_phase(uvm_phase phase);   
       super.build_phase(phase);   
       d=driver::type_id::create("d",this);   
       seqr=uvm_sequencer#(transaction)::type_id::create("seqr",this);   
       endfunction   

      virtual function void connect_phase(uvm_phase phase);   
      super.connect_phase(phase);   
      d.seq_item_port.connect(seqr.seq_item_export);   
      endfunction   

      endclass   

Explanation
Here, we are creating an agent class "agent" by extending the uvm_agent class which is already present in the object class. Using `uvm_component_util , we are registering our "agent" class. After that declare the object for the driver and sequencer class. Then, create the constructor [function new()] for the class "agent". Inside the build phase create the driver and sequencer using the "factory create method". Inside the connect phase, connecting the driver and sequencer.

Now, after this, we need to create an environment class.

Code Snap

   class environment extends uvm_env;   

     `uvm_component_utils(environment)   
     agent a;   
     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);    
     a=agent::type_id::create("a",this);   
     endfunction   

    endclass      

Explanation

Here, we are creating the "environment" class by extending the uvm_env class which is already predefined in the uvm_object class. Using `uvm_component_utils() for registering the class. Then, in the next line create an object to the "agent" class. After that declare a constructor function new for the environment class. Inside the bulid_phase, build the "agent" class.

Now, after creating an environment class we need to create test class. Below code is showing the creation of the test class.

Code Snap

    class test extends uvm_test;   
    `uvm_component_utils(test)   

    sequence1 s;   
    environment env;   

    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);   
     s=sequence1::type_id::create("s");    
     endfunction   

     virtual task run_phase(uvm_phase phase);   
     phase.raise_objection(this);   

     s.start(env.a.seqr);   

     phase.drop_objection(this);   
     endtask   

     endclass      

**Explanation**   
Here, we are declaring the class "test" by extending the uvm_test. Declaring the object for sequence and environment. After that, use the function new constructor for the "test" class. Inside the build_phase, build the "environment" and the "sequence1" class using the factory create method. Inside the run_phase, we raise the objection and then use the start method, where the source is a sequence and the destination is a sequencer. After completion of the task drop the objection.       


At last, we are writing the module. Below code snap is for module creation.    
```systemverilog 
           module tb;      
       initial begin      
           run_test("test");   
        end   
     endmodule   
      Example.1 - Sequence Creation Code  

Explanation
Now, the final step is to declare the module. run_test is the predefined function.

Github code link: https://github.com/mbits-mirafra/UVMCourse/blob/b7_Team_SiliconCrew/stimuls_generation/creating_sequences/sequence_driver.sv

Github log file link: https://github.com/mbits-mirafra/UVMCourse/blob/b7_Team_SiliconCrew/stimuls_generation/creating_sequences/sequence_driver.log

Output Snap
Below output snap shows all the method exution which we declared in the code. Here, in output we can see the random values of the variable a,band y.

seq1
             Output.1 - Output for sequence creation and moving it to driver  

2. `uvm_do sequence macros

How to use 'uvm_do sequence macros
we saw that a sequence calls on the tasks using start_item() and finish_item(). you can avoid putting all these statements in your code by simply calling the UVM Sequence macros that are uvm_do or uvm_do_with. and At the completion time, these macros will be substituted with calls to start_item() and finish_item()

There are primarily 7 types of UVM macros that can be executed in the default sequencer.

  1. `uvm_do
  2. `uvm_do_with
  3. `uvm_do_pri
  4. `uvm_do_pri_with
  5. `uvm_do_on
  6. `uvm_do_on_pri
  7. `uvm_do_on_with
  8. `uvm_do_on_pri_with
  • `uvm_do: Execute this sequence on the default sequencer with the item provided.

macros will identify if the argument provided is a sequence item and will call start() or start_item() accordingly.

  • `uvm_do_with: override any default constraints with inline values.

  • `uvm_do_pri: Execute based on the priority value, used when running multiple sequences simultaneously.

  • `uvm_do_pri_with: Execute based on priority and override default constraints with inline values.

diagram

Untitled Diagram drawio (17)

  Figure.2 - The above figure shows the 'uvm_do sequence macros 
  • Example
       class sequence1 extends uvm_sequence#(transaction);
        `uvm_object_utils(sequence1)

        function new(input string name="sequence1");
        super.new(name);
        endfunction

  
       virtual task pre_body();

       `uvm_info("SEQ1","Pre-Body",UVM_MEDIUM)
        endtask

        virtual task body();
        `uvm_info("SEQ1","Body",UVM_MEDIUM)
        repeat(1) begin
        `uvm_do(req)
        req.print();
        `uvm_do_with(req,{a==4;b==6;})
        req.print();
        `uvm_do_pri(req,5)
        req.print(uvm_default_table_printer);
        end
      endtask

      virtual task post_body();
        `uvm_info("SEQ1","Post-Body",UVM_MEDIUM)
      endtask

       endclass  
       Example.2 - uvm_do macros

In the above example, Here we were trying the uvm_do sequence macros method. In that code, we used only uvm_do and uvm_do_with methods. then more code explanations just refer the example 1. (starting code). while using the uvm_do method we can't customize the code things. this is the main drawback of the uvm_do macros. and **uvm_do_with ** this work like an inline constraint method.

GitHub lab code link : https://github.com/mbits-mirafra/UVMCourse/blob/b7_Team_SiliconCrew/stimuls_generation/do_macro_sequence/do_macro_sequence.sv

GitHub log code link : https://github.com/mbits-mirafra/UVMCourse/blob/b7_Team_SiliconCrew/stimuls_generation/do_macro_sequence/do_macro_sequence_log.log

  • output snap Untitled Diagram drawio (19)

         output.2 - output for uvm_do macros 
    

In the above output, we were using uvm_do macros to generate the sequence. And in that output first tree output is uvm_do which prints some random values and the second tree output is uvm_do_with 4 and 6 values only.


3. Sequence Start()

By calling the start() method of a sequence and supplying a pointer to the sequencer it will use to transmit sequence items to a driver, a sequence can be started. In the sequence, the body task is run after the start() method assigns the sequencer pointer to the sequencer handle called m_sequencer. The start method returns when the sequence body task is completed. Start() is a blocking function since it waits for the body task to complete and thus requires interaction with a driver.

The sequence will get executed upon calling the start of the sequence from the test.

 sequence_name.start(sequencer_name);   

Start method definition

     virtual task start ( uvm_sequencer_base sequencer,      
                         uvm_sequence_base parent_sequence = null,    
                         int this_priority = 1,         
                         bit call_pre_post = 1);   
Arguements Description
sequencer It is necessary to provide the sequencer on which to start a sequence
parent_sequence The sequence from which the current one was called is referred to as the parent_sequence. If parent_sequence = null, then this parent is the root parent sequence
this_priority By default this_priority takes the priority of its parent sequence
call_pre_post By default call_pre_post = 1, then pre_body and post_body will be called. If call_pre_post = 0, then pre_body and post_body will not be called

During sequence execution following methods are called.

Method type Methods Description
task pre_start It is a user-definable callback that is called before the optional execution of the pre_body task.
task pre_body This is intended to be executed before the body method
task pre_do It is a user-definable callback task that is called on parent sequence (if any) before the item is randomized and after sequence has issued wait_for_grant() call.
function mid_do It is a user-definable callback function that is called after the sequence item is randomized, and just before the item is sent to the driver.
task body It is the content of the body method that determines what the sequence does
function post_do It is a user-definable callback function that is called after completing the item using either put or item_done methods.
task post_body This is intended to be executed after the body method
task post_start It is a user-definable callback that is called after the optional execution of the post_body task.

Note

  1. mid_do and post_do are always function.
  2. pre_start and post_start are always called.

Example

        class sequence1 extends uvm_sequence#(transaction);   
          `uvm_object_utils(sequence1)   

          function new(input string name="sequence1");
           super.new(name);
          endfunction
 
          virtual task pre_start();
           `uvm_info("SEQ1","Pre-Start",UVM_MEDIUM)
          endtask
       
          virtual task pre_body();
           `uvm_info("SEQ1","Pre-Body",UVM_MEDIUM)
          endtask
 
          virtual task pre_do(bit is_item);
           `uvm_info("SEQ1","Pre-Do",UVM_MEDIUM)
          endtask
 
          virtual function void mid_do(uvm_sequence_item this_item);
           `uvm_info("SEQ1","Mid-Do",UVM_MEDIUM)
          endfunction
 
          virtual task body();
           `uvm_info("SEQ1","Body",UVM_MEDIUM)
            repeat(2) begin
            req=transaction::type_id::create("req");  
            start_item(req);
            assert(req.randomize());
            finish_item(req);
            end
          endtask
 
          virtual function void post_do(uvm_sequence_item this_item);
           `uvm_info("SEQ1","Post-Do",UVM_MEDIUM)
          endfunction
 
          virtual task post_body();
           `uvm_info("SEQ1","Post-Body",UVM_MEDIUM)
          endtask
 
          virtual task post_start();
           `uvm_info("SEQ1","Post-Start",UVM_MEDIUM)
          endtask

        endclass                                                                  

        class test extends uvm_test;
         `uvm_component_utils(test)

          sequence1 s;
          environment env;
          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);
           s=sequence1::type_id::create("s");
          endfunction

          virtual task run_phase(uvm_phase phase);
           s.start(env.a.seqr,null,0,1); // call_pre_post = 1
          endtask      
      Example.3 - Example for start function  

Github code link: https://github.com/mbits-mirafra/UVMCourse/blob/b7_Team_SiliconCrew/stimuls_generation/sequence_pre_post/sequence_pre_post.sv

Github log file link: https://github.com/mbits-mirafra/UVMCourse/blob/b7_Team_SiliconCrew/stimuls_generation/sequence_pre_post/sequence_pre_post.log

In the above example, we extended sequence1 from the uvm_sequence and for that, we created a factory in the method by using the create method. Also we have initialized the Factory registration Macro after that we declared the constructor method.A test class is extended using uvm_test class and inside that test class we declared the handle for sequence class as s and during its run phase we defined start method using sequence_handle.start().

When start method is called, in the background pre_start, post_start, pre_body, post body, pre_do, mid_do, post_do are created and it is executed internally. In case if we declare any statements inside this functions even it is executed along with the task body.

Output

If call_pre_post = 1, pre_body and post_body will be executed.
call_pre_post = 1

                             Output.3 - Output for start(pre_post=1)  
  • During the run_phase of test class we declared start method and inside that we declared call_pre_post argument value as 1. This makes the start method to create pre_body and post_body along with all do sub_macros.

If call_pre_post = 0, pre_body and post_body will not be executed.
call_pre_post = 0

                             Output.4 - Output for start(pre_post=0)   
  • During the run_phase of test class we declared start method and inside that we declared call_pre_post argument value as 0. This makes the start method to create only do sub_macros leaving the pre and post body.

Nested Sequence

  • Using start()
              class sequence1 extends uvm_sequence #(transaction);
             `uvm_object_utils(sequence1)

              function new(input string name="sequence1");
              super.new(name);
              endfunction
              virtual task pre_body();
             `uvm_info("SEQ1","Pre-Body",UVM_NONE)
              endtask

              virtual task body();
             `uvm_info("SEQ1","Body",UVM_NONE)
              endtask

              virtual task post_body();
              `uvm_info("SEQ1","Post_Body",UVM_LOW) 
              endtask
              endclass

              class sequence2 extends uvm_sequence #(transaction);
              `uvm_object_utils(sequence2)
              sequence1 seq;
              function new(input string name="sequence2");
              super.new(name);
              endfunction
              virtual task pre_body();
              `uvm_info("SEQ2","Pre-Body",UVM_NONE)
              endtask

              virtual task body();
              `uvm_info("SEQ2","Body",UVM_NONE)
              seq=sequence1::type_id::create("seq");
              seq.start(null);

              endtask

              virtual task post_body();
              `uvm_info("SEQ2","Post_Body",UVM_LOW)
              endtask
              endcalss
      Example.4 - Example for Nested sequence by using start method   

Github code link: https://github.com/mbits-mirafra/UVMCourse/blob/b7_Team_SiliconCrew/stimuls_generation/nested_seq/nested_seq_start/nested_seq_start.sv

Github log file link: https://github.com/mbits-mirafra/UVMCourse/blob/b7_Team_SiliconCrew/stimuls_generation/nested_seq/nested_seq_start/nested_seq_start_log.log

The above example is same as that of example2 slight change is that, we are extending sequence1, sequence2 from uvm_sequence. Inside sequence2 we are calling sequence1 using start() method. When start method is called, in the background pre_body, body, post_body are created and it is executed internally.

Output Snap:

Screenshot (422)

                             Output.5 - Output for nested sequence by using start  

When we are calling sequence1 inside sequence2. Execution flow is in a way that first the pre_body and body statements of sequence2 are executed and then, pre_body,body, post_body statements are getting executed. Lastly, the post_body statement of sequence2 is getting executed.

Untitled Diagram-Page-17 drawio

             Figure.3 - Flow for nested sequence by using start()  

**Note:-**No statements are getting skipped using start() method.

  • Using do_macros

The Concept of UVM Sequnce-do_macros() is same as that of UVM Sequence-start(). But the only difference is here instead of start we use do_macros().

Let us see the sequence diagram

flow chart me iodraw drawio (1)

             Figure.4 - Flow for nested sequence by using do_macro  

Here in the above flow-chart as we can observe that, since we are calling sequence2 it is undergoing only through it's pre-body(), body(), post-body. But not sequence1.

Example

       class sequence1 extends uvm_sequence #(transaction);
         `uvm_object_utils(sequence1)

     function new(input string name="sequence1");
       super.new(name);
     endfunction
     virtual task pre_body();
     `uvm_info("SEQ1","Pre-Body",UVM_NONE)
     endtask

     virtual task body();
     `uvm_info("SEQ1","Body",UVM_NONE)
     `uvm_do(req)
     req=transaction::type_id::create("req");
     start_item(req);
     assert(req.randomize());
     finish_item(req);
     endtask

     virtual task post_body();
     `uvm_info("SEQ1","Post_Body",UVM_LOW)
     endtask
       endclass



      class sequence2 extends uvm_sequence #(transaction);
     `uvm_object_utils(sequence2)
      sequence1 seq;
      function new(input string name="sequence2");
      super.new(name);
      endfunction
      virtual task pre_body();
     `uvm_info("SEQ2","Pre-Body",UVM_NONE)
      endtask

      virtual task body();
     `uvm_info("SEQ2","Body",UVM_NONE)
     `uvm_do(seq)
      endtask

     virtual task post_body();
    `uvm_info("SEQ2","Post_Body",UVM_LOW)
     endtask

     endclass


     class test extends uvm_test;
     `uvm_component_utils(test)
     sequence1 s1;
     sequence2 s2;
     environment env;

     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);
     s1=sequence1::type_id::create("s1");
     s2=sequence2::type_id::create("s2");
     endfunction

    virtual task run_phase(uvm_phase phase);
    phase.raise_objection(this);
    s2.start(env.a.seqr);
    phase.drop_objection(this);
    endtask
    endclass

    module tb;
    initial
    begin
    run_test("test"); 
    end
    endmodule
      Example.5 - Example for Nested sequence by using `uvm_do method 

Github code link: https://github.com/mbits-mirafra/UVMCourse/blob/b7_Team_SiliconCrew/stimuls_generation/nested_seq/nested_seq_do/nested_seq_do_macro.sv

Github log file link: https://github.com/mbits-mirafra/UVMCourse/blob/b7_Team_SiliconCrew/stimuls_generation/nested_seq/nested_seq_do/nested_seq_do_macro_log.log

In the above code the classes sequence1, sequence2 are extended from uvm_sequence, with pre_body(), body(), post_body statements. Generally Pre_body statements are executed before the task_body and the post_body statements are executed after the task_body.

In our example we used do_macros that is uvm_do(req) and uvm_do(seq) to execute the statements of a particular phase and we also extended test class from uvm_test, then we declared the constructor method during its build_phase.

we have used phase.raise_objection and phase.drop_objection to clear the conflicts between the sequencer and the driver which means during the execution of statements of the sequencer and the driver, driver statements are executed prior to sequencer which is a improper execution. Always driver statements should be executed after the execution of sequencer statements. So this type of execution can be achieved by using this objection library.

Now we will check what happens when to call only sequence2 in test.

**Output Snap:- **

Screenshot (421)

                             Output.6 - Output for nested sequence by using `uvm_do    

As we are calling only sequence2 it's going through all the post_body(), body(), pre_body() statements of sequence2. Escaping sequence1 we are getting only the body statements in sequence2. So we can say that whatever the extended sequence we are declaring in run task only those are executed, skipping other sequences.

Note:- Here we can observe that by using do_macros the post_body, pre_body statements are getting skipped.


4. Macros for pre-existing items

These macros are used to start sequences and sequence items that do not need to be created.
If we already have a data object that we simply want to send to the sequencer, we can use `uvm_send. There are different variations to this macro

  1. `uvm_send
  2. `uvm_send_pri  
  3. `uvm_rand_send
  4. `uvm_rand_send_pri
  5. `uvm_rand_send_with  
  6. `uvm_rand_send_pri_with

1. `uvm send:

This macro processes the item or sequence that has been created using `uvm_create.
The processing is done without randomization. Essentially, an uvm_do without the create or randomization. It directly sends a seq/item without creating and randomizing it. So, make sure the seq/item is created and randomized first. In this ,create() and randomize() are skipped.

  • Syntax:

          uvm_send(SEQ_OR_ITEM)
    
  • 2. `uvm_send_pri:

It is the same as `uvm_send but additionally, priority is also considered.This is the same as uvm_send except that the sequence item or sequence is executed with the priority specified in the argument. In this, create() and randomize() are skipped and priority is also considered.

  • Syntax:

          `uvm_send_pri(SEQ_OR_ITEM, PRIORITY)
    

3. `uvm_rand_send:

This macro processes the item or sequence that has been already been allocated (possibly with `uvm_create). The processing is done with randomization. Essentially, an uvm_do without the create. It directly sends a randomized seq/item without creating it. So, make sure the seq/item is created first.

  • Syntax:

          `uvm_rand_send(SEQ_OR_ITEM)
    

4. `uvm_rand_send_pri:

This is the same as `uvm_rand_send except that the sequence item or sequence is executed with the priority specified in the argument. It is combination of uvm_rand_send and uvm_send_pri. In this Only create() is skipped, the rest all other steps are executed with the priority mentioned.

  • Syntax:

           `uvm_rand_send_pri(SEQ_OR_ITEM, PRIORITY)
    

5. `uvm_rand_send_with:

This is the same as `uvm_rand_send except that the given constraint block is applied to the item or sequence in a randomize with a statement before execution. It directly sends a randomized seq/item with constraints but without creating it. So, make sure the seq/item is created first. create() is skipped, rest of the other steps are executed along with constraints defined in the second argument.

  • Syntax:

            `uvm_rand_send_with(SEQ_OR_ITEM, CONSTRAINTS)
    

6. `uvm_rand_send_pri_with:

This is the same as `uvm_rand_send_pri except that the given constraint block is applied to the item or sequence in a randomize with the statement before execution. It is a combination of uvm_rand_send_with and uvm_send_pri. create() is skipped, rest of the other steps are executed along with constraints defined with the priority mentioned.

  • Syntax:

         `uvm_rand_send_pri_with(SEQ_OR_ITEM, PRIORITY, CONSTRAINTS)
    

Code Snippet:

                        class sequence1 extends uvm_sequence#(transaction);
                       `uvm_object_utils(sequence1)

                        function new(input string name="sequence1");
                        super.new(name);
                        endfunction
                        transaction trans1;
                        transaction trans2;
                        //transaction trans3;

                         virtual task body();
                         `uvm_create(trans1)   // create the trans1 object

                         `uvm_info("Trans1","Trans1 is created",UVM_NONE);
                         void'(trans1.randomize());
                         `uvm_info("Trans1","Trans1 is randomize",UVM_NONE);
                         `uvm_send(trans1) // sending the trans1
                         trans1.print();
                         `uvm_info("Trans1", "Create , Randomization and send all done for trans1 ",UVM_NONE);

                         `uvm_create(trans2)
                         `uvm_info("Trans2","Trans2 is created",UVM_NONE)
                         `uvm_rand_send(trans2)   // Randomization and send of data in one step
                         `uvm_info("Trans","Trans2 is randomize and the send data",UVM_NONE)
                         trans2.print();
                         `uvm_info("Trans2","using with ",UVM_NONE)
                         `uvm_rand_send_with(trans2,{trans2.a==2;
                          trans2.b==4;})    // using inline constraint
                          trans2.print();
                         `uvm_info("Trans2","Create , randomization and send is done for trans2",UVM_NONE)

                         endtask
                         endclass
      Example.6 - Example for pre-existing functions   

Github code link: https://github.com/mbits-mirafra/UVMCourse/blob/b7_Team_SiliconCrew/stimuls_generation/pre_existing_macros/macro_stimulus.sv

Github log file link: https://github.com/mbits-mirafra/UVMCourse/blob/b7_Team_SiliconCrew/stimuls_generation/pre_existing_macros/macro_stimulus_log.log

Explanation:

Here, we are creating a sequence class "sequence1" by extending the uvm_sequence class which is already present in the object class. Using `uvm_object_utils we are registering our "sequence1" class. Then, create the constructor [function new()]for the class "sequence1".
Here, we are declaring a task in which we create the trans1 object and randomize it and after this, we are sending the trans1, So create, randomization, and send all are done for "trans1".
Here, we created the trans2 object and we are doing randomization and send of data in one step with the help of uvm_rand_send.
For the object trans2, we are using " uvm_rand_send_with" . by using this randomization, sending of data and using inline constraint are done in one step.

Output Snap:

tt drawio

                             Output.7 - Output for pre-existing methods       

In the above output, we are displaying trans1 and trans2. In the trans1 first we are creating the trans1 object after that we are randomizing the trans1 individually and after successful randomization, we are sending the data with the help of `uvm_send.
For the trans2 object, we are doing randomization and sending of data in one step with the help of uvm_rand_send.
Again for the trans2 object, we are doing randomization, sending data with the inline constraint in one step with the help of uvm_rand_send_with


5. 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 env.

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.

  • 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.

virtual seq drawio

             Figure.5 - Visualisation of Environment with Virtual Sequence & Virtual Sequencer  

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.

  • Example
      //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  
      Example.7 - Example for Virtual sequence    

Github code link: https://github.com/mbits-mirafra/UVMCourse/blob/b7_Team_SiliconCrew/stimuls_generation/virtual_sequence/virtual_sequence.sv

Github log file link: https://github.com/mbits-mirafra/UVMCourse/blob/b7_Team_SiliconCrew/stimuls_generation/virtual_sequence/virtual_sequence.log

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:

virseqnew drawio (1)
virseqnew11 drawio (1)

                             Output.8 - Output for Virtual Sequence Usage       

6. UVM Sequence Library

SEQUENCE LIBRARY :

UVM provides a class for randomly creating and running sequences. This class is called uvm_sequence_library. The uvm_sequence_library class is generally inherited from uvm_sequence, from this we can know that sequence library is also similar to a sequence. We can define multiple derived test sequences in a program and this is merged into a single test sequence which increases the random nature of execution. Such merging can be achieved by using uvm_sequence_library.

sequence_library

           Figure 1. Above figure shows the merging of many  sequences into a single sequence.   

The uvm_sequence_library also provides control to user in terms of sequence selection logic and which sequences to be executed prior to any other sequences. You can ever order the sequence items and can even remove the irrelevant sequence items.

sequence library hierarchy

           Figure 2. sequence library heirarchy.   

1. Sequence Registration Inside Sequence Library :

Sequences can be registered with a sequence library either by using a macro or function calls. To register a sequence(s) with every instance of a particular type of sequence library, the add_typewide_sequence() is used.
Code snippets for multiple uvm components and objects are shown below.

1. Calling “add_typewide_sequence” from sequence library:

add_typewide_sequence()is one of the method for registering the multiple sequences into a single sequence_library. This syntax is used when we are defining the sequences inside the sequence library class.

       class my_seq_lib extends uvm_sequence_library #(my_packet);
           . . .
           function new(string name = "my_seq_lib");
           . . .
              add_typewide_sequence(my_seq.get_type());
              add_typewide_sequence(my_seq2.get_type());
           endfunction
          . . .
        endclass: my_seq_lib

2. Calling “add_sequence” from testcase:

add_sequence()is one of the method for registering the multiple sequences into a single sequence_library. This syntax is used when we are defining the sequences inside the test class.

           class my_test extends uvm_test;
            . . .

                 virtual function void build_phase(uvm_phase phase);
                  . . .
                       seq_lib.add_sequence(my_seq.get_type());
                       seq_lib.add_sequence(my_seq2.get_type());
                  endfunction: build_phase
              . . .
            endclass  

3. Calling “uvm_add_to_seq_lib” from testcase:

uvm_add_to_seq_lib() is one of the method for registering the multiple sequences into a single sequence_library. This syntax is used when we are defining the sequences inside the test class and that too outside uvm_phases.

                class my_test extends uvm_test;

               `uvm_add_to_seq_lib(my_seq, my_seq_lib)
              `uvm_add_to_seq_lib(my_seq2, my_seq_lib)
               . . .
                
               endclass    

4. Calling “add_typewide_sequences” from sequence library:

add_typewide_sequences()is one of the method for registering the multiple sequences into a single sequence_library. This syntax is used when we are defining the sequences inside the sequence library class. Instead of adding each sequence one by one into the sequence_library, we can driectly add multiple sequences with the use of only one add_typewide_sequences(). By observing the below code snippet we can have the understand this method.

                   class my_seq_lib extends uvm_sequence_library #(my_packet);
                    . . .
                        function new(string name = "my_seq_lib");
                         . . .
                            add_typewide_sequences({my_seq.get_type(),
                             my_seq2.get_type()});
                         endfunction
                     . . .
                     endclass: my_seq_lib  

5. Calling “add_sequences’ from testcase:

add_sequences()is one of the method for registering the multiple sequences into a single sequence_library. This syntax is used when we are defining the sequences inside the test class. Instead of adding each sequence one by one into the sequence_library, we can driectly add multiple sequences with the use of only one add_sequences(). By observing the below code snippet we can have the understand this method.

                          class my_test extends uvm_test;
                             . . .

                              virtual function void build_phase(uvm_phase phase);
                                 . . .
                                 seq_lib.add_sequences({my_seq.get_type(),
                                 my_seq2.get_type()});
                              endfunction: build_phase
                             . . .
                          endclass    

2. Sequence Library Controls :

Selection mode:

  • Syntax : uvm_sequence_lib_mode selection_mode

It specifies the mode or the order in which the sequences to be executed and the selection of the sequence can be customized by the user using 4 variations. They are,

Enum Value Description
UVM_SEQ_LIB_RAND Random sequence selection (Default selection_mode).
UVM_SEQ_LIB_RANDC Random cyclic sequence selection
UVM_SEQ_LIB_ITEM Emit only items, no sequence execution
UVM_SEQ_LIB_USER Apply a user-defined random-selection algorithm
  1. Select rand : The index variable that is randomized to select the next sequence to execute when in UVM_SEQ_LIB_RAND mode.
  2. Select randc : The index variable that is randomized to select the next sequence to execute when in UVM_SEQ_LIB_RANDC mode.
  3. Select item : Generates data items only, no sequence is executed by using UVM_SEQ_LIB_ITEM mode.
  4. select sequence : Generates an index used to select the next sequence to execute. Overrides must return a value between 0 and max, inclusive. Used only for UVM_SEQ_LIB_USER selection mode.

Library behavior is controlled by properties.

  1. min_random_count : Lower constraint for random number (default 10).
  2. max_random_count : Upper constraint for random number (default 10).

This min and max random count helps in execution of sequence library class. There will be random number generated between this range and for that number of times the sequence library gets executed. For example, if there are 3 sequences added into a sequence_library and if min_random_count=5 and max_random_count=9, then the simulator will choose a number between this range(lets say it as 7). Generally the execution of the sequences depends on the selection_mode(here let us consider "randc" mode),then sequence1,sequence2 and sequence3 are executed for 2 times each and any one sequence will be executed for one more iteration.

Sequence Registration :

All classes of sequences are derived directly from uvm_sequence (as shown in figure 2) and those sequences are merged into a single sequence using uvm_sequence_library. This sequence_library has to be registered with the factory using uvm_sequence_library_utils macro.

This class must be instantiated and also need to create constructor while using in the other classes. We also need to call init_sequence_library in the constructor in order to setup and construct the library infrastructure. Init library cannot have a task body, cannot define its own sequence but it can only have predefined sequences into the library.

  • Example :
     class my_seq_lib extends uvm_sequence_library#(transaction);
          `uvm_object_utils(my_seq_lib)
          `uvm_sequence_library_utils(my_seq_lib)
           seq1 s1;
           seq2 s2;
                  
           function new(string name="my_seq_lib");
                 super.new(name);
              selection_mode=UVM_SEQ_LIB_RANDC;
              min_random_count=5;
              max_random_count=10;

             s1=seq1::type_id::create("s1");
             s2=seq2::type_id::create("s2");
             add_typewide_sequence(s1.get_type());
             add_typewide_sequence(s2.get_type());

             init_sequence_library();
       endfunction

  endclass



 class test extends uvm_test;
   `uvm_component_utils(test)
     my_seq_lib seq_lib;
    environment env;

  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);
     seq_lib=my_seq_lib::type_id::create("seq_lib");
  endfunction

  virtual task run_phase(uvm_phase phase);
    phase.raise_objection(this);
     uvm_config_db#(uvm_sequence_base)::set(this,"env.a.seqr.main_phase","default_sequence",seq_lib);
     seq_lib.print();
   phase.drop_objection(this);
  endtask

endclass

   module tb;
     initial begin
       run_test("test");
      end
              endmodule

In the above example, we extended sequence1 and sequence 2 from the uvm_sequence and for that we created a factory in the method by using the create method. Also we extend the class name by my_seq_lib by using UVM inbuild library uvm_sequence_library and after that initializing the Factory registration Macro. Then declaring the constructor method. seq_lib,s1 and s2 are the handles of the sequence library and seq classes. By using the following syntax, created the Factory method handle_name= class_name::type_id::create("handle_name");

Library behavior is controlled by properties that is min_random_count and max_random_count and it generates a random number between that range. For the selection of sequences from the library we have selection modes. In this example we used randc as a selection mode and we used init_sequence_library to set and construct the sequence_library class in order to generate the sequences from sequence_library we used uvm_config_db method.

In our example the field name for config_db is default_sequence which internally generates the random sequence based on the specified selection_mode and passes that sequence to sequence_libary without calling any get method in sequence_library class. This is because default_sequence field internally have a get method which is pointing to the sequence_library class.

GitHub lab code link https://github.com/mbits-mirafra/UVMCourse/blob/b7_Team_SiliconCrew/stimuls_generation/sequence_library/sequence_library.sv

GitHub log code link https://github.com/mbits-mirafra/UVMCourse/blob/b7_Team_SiliconCrew/stimuls_generation/sequence_library/sequence_library.log

  • Output Snap : uvm_seq_lib

          Output 1. Output of different sequences obtained after merging them into a single sequence library.   
    

uvm_seq_lib_report

                                       Output 2. Output for uvm report. 

7. UVM Sequence Arbitration

When multiple sequences trying to access a same driver at the same time, the sequencer schedules the sequences through a process called arbitration. The sequencer gives access to certain sequence over others based on the modes called arbitration modes.

image

                             Figure.8 - Arbitration in Sequencer

In the above figure, these 4 concurrent sequences races to gain the grant so that their Transaction items could be sent to the DUT via Driver.

There are 6 different arbitration modes. They are:

Arbitration Name Algorithm
SEQ_ARB_FIFO This is the default sequencer arbitration algorithm. It grants requests in FIFO order. Priorities will not considered.
SEQ_ARB_RANDOM It grants requests in random. Priorities will not considered.
SEQ_ARB_WEIGHTED It grants requests based on the priority values.
SEQ_ARB_STRICT_FIFO Sequences with high priority value will be granted in FIFO order.
SEQ_ARB_STRICT_RANDOM Sequences with high priority value will be granted randomly.
SEQ_ARB_USER Grants are done based on the user defined algorithm.

Note: Different simulators and versions have different arbitration names like as UVM_SEQ_ARB_FIFO or SEQ_ARB_FIFO.

set_arbitration method

To set a particular arbitration mechanism, the set_arbitration function is used and it is defined in the uvm_sequencer_base class.

syntax:

<sequencer_name>.set_arbitration(<Arbitration_name>);

get_arbitration method :

The get_arbitration function returns the current arbitration mode or algorithm set for the sequencer.

syntax:

<sequencer_name>.get_arbitration();

setting priority value :

We are executing sequences using start() method. The start method has the this_priority parameter, by default this_priority parameter has the default value is 100. So this value can be changed when the start() method was called.

syntax:

<sequence_name>.start(<sequencer>,.this_priority=<priority value>)

SEQ_ARB_FIFO

This is the default arbitration method where the sequences are started in parallel and execute them in the FIFO order without the consider of the priority values.

Let understand this by an examples with priority and without priority values.

without priority :

   class test extends uvm_test;
    ...
    ...
   sequence1 s[4];
    ...
    ...
    e.a.seq.set_arbitration(SEQ_ARB_FIFO);
    fork

      s[0].start(e.a.seq);
      s[1].start(e.a.seq);
      s[2].start(e.a.seq);
      s[3].start(e.a.seq);

    join
    ...
    ...
    endclass
      Example.9 - Example for Sequence Arbitration FIFO Without Priority  

Here in this example the 4 sequences are created and starts in parallel and not giving any priority, so by default it has the priority value has 100.

output :

image

                             Output.11 - Output of SEQ_ARB_FIFO without priority.

Here all the 4 sequences are started in parallel inside a fork join block and the sequences has ended in the order that the sequence s[0] has ended first as it is comes in first then s[1], s[2] and last s[3] because it undergoes in the FIFO order.

Git Lab code link : https://github.com/mbits-mirafra/UVMCourse/tree/b7_Team_SiliconCrew/stimuls_generation/sequence_arbitration/UVM_SEQ_ARB_FIFO/without_priority

Git Lab output link : https://github.com/mbits-mirafra/UVMCourse/blob/b7_Team_SiliconCrew/stimuls_generation/sequence_arbitration/UVM_SEQ_ARB_FIFO/without_priority/seq-arb_fifo.log

with priority :

   class test extends uvm_test;
    ...
    ...
   sequence1 s[4];
    ...
    ...
    e.a.seq.set_arbitration(SEQ_ARB_FIFO);
    fork

      s[0].start(e.a.seq,.this_priority(200));
      s[1].start(e.a.seq,.this_priority(400));
      s[2].start(e.a.seq,.this_priority(300));
      s[3].start(e.a.seq,.this_priority(100));


    join
    ...
    ...
    endclass
      Example.10 - Example for Sequence Arbitration FIFO Without Priority  

**Github code link:**https://github.com/mbits-mirafra/UVMCourse/tree/b7_Team_SiliconCrew/stimuls_generation/sequence_arbitration/UVM_SEQ_ARB_FIFO/without_priority

Github log file link: https://github.com/mbits-mirafra/UVMCourse/blob/b7_Team_SiliconCrew/stimuls_generation/sequence_arbitration/UVM_SEQ_ARB_FIFO/without_priority/seq-arb_fifo.log

In this example the priorities are given. The sequence s[1] has the highest priority then s[2], s[0] and s[3].

output :

image

                             Output.12 - Output of SEQ_ARB_FIFO with priority.

The output shows that the sequences are executed in the first in first out order but not based upon the priority values.

Git Lab code link : https://github.com/mbits-mirafra/UVMCourse/blob/b7_Team_SiliconCrew/stimuls_generation/sequence_arbitration/UVM_SEQ_ARB_FIFO/with_priority/

Git Lab output link : https://github.com/mbits-mirafra/UVMCourse/blob/b7_Team_SiliconCrew/stimuls_generation/sequence_arbitration/UVM_SEQ_ARB_FIFO/with_priority/seq-arb_fifo.log

SEQ_ARB_RANDOM

The sequences are executed in random manner but not consider the priority values.

without priority :

   class test extends uvm_test;
    ...
    ...
   sequence1 s[4];
    ...
    ...
    e.a.seq.set_arbitration(SEQ_ARB_RANDOM);
    fork

      s[0].start(e.a.seq);
      s[1].start(e.a.seq);
      s[2].start(e.a.seq);
      s[3].start(e.a.seq);

    join
    ...
    ...
    endclass
      Example.11 - Example for Sequence Arbitration Random Without Priority  

output :

image

                             Output.13 - Output of SEQ_ARB_RANDOM without priority.

Here the sequences are executed randomly based upon the simulator generated random sequences.

Git Lab code link : https://github.com/mbits-mirafra/UVMCourse/tree/b7_Team_SiliconCrew/stimuls_generation/sequence_arbitration/UVM_SEQ_ARB_RANDOM/without_priority

Git Lab output link : https://github.com/mbits-mirafra/UVMCourse/blob/b7_Team_SiliconCrew/stimuls_generation/sequence_arbitration/UVM_SEQ_ARB_RANDOM/without_priority/seq_arb_random.log

with priority :

   class test extends uvm_test;
    ...
    ...
   sequence1 s[4];
    ...
    ...
    e.a.seq.set_arbitration(SEQ_ARB_RANDOM);
    fork

      s[0].start(e.a.seq,.this_priority(200));
      s[1].start(e.a.seq,.this_priority(100));
      s[2].start(e.a.seq,.this_priority(500));
      s[3].start(e.a.seq,.this_priority(100));

    join
    ...
    ...
    endclass
      Example.12 - Example for Sequence Arbitration Random With Priority  

In this example the priorities to the sequences are given.

output :

image

                             Output.14 - Output of SEQ_ARB_RANDOM with priority.

Here the the sequences executed randomly but not depend on the priority values so sequence s[3] has ended first even though it has least priority value, then s[2], s[1] and s[0].

Git Lab code link : https://github.com/mbits-mirafra/UVMCourse/blob/b7_Team_SiliconCrew/stimuls_generation/sequence_arbitration/UVM_SEQ_ARB_RANDOM/with_priority/

Git Lab output link : https://github.com/mbits-mirafra/UVMCourse/blob/b7_Team_SiliconCrew/stimuls_generation/sequence_arbitration/UVM_SEQ_ARB_RANDOM/with_priority/seq_arb_random.log

SEQ_ARB_WEIGHTED

In this arbitration mode the sequences are executed based on the weight of the sequence. Here the priority values are considered as the weighted value. So the more priority value the more chance to execute that sequence. If two or more sequences has the same weighted value or priority value, the simulator executes sequences in random manner.

without priority :

   class test extends uvm_test;
    ...
    ...
   sequence1 s[4];
    ...
    ...
    e.a.seq.set_arbitration(SEQ_ARB_WEIGHTED);
    fork

      s[0].start(e.a.seq);
      s[1].start(e.a.seq);
      s[2].start(e.a.seq);
      s[3].start(e.a.seq);

    join
    ...
    ...
    endclass
      Example.13 - Example for Sequence Arbitration Weighted Without Priority   

output :

image

                             Output.15 - Output of SEQ_ARB_WEIGHTED without priority.

The output above sequencer executes the sequences in the random manner because all the sequences has the same default priority value or weighted value.

Git Lab code link : https://github.com/mbits-mirafra/UVMCourse/blob/b7_Team_SiliconCrew/stimuls_generation/sequence_arbitration/UVM_SEQ_ARB_WEIGHTED/without_priority/

Git Lab output link : https://github.com/mbits-mirafra/UVMCourse/blob/b7_Team_SiliconCrew/stimuls_generation/sequence_arbitration/UVM_SEQ_ARB_WEIGHTED/without_priority/seq_arb_weighted.log

with priority :

   class test extends uvm_test;
    ...
    ...
   sequence1 s[4];
    ...
    ...
    e.a.seq.set_arbitration(SEQ_ARB_RANDOM);
    fork

      s[0].start(e.a.seq,.this_priority(300));
      s[1].start(e.a.seq,.this_priority(100));
      s[2].start(e.a.seq,.this_priority(400));
      s[3].start(e.a.seq,.this_priority(100));

    join
    ...
    ...
    endclass
      Example.14 - Example for Sequence Arbitration Weighted With Priority   

Here the priority values acts like as the weighted value.

output :

image

                             Output.16 - Output of SEQ_ARB_WEIGHTED with priority.

As you can see in the output the sequence s[2] is executed and ended first because it has the high weighted value, then s[2], s[1] and last s[0].

Git Lab code link : https://github.com/mbits-mirafra/UVMCourse/blob/b7_Team_SiliconCrew/stimuls_generation/sequence_arbitration/UVM_SEQ_ARB_WEIGHTED/with_priority/

Git Lab output link : https://github.com/mbits-mirafra/UVMCourse/blob/b7_Team_SiliconCrew/stimuls_generation/sequence_arbitration/UVM_SEQ_ARB_WEIGHTED/with_priority/seq_arb_weighted.log

SEQ_ARB_STRICT_FIFO

In this strict FIFO arbitration mode the sequencer executes the sequences based on the priority value. The high priority value sequence has first occurred. If two or more sequences has the same priority value then sequencer executes them in the FIFO order.

without priority :

    class test extends uvm_test;
    ...
    ...
   sequence1 s[4];
    ...
    ...
    e.a.seq.set_arbitration(SEQ_ARB_STRICT_FIFO);
    fork

      s[0].start(e.a.seq);
      s[1].start(e.a.seq);
      s[2].start(e.a.seq);
      s[3].start(e.a.seq);

    join
    ...
    ...
    endclass
      Example.15 - Example for Sequence Arbitration Strict FIFO Without Priority  

Here priority values are not given so all the sequences has the same priority values so all the sequences are executed by the sequencer in FIFO order like as the arbitration mode "SEQ_ARB_FIFO".

output :

image

                             Output.17 - Output of SEQ_ARB_STRICT_FIFO without priority.

In this output the sequences are executed in the FIFO order. The first one first executed then next and soon.

Git Lab code link : https://github.com/mbits-mirafra/UVMCourse/blob/b7_Team_SiliconCrew/stimuls_generation/sequence_arbitration/UVM_SEQ_ARB_STRICT_FIFO/without_priority/

Git Lab output link : https://github.com/mbits-mirafra/UVMCourse/blob/b7_Team_SiliconCrew/stimuls_generation/sequence_arbitration/UVM_SEQ_ARB_STRICT_FIFO/without_priority/seq_arb_strict_fifo.log

with priority :

    class test extends uvm_test;
    ...
    ...
   sequence1 s[4];
    ...
    ...
    e.a.seq.set_arbitration(SEQ_ARB_STRICT_FIFO);
    fork

      s[0].start(e.a.seq,.this_priority(300));
      s[1].start(e.a.seq,.this_priority(100));
      s[2].start(e.a.seq,.this_priority(400));
      s[3].start(e.a.seq,.this_priority(100));

    join
    ...
    ...
    endclass  
      Example.16 - Example for Sequence Arbitration Strict FIFO With Priority    

output :

image

                             Output.18 - Output of SEQ_ARB_STRICT_FIFO with priority.

Here in this output sequence s[2] was executed first because it has high priority value(400), next s[0] whose priority value value is 300 then s[1] and s[3], both has same priority value so they executed in FIFO order so s[1] is executed first than s[3] because it comes first.

Git Lab code link : https://github.com/mbits-mirafra/UVMCourse/blob/b7_Team_SiliconCrew/stimuls_generation/sequence_arbitration/UVM_SEQ_ARB_STRICT_FIFO/with_priority/

Git Lab output link : https://github.com/mbits-mirafra/UVMCourse/blob/b7_Team_SiliconCrew/stimuls_generation/sequence_arbitration/UVM_SEQ_ARB_STRICT_FIFO/with_priority/seq_arb_strict_fifo.log

SEQ_ARB_STRICT_RANDOM

The SEQ_ARB_STRICT_RANDOM arbitration mode, the sequencer executes the sequences based on the priority value. If two or more sequences has the same priority value then sequencer schedules them in the random order.

without priority :

    class test extends uvm_test;
    ...
    ...
   sequence1 s[5];
    ...
    ...
    e.a.seq.set_arbitration(SEQ_ARB_STRICT_RANDOM);
    fork

      s[0].start(e.a.seq);
      s[1].start(e.a.seq);
      s[2].start(e.a.seq);
      s[3].start(e.a.seq);
      s[4].start(e.a.seq);

    join
    ...
    ...
    endclass 
      Example.17 - Example for Sequence Arbitration Strict Random Without Priority      

Here in this code the priority values will not given so the sequencer schedules the sequences in random manner like as the SEQ_ARB_RANDOM mode.

image

                             Output.19 - Output of SEQ_ARB_STRICT_RANDOM without priority.

The output shows that the sequences are executed and completed in random manner based upon the simulator.

Git Lab code link : https://github.com/mbits-mirafra/UVMCourse/blob/b7_Team_SiliconCrew/stimuls_generation/sequence_arbitration/UVM_SEQ_ARB_STRICT_RANDOM/without_priority/

Git Lab output link : https://github.com/mbits-mirafra/UVMCourse/blob/b7_Team_SiliconCrew/stimuls_generation/sequence_arbitration/UVM_SEQ_ARB_STRICT_RANDOM/without_priority/seq_arb_strict_random.log

with priority :

    class test extends uvm_test;
    ...
    ...
    sequence1 s[5];
    ...
    ...
    e.a.seq.set_arbitration(SEQ_ARB_STRICT_RANDOM);
    fork

      s[0].start(e.a.seq,.this_priority(300));
      s[1].start(e.a.seq,.this_priority(200));
      s[2].start(e.a.seq,.this_priority(200));
      s[3].start(e.a.seq,.this_priority(200));
      s[4].start(e.a.seq,.this_priority(200));
      s[5].start(e.a.seq,.this_priority(500));

    join
    ...
    ...
    endclass
      Example.18 - Example for Sequence Arbitration Strict Random With Priority      

output :

image

                             Output.20 - Output of SEQ_ARB_STRICT_RANDOM with priority.

Here the sequences are executed based upon the priority value. s[5] has high priority value so it executes first then s[0] has priority value 300 so it executes next. Remaining sequences has same priority values so they are executed in randomly.

Git Lab code link : https://github.com/mbits-mirafra/UVMCourse/blob/b7_Team_SiliconCrew/stimuls_generation/sequence_arbitration/UVM_SEQ_ARB_STRICT_RANDOM/with_priority/

Git Lab output link : https://github.com/mbits-mirafra/UVMCourse/blob/b7_Team_SiliconCrew/stimuls_generation/sequence_arbitration/UVM_SEQ_ARB_STRICT_RANDOM/with_priority/seq_arb_strict_random.log

SEQ_ARB_USER

If none of the above modes does not satisfy your needs you can create your own arbitration scheme by extracting the uvm_sequencer class and defines it user_priority_arbitration function.

syntax :
virtual function integer user_priority_arbitration(integer avail_sequences[$])

This method inputs a Sequences queue as an argument. The user implemented algorithm needs to return an integer to select one of the Sequence from the Sequence queue.

By default the function has the following code:

virtual function integer user_priority_arbitration(integer avail_sequences[$]);
    return avail_sequences[0];
endfunction

snippet :

class sequencer extends uvm_sequencer#(transaction);
 ...
 ...
 virtual function integer user_priority_arbitration(integer avail_sequences[$]);
    int end_index;
    end_index=avail_sequences.size()-1;
    return(avail_sequences[end_index]);
  endfunction

endclass

 class test extends uvm_test;
    ...
    ...
   sequence1 s[5];
    ...
    ...
    e.a.seq.set_arbitration(SEQ_ARB_USER);
    fork

      s[0].start(e.a.seq);
      s[1].start(e.a.seq);
      s[2].start(e.a.seq);
      s[3].start(e.a.seq);
      s[4].start(e.a.seq);

    join
    ...
    ...
 endclass
      Example.19 - Example for Sequence Arbitration Of User-Defined type      

In the above example of user defined user_priority_arbitration() method, Sequence priority order is reversed by overriding the function.

output :

image

                             Output.21 - Output of SEQ_ARB_USER .

The output shows that the sequences are scheduled and executed by the sequencer in the reverse order from s[4] to s[0].

Git Lab code link : https://github.com/mbits-mirafra/UVMCourse/blob/b7_Team_SiliconCrew/stimuls_generation/sequence_arbitration/UVM_SEQ_ARB_USER/

Git Lab output link : https://github.com/mbits-mirafra/UVMCourse/blob/b7_Team_SiliconCrew/stimuls_generation/sequence_arbitration/UVM_SEQ_ARB_USER/seq_arb_user.log


Clone this wiki locally