Non parameterized class with methods requiring parameters

In C++ you can have a class without parameters (templates), and have methods that have parameters (templates). E.g.:

class C
{
    public:
        int cnt;
        template<int W>
            void add() {
                for(int i=0;i<W;i++) {
                    cnt++;
                }
            }
};

I am in need for such a thing in SystemVerilog, but I do not seem to be able to figure out how to do so in a simple way. The LRM (section 13.8) tells me that to do a parameterized function (and so assume also a method in a non-parameterized class should be wrapped by a parameterized class.

This is the code that resembles what I need to do:

class T#(int W);
    bit [W-1:0] s;
    function new();
        s=2**(W-1);
    endfunction
endclass

class C;
    bit [31:0] cnt;
    function void print();
        $display("cnt=%0d",cnt);
    endfunction
    function void incr();
        cnt=cnt+1;
    endfunction
    function void clear();
        cnt=0;
    endfunction

    function void addT10(T#(10) t);
        cnt=cnt+t.s;
    endfunction
    function void addT9(T#(9) t);
        cnt=cnt+t.s;
    endfunction

endclass

module Top();

    initial begin

        automatic C myC=new();
        automatic T#(10) myT10=new();
        automatic T#(9) myT9=new();
        myC.clear();
        myC.addT10(myT10);
        myC.addT9(myT9);
        myC.print();

        $finish;
     end

endmodule

But obviously I do not want to write all the addTxx functions for any possible value of the W parameter. How can I write addT once, so that it can accept any T class with whatever parameter?

My first try was to add a nested class around the add function, i.e. add inside the C class:

    class add_class#(int W);
        static function void add(T#(W) t);
            cnt=cnt+t.s;
        endfunction
    endclass

However, this doesnt work as the static function is obviously not allowed to access the non-static variable “cnt”.
I have ended up with:

 
class T#(int W);
    bit [W-1:0] s;
    function new();
        s=2**(W-1);
    endfunction
endclass

class C;
    bit [31:0] cnt;
    function void print();
        $display("cnt=%0d",cnt);
    endfunction
    function void incr();
        cnt=cnt+1;
    endfunction
    function void clear();
        cnt=0;
    endfunction

    class add_class#(int W);
        function void add(C C_handle, T#(W) t);
            C_handle.cnt=C_handle.cnt+t.s;
        endfunction
    endclass

endclass

module Top();

initial begin

    automatic C myC=new();
    automatic T#(10) myT10;
    C::add_class#(10) A;
    myT10=new();
    A=new();
    myC.clear();
    A.add(myC,myT10);
    myC.print();

    $finish;
end

endmodule

I think it works, but it looks really messy.

Isn’t there a much simpler way of doing? I am also surprised that it does not let me make ‘cnt’ local any longer?

In reply to NiLu:

It might help to explain the application you are trying to achieve with this kind of template. Your example has no benefit in being declared as a template; the value W could have been just as easily an argument to the function add, or to the class constructor.

In reply to dave_59:

If you are referring to my C++ example, you are right. There it does not make sense to use a template. I maybe shouldn’t have placed this example, I just put this to explain the kind of syntax I was looking for.

The rest of the example code makes more sense. To be more specific this is the application that I would like to use this for:

We have a bunch of classes to represent datatypes. E.g. We have a class Complex that stores a complex number (e.g. members Re and Im), we have a class Polar that stores a polar number. They also contain some meta data about the sample. These classes are parameterized, e.g. to set the sample word length. In the example this is the parameterized T class.

On the other hand we have a bunch of classes that can do stuff on numbers. For example we have a class to plot waveforms. This is quite a big class with a lot of members and methods that has been inherited from our old C++ testbench. It for example has a method

function void add_Y_data (const ref real data[], input int x_delta, input int x_start, input string label);

For sure such a thing must be possible, it would be quite restrictive if we can’t pass parameterized classes to another class’ s method.

This will store the data to be plotted in the plotting class, define an x axis, label, etc…
It has several add_data methods, e.g. plotting for real, for plotting real[2],… and similarly for plotting XY plots.
When the .plot() method is called it will plot all the registered waveforms on top of eachother.

We often need to plot an array of Complex, or an array of Polar, etc…
So it would be ideal if we could have methods in the plotter class:

function void add_Y_data (const ref Complex#(W) data[], input int x_delta, input int x_start, input string label);
function void add_Y_data (const ref Polar#(W) data[] input string label);
etc...

Inside those methods I would just call $itor() on for example data[i].Re in case of complex. The issue is being able to pass the parameterized Complex number into the plotter class method.

In reply to NiLu:

The problem is that once you create different specializations of your parameterized classes they diverge, i.e. a Complex #(2) isn’t compatible to a Complex #(3). If you want to show there is a relationship between them, you could declare a base class, ComplexBase, that defines the API that can be used with complex numbers:


virtual class ComplexBase;
  pure virtual function bit[`MAX_WIDTH - 1:0] get_real();
  pure virtual function bit[`MAX_WIDTH - 1:0] get_imag();
endclass


class Complex #(int W) extends ComplexBase;
  protected bit [W-1:0] real;
  protected bit [W-1:0] imag;

  function new(bit [W-1:0] real, bit [W-1:0] imag);
    this.real = real;
    this.imag = imag;
  endfunction

  virtual function bit[`MAX_WIDTH - 1:0] get_real();
    return real;
  endfunction

  virtual function bit[`MAX_WIDTH - 1:0] get_imag();
    return imag;
  endfunction
endclass

This way you can declare your Plotter function to take a ComplexBase:


class Plotter;
  function void add_Y_data(ComplexBase complex, ...);
    // do stuff here by calling complex.get_real/imag()
  endfunction
endclass

It’s also not the pretties thing ever, since we’re always going to have to work with really large numbers (`MAX_WIDTH wide) inside the plotter, but I can’t see any way to work around it, since we need a return type in the base class.

With respect your polar coordinates, I might be wrong, but isn’t that just another representation of complex numbers? I mean, why have a separate class for polar coordinates and not just build in the functionality to get polar coordinates from any Complex number? It might still be nice to create a Polar class that takes the radius and the angle as constructor arguments (since you can’t have multiple constructors):


virtual class ComplexBase;
  // ... all the stuff from before for Cartesian coordinates

  pure virtual function bit[`MAX_WIDTH - 1:0] get_r();
  pure virtual function bit[`MAX_WIDTH - 1:0] get_theta();
endclass


class Polar #(int W) extends Complex #(int W);
  // constructor taking polar coords
  function new(...);
    // convert from polar coords to Cartesian
  endfunction

  virtual function bit[`MAX_WIDTH - 1:0] get_r();
    //compute it from real and imag
    return sqrt(real**2 + imag**2);
  endfunction
endclass

By extending Polar from Complex you can just as well pass a Polar object to the Plotter and it will know what to do. You might be problems with quantization and such, though, as r will usually be a real number when real and imag are integers. You could also just extend Polar from ComplexBase:


class Polar #(int W) extends Complex #(int W);
  protected bit [W-1:0] r;
  protected real theta;
  
  // constructor taking polar coords
  function new(...);
    // ...
  endfunction

  // ...
endclass

Timi, Thanks for your answer. I think I can indeed get away with some stuff in a base class common to all types. probably not exactly as you demonstrated, but I can add virtual functions to the data types that tell how the type has to be plotted or so, but that is not a nice encapsulation.
Btw: I really need a polar class for some quantization error reasons.

So we can come up with not so clean solutions (eg the thing with the subclass does work I think, it is just an awful syntax).

I wonder why the Systemverilog standard doesn’t simply allow parameterized class methods like c++ does. That would make things so simple.

In reply to NiLu:

A lot of other additions would have also made things simpler. You do have to remember that SystemVerilog isn’t a general purpose programming language. For a lot of people’s needs it’s enough. You might be better off writing some parts of your testbench in C++.

In reply to Tudor Timi:

The thing is we want to use UVM which up to now is systemverilog only. Getting C++ in Systemverilog looks even more restrictive (DPI limitations).