Thursday, 29 June 2017

Default Sequence

There are two usual ways to run the sequence:
  • Using default_sequence
  • Using start method

How default_sequence works?

Inside uvm_sequencer_base.svh
---------------------------------------------------------------------------------
function void uvm_sequencer_base::start_phase_sequence(uvm_phase phase);
...
(uvm_config_db #(uvm_object_wrapper)::get(
               this, {phase.get_name(),"_phase"}, "default_sequence", wrapper) && wrapper != null)
...
--------------------------------------------------------------------------------------

This API will do the following:
  • get default_sequence 
  • cast this uvm_object_wrapper into uvm_sequence_base
  • start the sequence

Who calls this above method?

Inside uvm_task_phase.svh:
--------------------------------------------------------------------------------------
  virtual function void execute(uvm_component comp,
                                                 uvm_phase phase);
...
      if ($cast(seqr,comp))
          seqr.start_phase_sequence(phase);
...
--------------------------------------------------------------------------------------
execute method is called for all phases of component.

And if the component is Sequencer it will call start_phase_sequence() method, to check if the default_sequence has been set for this method.

If we run sequence using both the approach:
Registering default sequence in main phase of sequencer and starting another sequence in main phase of testcase, default sequence will be executed first as main phase is called in bottom-up hierarchy.

Tuesday, 27 June 2017

Do we need p_sequencer?

You need p_sequencer only if you want to access the property of user defined sequencer.

Let us understand this with example.
User defined sequencer:
--------------------------------------------------------------------------
class user_seqr extends uvm_sequencer;
  int agent_id = 1;
 
endclass: user_seqr
-------------------------------------------------------------------------

Trying to access the property of user_seqr using m_sequencer:
--------------------------------------------------------------------------
class user_seq extends uvm_sequence

  $display(“Agent ID: %0d”, m_sequnencer.agent_id);

endcalss: user_seq
--------------------------------------------------------------------------
Output:
Error-[MFNF] Member not found
my_seq.sv, 20
"this.m_sequencer."
  Could not find member 'agent_id' in class 'uvm_sequencer_base'


Trying same with p_sequencer:
--------------------------------------------------------------------------
class user_seq extends uvm_sequence

`uvm_declare_p_sequencer(user_seqr)

  $display(“Agent ID: %0d”, p_sequnencer.agent_id);

endcalss: user_seq
--------------------------------------------------------------------------
Output:
Agent ID: 1

Why this difference?


So this difference can be explained with OOP example:
--------------------------------------------------------------------------
class parent;
endclass: parent
class child extends parent;

  virtual function info();
   $display("Child::info");
  endfunction: info

endclass: child

module tb_top();
  parent p;
  child  c1;
  child  c2;

  initial
  begin
    c1 = new();
    p = c1;
    p.info();
  end
endmodule: top
--------------------------------------------------------------------------
Output:
Error-[MFNF] Member not found
tb_top.sv, 39
"p."
  Could not find member 'info' in class 'parent'


[We can access the property of child class if parent class handle is pointing to child class,ut only condition is that property definition must be there inside parent class.]

Same case is here for m_sequencer and p_sequencer, where m_seuqencer is handle of uvm_sequencer_base class and p_sequecner is handle of user_sequencer.

So when we call,

  `uvm_declare_p_sequecner(user_seqeucner)

UVM takes the handle of user_sequencer called p_sequencer and do the casting of m_sequencer into p_sequencer.

Same way we can do in our polymorphism example, we will just modify our tb_top file
--------------------------------------------------------------------------

module tb_top();
  parent p;
  child  c1;
  child  c2;

 
initial
  begin
    c1 = new();
    p = c1;
    $cast(c2, p);
    c2.info();

  end
endmodule: top

--------------------------------------------------------------------------
Output:
Child::info


So difference between m_sequencer and p_sequencer is the difference between p and c2 in our example.

If we map our example p_sequencer and m_sequencer:
parent is uvm_sequencer_base and its handle p is m_sequencer.
child is user_sequencer and its handle c2 is p_sequencer.

Wednesday, 14 June 2017

run_test() and UVM_TESTNAME

