HomeSystemVerilog VerificationDay 7 — Classes & Objects
DAY 7 · OOP PHASE

SystemVerilog Classes & Objects — Class Declaration, Handles, new()

By EcrioniX · Updated Jun 13, 2026

Up to Day 6, you've built the structural testbench: interfaces, clocking blocks, and synchronous drive/sample patterns. Now you step into the software side of verificationobject-oriented programming with SystemVerilog classes. Classes let you create reusable, encapsulated verification components (drivers, monitors, scoreboards) that are dynamic, flexible, and easy to extend.

CLASS ANATOMY PROPERTIES (Data Members) logic [7:0] data; logic valid; string addr; Fields per object METHODS (Behavior/Functions) function new(); function display(); function randomize(); Behavior per object tr = new(); // Handle tr points to newly allocated object

What is a class in SystemVerilog?

A class is a template for creating objects. It defines:

Unlike Verilog modules which are structural and instantiated statically in the module hierarchy, classes are software objects created dynamically at runtime. This is essential for testbenches where you may create thousands of transactions, drivers, or monitors on the fly.

When should you use a class vs a module?

Use modules for structural hardware: top-level testbench, clock generation, module instantiation, interface creation. Use classes for behavioral testbench components: transaction generators, drivers, monitors, scoreboards, any object that encapsulates data and algorithms.

Class declaration syntax

Basic class structure

class transaction;
  // Properties
  logic [7:0]  data;
  logic        valid;
  string       addr;

  // Constructor
  function new();
    data = 0;
    valid = 0;
    addr = "";
  endfunction

  // Methods
  function display();
    $display("data=%0h valid=%b addr=%s", data, valid, addr);
  endfunction
endclass

Properties: what the object holds

Properties (member variables) are declared inside the class, before any methods. They can be logic, int, string, arrays, queues, or even handles to other objects. Each object instance has its own copy of these properties — if two objects have a data property, changing one doesn't affect the other.

Methods: what the object does

Methods are functions declared inside the class. They can read and write the object's properties. A method can have return types, arguments, and local variables just like a normal function. Inside a method, you access properties directly by name — no prefix needed (though you can use this.property for clarity).

The constructor: new()

The new() function is called automatically when you create an object with new. It typically initializes properties and can accept arguments. For example:

class fifo;
  int depth;
  int width;

  function new(int d = 16, int w = 8);
    depth = d;
    width = w;
  endfunction
endclass

// Create a 32-deep, 16-bit wide FIFO
fifo f = new(32, 16);

What is a handle?

A handle is a reference (pointer) to an object. When you declare transaction tr; you are declaring a handle named tr. Initially it is null (points to nothing). When you execute tr = new() you allocate a new object and tr points to it.

CodeWhat happens
transaction tr;Declare a handle; tr is null initially
tr = new();Create new object; tr now points to it
tr.data = 8'hAA;Write property of the object tr points to
val = tr.data;Read property of the object tr points to
tr = null;tr no longer points to anything (object deleted)

Handles are passed by reference: if you pass a handle to a function and that function modifies the object, the original caller sees the changes. This is fundamentally different from simple data types (which are passed by value).

The new() operator and object allocation

The new() operator:

Copy vs reference with handles

Two different handles can point to the same object:

transaction tr1 = new();
transaction tr2 = tr1;  // tr2 points to the SAME object as tr1

tr1.data = 8'hAA;
$display(tr2.data);     // Prints 8'hAA — they share the object

tr2 = new();            // Create new object; tr2 now points to it
tr1.data = 8'hBB;
$display(tr2.data);     // Still 0 — tr2 points to a different object

Complete example: uart_transaction class

uart_transaction.sv — Complete class with properties, methods, constructor
// ============================================================
// uart_transaction.sv — UART transaction class example
// EcrioniX · SV Verification Course · Day 7
// ============================================================

