Wednesday, 24 May 2017

Packet flow: From Sequence to Driver and Drive to Sequence



User code:



REQ-RSP Packet flow diagram:



NOTE:: In the above diagram for Sequence to Driver packet flow black ink is used and for Driver to Sequence packet flow blue ink is used. .


Sequence to Driver:
1. In run_phase of testcase we start the sequence: seq.start(sequencer);

2. Now sequence's start method calls the body of the sequence. Inside the body of sequence, we have created the packet. Once packet is getting created, start_item(pkt) is called.

3. This start_item() calls the wait_for_grant() method of sequencer. Which pushes the current sequence inside the sequence queue of sequencer. And then it waits for that sequence entry to get granted.

4. Driver calls the get_next_item() method implemented in sequencer. This method is blocking until the sequence is available inside the sequence queue of sequencer. And out of available sequences, sequencer selects the sequence based on the arbitration mode set.

5. Once the sequence entry gets granted, sequence comes out of the start_item() and it randomizes the packet as per requirement (in body task). Now once packet got randomized, sequence calls the finish_item().

6. finish_item() calls send_request() method defined in sequencer. In send_request() it pushes the packet inside the request queue of sequencer.

7. In get_next_item(), once sequence got selected it waits for the packet to be available inside request queue of sequencer. Once packet is available, it is handed over to driver as an output of get_next_item(). And driver will start driving this packet on interface.

8. After send_request(), sequence calls wait_for_item_done() method of sequencer. This method waits for wait_for_item_seq_id variable to be updated with its own sequence id value.[Ultimately, it waits for the indication that driver has completed the driving of current sequence packet]

9. Once packet has been driven by the driver, driver will call item_done method of sequencer. This item_done method updates the wait_for_item_seq_id variable with the sequence id value of granted sequence whose packet has been driven by driver.

10. This will unblock wait_for_item_done and sequence returns from finish_item().


Driver to Sequence:
1. When sequence is done with finish_item(), sometimes response is required by the sequence. And that RSP packet can be get by the sequence using get_response() method, which is blocking.

2. This method waits for the size of the response queue of sequence to be non-zero.

3. Whenever driver calls item_done() method of sequencer, it may or may not pass packet as response. If the driver sends RSP packet then this packet is passed to the put_response method. This put_response method of sequencer calls put_response method of sequence, which pushes the RSP packet inside its own response queue.

4. This will unblock the get_response(), as packet is available inside the response queue. And it will return RSP as an output of get_response().

Friday, 19 May 2017

Sequence Arbiteration

When we start multiple sequences in testcase or virtual sequence, all these sequences are getting stored inside the sequencer in queue. So we can assign the priority when we want to prioritize particular sequence over other sequences.

UVM Sequencer has six arbitration modes:
  • UVM_SEQ_ARB_FIFO (This is default mode)
  • UVM_SEQ_ARB_RANDOM
  • UVM_SEQ_ARB_STRICT FIFO
  • UVM_SEQ_ARB_STRITCT_RANDOM
  • UVM_SEQ_ARB_WEIGHTED
  • UVM_SEQ_ARB_USER

Code:
---------------------------------------------------------------------
class my_seq extends uvm_sequence#(my_packet);
  ...

  virtual task body();
    m_sequencer.set_arbitration(SEQ_ARB_FIFO);
  
    pkt = my_packet::type_id::create("pkt");
    start_item(pkt);
    pkt.randomize() with {pkt.data == id;}; 
    finish_item(pkt);
  endtask: body

endclass: my_seq
---------------------------------------------------------------------

---------------------------------------------------------------------
class my_vir_seq extends uvm_sequence;
my_seq seq[];

  ...
  virtual task body();
    seq = new[4];
   
    for (int i = 0; i < seq.size; i++)
    begin
      seq[i] = my_seq::type_id::create($sformatf("seq[%0d]", i));
      seq[i].id = i + 1;
    end
    fork
       seq[0].start(p_sequencer.my_seqr, .this_priority(100)); // 100 is the default priority
       seq[1].start(p_sequencer.my_seqr, .this_priority(50)); 
       seq[2].start(p_sequencer.my_seqr, .this_priority(150));
       seq[3].start(p_sequencer.my_seqr, .this_priority(49)); 
    join
  endtask: body

