The name of a class in SystemVerilog declares its type, so that when an object is constructed from the class with new, the methods and members of the class then determine how the object interacts with other objects in the test environment. Even though 2 classes may be structurally the same, their compatibility will be determined by the type (class) name. In SystemVerilog, a sub-class can be declared that extends a super class. This means that the sub-class is a syb-type or specialisation and inherits the super class' methods and members as if they were declared in the sub-class itself.
Just using these ideas allows one to model the relationship between objects: For instance, given a declaration for a transaction, one may wish to sub-type the declaration and create a more specialised transaction for an application with a particular address map:
class Bus_trans; static int next_ID; const int ID; rand T_dir dir; rand T_addr addr; rand T_data data; function new; ID = next_ID++; endfunction: new function void print; ... endclass: Bus_trans
This general purpose transaction can be extended:
class Mem_map_trans extends Bus_trans; rand enum { ROM, RAM, IO } area; constraint address_map { area == ROM -> addr inside {['h0000:'h7FFF]}; area == RAM -> addr inside {['h8000:'hDFFF]}; area == IO -> addr inside {['hFF80:'hFFFF]}; } constraint ROM_read_only { area == ROM -> dir == dir_Rd; } constraint IO_byte_wide { area == IO -> data[15:8] == 0; } constraint area_choice { area dist { ROM := 70, RAM := 20, IO := 10 }; } function string psprint(); $sformat(psprint, "%s cycle: %s", area.name(), super.psprint() ); endfunction : psprint function new (Stream owner = null); super.new(owner); endfunction : new endclass
The sub-class (Mem_map_trans) can access fields and methods declared in the super (also referred to as base) class while adding the abstract control knob, area, to represent the address map. The base class can be repeatedly sub-typed, as can the sub-classes so that a class hierarchy can be developed with relationships that can be represented in a UML diagram.
The point is that the general class declaration at the root of the hierarchy contains class member declarations for all the sub typed classes. As well as method declarations like print and copy. When developing a class hierarchy, one would like the root of the hierarchy to represent a blue-print for sub classes. We can have the concept of a transaction (the abstract, blue-print) and from that extend and build an actual transaction, for instance, the memory mapped transaction. The concept of an abstract class, declared in SystemVerilog as a virtual class, allows us to model this type of relationship. So we can just change the declaration for the transaction class:
virtual class Transaction; ... endclass
And that would work. But to be useful, we need to know several features of an abstract class:
- The virtual class can be considered to be an incomplete declaration, because it might be known that certain methods will be needed in the sub classes - it may not be possible to determine how those methods will work. Thus the virtual class might declare some pure virtual methods.
virtual class Transaction; ... pure virtual function string convert2string(); ... endclass: Transaction
- The virtual class is a template and cannot be instanced. While it is possible (and useful) to declare a variable of the virtual class type, it is not allowed to instantiate an object using new(). The reason for this follows on from the previous point - the pure virtual methods declare the prototype for a method that must be implemented in a 'real' or concrete class.
class Basic_transaction extends Transaction; rand addr_t address; rand data_t data; rand dir_t dir; function new(string name = ""); super.new(name); endfunction virtual function string convert2string(); return $sformatf("address = %9x data = %9x dir = %0s", address, data, dir.name()); endfunction ... endclass: Basic_transaction
- A variable (or handle) of the virtual class type can be assigned a reference to an object of any of its sub-types. This is upcasting and allows code to be written using a common base class handle and reused with different actual object references. Downcasting is also allowed in SystemVerilog using $cast(), but certain checks are applied before the cast takes place.
... Transaction t; Basic_transaction b; b = new(); // allowed/necessary, but cannot construct t ... t = b; // upcast: t holds a reference to an object of type Basic_transaction ... $cast(b, t); // downcast - with checks
The virtual class can declare all the method types that can be used in a concrete class as well as data members, but can also declare a pure virtual method, which can only be declared in a virtual class.