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