Three are two ways to provide testcase name:
  1. In top module, run_test(<testcase_name>)
  2. Command line argument, UVM_TESTNAME=<testcase_name>
So here we're calling run_test inside top module. Now this run_test is implemented inside the uvm_globals.svh. This method is not defined in any class, this is a method defined in package.

Inside uvm_globals.svh :
--------------------------------------------------------------------------------
task run_test (string test_name="");
  uvm_root top;
  top = uvm_root::get();
  top.run_test(test_name);
endtask
--------------------------------------------------------------------------------
This run_test() is calling run_test of uvm_root and here name of testcase is getting stored inside "test_name" variable.

Implementation of command-line UVM_TESTNAME is also done inside run_test() of uvm_root.

Inside uvm_root file:
--------------------------------------------------------------------------------
task uvm_root::run_test(string test_name="");
...
  test_name_count = clp.get_arg_values("+UVM_TESTNAME=", test_names);

  // If at least one, use first in queue.
  if (test_name_count > 0) begin
    test_name = test_names[0];
    testname_plusarg = 1;
  end
...
endtask
--------------------------------------------------------------------------------

So whenever we pass UVM_TESTNAME it will be stored inside the "test_name" variable of uvm_root, which means if we have passed testcase name using run_test(), it will be overwritten by testcase name passed through UVM_TESTNAME.

Example:
--------------------------------------------------------------------------------
module top();
...
initial begin
run_test("my_test");
end
endmodule: top
--------------------------------------------------------------------------------

Testname passed through command line argument:
--------------------------------------------------------------------------------
./simv +UVM_TESTNAME=user_test
--------------------------------------------------------------------------------

If we have passed testcase name using both the above methods, user_test will be run as it is passed through command line argument.

PS:: Not only run_test() but global_stop_request(), set_global_timeout() [and many more] are also implemented inside uvm_globals.svh

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

Sunday, 30 April 2017

p_sequencer vs. m_sequencer

This is probably the most used and still most confusing for me in UVM.
In simple words, p_sequencer is just the dynamic casting of m_sequencer.

In testcase whenever we call start method to run the sequence,
 ------------------------------------------------------------------------------------------
class test extends uvm_test; 
 ...
  seq.start(sequencer);
...
endclass: test
 ------------------------------------------------------------------------------------------

This start method is the source code of UVM class uvm_sequence_base:
------------------------------------------------------------------------------------------
virtual task start (uvm_sequencer_base sequencer, 
                    uvm_sequence_base parent_sequence = null, 
                    int this_priority = -1, 
                    bit call_pre_post = 1);

...
  set_sequencer(sequnecer);
...
endtask
------------------------------------------------------------------------------------------

Inside uvm_sequence_item:
------------------------------------------------------------------------------------------
virtual function void set_sequencer(uvm_sequencer_base sequencer);
  m_sequencer = sequencer;
  m_set_p_sequencer();
endfunction

virtual function void m_set_p_sequencer();
  return; 
endfunction
------------------------------------------------------------------------------------------

So, as we can see whenever we call start method of sequence it's m_sequencer(a variable of uvm_sequencer) is getting set. This variable stores any object derived from uvm_sequencer. And it can access all the properties of uvm_sequencer class. 

Now what if user wants to access the property of it's own user defined sequencer, for that we have to have p_sequencer which points to the sequencer of our environment. 

p_sequencer only exists if we call uvm_decalre_p_sequencer macro, which is defined inside the UVM source code: 
------------------------------------------------------------------------------------------
`define uvm_declare_p_sequencer(SEQUENCER) \
SEQUENCER p_sequencer;\
virtual function void m_set_p_sequencer();\
   super.m_set_p_sequencer(); \
   if( !$cast(p_sequencer, m_sequencer)) \
   `uvm_fatal("DCLPSQ", \
   $sformatf("%m %s Error casting p_sequencer, please verify that   this sequence/sequence item is intended to execute on this type of sequencer", get_full_name())) \
endfunction
 ------------------------------------------------------------------------------------------

This macro is called inside the user defined sequence and the sequencer passed when we call start method (from the testcase) is casted to p_sequencer.