class uart_transaction;
  // ---- Properties ----
  rand logic [7:0]  data;         // payload byte
  rand logic        parity;       // parity bit
  logic             parity_error; // error flag
  string            operation;    // "tx" or "rx"
  int unsigned      timestamp;    // when this transaction occurred

  // ---- Constructor ----
  function new(string op = "tx");
    data = 0;
    parity = 0;
    parity_error = 0;
    operation = op;
    timestamp = 0;
  endfunction

  // ---- Methods ----

  // Display the transaction
  function void display();
    $display("[%0t] UART %s: data=0x%02h parity=%b parity_error=%b timestamp=%0d",
             $time, operation, data, parity, parity_error, timestamp);
  endfunction

  // Calculate parity (even parity: count of 1's should be even)
  function logic calc_parity();
    logic p = 0;
    for (int i = 0; i < 8; i++)
      p = p ^ data[i];
    return p;  // Returns 1 if odd number of 1's
  endfunction

  // Check if parity is correct
  function logic is_parity_ok();
    logic calculated_parity = calc_parity();
    return (calculated_parity == parity);
  endfunction

  // Randomize with constraints
  function void randomize_payload();
    if (!randomize())
      $fatal(1, "Randomization failed");
    parity = calc_parity();
  endfunction

  // Compare two transactions
  function logic compare(uart_transaction other);
    if (other == null)
      return 0;
    return (data == other.data) && (parity == other.parity);
  endfunction

  // Copy all fields from another transaction
  function void copy_from(uart_transaction source);
    if (source == null) begin
      $warning("Attempted to copy from null transaction");
      return;
    end
    this.data = source.data;
    this.parity = source.parity;
    this.parity_error = source.parity_error;
    this.operation = source.operation;
    this.timestamp = source.timestamp;
  endfunction

endclass : uart_transaction

// ---- Example usage ----
module uart_transaction_demo;
  initial begin
    // Create transaction objects
    uart_transaction tr1 = new("tx");
    uart_transaction tr2 = new("rx");

    // Set properties
    tr1.data = 8'hA5;
    tr1.parity = 1'b1;  // odd number of 1's in 0xA5: parity=1
    tr1.timestamp = 100;
    tr1.display();

    // Check parity
    if (tr1.is_parity_ok())
      $display("tr1 parity is correct");
    else
      $display("tr1 parity ERROR");

    // Create and randomize another
    tr2.data = 8'h3C;
    tr2.parity = tr2.calc_parity();
    tr2.operation = "rx";
    tr2.display();

    // Copy transaction
    uart_transaction tr3 = new();
    tr3.copy_from(tr1);
    tr3.operation = "copied";
    tr3.display();

    // Compare
    if (tr1.compare(tr3))
      $display("tr1 and tr3 have same data/parity");

    $finish;
  end
endmodule : uart_transaction_demo

Important: handle initialization

Always check if a handle is null before using it. If you pass a null handle to a function that expects to access its properties, you'll get a fatal error. Use if (handle != null) before dereferencing.

The this keyword

Inside a method, this is an implicit reference to the current object. It is rarely necessary — you can access properties directly — but it is useful for clarity or when you have a local variable with the same name as a property:

function void set_data(logic [7:0] data);
  this.data = data;  // 'this.data' is the property, 'data' is the argument
endfunction

Classes vs modules: key differences

AspectModuleClass
InstantiationStatic, at compile timeDynamic, at runtime with new()
Port connectionsExplicit ports/interfacesNo ports — access via handle
Multiple instancesFixed count in hierarchyUnlimited; create as many as needed
LifetimeEntire simulationExists until handle set to null
Use caseStructural — clocks, resets, DUTBehavioral — drivers, monitors, TX

Why classes are essential for UVM testbenches

UVM (Universal Verification Methodology) is built entirely on classes because:

Day 7 takeaways

Frequently Asked Questions

What is the difference between a class and a module?

Modules are structural and static — instantiated at compile time, they form the hardware hierarchy. Classes are software objects created dynamically at runtime. Use modules for testbench infrastructure (top level, clocks, interfaces); use classes for testbench behavior (drivers, monitors, transactions, scoreboards).

What is a handle?

A handle is a reference (pointer) to an object. Declaring transaction tr; creates a null handle. Executing tr = new(); allocates an object and makes tr point to it. Multiple handles can point to the same object. If you assign tr to another handle, both point to the same object until one is reassigned.

What does new() do?

The new() operator allocates memory for a new object, calls the class's new() constructor to initialize it, and returns a handle to it. A class can have only one constructor (the new() function). If you define a custom new() with arguments, you can initialize the object when creating it: uart_transaction tr = new("rx");

Can I copy an object?

You can copy the handle (both point to the same object), but to copy the object's contents you must write a method that copies each field. The example's copy_from() method demonstrates this. Assigning one handle to another (tr2 = tr1) makes both point to the same object — changes to one affect the other.

Previous
← Day 6: Clocking Blocks

← Full course roadmap