SystemVerilog Parameterized Classes

A Parameter is a kind of a constant that represents a value change or a data type. The compiler evaluates Parameter expression as part of its elaboration and code generation phases before the Simulation starts. So we can use the Parameter as part of the declaration of another type or use a Parameter value in lets say range of an array declaration. Note: We can not change a Parameter once a Simulation starts.

SystemVerilog uses a “#” sign to list the Parameter names in a Class Header to define a Generic Class. When we reference the Generic Class we also use the pound (#) sign to provide a list of Parameter Assignments or Overrides. When we specify a default Parameter in a Class Header, we don’t have to provide an Overrides for that Parameter when referencing that Class.

A Generic Class & actual Parameter values is called a Specialization.

Following SystemVerilog code demonstrates the usage of Parameterized Class Vs Base Class Inheritance with Non-default Specialization.


///// Parameterized Class Declaration
class transaction #(type T = int, type A = bit, int R = 3);
 
 /// CRC Declaration
 T crc;
 
 /// Source Address
 bit [R:0] SA;
 
 /// Dynamic Array Declaration
 T dynam [];
 
 /// Constructor
 function new (A a);
   SA = a;
 endfunction: new
 
 /// Calculate CRC Task
 task CalCrc (T t);
   this.crc = t;
 endtask: CalCrc
 
 /// Print Function
 function void print;
   $display("Value of R = %0d, Source Address = %b, No. of DyArr entries = %0d, CRC = %b, Dynamic Element[3] = %b ", this.R, this.SA, this.dynam.size(), this.crc, dynam[3]);
 endfunction: print
 
 endclass: transaction
///// Extended Class with Non-default Specialization
class my_transaction extends transaction #(bit [3:0], int, 9);
 
///  Newly Added Property
 int errBit;
 
/// Constructor
 function new (A b);
   super.new(b);
 endfunction: new
 
endclass: my_transaction

///// Top Module
module top;
 
 initial begin
   fork
     begin
      transaction #(bit [7:0], bit [3:0]) txn;
      txn = new(4);
      txn.dynam = new[5];
      foreach (txn.dynam[i])
        txn.dynam[i] = i;
      txn.CalCrc(6);
      txn.print;
     end
     begin
      my_transaction mtxn;
      mtxn = new(5);
      mtxn.dynam = new[5];
      foreach (mtxn.dynam[i])
        mtxn.dynam[i] = i;
      mtxn.CalCrc(7);
      mtxn.print;
    end 
  join
 end
  
endmodule: top

In the above code, we’ve a Parameterized Base Class i.e. transaction. Three Parameters i.e. T, A & R are declared with their default type in this Base Class. Three Properties i.e. crc, SA & dynam are declared using Parameters. Two Methods i.e constructor & CalCrc() are also declared. A print() Method is also declared to display the values of different Properties.

An Extended Class i.e my_transaction is declared with Non-default Specialization. Note, in case we define the Extended Class without any customized Parameters, default Parameters types will be applied to the Extended Class.

Inside the top module, with-in fork…join, two process threads are spawned. One thread is for Base Class Object and another one is for Extended Class Object. Base Class variable is declared with the intended choice of types for the declared Parameters. Base Class Object is constructed with required argument. Dynamic array is constructed & initialized with different values. Method CalCrc() is called with a choosed argument and finally the Method print() is called to display the expected values.

Almost similar operation is performed for the another spawned thread i.e. for Extended Class. But, notice that Parameter size or type is different & results from the print() Method call for the Extended Class Object is also different.

Hence from this example, its evident that Class Parameterization offers a Great Flexibility to provide various different types of inputs without touching the Source Code.

Parameterized Classes with Static Properties:

We know from Static Properties & Methods in earlier post that Static Properties gets allocated and initialized before time zero but thats does not hold true as soon as declaring a Generic Class with Parameters. The distinction between a Parameterized Class Generic template Vs the Specialization of that Class becomes very important. Static Class Properties do not get allocated unless their Class is Specialized. There is an unique instance in initialization of its Static Properties for each Specialization.

Lets observe this behavior using the following SystemVerilog code:


///// Parameterized Class Declaration
class txn_param #(type T = int);
 
 /// Static CRC Declaration
 static T crc; 
 
endclass: txn_param

///// Class without Parameterization
class txn_without_param;
 
 /// Static CRC Declaration
 static int wcrc;
 
endclass: txn_without_param

module top;
 
 initial begin
 
   $display("wcrc = %0d", txn_without_param::wcrc);

   $display("crc = %0d", txn_param::crc);
 
 end
 
 endmodule: top

