A constructor is simply a method used to create a new object of a particular class data type. C/C++ requires complex memory allocation techniques, and improper de-allocation there can lead to memory leaks and other behavioral issues. SystemVerilog, although not a general-purpose programming language, handles object construction simply and provides automatic garbage collection — you never have to manually free a class object yourself.
9 min read | Intermediate Level
What You'll Learn
- What a constructor is, and what happens if you don't write one yourself
- How constructor chaining works between a base and derived class via
super.new() - Why a constructor can never be declared
virtualorstatic - How a "typed constructor" lets you construct a subclass object directly into a base-class handle
Explicitly Defining a Constructor
// Define a class called "Packet" with a 32-bit variable to store address
// Initialize "addr" to 32'hfade_cafe in the new function, also called constructor
class Packet;
bit [31:0] addr;
function new ();
addr = 32'hfade_cafe;
endfunction
endclass
module tb;
// Create a class handle called "pkt" and instantiate the class object
initial begin
// The class's constructor new() fn is called when the object is instantiated
Packet pkt = new;
// Display the class variable - Because constructor was called during
// instantiation, this variable is expected to have 32'hfade_cafe;
$display ("addr=0x%0h", pkt.addr);
end
endmodule
Declaring pkt and calling new creates an object of class Packet and automatically calls the new() function inside the class. new() is the class constructor — a way to initialize class variables with some value. It has no return type, and it's non-blocking (no time-consuming statements allowed inside it).
Output
ncsim> run addr=0xfadecafe ncsim: *W,RNQUIE: Simulation is complete.
Quick Check
Q: If function new(int val); required an argument with no default value, what would Packet pkt = new; (called with no arguments) do?
Show Answer
It would fail to compile. A constructor argument with no default value is required at every call site — calling new without supplying it is a compile-time error, the same as calling any other function with a missing required argument.
The Implicit Constructor
If a class doesn't define its own new() function, SystemVerilog automatically provides one. Every property then gets its type's own default value — here, addr initializes to zero, since that's the default for bit:
// Define a simple class with a variable called "addr"
// Note that the new() function is not defined here
class Packet;
bit [31:0] addr;
endclass
module tb;
// When the class object is instantiated, then the constructor is
// implicitly defined by the tool and called
initial begin
Packet pkt = new;
$display ("addr=0x%0h", pkt.addr);
end
endmodule
Output
ncsim> run addr=0x0 ncsim: *W,RNQUIE: Simulation is complete.
Constructor Chaining in Inherited Classes
A derived class's new() method typically calls its parent class's constructor first, using super.new(). Once the base class constructor completes, each property defined in the derived class gets its own default value, and only then does the rest of the derived class's new() method run:
// Define a simple class and initialize the class member "data" in new() function
class baseClass;
bit [15:0] data;
function new ();
data = 16'hface;
endfunction
endclass
// Define a child class extended from the above class with more members
// The constructor new() function accepts a value as argument, and by default is 2
class subClass extends baseClass;
bit [3:0] id;
bit [2:0] mode = 3;
function new (int val = 2);
// The new() function in child class calls the new function in
// the base class using the "super" keyword
super.new ();
// Assign the value obtained through the argument to the class member
id = val;
endfunction
endclass
module tb;
initial begin
// Create two handles for the child class
subClass sc1, sc2;
// Instantiate the child class and display member variable values
sc1 = new ();
$display ("data=0x%0h id=%0d mode=%0d", sc1.data, sc1.id, sc1.mode);
// Pass a value as argument to the new function in this case and print
sc2 = new (4);
$display ("data=0x%0h id=%0d mode=%0d", sc2.data, sc2.id, sc2.mode);
end
endmodule
The order of operations here is: subClass's new() starts, immediately calls super.new(), baseClass's constructor runs to completion (setting data), control returns to subClass, its own properties get their declared or default values (mode = 3), and finally the rest of subClass's constructor body runs (id = val).
Output
ncsim> run data=0xface id=2 mode=3 data=0xface id=4 mode=3 ncsim: *W,RNQUIE: Simulation is complete.
Quick Check
Q: If subClass's constructor didn't explicitly call super.new() at all, would sc1.data still show 0xface?
Show Answer
Yes, in this specific case. Since baseClass's constructor takes no required arguments, SystemVerilog implicitly calls it automatically at the start of the derived class's constructor even if you never write super.new() yourself. This only breaks down if the base constructor requires an argument with no default — then an explicit super.new(...) becomes mandatory.
Why a Constructor Can't Be virtual or static
A constructor can be declared local or protected (see SystemVerilog local vs protected for what those restrict), but never static or virtual:
class ABC;
string fruit;
// Note that the constructor is defined as "virtual" which is not allowed
// in SystemVerilog. Class constructor cannot be "static" either
virtual function new ();
fruit = "Apple";
endfunction
endclass
// Instantiate the class object and print its contents
module tb;
initial begin
ABC abc = new();
$display ("fruit = %s", abc.fruit);
end
endmodule
Output
virtual function new ();
|
ncvlog: *E,BADQAL (testbench.sv,6|21): Lifetime or qualifier(s) 'virtual' not allowed before function new declaration.
Typed Constructors
You can call a subclass's new() function and assign the result directly to a handle of the base class, all in a single statement, by referencing the subclass's constructor with the scope resolution operator :::
class C;
endclass
class D extends C;
endclass
module tb;
initial begin
C c = D::new;
end
endmodule
c, a handle of base class C, now references a newly constructed object of type D. This achieves the exact same effect as the more verbose two-step version below:
module tb;
initial begin
D d = new;
C c = d;
end
endmodule
Quick Check
Q: After C c = D::new;, is c an object of type C, or type D?
Show Answer
Type D. The typed constructor built an actual D object — c is just a base-class-typed handle pointing to it. This is exactly the same distinction covered in SystemVerilog Class Handle: the handle's declared type and the object it actually points to don't have to match, as long as the object's type is compatible with (here, derived from) the handle's type.
Where Is This Used?
Typed constructors are handy in factory-style code, where you want to build a specific derived object but store or pass it around using a common base-class handle — exactly the pattern UVM's own factory mechanism is built on, letting a testbench create objects of a specific type while working with them generically through their base class interface.
Common Beginner Mistakes
Mistake #1: Assuming properties start as garbage without an explicit constructor
As the implicit constructor example shows, an object's properties still get well-defined values — the type's own default (like 0 for numeric types) — even with no new() written at all. Why? SystemVerilog always provides an implicit constructor if you don't write your own, so properties are never left in an undefined or garbage state.
Mistake #2: Declaring a constructor as virtual or static
Both are illegal on function new(), and produce a compile-time error. Why? A constructor's job is to build one specific object; the language simply doesn't allow the virtual or static qualifiers on it, even though local and protected are both fine.
Mistake #3: Assuming a derived class's own properties are set before the base constructor runs
It's the opposite order. Why? super.new() runs the base class's constructor to completion first; only after that do the derived class's own properties get their default or declared values, followed by the rest of the derived constructor's body.
Mistake #4: Confusing a typed constructor with a later handle reassignment
C c = D::new; actually constructs the object as type D, in one step — it's not the same as constructing a C and reassigning it later. Why? The distinction matters for polymorphism: c behaves as a genuine D object underneath, even though its declared handle type is C.
Practice Exercise
Challenge: Three Levels of Constructor Chaining
Extend the baseClass/subClass example with a third class, grandchildClass, that extends subClass and adds its own property tag (default value 7). Its constructor should call super.new() and accept its own argument for id. Predict what data, id, mode, and tag will show when constructed with no arguments.
Hint
The chain now runs three levels deep: grandchildClass::new() calls super.new() (which is subClass::new()), which itself calls super.new() (which is baseClass::new()) — each one completes fully before control returns to the level that called it.
Solution
class grandchildClass extends subClass;
bit [3:0] tag = 7;
function new (int val = 2);
super.new (val);
endfunction
endclass
module tb;
initial begin
grandchildClass gc;
gc = new ();
$display ("data=0x%0h id=%0d mode=%0d tag=%0d", gc.data, gc.id, gc.mode, gc.tag);
end
endmodule
data=0xface id=2 mode=3 tag=7. baseClass::new() sets data; back in subClass::new(), mode gets its default and id is set from val (defaulting to 2); back in grandchildClass::new(), tag gets its own default of 7 — the same base-first, then-each-level ordering, just extended one level further.
Summary
In this tutorial, you learned:
- A constructor (
new()) initializes a class object's properties when it's created, and has no return type - If a class defines no constructor of its own, SystemVerilog provides an implicit one, and every property gets its type's default value
- A derived class's constructor calls its base class's constructor via
super.new(), which runs to completion before the derived class's own properties are initialized - A constructor can be
localorprotected, but nevervirtualorstatic - A typed constructor (
C c = D::new;) builds a subclass object and assigns it directly to a base-class handle in one statement