A class is the foundation of Object-Oriented Programming (OOP) in SystemVerilog. Instead of scattering related signals and the functions that act on them across your testbench, a class lets you group them into a single, reusable unit. This article walks through class basics — declaring a class, building a constructor, and creating objects — using a simple packet example you'll recognize from real verification environments.
8 min read | Intermediate Level
What You'll Learn
- What a SystemVerilog class is and why testbenches are built around it
- How to declare a class, create objects, and access their properties/methods
- How to build arrays of class objects
What Is a Class?
class is a user-defined data type — an OOP construct — used to encapsulate properties (data) and methods (tasks/functions) that operate on that data. Think of a class as a blueprint: it describes what an object looks like and what it can do, but it isn't usable on its own until you create an actual object from it.
As the diagram shows, myPacket is the blueprint. Every time you call new(), SystemVerilog allocates a fresh, independent object — pkt0 and pkt1 each get their own copy of header, encode, mode, data, and stop. The method display(), however, is defined once and shared by every object.
Syntax
class class_name;
// property declarations
// method declarations (functions/tasks)
endclass
Parameters:
class_name— identifier for the class (required)- Properties — variables declared inside the class body, accessible via the dot operator on an object handle
- Methods —
functionortaskdeclarations that operate on the class's own properties
Variations:
A class with a constructor that has default argument values:
class myClass;
int data;
function new(int data = 0);
this.data = data;
endfunction
endclass
Here's a complete class example:
class myPacket;
bit [2:0] header;
bit encode;
bit [2:0] mode;
bit [7:0] data;
bit stop;
function new (bit [2:0] header = 3'h1, bit [2:0] mode = 5);
this.header = header;
this.encode = 0;
this.mode = mode;
this.stop = 1;
endfunction
function void display ();
$display ("Header = 0x%0h, Encode = %0b, Mode = 0x%0h, Stop = %0b",
this.header, this.encode, this.mode, this.stop);
endfunction
endclass
A few key things to note in the example above:
function new()is called the constructor and is automatically called upon object creation- this keyword is used to refer to the current object's own properties/methods — it distinguishes the class property header from the constructor argument of the same name
display()is a function, and rightly so, because displaying values does not consume simulation time. Tasks are reserved for code that needs to model a delay or wait on an eventfunction new()has default values for its arguments, so callingnew()with no arguments creates a packet with header = 3'h1, mode = 3'h5, and the rest set by the body of the constructor
A class declaration by itself does not create any storage or consume any memory. Memory is only allocated when you call new() to construct an object.
Where Are Classes Used?
Almost every reusable testbench component — transactions, drivers, monitors, scoreboards — is written as a SystemVerilog class. The UVM library itself is essentially a large set of base classes (uvm_component, uvm_sequence_item, and so on) that verification engineers extend to build their own environments.
How Do I Access Properties and Methods?
To use a class, you create an object of it, which acts as a handle to its properties and methods. Object handles are declared just like any other variable, but they don't point to real storage until new() is called.
module tb_top;
myPacket pkt0, pkt1;
initial begin
pkt0 = new (3'h2, 3'h3);
pkt0.display ();
pkt1 = new ();
pkt1.display ();
end
endmodule
Output
Header = 0x2, Encode = 0, Mode = 0x3, Stop = 1 Header = 0x1, Encode = 0, Mode = 0x5, Stop = 1
Notice that pkt0 and pkt1 hold completely independent data. Changing pkt0.header later has no effect on pkt1.header — each call to new() creates a distinct copy of the class's properties.
Quick Check
Q: If you declare myPacket pkt0; but never call pkt0 = new();, what happens when you try pkt0.display();?
Show Answer
pkt0 holds the default value null until it is constructed. Calling a method on a null handle causes a run-time error (commonly reported as a null object access / segmentation fault by the simulator), because there's no actual object in memory to operate on.
How Do I Create an Array of Class Objects?
An array of class objects can be created in a way similar to how you create an array of any other data type, such as int. Each element of the array is its own object handle and must be constructed individually.
module tb_top;
myPacket pkt0 [3];
initial begin
for(int i = 0; i < $size (pkt0); i++) begin
pkt0[i] = new ();
pkt0[i].display ();
end
end
endmodule
Since each myPacket object had no arguments passed to its constructor new(), default values are applied for every element.
Output
Header = 0x1, Encode = 0, Mode = 0x5, Stop = 1 Header = 0x1, Encode = 0, Mode = 0x5, Stop = 1 Header = 0x1, Encode = 0, Mode = 0x5, Stop = 1
Declaring myPacket pkt0 [3]; only reserves three handles — it does not allocate three objects. Forgetting to call new() inside the loop is a common mistake that leaves every handle pointing to null.
Common Beginner Mistakes
Mistake #1: Forgetting to call new()
Wrong: myPacket pkt0; pkt0.display();
Right: myPacket pkt0; pkt0 = new(); pkt0.display();
Why? Declaring a handle only creates a reference variable; it does not build the object. You must explicitly call the constructor before using any property or method.
Mistake #2: Mixing up the order of positional constructor arguments
Wrong: Calling new(3'h3, 3'h2) when you meant header = 3'h2 and mode = 3'h3.
Right: Pass arguments in the same order they're declared in function new(), or check the constructor signature before calling it.
Why? SystemVerilog matches positional arguments by order, not by name. Swapping two arguments of the same type compiles fine but silently assigns the wrong value to each property.
Mistake #3: Confusing constructor arguments with class properties of the same name
Wrong: Writing header = header; inside function new(bit [2:0] header) and expecting it to assign the class property.
Right: Use this.header = header; to explicitly target the class property.
Why? Inside the constructor, the local argument header shadows the class property of the same name. The this keyword resolves the ambiguity.
Practice Exercise
Challenge: Build a Config Class
Write a new class called myConfig with two properties: addr_width (an 8-bit value) and data_width (an 8-bit value). Give the constructor default values of 8 for addr_width and 32 for data_width. Add a display() method that prints both values. Then, in a module, create two objects: one using the defaults, and one that overrides both values to 16 and 64.
Hint
Follow the same pattern used in myPacket: declare the properties, write function new() with default argument values, use this to assign them, and write display() using $display.
Solution
class myConfig;
bit [7:0] addr_width;
bit [7:0] data_width;
function new (bit [7:0] addr_width = 8, bit [7:0] data_width = 32);
this.addr_width = addr_width;
this.data_width = data_width;
endfunction
function void display ();
$display ("addr_width = %0d, data_width = %0d", addr_width, data_width);
endfunction
endclass
module tb_top;
myConfig cfg0, cfg1;
initial begin
cfg0 = new ();
cfg0.display ();
cfg1 = new (16, 64);
cfg1.display ();
end
endmodule
Just like pkt0 and pkt1 earlier in this article, cfg0 and cfg1 are independent objects of the same class — each holds its own copy of addr_width and data_width.
Summary
In this tutorial, you learned:
- A class encapsulates properties (data) and methods (functions/tasks) into a single reusable unit
- Objects are created from a class using
new(), and each object holds its own independent copy of the class's properties - Arrays of class objects must be constructed individually, one
new()call per element - The
thiskeyword resolves ambiguity between constructor arguments and class properties of the same name