SystemVerilog Series · SV-11a

SystemVerilog Series — SV-11a: Classes, Objects & Methods — VLSI Trainers
SystemVerilog Series · SV-11a

Classes, Objects & Methods

The full SystemVerilog object model — class syntax, object handles and garbage collection, constructors with arguments, properties and methods, static members, this, handle assignment vs shallow copy vs deep copy, inheritance, super, data hiding, virtual methods, polymorphism, scope resolution, parameterised classes, and memory management.

📚 What is a Class?

A class is a user-defined type that bundles data (properties) with the operations that act on that data (methods). An object is an instance of a class, created dynamically at runtime. Classes are the foundation of SystemVerilog’s verification capabilities — constrained-random testbenches, scoreboards, drivers, monitors, and UVM components are all built from classes.

Four things distinguish classes from structs in SV:

  1. Dynamic allocation — objects exist only when explicitly created with new; the declaration just creates a handle.
  2. Strong typing — unlike packed structs, objects cannot be implicitly assigned to objects of different types.
  3. Safe handles — like Java references, not raw C pointers; no pointer arithmetic.
  4. True polymorphism — inheritance, virtual methods, abstract classes.

📄 Class Syntax

// Basic class declaration
class MyClass;
  // class items here
endclass

// With lifetime
class automatic MyClass; endclass  // explicit automatic (default for classes)

// With closing name
class MyClass;
endclass : MyClass   // SV: closing name must match

// Extending another class
class SubClass extends BaseClass; endclass

// Parameterised class
class Stack #(type T = int); endclass

// Abstract class (cannot be instantiated)
virtual class BasePacket; endclass
Convention: capitalise the first letter of a class name (Packet, Driver, Scoreboard) to distinguish class types from variables at a glance.

📦 The Packet Example — A Complete Class

This annotated example introduces every element of a basic class definition.

class Packet;
  // ── Class properties (data fields) ──────────────────────────────
  bit  [3:0]  command;
  bit  [40:0] address;
  bit  [4:0]  master_id;
  integer     time_requested;
  integer     time_issued;
  integer     status;

  // ── Constructor: called automatically when new is used ──────────
  function new();
    command   = IDLE;
    address   = 41'b0;
    master_id = 5'bx;
  endfunction

  // ── Public methods ───────────────────────────────────────────────
  task clean();
    command   = 0;
    address   = 0;
    master_id = 5'bx;
  endtask

  task issue_request(int delay);
    // ... send request to bus after delay
  endtask

  function integer current_status();
    current_status = status;  // assign to function name = return value
  endfunction

endclass

📌 Objects — Handles and null

A class variable holds an object handle — essentially a safe reference to a dynamically-allocated object. Declaring a class variable does not create an object; the handle starts as null. The object is created by calling new.

Packet p;
null
declaration: handle exists, object does NOT
p = new;
Packet object
{command=IDLE
address=0
master_id=x}
new allocates object, runs constructor, returns handle
Packet p;             // handle declared — p is null
p = new;             // object created — p now holds valid handle

// Combine into one line
Packet q = new;       // declare + create together

// Null check before use
task task1(integer a, obj_example myexample);
  if (myexample == null) myexample = new;  // lazy creation
endtask
Accessing a null handle is illegal. Calling a non-static method or reading a non-static property through a null handle is undefined behaviour — implementations may issue a runtime error. Always check if (h == null) before using a handle that might not have been initialised.

📋 Handle vs C Pointer vs chandle

OperationC pointerSV object handleSV chandle
Arithmetic (increment, etc.)AllowedNot allowedNot allowed
For arbitrary data typesAllowedNot allowedNot allowed
Dereference when nullError (possible crash)Not allowed (defined error)Not allowed
CastingAllowed (unsafe)Limited (via $cast)Not allowed
Assign to address of data typeAllowedNot allowedNot allowed
Unreferenced objects garbage collectedNo (manual free)Yes (automatic)No
Default valueUndefinednullnull
For class hierarchiesC++ onlyAllowedNot allowed

📌 Object Properties