In this code, we’ve taken two Classes i.e. “txn_without_param” & “txn_param“. One Class is Parameterized and another is without any Parameter declaration. There is a Static Member i.e “crc” and “wcrc” in each of the Classes. Using the $display inside the initial procedural statement, we can see that Static Property “wcrc” is accessed and initialized using the Class definition without any Object construction. Yet trying to access the Static Property “crc” inside the Parameterized Class “txn_param” in exactly same way generates following Warning/Future version ERROR by the EDA tool:


Warning-[PCSRMIO] Class scope used outside of class
testbench.sv, 25 "txn_param::crc"
 An unspecialized class scope '::' reference was seen. This refers to the generic class, and may only be used inside the class 'txn_param'. To access a static member of the default specialization outside the class 'txn_param', use 'txn_param#()::<staticMemember>' instead. This will be an error in a future release.

In case we run the above code with following modified line, there is NO Warning/Future Error:

$display("crc = %0d", txn_param::crc);
$display("crc = %0d", txn_param #()::crc);

In the above line, Specialization is done using the Default Parameters values. Hence, Bottom line is – Static Properties do not get allocated until Generic Parameterized Classes are Specialized.

Now moving further, lets analyze, What happens in terms of Static Properties when the Generic Parameterized Class is Specialized in different ways and having multiple instances?

Every Specialization has unique set of Static Properties. So N number of Specialization will create N number of Static Properties. Lets undergo with an example below to comprehend it more in detail:


program param_stack;

class stack #(type T = int);
 
 int m_cnt;
 static int counter = 2;
 
 function new;
   m_cnt = counter++;
 endfunction: new 
 
endclass: stack

 
class stacked extends stack #(real);
 
endclass: stacked

typedef stack #(byte) stack_byte;
typedef stack #() stact_int;

 stack_byte S1 = new();
 stack_byte S2 = new();
 stack S3 = new();
 stack #(bit) S4 = new();
 stacked S5 = new();

initial begin 
 $display ("Counter value of S1 instance = %0d", stack #(byte)::counter);
 $display ("Counter value of S2 instance = %0d", stack_byte:: counter);
 $display ("Counter value of S3 instance = %0d", stack #()::counter);
 $display ("Counter value of S4 instance = %0d", stack#(bit)::counter);
 $display ("Counter value of S5 instance = %0d", stacked::counter);
end
 
endprogram: param_stack

Looking at the above example code, we’ve added a Static Property i.e. counter as part of the Generic Class i.e. “stack“. But there is no allocation of the “counter” until there is a reference that Specializes the Class “stack“. The two typedef create two unique Specializations of the Generic Class “stack” and therefore two instances of Static Property “counter” are created. We allocated the first counter upon Specializing “stack” with a byte type and allocated the second counter upon Specializing the “stack” with its default type, an ‘int‘.

As the next step, first we declare two variables S1 & S2 of Specialized “stack_byte” type and constructs the two Object that share the same Static Property i.e.”counter“. So after both initialization the “counter” associated with this Specialization is left with value 4.  Variable S3 is declared next & creates an Object of the Class “stack” with the default Parameter is set to an “int“. Since this Specialization matches to the Specialization created when we created a typedefstack_int” above, it uses the existing Specialization & the “counter” associated with default Specialization will left at 3.

In the next line, we declare another variable S4 & created another unique Specialization of Generic Class “stack” of type “bit“. The counter value after this initialization will be 3 again. Finally, we declared a variable i.e. S5 for the Extended Class “stacked” & constructed an unique Specialization of type “real“. Counter value of S5 will be again 3.

To confirm the values of Static Member “counter” out of these unique Specialization of different Classes, Generic as well as Extended, we used the $display statements with Class Scope Operator i.e. “::” as shown in the code above.

So the key learning from this topic is that every time an Unique Specialization is created for a Parameterized Class, a new instance of Static Property is initialized.


With this, we reached to the end of this post about “Parameterized Classes” and its application with Static Properties. As a recap, we discussed about how a Class can use Parameters to transform into a Generic Class, about Specialization, about Static Properties behavior in Parameterized Classes, about Class Scope operator usage with Parameterized Classes. We’ve gone through different example codes to comprehend these topics.

I believe all this will certainly provide a fair information to you about SystemVerilog OOP based Parameterized Classes. For further information, please refer the many other resources available online/offline. Please share your inputs/suggestions/comments. I’ll look forward to hear from you.

Signing off here..with a yet another post, I’ll see you soon. Till then Bye! 🙂