Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • Start Here6
  • SystemVerilog Language Foundations28
  • Object-Oriented SystemVerilog for Verification30
    • Classes, Objects & Handles
      • Class Declaration & Objects
      • Handles vs Objects
      • Constructors & super.new()
      • Shallow vs Deep Copy
      • this, Scope & Nested Classes
    • Inheritance and Polymorphism
    • Static, Encapsulation & Parameterized Classes
    • Virtual Interfaces: the Class-to-Pins Bridge
    • Events, Semaphores, Mailboxes and Processes
    • OOP Patterns for Testbenches
  • Constraint Randomization — Interview-Critical Deep Dive36
  • SystemVerilog Assertions — Interview-Critical Deep Dive40
  • Functional Coverage in SystemVerilog22
  • Testbench Architecture in SystemVerilog23
  • Advanced Language & Integration25
  • Senior & Interview Preparation21
  • Principal-Level Verification28
Loading lesson

‹Classes, Objects & Handles
  1. Topics
  2. SystemVerilog
  3. Object-Oriented SystemVerilog for Verification
  4. Classes, Objects & Handles
  5. Class Declaration & Objects

Part 2 · OOP for Verification · Intermediate

Class Declaration & Objects

Declare a class, build objects with new, and follow the construction flow step by step.

On this page

    Why this matters

    A test needs thousands of packets, each with different contents, created while it runs. Modules are fixed before time zero, so they cannot do it. Classes can: new builds a fresh object whenever the test asks.

    After this lesson you can: declare a class with properties and methods, build objects with new, and explain the four steps that run inside new.

    Blueprint and instance

    A class declaration allocates no memory. It lists properties and methods, and nothing more.

    Calling new allocates one object on the simulator heap and runs its constructor. A module instance exists from elaboration onward, while an object exists only from its new call until the last handle to it disappears.

    Predict first A testbench declares bus_txn t; and has not called new yet. What does t hold, and what happens if the next line reads t.len?

    t holds null: it is a handle that points at nothing. Reading t.len is a null dereference, which stops the simulation with a fatal error at run time. The compiler cannot catch it, because whether t is null is only known while the test runs.

    A first transaction class

    A transaction describes one operation as data. This one pairs fields with two behaviours, print and compare. The testbench builds two objects with the same contents, compares them, then changes one field.

    Run it on Verilator: Icarus 13 does not support comparing class handles.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    class bus_txn;
      // -------- properties (per-object state) --------
      bit [31:0]   addr;
      bit [31:0]   data;
      bit          write;
      int unsigned len = 1;
      string       name;
    
      // -------- methods (behavior) --------
      function new(string name = "bus_txn");
        this.name = name;
      endfunction
    
      function void print();
        $display("[%s] %s addr=%0h data=%0h len=%0d",
                 name, write ? "WR" : "RD", addr, data, len);
      endfunction
    
      function bit compare(bus_txn other);
        return (addr == other.addr) && (data == other.data) &&
               (write == other.write) && (len == other.len);
      endfunction
    endclass
    
    module tb_bus_txn;
      int errors = 0;
      initial begin
        bus_txn t1;
        bus_txn t2;
        t1 = new("t1");
        t1.addr = 'h1000; t1.data = 'hCAFE; t1.write = 1; t1.len = 4;
        t2 = new("t2");
        t2.addr = 'h1000; t2.data = 'hCAFE; t2.write = 1; t2.len = 4;
        t1.print();
        t2.print();
        if (!t1.compare(t2)) errors++;   // same contents
        if (t1 == t2) errors++;          // == compares handles: two objects differ
        t2.data = 'hBEEF;
        if (t1.compare(t2)) errors++;    // contents now differ
        if (errors == 0) $display("PASS: t1 and t2 match by contents, are two objects, and differ after one write");
        else $fatal(1, "FAIL: %0d bus_txn checks failed", errors);
        $finish;
      end
    endmodule
    Terminal
    [t1] WR addr=1000 data=cafe len=4
    [t2] WR addr=1000 data=cafe len=4
    PASS: t1 and t2 match by contents, are two objects, and differ after one write

    compare looks at contents, while == on two handles asks whether they point at the same object. The next lesson is about that difference.

    The construction flow

    Calling t = new runs a fixed sequence. Memory is allocated, every property takes its type default or declared initializer, the constructor body runs, and the reference is returned into the handle.

    Four steps run inside every new. The handle only stops being null at the last one.

    Step

    What the simulator does

    Visible effect

    Allocate

    Reserves one object on the heap

    Handle still null until return

    Default

    Applies type defaults and initializers

    Stable values inside new

    Construct

    Runs the constructor body

    Name stored, sub-objects built

    Return

    Writes the reference into the handle

    Object reachable through t

    Testbench

    Four objects are built in a loop, then mutated through one handle. The checks prove fresh construction gives independent objects.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    class txn_l1;
      string name;
      int unsigned addr;
      int unsigned len;
      function new(string name = "txn");
        this.name = name;
        this.addr = 0;
        this.len = 1;
      endfunction
    endclass
    
    module tb_l1_build;
      int errors = 0;
      initial begin
        txn_l1 d;
        txn_l1 items[4];
        d = new();
        if (d.len != 1) errors++;
        if (d.addr != 0) errors++;
        for (int i = 0; i < 4; i++) begin
          items[i] = new();
          items[i].addr = 32'h1000 + i;
          items[i].len = i + 1;
        end
        if (items[0] == items[1]) errors++;
        items[0].addr = 32'h9999;
        if (items[1].addr != 32'h1001) errors++;
        if (items[2].len != 3) errors++;
        if (errors == 0) $display("PASS: 4 independently constructed txns hold distinct fields");
        else $fatal(1, "FAIL: construction independence checks");
        $finish;
      end
    endmodule
    Terminal
    PASS: 4 independently constructed txns hold distinct fields

    Common pitfall

    Declaring a handle and calling methods without new is a null dereference. It is fatal at runtime, not a compile error. Keep construction near ownership boundaries so the missing new is easy to find.

    Interview

    The construction trio recurs in interviews.

    • Class versus object: a type against a runtime instance, with no memory until new.

    • Where objects live: the simulator heap, with no hierarchical path to an object.

    • No always blocks in classes: behavior runs in tasks and functions started explicitly.

    Check yourself A class declaration names three properties and two methods. How much memory does the declaration itself allocate?

    None. Memory appears only when new runs. The declaration is a type.

    Check yourself Two handles are built by two new calls and given identical fields. Is t1 == t2 true?

    No. == on handles asks whether they point at the same object, and two new calls build two objects. Equal contents need a compare method, as bus_txn has.

    Project step

    Add a pkt_txn class for PacketFIFO packets: fields kind, dest, len and a payload queue; a header() that packs the header byte; a check_byte() that XORs the header with every payload byte; and sprint() and compare(). Check it against the Part 0 packet: header 8'h22 and check byte 8'hBB.

    One way to write it. kind_e is an enum, and payload is a queue of bytes, both from Part 1. Run it on Verilator: Icarus 13 does not support a queue inside a class.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    typedef enum bit [1:0] {DATA, CTRL, ERR} kind_e;
    
    // PacketFIFO transaction: one packet as data, not as bytes on pins.
    class pkt_txn;
      kind_e     kind;
      bit [1:0]  dest;
      bit [3:0]  len;
      bit [7:0]  payload[$];
    
      function bit [7:0] header();
        return {kind, dest, len};
      endfunction
    
      // XOR of the header and every payload byte.
      function bit [7:0] check_byte();
        bit [7:0] c = header();
        foreach (payload[i]) c ^= payload[i];
        return c;
      endfunction
    
      function string sprint();
        string s = $sformatf("%s dest=%0d len=%0d payload=", kind.name(), dest, len);
        foreach (payload[i]) s = {s, $sformatf("%h ", payload[i])};
        return {s, $sformatf("check=%h", check_byte())};
      endfunction
    
      function bit compare(pkt_txn other);
        if (kind != other.kind || dest != other.dest || len != other.len) return 0;
        if (payload.size() != other.payload.size()) return 0;
        foreach (payload[i]) if (payload[i] != other.payload[i]) return 0;
        return 1;
      endfunction
    endclass
    
    module tb_pkt_txn;
      int errors = 0;
      initial begin
        pkt_txn p;
        pkt_txn q;
        p = new();
        p.kind = DATA; p.dest = 2; p.len = 2;
        p.payload = '{8'hA5, 8'h3C};
        $display("%s", p.sprint());
        if (p.header() != 8'h22) errors++;      // the Part 0 header byte
        if (p.check_byte() != 8'hBB) errors++;  // and its check byte
        q = new();
        q.kind = DATA; q.dest = 2; q.len = 2;
        q.payload = '{8'hA5, 8'h3C};
        if (!p.compare(q)) errors++;            // same packet, two objects
        q.payload[1] = 8'h00;
        if (p.compare(q)) errors++;             // one payload byte differs
        if (errors == 0) $display("PASS: pkt_txn builds header 22 and check byte bb, and compare sees a one-byte change");
        else $fatal(1, "FAIL: %0d pkt_txn checks failed", errors);
        $finish;
      end
    endmodule
    Terminal
    DATA dest=2 len=2 payload=a5 3c check=bb
    PASS: pkt_txn builds header 22 and check byte bb, and compare sees a one-byte change

    Remember

    • A class is a type: declaring it allocates nothing.

    • new allocates, applies defaults, runs the constructor and returns the reference into the handle.

    • A handle is null until new returns, and using a null handle is a run-time fatal, not a compile error.

    Rule of thumb: a class is the blueprint, new builds the object, the handle points at it.

    How this was checked

    Run with Verilator 5.048 (--binary --timing) on 2026-10-18; every bench printed the PASS line shown, with no warnings. Icarus 13 cannot run the ones that compare class handles or keep a queue inside a class.

    The benches build a handful of objects and check their fields. They show how construction behaves on this simulator; memory use and object lifetime are covered in the next lesson.

    Related topics

    • Handles vs Objects

    • Constructors & super.new()

    Previous

    35 / 259

    Next: Handles vs Objects

    Up next

    Up nextHandles vs Objects

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Constructors & super.new()
    • Shallow vs Deep Copy
    • this, Scope & Nested Classes