A class property is accessed via the dot operator on an object handle. Any SV data type can be a class property except net types (wire, tri) — nets are incompatible with dynamically allocated storage.

Packet p = new;

// Reading and writing properties via handle
p.command        = INIT;
p.address        = $random;
packet_time      = p.time_requested;

// Chained access through nested objects
b1.a.j              // reach into a (property of b1), access j
p.next.next.val    // traverse linked list — chain of handles

Object Methods

A method is called via the dot operator — the object is the implicit “first argument”. Properties of the class are directly accessible inside the method without any qualification.

Packet p = new;
status = p.current_status();  // access method through handle
p.clean();                    // call task method — modifies p's properties

// NOT: status = current_status(p);  — not OOP style
// The object is the focus, not the function call.
// Methods operate on their own instance's properties.

// All class methods have AUTOMATIC lifetime for arguments/locals by default
// (unlike module-level tasks which default to static)

🛠 Constructors (new)

Every class has a new constructor — either explicitly defined or auto-generated. The constructor runs every time an object is created. It cannot have timing controls (it must be non-blocking).

// Default constructor: initialises all properties to type defaults
// Called automatically even if you don't define new()

// Custom constructor: initialise specific properties
class Packet;
  integer command;
  function new();
    command = IDLE;
  endfunction
endclass

// Constructor with arguments (allows runtime customisation)
class Packet;
  integer      command;
  bit [12:0]  address;
  integer      time_requested;

  function new(int cmd = IDLE, bit[12:0] adrs = 0, int cmd_time);
    command        = cmd;
    address        = adrs;
    time_requested = cmd_time;
  endfunction
endclass

// Calling with arguments
Packet p = new(STARTUP, $random, $time);

// Initialiser in declaration runs BEFORE the constructor body
class Demo;
  int x = 10;           // set to 10 first
  function new();
    x = 20;             // then overridden by constructor → x = 20
  endfunction
endclass
new is not a function, has no return type. The LHS of the assignment (Packet p = new(...)) determines the object type. You cannot call new to get an integer or any other non-class type. The return type is always the class being constructed.

📌 Static Properties and Methods

A static property has one shared copy across all instances of the class. A static method can be called without a class instance and can only access static members.

class Packet;
  static integer fileId = $fopen("data", "r");  // shared by ALL Packet objects
  integer        seq_num;                        // per-instance
endclass

// Access static property through any instance (or via class name)
Packet p;
c = $fgetc(p.fileId);    // via instance — OK even before calling new
c = $fgetc(Packet::fileId); // via class name — preferred for statics

// Static method: callable without an instance
class id;
  static int current = 0;
  static function int next_id();
    next_id = ++current;  // OK — static can access static property
    // x = this.y;         // ILLEGAL — static cannot access 'this' or non-static
  endfunction
endclass

int my_id = id::next_id();  // call without any instance
Static method vs method with static lifetime — not the same thing. static task foo() — a static class method: callable without an instance, no access to this or non-static members.
task static bar() — a non-static class method with static variable lifetime: normal instance method, but its local variables persist between calls.

📌 this Keyword

this is a predefined handle that refers to the current object inside a non-static method. It is used to disambiguate when a local variable shadows a class property of the same name.

class Demo;
  integer x;                    // class property
  function new(integer x);      // argument shadows class property
    this.x = x;                  // this.x = class property, x = argument
  endfunction
endclass

// Without 'this': x = x would be a no-op (argument assigned to argument)
// With 'this': the class property gets the argument value — correct

// this can also be passed as an argument to another task/function
class Observer;
  task observe(Demo d); endtask
endclass
class Demo2;
  Observer obs;
  task report();
    obs.observe(this);  // pass a handle to myself
  endtask
endclass

🔂 Assignment, Renaming, and Copying

SV has three distinct ways to “copy” an object. Understanding which one is happening is essential to writing correct testbench code.

Handle assignment — aliases (NOT a copy)

Packet p1 = new;
Packet p2;
p2 = p1;        // p2 now points to the SAME object
p2.command = 5; // ALSO changes p1.command!
// one object, two names