As we can see in the above code, p_sequencer is just the dynamic casting of m_sequencer with the user defined type class. So ultimately both points to the same thing with different type of class.

Properties of user defined sequencer can be accessed through p_sequencer and that is the reason we required p_sequencer.  














Friday, 28 April 2017

Polymorphism and $cast

Polymorphism 

In OOPS polymorphism means same operation to be performed in wide range. 

-------------------------------------------------------------------------------------
Example: 

class India; 

task my_print();
  $display("This is India");
endtask: my_print

endclass: India

class Kerala extends India; 

task my_print();
  $display("This is Kerala");
endtask: my_print

endclass: Kerala

module top();

India i_var;
Kerala k_var;

initial 
begin
  i_var = new();
  i_var.my_print();
  k_var = new();
  i_var = k_var;
  i_var.my_print();
end

endmodule

Output:
This is India
This is India
-------------------------------------------------------------------------------------

In OOPS, base class can also access the method of child class if the it has the same method defined it it's own class(Here, my_print) and when the base class variable has the child object (Here, i_var = k_var). For that, key is "virtual"

-------------------------------------------------------------------------------------
Example: 

class India; 

virtual task my_print();
  $display("This is India");
endtask: my_print

endclass: India

class Kerala extends India; 

virtual task my_print();
  $display("This is Kerala");
endtask: my_print

endclass: Kerala

module top();

India i_var;
Kerala k_var;

initial 
begin
  i_var = new();
  i_var.my_print();
  k_var = new();
  i_var = k_var;
  i_var.my_print();
end

endmodule

Output:
This is India
This is Kerala
-------------------------------------------------------------------------------------

If method is declared as virtual in the base class, it is still considered as virtual in all the child calss even it is not defined as virtual.

$cast

It is always legal in OOPS, that base class variable has child class object, but it is not true in reverse case. 

-------------------------------------------------------------------------------------
module top();

India i_var;
Kerala k_var;

initial 
begin
  i_var = new();
  k_var = new();
  k_var = i_var;  // Illegal
end

endmodule
-------------------------------------------------------------------------------------

However, base class object can still be assigned to child class, if that base class variable has the child class object. For that $cast is used.

In a simple way, if we want to use the method which is defined inside the child class but not in the parent class, we have to revert back to the variable of child class instead of parent class. 

-------------------------------------------------------------------------------------
Example: 

class India; 
endclass: India

class Kerala extends India; 

virtual task my_print();
  $display("This is Kerala");
endtask: my_print

endclass: Kerala

module top();

India i_var;
Kerala k_var;

initial 
begin
  k_var = new();
  i_var = k_var;
  perform_cast(i_var);
end

task perform_cast(India ind) ;
  Kerala ker;
  $cast(ker, ind);
  ker.my_print();
endtask: perform_cast

endmodule

Output:
This is Kerala
-------------------------------------------------------------------------------------

If parent class variable is not having the object of  child class,
 -------------------------------------------------------------------------------------
Example: 

class India; 
endclass: India

class Kerala extends India; 

virtual task my_print();
  $display("This is Kerala");
endtask: my_print

endclass: Kerala

module top();

India i_var;
Kerala k_var;

initial 
begin
  i_var = new();
  perform_cast(i_var);
end

task perform_cast(India ind) ;
  Kerala ker;
  if (!$cast(ker, ind))
  begin
      $display("This casting is not legal");
  end
  else
  begin
    ker.my_print();
  end
endtask: perform_cast

endmodule

Output:
This casting is not legal
-------------------------------------------------------------------------------------

Monday, 10 April 2017

Static Property and Methods

Static Property

Usually class properties don't get created until its object get constructed. But the exception is "Static"


class packet;
  int id;
  static int pkt_id;
endclass

...
packet pkt1, pk2;
pkt1 = new();
pkt2 = new();

pkt1.id = packet::pkt_id++;
pkt2.id = packet::pkt_id++;
...

Output:
pkt1.id = 1
pkt2.id = 2


To access the static property, we have to use "class_name::property".
Static keyword added before the property becomes global through out the class type.


Static Method

