Creation of user-defined phases in UVM is a possibility although it may hinder in complete re-usability of the testbench. There are chances for components to go out of sync and cause errors related to null pointer handles. But, in case you decide that you have to use one for your project, keep reading.
Some of the main steps to using a custom phase are:
- Create and define a new phase class
- Add the new phase to an existing schedule
- Use the new phase in a component that supports this phase
We already have an idea of how registers are laid out in a memory map from Introduction. So we'll simply use existing UVM RAL (Register Abstraction Layer) classes to define individual fields, registers and register-blocks. A register model is an entity that encompasses and describes the hierarchical structure of class objects for each register and its individual fields. We can perform read and write operations on the design using a register model object.
Every register in the model corresponds to an actual hardware register in the design. There are two kinds of variables inside the register within a model.
This is the value we would like the design to have. In other words, the model has an internal variable to store a desired value that can be updated later in the design. For example, if we want the register REG_STAT in the design to have a value of 0x1234_5678, then the desired value of that register has to be set to 0x1234_5678 within the model and an update task should be called for this to be reflected in the design.
Why do we need parameterization for classes ?
At times it would be much easier to write a generic class which can be instantiated in multiple ways to achieve different array sizes or data types. This avoids the need to re-write code for specific features like size or type and instead allow a single specification to be used for different objects. This is achieved by extending the SystemVerilog parameter mechanism to classes.
Parameters are like constants that are local to the specified class. Classes are allowed to have default value for each parameter that can be overridden during class instantiation.
// Declare parameterized class class <name_of_class> #(<parameters>); class Trans #(addr = 32); // Override class parameter <name_of_class> #(<parameters>) <name_of_inst>; Trans #(.addr(16)) obj;
Given below is a parameterized class which has size as the parameter that can be changed during instantiation.
// A class is parameterized by #(
) // Here, we define a parameter called "size" and gives it // a default value of 8. The "size" parameter is used to // define the size of the "out" variable class something #(int size = 8); bit [size-1:0] out; endclass module tb; // Override default value of 8 with the given values in #() something #(16) sth1; // pass 16 as "size" to this class object something #(.size (8)) sth2; // pass 8 as "size" to this class object typedef something #(4) td_nibble; // create an alias for a class with "size" = 4 as "nibble" td_nibble nibble; initial begin // 1. Instantiate class objects sth1 = new; sth2 = new; nibble = new; // 2. Print size of "out" variable. $bits() system task will return // the number of bits in a given variable $display ("sth1.out = %0d bits", $bits(sth1.out)); $display ("sth2.out = %0d bits", $bits(sth2.out)); $display ("nibble.out = %0d bits", $bits(nibble.out)); end endmodule
ncsim> run sth1.out = 16 bits sth2.out = 8 bits nibble.out = 4 bits ncsim: *W,RNQUIE: Simulation is complete.
Pass datatype as a parameter
Data-type is parameterized in this case and can be overridden during instantiation. In the previous case, we defined parameters to have a specific value.
super keyword is used from within a sub-class to refer to properties and methods of the base class. It is mandatory to use the
super keyword to access properties and methods if they have been overridden by the sub-class.
super keyword can only be used within a class scope that derives from a base class. The code shown below will have compilation errors because extPacket is not a child of Packet. Note that
new method is implicitly defined for every class definition, and hence we do not need a
new defintion in the base class Packet.
class Packet; int addr; function display (); $display ("[Base] addr=0x%0h", addr); endfunction endclass class extPacket; // "extends" keyword missing -> not a child class function new (); super.new (); endfunction endclass module tb; Packet p; extPacket ep; initial begin ep = new(); p = new(); p.display(); end endmodule
super.new (); | ncvlog: *E,CLSSPX (testbench.sv,12|8): 'super' can only be used within a class scope that derives from a base class.
Now let us see the output when extPacket is a derivative of class Packet.
class extPacket extends Packet; // extPacket is a child class of Packet function new (); super.new (); endfunction endclass
You can see from the simulation result below that there were no compilation errors.Simulation Log
ncsim> run [Base] addr=0x0 ncsim: *W,RNQUIE: Simulation is complete.
Accessing base class methods
In the example shown below, display method of the base class is called from the display method of the child class using
class Packet; int addr; function display (); $display ("[Base] addr=0x%0h", addr); endfunction endclass class extPacket extends Packet; function display(); super.display(); // Call base class display method $display ("[Child] addr=0x%0h", addr); endfunction function new (); super.new (); endfunction endclass module tb; Packet p; extPacket ep; initial begin ep = new(); p = new(); ep.display(); end endmodule
ncsim> run [Base] addr=0x0 [Child] addr=0x0 ncsim: *W,RNQUIE: Simulation is complete.
In Inheritance, we saw that methods invoked by a base class handle which points to a child class instance would eventually end up executing the base class method instead of the one in child class. If that function in the base class was declared as
virtual, only then the child class method will be executed.
bc = sc; // Base class handle is pointed to a sub class bc.display (); // This calls the display() in base class and // not the sub class as we might think
We'll use the same classes from previous session and do a comparison with and without
Without virtual keyword
// Without declaring display() as virtual class Packet; int addr; function new (int addr); this.addr = addr; endfunction // This is a normal function definition which // starts with the keyword "function" function void display (); $display ("[Base] addr=0x%0h", addr); endfunction endclass module tb; Packet bc; ExtPacket sc; initial begin sc = new (32'hfeed_feed, 32'h1234_5678); bc = sc; bc.display (); end endmodule
ncsim> run [Base] addr=0xfeedfeed ncsim: *W,RNQUIE: Simulation is complete.
Note that the base class
display() function gets executed.