new p1 — shallow copy (new object, shared sub-objects)

Packet p1 = new;
Packet p2 = new p1; // copies all properties of p1
p2.command = 99;    // only p2 changes
// BUT: nested objects (handles) are still shared!

Shallow copy in detail — the B.a.j example

class A;
  integer j = 5;
endclass

class B;
  integer i = 1;
  A a = new;  // B creates its own A object
endclass

function integer test;
  B b1 = new;     // creates B object, which creates A object inside
  B b2 = new b1;  // shallow copy: b2 gets its own B fields BUT shares b1's A object

  b2.i   = 10;    // b2.i = 10, b1.i still = 1   (own copy of 'i')
  b2.a.j = 50;    // ALSO changes b1.a.j = 50  (shared 'a' handle!)

  test = b1.i;    // = 1  (not changed)
  test = b1.a.j; // = 50 (shared A object was modified)
endfunction

Deep copy — must be implemented manually

class Packet;
  bit[7:0] data[];
  Packet   next;

  function Packet deep_copy();
    Packet copy = new this;      // shallow copy first
    copy.data = new[data.size](data); // deep copy the dynamic array
    if(next != null)
      copy.next = next.deep_copy();  // recursively deep copy
    return copy;
  endfunction
endclass

🔁 Inheritance and extends

A subclass extends a parent class, inheriting all its properties and methods. SystemVerilog uses single inheritance — each class has at most one parent.

class LinkedPacket extends Packet;
  LinkedPacket next;           // new property not in Packet

  function LinkedPacket get_next();
    get_next = next;           // new method not in Packet
  endfunction

  // LinkedPacket inherits: command, address, master_id,
  // time_requested, time_issued, status, clean(), issue_request(), current_status()
endclass

// Subclass handle can be assigned to parent class variable (widening)
LinkedPacket lp = new;
Packet       p  = lp;   // OK — every LinkedPacket IS a Packet

// Through p, ONLY Packet members are visible (not LinkedPacket-specific ones)
// Overridden non-virtual methods show the PARENT's version when called via p

📌 super Keyword

super accesses members of the immediate parent class from within a derived class, needed when a member is overridden and both versions are needed.

class Packet;                   // parent
  integer value;
  function integer delay();
    delay = value * value;
  endfunction
endclass

class LinkedPacket extends Packet; // derived
  integer value;               // overrides parent's value
  function integer delay();     // overrides parent's delay
    delay = super.delay()       // call parent's delay()
            + value * super.value; // use both this class's AND parent's value
  endfunction
endclass

// super.new() must be the FIRST statement in a derived class constructor
class EtherPacket extends Packet;
  function new();
    super.new();       // MUST be first — initialises parent before child
    value = 10;        // child-specific init
  endfunction
endclass

// Can also pass arguments with extends
class EtherPacket extends Packet(5); // always passes 5 to Packet::new
endclass
super only reaches one level up. super.super.x is illegal. To access a grandparent member, the parent class method must itself call super.

🔒 Data Hiding — local, protected, public

(no qualifier)
public — accessible from anywhere that has the object handle. Default for all class members.
protected
Accessible inside the class and its subclasses. Not visible outside the class hierarchy.
local
Accessible only inside this class. Not visible in subclasses — most restrictive.
class Packet;
  local integer     i;     // only visible inside Packet's methods
  protected integer j;     // visible in Packet and its subclasses
  integer           k;     // public — visible everywhere

  function integer compare(Packet other);
    // local class properties CAN be accessed from a different instance of the SAME class
    compare = (this.i == other.i);  // both i's accessible here
  endfunction
endclass

class LinkedPacket extends Packet;
  task do_stuff();
    j = 10;    // OK — j is protected, visible in subclass
    // i = 5;  // ILLEGAL — i is local, NOT visible in subclass
    k = 20;    // OK — k is public
  endtask
endclass

Packet p = new;
// p.i = 5;  // ILLEGAL — local: cannot access from outside the class
// p.j = 5;  // ILLEGAL — protected: cannot access from outside the hierarchy
p.k = 5;    // OK — public

📌 const Class Properties

Two kinds of constant properties exist in SV classes: global constants (value fixed at declaration) and instance constants (value set once per object in the constructor).

class Jumbo_Packet;
  const int max_size = 9 * 1024;  // GLOBAL constant — same for all instances
                                       // typically also declared static
  byte payload[];
  function new(int size);
    payload = new[size > max_size ? max_size : size];
    // max_size = 100;  // ILLEGAL — global const cannot be re-assigned
  endfunction
endclass

class Big_Packet;
  const int size;     // INSTANCE constant — unique per object
                          // no initial value here
  byte payload[];
  function new();
    size = $random % 4096; // ONE assignment in constructor — OK
                               // cannot assign again anywhere else
    payload = new[size];
  endfunction
endclass
Global const is typically also static. If all instances share the same value anyway, declaring it static const saves memory — only one copy exists. An instance constant cannot be static because that would prevent the per-object assignment in the constructor.

Virtual Methods and Abstract Classes

A virtual method in a base class defines a contract that all derived classes must fulfil. When called through a base-class handle, the most-derived version of the method runs — this is the key to polymorphism.

virtual class BasePacket;    // abstract: cannot instantiate directly
  virtual function integer send(bit[31:0] data);
  endfunction               // body empty — pure virtual
endclass

class EtherPacket extends BasePacket;
  function integer send(bit[31:0] data);
    // Ethernet-specific implementation
    return 1;
  endfunction
endclass

// Non-virtual override: only dispatches correctly if called via a derived handle
class Packet;
  integer i = 1;
  function integer get(); get = i; endfunction  // NOT virtual
endclass
class LinkedPacket extends Packet;
  integer i = 2;
  function integer get(); get = -i; endfunction // overrides, NOT virtual
endclass

LinkedPacket lp = new;
Packet       p  = lp;
int j;
j = p.i;       // j = 1  (Packet's i, not LinkedPacket's)
j = p.get();   // j = 1  (Packet's get(), NOT -2 or -1 — not virtual!)
j = lp.get(); // j = -2 (LinkedPacket's get() — called via derived handle)

🔄 Polymorphism — Dynamic Method Dispatch

With virtual methods, a base-class handle calls the correct derived-class implementation at runtime, even when the compiler didn’t know which derived class would be stored.

BasePacket packets[100];   // array of base-class handles

// Store different derived types
EtherPacket ep = new;  // extends BasePacket
TokenPacket tp = new;  // extends BasePacket
GPSSPacket  gp = new;  // extends EtherPacket (which extends BasePacket)

packets[0] = ep;
packets[1] = tp;
packets[2] = gp;

// Virtual dispatch: calls the RIGHT implementation at runtime
packets[1].send();  // calls TokenPacket::send(), not EtherPacket::send()
packets[2].send();  // calls GPSSPacket::send() (if overridden), or EtherPacket::send()

// Downcast using $cast — required for derived-class-specific operations
EtherPacket ep2;
if($cast(ep2, packets[0]))   // succeeds: packets[0] holds an EtherPacket
  ep2.ether_specific_method();
if(!$cast(ep2, packets[1]))  // fails: packets[1] is a TokenPacket
  $display("Not an EtherPacket");

:: Class Scope :: Operator

The :: operator accesses a specific named scope’s members — class names, package names, or static members — without needing an instance.

class Base;
  typedef enum {bin, oct, dec, hex} radix;
  static task print(radix r, integer n); endtask
endclass

int bin = 123;         // local variable also named 'bin'
Base b = new;

// Disambiguate enum constant from local variable
b.print(Base::bin, bin);  // Base::bin = enum value, bin = integer 123
Base::print(Base::hex, 66); // call static method without instance

// :: also accesses type declarations inside classes
class StringList;
  class Node;   // nested class
    string name; Node link;
  endclass
endclass
class StringTree;
  class Node;   // different Node in different scope
    string name; Node left, right;
  endclass