class packet;
  int id;
  static int pkt_id;

  static function int unique_id;
     return pkt_id ++;
  endfunction
endclass

...
packet pkt1, pkt2;
pkt1 = new();
pkt2 = new();
pkt1.id = packet::unique_id;
pkt2.id = packet::unique_id;
...

Output:
pkt1.id = 1
pkt2.id = 2

Static method can not access to non static class property. Also there is no "this" for accessing static property or method.

Shalllow copy Deep copy

Shallow copy

class A;
  int j = 5;
endclass

class B;
  int i = 1;
  A a = new;
endclass

B b1, b2;
  b1 = new;
  b2 = new b1;
  b2.i = 10;
  b2.a.j = 7;
...

Output:
b1.i = 1
b2.i = 10
b1.a.j = 7
b2.a.j = 7


When we copy a class variable we are just copying the handle(pointer) not the object(memory). So here both the variables have same handle(pointer). This works well if the fields are values, but may not be what you want for fields that point to dynamically allocated memory. The pointer will be copied. but the memory it points to will not be copied -- the field in both the original object and the copy will then point to the same dynamically allocated memory.

When you do shallow copy all properties of the class will be duplicated(all properties are copied to new memory locations) in new memory except for objects. Shallow copy copies only the object handles.

Deep copy

class A;
  int j = 5;
endclass

class B;
  int i = 1;
  A a = new;

  function copy(B source);
    this.i = source.i;
    this.a = new source.a
   endfunction : copy
endclass

...
B b1, b2;
  b2.copy(b1);
  b2.i = 10;
  b2.a.j = 7;
...

Output:
b1.i = 1
b2.i = 10
b1.a.j = 5
b2.a.j = 7

For Deep copy we have to explicitly create logic to copy all the property of one class to another and if it has object as a property of other class we have to allocate memory for it and then copy the same.







Basic OOP: Constructor, Class handle and class object

Every class has in built method called: new (known as constructor).

Let's see an example,

class packet;
  int val;
endclass: packet

When we take the instance of this calss,

packet pkt; // This is just the variable of the data type packet.

We can not directly use this pkt to access the packet class property. We have to construct,

pkt = new; // This class variable is now class object.

Object can only be created if we call new of the class variable. Ultimately, class object is the class variable with it's dedicated memory. So whenever we call constructor of the class variable, memory is getting allocated for that class variable and it's memory pointer (known as class handle) is stored inside the class variable.

User can also overwrite this constructor,

class packet;
...
function new (int _val_);
  val = _val_;
endfunction
endclass

packet pkt = new(5);


Example:

Packet pkt1, pkt2;

pkt1 = new();


 _____________
|___0x80______|
|   int val             |
|_____________|

pkt2 = new();
 _____________
|___0x84______|
|   int val             |
|_____________|

pkt1 = new();
 _____________
|___0x88______|
|   int val             |
|_____________|
 _____________
|___0x80______|
|   Unused           |
|_____________|

pkt2 = pkt1;
 _____________
|___0x88______|
|   int val             |
|_____________|
 _____________
|___0x84______|
|   Unused           |
|_____________|

Now both pkt2 and pkt1 both are variables are pointing to the same memory (0x84). Because
pkt2 = pkt1 copies memory pointer only.

pkt1 = new();

 _____________
|___0x90______|
|   int val             |
|_____________|   // For pkt1
 _____________
|___0x88______|
|   int val             |
|_____________|  // For pkt2

pkt2 = null;
 _____________
|___0x90______|
|   int val             |
|_____________|   // For pkt1
 _____________
|___0x88______|
|   Unused           |
|_____________| 




Wednesday, 15 February 2017

Type overriding using UVM factory

Type overriding means that every time a component class type is created in the Testbench hierarchy, a substitute type i.e. derived class of the original component class, is created in its place, which applies to all the instances of that component type. In UVM, below are the methods by which we can override the class:
  • set_type_override_by_type
  • set_type_override_by_name

There's no difference in the final result either we override by type or name.

In below example, our purpose is to override driver class without making change in existing environment, so we created extended class which is extended from the original driver class.

Factory Code:


