Class and Object-Oriented Programming

One of the biggest features introduced in SystemVerilog is the ability to write functional model in an object-oriented manner, i.e. using the class construct. Although class construct is not synthesizable unfortunately, class provides a more software-oriented approach to model hardware behavior for verification. SystemVerilog supports the following object-oriented programming (OOP) practice: - Inheritance - Polymorphism

In addition to the normal OOP, SystemVerilog also allows type parametrization for the class (similar to templates in C++)

5.1 Class Definition and Syntax

The syntax of class is similar to that of C++. The constructor is called new and unfortunately there is no function overloading. However, similar to Python, users can provide default arguments to allow different usage. Here is a simple example of class:

Notice that like Java/Python, each instance of the object in SystemVerilog is a “reference” and there is no pointer type. Keyword null is used to denoted null reference. You will get null exceptions when you try to access members of a null object. We can enhance the Node constructor with default argument in such way that users can also pass in the next reference while constructing the object:

By default, every member declared in the class is public, meaning any code can modify the attributes or call the functions regardless of the scope. To add private access modifier, we can use local keyword. For instance, we can have:

To instantiate the object, we can use new keyword, similar to Java/C#. To access member attributes or function, we can either use their identifiers directly or use this keyword to avoid naming conflicts, similar to that of C++. Here is a complete example of single-linked-list that has proper access modifier:

Object in SystemVerilog is managed by the simulator runtime and thus garbage-collected, similar to Java/C#. As a result of that, we may also see performance downgrade when garbage collection hurts our simulation flow. As premature optimization is the root of all evil, we should be careful about optimization on memory allocation during verification.

You can also have static methods in the class, using the keyword static, e.g. static function string name();. static function follows the same semantics as normal function, e.g. their scope and lifetime. As a result, automatic keyword is required in some cases.

5.1.1 Out-of-Block Declarations

Similar to other OOP languages, SystemVerilog allows you to move the method implementation our of the body of the class declaration. Todo so, the method inside of the class declaration has to be marked as extern. Then, in the same scope, we can add the implementation following the class declaration, as shown below:

Notice that in the out-of-block declaration, we have to specify the full namespace of the function, i.e. prefixing it with class name and scope resolution operator ::. Similar to C++, we don’t need to specify function modifier such as extern or virtual in the out-of-block declaration.

Although the language allows default argument in the out-of-block declaration, it is confusing and difficult to maintain. We recommend default argument in C++ style, where default arguments are only specified in the function prototype, as shown below:

5.1.2 Forward Declaration

Similar to C++, if the class declaration needs to refer to another class declared later or else where, we can use keyword typedef for forward declaration as shown below:

5.2 Class Inheritance

SystemVerilog allows class inheritance and polymorphism via inheritance. Although the semantics are similar to other software programming languages, there are some nuance difference that creates somme gotcha moments and thus is one of the popular source of bugs.

To inherit from the base class, we can use the keyword extend and we can override any methods marked as virtual, as shown in the example below:

In the code example, we first declare the base class Vehicle, and then inherit it with Car class. Notice that num_seats is marked as virtual, allowing it to be overridden by child classes.

If the base class has a constructor function, the child class must call the base class constructor using the keyword super:

We can also specify constant values that cannot be modified. Any attempt to modify constant values will result in compiler error:

Abstract class and abstract methods are also supported in SystemVerilog and their semantics is identical to most OOP languages: 1. Users cannot directly instantiate an abstract class - a compiler error will be issued. 2. Users cannot inherit an abstract class without fully implement abstract methods.

To declare the class as an abstract class, we can declare the class as virtual class using the keyword virtual. To indicate an abstract method, we can use a new keyword pure in addition to virtual, e.g. pure virtual function. Here is an example reusing the vehicle class.

To allow child class access parent attributes and function, we can use the keyword modifier protected. Similar to C++, attributes and functions marked as protected are private outside the class scope, but are visible to child class. Users shall use protected properly for best OOP practice.

5.3 Polymorphism

SystemVerilog supports Polymorphism via inheritance. Similar to other OOP languages, parent class variable can hold references to child class instances, as shown below:

One common gotcha is that if the base class method is not marked as virtual yet is still overridden in the child class, the base class’s method will be called when used as base class, as shown below:

Advanced linters may catch such errors, but most simulators will not issue a warning. Readers should always make sure adding proper modifiers such as virtual.

5.4 Class Parametrization

Like module or interface, you can parametrize class types in a similar way to templates in C++. Unfortunately there is not much compiler elaboration supports so you cannot do much meta-programming in SystemVerilog. However, with typedef keyword, we can get most of the things done with class parametrization.

5.4.1 Value Parametrization

The simplest way to parametrize a class is to parametrize the values. One usage for that is to parametrize the logic width or pre-allocated size of class variables, as shown below:

To instantiate the class, we can do something similar to module instantiate with parameters:

Value parametrization is useful in cases where the width/size of the variable or constant values that can only be set at compile time. For other values we recommend to use class constructor instead.

5.4.2 Type Parametrization

SystemVerilog allows type parametrization for classes in a similar manner as generics in Java/C#. Below is an example of how to use class with type parametrization.

In the example, we define a Node class that can be instantiated with variables types, e.g. int or even other classes. The type for Node class that has a parametrized type specified can be referred as Node #(T), where T can be any type. To avoid duplicated code, we can use typedef to redefine the type as follows:

We can use the same syntax as class with parametrized values to instantiate a class with parametrized types:

Type checking for class with parametrized types happens at compile time and designers can use the error information from the compiler to fix their code.

5.5 Interface Class

Interface class is an OOP concept borrowed from languages such as Java and C#. It defines public functions and other public information about the class. However, one major distinction between interface class and other class is that typically interface class does not have any implementation details (some recent change in some OOP languages may change this fact).

To declare an interface class, we can use the following syntax:

Notice that every functions in defined in the interface class should be pure virtual. Interface class can inherit from another interface class using the keyword extends, as shown below:

For a class to implement the interface class, we can use the keyword implements, as shown below. A class can implement as many interface class as the designers see fit.

You can also put other non-implementation related information in the interface class, such as enum definition, as shown below:

As with normal class, interface class can also have parametrized types. However, there are some syntax restriction on how interface are used: