SV-02

SystemVerilog
Classes & Object-Oriented Programming

EcrioniX · SystemVerilog Series· ~18 min read· 7 Code Examples
Class Hierarchy — Verification Transaction Model
base_transaction + addr, data, kind + display() virtual write_txn extends base_txn + strobe, display() override read_txn extends base_txn + expected_data, display() override extends extends

Why OOP in Verification?

Verilog testbenches quickly become spaghetti — hundreds of tasks, signals passed everywhere, no reuse. SystemVerilog classes solve this by letting you model a transaction (e.g., an AXI write) as an object with its own address, data, and behavior — then pass it by handle between components (driver, monitor, scoreboard) without copying data.

1. Basic Class: Properties, Constructor, Methods

SystemVerilog
class Packet;
  // Properties (class variables)
  bit [31:0] addr;
  bit [31:0] data;
  bit         write;
  int         id;

  // Static property: shared across all Packet objects
  static int  pkt_count = 0;

  // Constructor — called by new()
  function new(bit [31:0] a = 0, bit [31:0] d = 0, bit w = 1);
    addr  = a;
    data  = d;
    write = w;
    id    = ++pkt_count;
  endfunction

  // Method
  function void display();
    $display("[PKT#%0d] %s addr=%08h data=%08h",
             id, write ? "WR" : "RD", addr, data);
  endfunction
endclass

// Usage
initial begin
  Packet p1, p2;
  p1 = new(32'hDEAD_0000, 32'hCAFE_BEEF, 1);
  p2 = new(32'hABCD_1234, 0, 0);
  p1.display();    // [PKT#1] WR addr=DEAD0000 data=CAFEBEEF
  p2.display();    // [PKT#2] RD addr=ABCD1234 data=00000000
  $display("Total packets: %0d", Packet::pkt_count);
end

2. Handles — References, Not Values

A handle is a pointer to an object on the heap. Assigning one handle to another does NOT copy the object — both handles point to the same data:

SystemVerilog — Handle semantics
Packet p1, p2, p3;

p1 = new(32'h1000, 32'hAABB, 1);
p2 = p1;        // p2 points to SAME object as p1
p2.data = 32'hFFFF;
$display(p1.data);   // prints FFFF — same object!

// Shallow copy: new fields, same handle values inside
p3 = new p1;
p3.addr = 32'h2000;
$display(p1.addr);   // still 1000 — p3 is a separate object

// Null check before use
Packet p_null;   // defaults to null
if (p_null == null)
  $display("Handle is null — object not yet created");

3. Inheritance — extends

Use extends to build a specialized class on top of a base class. The derived class inherits all properties and methods, and can add new ones or override existing ones:

SystemVerilog — Inheritance
// Base transaction
class BaseTxn;
  bit [31:0] addr;
  bit [31:0] data;
  virtual function void display();
    $display("BASE: addr=%0h data=%0h", addr, data);
  endfunction
endclass

// Write transaction: adds strobe
class WriteTxn extends BaseTxn;
  bit [3:0] strobe;

  function new(bit[31:0] a, d, bit[3:0] s = 4'hF);
    super.addr = a;    // access parent via super
    super.data = d;
    strobe = s;
  endfunction

  virtual function void display();   // override
    $display("WRITE: addr=%0h data=%0h strobe=%b", addr, data, strobe);
  endfunction
endclass

// Read transaction: adds expected data
class ReadTxn extends BaseTxn;
  bit [31:0] exp_data;

  virtual function void display();
    $display("READ:  addr=%0h exp=%0h got=%0h %s",
             addr, exp_data, data, (data===exp_data) ? "PASS" : "FAIL");
  endfunction
endclass

4. Polymorphism — One Handle, Many Types

A base class handle can hold a derived object. When a virtual method is called through the base handle, SystemVerilog dispatches to the actual derived class's method at runtime:

SystemVerilog — Polymorphism
initial begin
  BaseTxn  txn_q[$];      // queue of base handles
  WriteTxn wr;
  ReadTxn  rd;

  wr = new(32'h1000, 32'hDEAD);
  rd = new(); rd.addr = 32'h2000; rd.exp_data = 32'hBEEF;

  txn_q.push_back(wr);
  txn_q.push_back(rd);

  // Calls the correct display() for each type — polymorphism
  foreach (txn_q[i])
    txn_q[i].display();
  // Output: WRITE: addr=1000 data=dead strobe=1111
  //         READ:  addr=2000 exp=beef got=0 FAIL

  // Type check / cast
  WriteTxn tmp_wr;
  if ($cast(tmp_wr, txn_q[0]))
    $display("First txn is WriteTxn, strobe=%b", tmp_wr.strobe);
end
⚠️
$cast vs direct assignment: You can assign a derived handle to a base handle directly. The reverse (base → derived) requires $cast(derived_h, base_h) — it returns 1 if the type matches, 0 otherwise. Never force-cast without checking.

5. Abstract Classes — Virtual-Only Interface

A virtual class cannot be instantiated directly — it defines a contract that all subclasses must implement:

SystemVerilog — Abstract class
virtual class BaseDriver;
  // Pure virtual method: subclasses MUST override this
  pure virtual task drive(BaseTxn txn);

  // Concrete method shared by all subclasses
  function void pre_drive_check(BaseTxn txn);
    if (txn == null) $fatal("Null transaction passed to driver");
  endfunction
endclass

class APBDriver extends BaseDriver;
  virtual interface apb_if vif;

  task drive(BaseTxn txn);    // implements pure virtual
    pre_drive_check(txn);
    @(posedge vif.clk);
    vif.PSEL   = 1;
    vif.PADDR  = txn.addr;
    vif.PWDATA = txn.data;
    // ... APB handshake
  endtask
endclass

6. Complete Example — AXI Scoreboard Class

A real verification pattern: the scoreboard stores expected results and compares them against actual DUT output using a class-based approach:

SystemVerilog — Scoreboard class
class Scoreboard;
  // Associative array: addr → expected data
  bit [31:0] exp_mem [bit [31:0]];
  int pass_cnt, fail_cnt;

  function void write(bit[31:0] addr, data);
    exp_mem[addr] = data;
  endfunction

  function void check(bit[31:0] addr, actual);
    if (!exp_mem.exists(addr)) begin
      $warning("Scoreboard: unexpected read from addr %0h", addr);
      return;
    end
    if (actual === exp_mem[addr]) begin
      pass_cnt++;
      $display("PASS addr=%0h data=%0h", addr, actual);
    end else begin
      fail_cnt++;
      $error("FAIL addr=%0h expected=%0h got=%0h",
             addr, exp_mem[addr], actual);
    end
    exp_mem.delete(addr);
  endfunction

  function void report();
    $display("=== Scoreboard: PASS=%0d FAIL=%0d ===", pass_cnt, fail_cnt);
    if (exp_mem.size() > 0)
      $error("%0d expected reads never checked!", exp_mem.size());
  endfunction
endclass

Key OOP Concepts — Quick Reference

ConceptSyntaxPurpose
classclass MyClass; ... endclassDefine a blueprint
new()obj = new(args)Allocate object on heap
extendsclass Child extends ParentInheritance
supersuper.method()Call parent's method
virtualvirtual function display()Enable polymorphism
virtual classvirtual class BaseAbstract class — no direct instantiation
pure virtualpure virtual task drive()Force subclass to implement
staticstatic int countShared across all instances
$cast$cast(derived_h, base_h)Downcast with type check
thisthis.fieldReference current object's field
🔗
Next: Constrained-Random Verification (SV-03) — add rand fields to your classes and use constraint blocks to auto-generate legal stimulus.