// When ~replace~ is 1, a previous override on ~original_type_name~ is replaced, otherwise a previous override, if any, remains intact. Yet to check.

Driver Code:


Extended driver Code:


In test, we can override the driver class by using "set_type_override_by_type" function.

Overriding in Test:


Output:

In test, we can override the driver class by using "set_type_override_by_name" function.

Overriding in Test:


Output:

Instance overriding using UVM factory

In instance overriding, as name indicates it substitutes only a particular instance of the component. In UVM, below are the methods by which we can override the instance of particular class:
  • set_inst_override_by_type
  • set_inst_override_by_name

There's no difference in the final result either we override by type or name.

In below example, our purpose is to override driver instance without making change in existing environment, so we created extended class which is extended from the original driver class.

Factory Code:


Driver Code:


Extended driver Code:


In test, we can override the driver class by using "set_inst_override_by_type" function.

Overriding in Test:


Output:


In test, we can override the driver class by using "set_inst_override_by_name" function.

Overriding in Test:


Output:




Tuesday, 14 February 2017

uvm_analysis_imp_decl macro

NEED for uvm_analysis_imp_decl macro

Sometimes in the same component we need to have two analysis "Imp" export. For example, In Scoreboard we want to compare the data coming from tx_monitor of Agent1 with the data coming from rx_monitor of Agent2. 

So to get the transactions from two different component we have to have two analysis "Imp" export in the same component. One which will be connected with tx_monitor and other which will be connected with rx_monitor. Here, for two analysis "Imp" export we can not define the same write() method for both. So UVM provides the solution by defining the macro `uvm_analysis_imp_decl macro. This macro allows to declare a specialized "imp"-style analysis export, by which its function write can be renamed as write_SUFFIX. 

Code:


Analysis Port, Export and "Imp" Export


Analysis port is a part of TLM. Just like other ports (uvm_put_port, uvm_get_port etc), analysis port is also extended from uvm_port_base class. 

-------------------------------------------------------------------------------------------------------------------------
Outline of TLM
TLM stands for Transaction Level Modelling, which means communication happens between different components by transferring transaction from one component to other [i.e. Sequencer to Driver, Monitor to Scoreboard etc].
In UVM, transaction is a class object, uvm_transaction extended from uvm_object. In simple words, you can also think of a packet class with fields like address, rd_wr, rd_en, wr_en, data etc.. 

Usually two components are connected, component which transmits the transaction has TLM port and component which receives the transaction has TLM export. And connection of this two components happen on any top level component. Each TLM connection has its defined method, which is called by Port and implemented by "Imp" Export.
-------------------------------------------------------------------------------------------------------------------------

Advantage of analysis port over other port is, analysis port can have any number of exports. Single analysis port can have 0, 1 or more than 1 number of exports at the time.

Analysis port requires an implementation of void function write() inside the analysis "Imp" export. Here we can see that, this is non-blocking as write() is a function. All the "Imp" exports connected with particular port has to implement write() method. So whenever port calls the write() function, all the write functions of the connected "Imp" exports will be executed.

If one analysis port is connected with 3 analysis exports then all 3 "Imp" exports should have write() method implemented. It is also okay if out of 3 any or all "Imp" exports have not implemented write() function, as it has been already implemented inside the analysis export class.

// If port has no export connected and still we call write method then which write() method would be called?

Difference between Analysis Export and Analysis "Imp" Export

Analysis "Imp" export is just like other analysis export which has implemented write() method inside it.

So analysis export is used for hierarchical connection, where the last component in the hierarchy (or the child component) has analysis "Imp" export with implemented write() function.

 

Analysis FIFO

Whenever write() function is getting called by port, it will be out of it in 0 time. So if we want to perform any time consuming operation on this received transaction we need some kind of buffer to store this received transaction, Analysis FIFO is that buffer.

Below is the snippet of analysis_fifo code inside UVM:


Last component in the hierarchy can be analysis export which can be connected with analysis fifo as fifo has already instance analysis "Imp" export. So when the export is connected with fifo then whenever port calls the write() method, analysis fifo will directly push the data the queue [as shown in above code].