endclass
// StringList::Node ≠ StringTree::Node — unambiguous
StringList::Node n1;
StringTree::Node n2;

📄 Out-of-Block Declarations (extern)

Method bodies can be defined outside the class using extern for the prototype and the class name qualifier for the implementation. This separates interface from implementation.

class Packet;
  Packet next;

  function Packet get_next();  // inline — short enough
    get_next = next;
  endfunction

  // extern: prototype only — body is outside the class
  extern protected virtual function int send(int value);
endclass

// Out-of-block body: drop qualifiers, add ClassName::
function int Packet::send(int value);
  // implementation here
endfunction
// Must be in the same scope as the class declaration.
// Prototype and body must match exactly (same argument types, same return type).

📋 Parameterised Classes

Classes can be parameterised like modules — using value parameters for sizes and type parameters for element types. Each combination of parameters is a separate specialisation.

// Value parameter
class vector #(int size = 1);
  bit [size-1:0] a;
  static int count = 0;     // EACH specialisation has its OWN count
endclass

vector #(10)        vten;   // 10-bit vector
vector #(.size(2))  vtwo;   // named parameter
typedef vector#(4)  Vfour;  // typedef for reuse

// Type parameter — generic container
class stack #(type T = int);
  local T items[];
  task push(T a); endtask
  task pop(ref T a); endtask
endclass

stack               is;    // default: stack of int
stack#(bit[1:10])  bs;    // stack of 10-bit vectors
stack#(real)        rs;    // stack of reals
typedef stack#(Vfour) Stack4;

// Parameterised inheritance
class C #(type T = bit); endclass
class D1 #(type P = real) extends C;             // C with T=bit (default)
class D2 #(type P = real) extends C #(integer); // C with T=integer
class D3 #(type P = real) extends C #(P);       // C with T=P (forwarded)

📌 typedef class — Forward Declarations

When two classes need handles to each other, declare one forward with typedef class before defining it.

typedef class C2;   // forward declaration — C2 is of type class

class C1;
  C2 c;            // uses C2 before it is fully declared
endclass

class C2;
  C1 c;
endclass

🔌 Memory Management

SystemVerilog uses automatic garbage collection — objects are reclaimed when no handle refers to them. You never call free(). This eliminates memory leaks and dangling pointers.

myClass obj = new;
fork
  task1(obj);   // both tasks hold a reference to obj
  task2(obj);
join_none

// obj cannot be freed here — task1 and task2 are still using it
// When BOTH tasks finish AND obj itself goes out of scope,
// the garbage collector reclaims the memory automatically

obj = null;   // parent no longer needs obj — but task1 and task2 still hold refs
// Object stays alive until all references are dropped
Garbage collection rules: An object lives as long as any handle anywhere in the simulation points to it. When the last handle is set to null or goes out of scope, the object becomes eligible for collection. You never need to explicitly free anything — and you cannot accidentally cause a dangling pointer.

📋 Quick Reference

Class keyword glossary

Keyword/syntaxWhat it does
class Name; endclassDeclare a class type
virtual classAbstract class — cannot be instantiated
extends BaseInherit all members of Base
Name h = new(args)Create object, run constructor, store handle
h == nullTest for uninitialised handle
h2 = new h1Shallow copy of h1 into new object h2
static property/methodShared across all instances; no this in static method
thisHandle to current object; only in non-static methods
super.memberAccess overridden parent member from derived class
super.new(args)Call parent constructor — must be first in child’s new()
localPrivate — class-only; not visible in subclasses
protectedVisible in class and subclasses; not outside
virtual functionPolymorphic — derived version called even via base handle
extern functionPrototype inside class; body defined outside with Class::
class Name #(type T=int)Parameterised class — separate specialisation per parameter set
Class::memberScope resolution — static members, type declarations
typedef class C2Forward declaration for mutually-referencing classes
Coming next: SV-12 covers Section 12 — Random Constraints: rand and randc properties, constraint blocks, randomize(), constraint inheritance, inline randomize() with, disabling constraints with constraint_mode(), and pre/post randomisation hooks.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top