endclass: my_vir_seq
---------------------------------------------------------------------

In above code, we have set the priority inside the child sequence, the reason behind that is all these sequences are going to start on that sequencer and queue of this sequencer is going to fill with the sequences.

UVM_SEQ_ARB_FIFO

Here sequencer grants sequences in FIFO order regardless of the mentioned priority.

So in blow order driver will get the packet:
UVM_INFO my_driver.sv(15) @ 0: uvm_test_top.env.agent.driver [driver] Received data is 1
UVM_INFO my_driver.sv(15) @ 0: uvm_test_top.env.agent.driver [driver] Received data is 2
UVM_INFO my_driver.sv(15) @ 0: uvm_test_top.env.agent.driver [driver] Received data is 3
UVM_INFO my_driver.sv(15) @ 0: uvm_test_top.env.agent.driver [driver] Received data is 4


UVM_SEQ_ARB_RANDOM

Here, we will do below modification in child sequence:
---------------------------------------------------------------------
m_sequencer.set_arbitration(SEQ_ARB_RANDOM);
---------------------------------------------------------------------

In this case, sequencer randomly grants the sequences without bothering about the priority.
So the output we got:
UVM_INFO my_driver.sv(15) @ 0: uvm_test_top.env.agent.driver [driver] Received data is 4
UVM_INFO my_driver.sv(15) @ 0: uvm_test_top.env.agent.driver [driver] Received data is 1
UVM_INFO my_driver.sv(15) @ 0: uvm_test_top.env.agent.driver [driver] Received data is 3
UVM_INFO my_driver.sv(15) @ 0: uvm_test_top.env.agent.driver [driver] Received data is 2

UVM_SEQ_ARB_STRICT_FIFO

Here, we will do below modification in child sequence:
---------------------------------------------------------------------
m_sequencer.set_arbitration(SEQ_ARB_STRICT_FIFO);
---------------------------------------------------------------------

And in virtual sequence:
---------------------------------------------------------------------
    seq[0].start(p_sequencer.my_seqr, .this_priority(100));
    seq[1].start(p_sequencer.my_seqr, .this_priority(50));
    seq[2].start(p_sequencer.my_seqr, .this_priority(150));
    seq[3].start(p_sequencer.my_seqr, .this_priority(150));
---------------------------------------------------------------------

In this case, sequencer will strictly follow the priority of each and every sequence inside the FIFO.
So the output would be:

UVM_INFO my_driver.sv(15) @ 0: uvm_test_top.env.agent.driver [driver] Received data is 3
UVM_INFO my_driver.sv(15) @ 0: uvm_test_top.env.agent.driver [driver] Received data is 4
UVM_INFO my_driver.sv(15) @ 0: uvm_test_top.env.agent.driver [driver] Received data is 1
UVM_INFO my_driver.sv(15) @ 0: uvm_test_top.env.agent.driver [driver] Received data is 2

Here, seq[2] and [3] has the same priority but as it is a strict FIFO, it will grant the closest one.

UVM_SEQ_ARB_STRICT_RANDOM

Here, we will do below modification in child sequence:
---------------------------------------------------------------------
m_sequencer.set_arbitration(SEQ_ARB_STRICT_RANDOM);
---------------------------------------------------------------------

And in virtual sequence:
---------------------------------------------------------------------
    seq[0].start(p_sequencer.my_seqr, .this_priority(100));
    seq[1].start(p_sequencer.my_seqr, .this_priority(50));
    seq[2].start(p_sequencer.my_seqr, .this_priority(150));
    seq[3].start(p_sequencer.my_seqr, .this_priority(150));
    seq[4].start(p_sequencer.my_seqr, .this_priority(150));
---------------------------------------------------------------------

In this case, sequencer will strictly follow the priority but when two or more sequences are having the same priority, sequencer will randomly pick the sequences that have the same priority. So the output we got:

