Emacs, scripting and anything text oriented.

Generics (not exactly) in SystemVerilog

Kaushal Modi

Using 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 type T.
  • Line 2 is a compile time check (using when) to see if T is a string, and then concatenates a and b inputs using the & string concat operator.
  • For other types, it does the expected “a + b”.

proc `+`[T](a, b: T): T =
  when T is string:
    a & b
    a + b

echo 1 + 2
echo 100.1 + 100.2
echo "x" + "y" + "z"
Code Snippet 1: Example of generics in Nim

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;
Code Snippet 2: add_int for adding integers

If you need an add function for real numbers, you would do:

function real add_real(input real a, b);
  return a + b;
Code Snippet 3: add_real for adding reals (doubles)

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;
Code Snippet 4: Parameterized class and method

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;
Code Snippet 5: Virtual parameterized class and static method

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};
      return a + b;
Code Snippet 6: add method with a condition for string type – Won't compile!


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;
  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"

endmodule : test
Code Snippet 7: Parameterized class math with static function add

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.

References #

IEEE Standard for SystemVerilog–Unified Hardware Design, Specification, and Verification Language. (2018). IEEE Std 1800-2017 (Revision of IEEE Std 1800-2012), 1–1315. https://doi.org/10.1109/IEEESTD.2018.8299595
Nim contributors. (2022). Nim Manual [Website]. In Nim. https://nim-lang.org/docs/manual.html