Generics (not exactly) in SystemVerilog
— Kaushal ModiUsing parameterized classes with static functions to make up for the lack of generics in SystemVerilog.
Many modern languages like Nim allow defining Generic functions types, etc that can work for any type. See Nim Manual 2022 Generics.
This post demonstrates how you can make generics kind of (if you really squint your eyes, you can see it 😄) work with SystemVerilog.
A real Generics example #
The focus of this post is not how to write generics in Nim, but here’s a quick summary:
- Line 1 overloads the
+
operator for any typeT
. - Line 2 is a compile time check (using
when
) to see ifT
is a string, and then concatenates a and b inputs using the&
string concat operator. - For other types, it does the expected “a + b”.
3
200.3
xyz
We will now see how to write something like this in SystemVerilog.
Poor man’s Generics in SystemVerilog #
SystemVerilog is a strongly typed compiled language. So you need to define the types for function and task arguments.
So if you need an add function for integers, you would do:
function int add_int(input int a, b);
return a + b;
endfunction
If you need an add function for real numbers, you would do:
function real add_real(input real a, b);
return a + b;
endfunction
I am using the add functions here only to demonstrate the concept. In real world code, you would come across many cases where you would want to avoid such function redefinitions. Some examples are: dealing with objects of different classes, or queues with elements of different types (e.g. queues of ints, queues of strings), or dynamic arrays of different types, etc.
Parameterized Classes #
In add_int
, we see that the type int
has to be specified in the
function signature. That int
can be replaced by a parameter
representing a type only if that function is defined inside a
parameterized class (See § Parameterized classes 2018, sec. 8.25).
So it would look like this:
class math #(parameter type T = int);
function T add(input T a, b);
return a + b;
endfunction
endclass
This code, though has a problem — In order to call that function, we would first need to construct an object of that class for each type T we plan to use.
Static methods #
The solution to that is to declare the methods/functions in the class as static. That way, that method can be called directly without constructing the object first. See § Static methods 2018, sec. 8.10.
And because we don’t need to construct an object of that class, and don’t want anyone else to unnecessary construct it, we declare the class as virtual.
virtual class math #(parameter type T = int);
static function T add(input T a, b);
return a + b;
endfunction
endclass
Now we can call math #(int)::add(1, 2)
and math #(real)::add(100.1, 100.2)
and get the expected results.
Should compile with all types T #
But.. what if the type T is string ..
$typename
system function (See § Type name function 2018, sec. 20.6.1) comes to our help here. This function takes in a
variable identifier and returns a string name of that variable’s
type. So if a variable x is a string, $typename(x)
will return
"string"
.
So, we can define the add
method like this, right?
virtual class math #(parameter type T = int);
static function T add(input T a, b);
if ($typename(a) == "string")
return {a, b};
else
return a + b;
endfunction
endclass
❌ Wrong ❌
Even if you have the code execute conditionally based on the
$typename
, note that the if ($typename(a) == "string")
check is happening at run time, and so the
entire code needs to compile for any type T that’s planned to be
used.
In the above example, because the {a, b}
concatenation isn’t meant
for types like ints and reals, it will fail compilation.
That’s the reason for the “(not exactly)” in this post’s title 😄
In order to make add
work with strings as well, we need this messy
cast when the type name is “string” : $cast(ret_val, $sformatf("%s%s", a, b));
. It’s basically
telling the compiler: “Trust me, I know what I am doing.. I will be
executing this code only when the ret_val is a string.”
Final Code #
Finally, here’s the entire code with the generic add
method and
some test code.
virtual class math #(parameter type T = int);
static function T add(input T a, b);
if ($typename(a) == "string") begin
T ret_val;
$cast(ret_val, $sformatf("%s%s", a, b));
return ret_val;
end else begin
return a + b;
end
endfunction : add
endclass : math
module test;
$display("1 + 2 = %p", math #(int)::add(1, 2)); // 3
$display("1.1 + 2.2 = %p", math #(real)::add(1.1, 2.2)); // 3.3
$display("1 + 1 = %p", math #(bit)::add(1, 1)); // 0
$display("abc + def = %p", math #(string)::add("abc", "def")); // "abcdef"
$finish;
end
endmodule : test
Cluelib #
If you liked how these “generics” work in SystemVerilog and how the
math #(int)::add(1, 2));
syntax
looks, check out the cluelib library by cluelogic.com. It is an
amazing SystemVerilog library with a big collection of “generic”
functions like above for handling strings, queues and dynamic arrays,
and a lot more. You can take a peek at its API here.