UVM_INFO my_driver.sv(15) @ 0: uvm_test_top.env.agent.driver [driver] Received data is 3
UVM_INFO my_driver.sv(15) @ 0: uvm_test_top.env.agent.driver [driver] Received data is 5
UVM_INFO my_driver.sv(15) @ 0: uvm_test_top.env.agent.driver [driver] Received data is 4
UVM_INFO my_driver.sv(15) @ 0: uvm_test_top.env.agent.driver [driver] Received data is 1
UVM_INFO my_driver.sv(15) @ 0: uvm_test_top.env.agent.driver [driver] Received data is 2

UVM_SEQ_ARB_WEIGHTED

Here, we have done below modification in child sequence:
---------------------------------------------------------------------
m_sequencer.set_arbitration(SEQ_ARB_WEIGHTED);
---------------------------------------------------------------------
This is bit complicated than other arbitration methods. The idea of this mode is higher priority sequences will get more chance to be granted.

How it works: Summation of all the sequence priorities inside the request queue is happened. (Here, 100 + 50 + (150*3) = 600). Now from 0 to 599 any random number has been chosen. From the front of the FIFO it start summation of the priority number each sequence has, when the summation  exceeds the randomly selected number, that sequence is selected.

Let's say, from 0 to 599 randomly selected number is 219. So now from the front of the FIFO it will start adding each sequence's priority. Here it starts with 100 then 150 (100+50) and then 300 (100+50+150). As 300 is bigger than 219, Sequence 3 will be selected. Similar will continue till there's request inside the FIFO.

Here output we got:
UVM_INFO my_driver.sv(15) @ 0: uvm_test_top.env.agent.driver [driver] Received data is 3
UVM_INFO my_driver.sv(15) @ 0: uvm_test_top.env.agent.driver [driver] Received data is 5
UVM_INFO my_driver.sv(15) @ 0: uvm_test_top.env.agent.driver [driver] Received data is 4
UVM_INFO my_driver.sv(15) @ 0: uvm_test_top.env.agent.driver [driver] Received data is 1
UVM_INFO my_driver.sv(15) @ 0: uvm_test_top.env.agent.driver [driver] Received data is 2

UVM_SEQ_ARB_USER

Here, we have done below modification in child sequence:
---------------------------------------------------------------------
m_sequencer.set_arbitration(SEQ_ARB_USER);
---------------------------------------------------------------------
Here, user can write his/her own logic to select the sequences available in queue. For that user has to define user_priority_arbitration function inside its own sequencer. This function is being called by uvm_sequencer_base only if arbitration type is SEQ_ARB_USER.

In our case, in sequencer file we have added below logic to select the lowest priority sequence first:  
---------------------------------------------------------------------
  virtual function integer user_priority_arbitration( integer avail_sequences[$] );
    int lowest_pri;
    int lowest_pri_id;
  
    lowest_pri = m_get_seq_item_priority(arb_sequence_q[avail_sequences[0]]);
    lowest_pri_id = 0;
    for (int i = 1; i < avail_sequences.size(); i++)
    begin
      if (m_get_seq_item_priority(arb_sequence_q[avail_sequences[i]]) < lowest_pri)
      begin
        lowest_pri = m_get_seq_item_priority(arb_sequence_q[avail_sequences[i]]);
        lowest_pri_id = i;
      end
    end
    return avail_sequences[lowest_pri_id];
  endfunction: user_priority_arbitration
---------------------------------------------------------------------

Based on the above code, output we got:
UVM_INFO my_driver.sv(15) @ 0: uvm_test_top.env.agent.driver [driver] Received data is 2
UVM_INFO my_driver.sv(15) @ 0: uvm_test_top.env.agent.driver [driver] Received data is 1
UVM_INFO my_driver.sv(15) @ 0: uvm_test_top.env.agent.driver [driver] Received data is 3
UVM_INFO my_driver.sv(15) @ 0: uvm_test_top.env.agent.driver [driver] Received data is 4
UVM_INFO my_driver.sv(15) @ 0: uvm_test_top.env.agent.driver [driver] Received data is 5