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. Handles vs Objects

Part 2 · OOP for Verification · Intermediate

Handles vs Objects

Handles alias objects, null handles crash at runtime, and reachability decides lifetime.

On this page

    Why this matters

    A list of saved packets that quietly changes after it was saved is one of the most common testbench bugs. It happens because assigning a handle copies a reference, never an object.

    After this lesson you can: predict what assignment between handles does, guard against null handles, and say what keeps an object alive.

    Handles are references

    A class variable is never the object itself. It is a handle: a reference to an object on the heap, or null.

    Every operation on a class variable goes through the reference. Reading t.addr means following handle t, finding the object, and reading its field.

    Statement

    What really happens

    Objects after

    txn t;

    A handle is declared with value null

    0

    t = new();

    One object is built; t points at it

    1

    b = a;

    The reference is copied; no object is built

    Still 1

    c = new a;

    A fresh object copies scalar fields one level

    2, sharing sub-objects

    Assignment aliases

    Predict first a = new(); a.addr = 'h1000; b = a; b.addr = 'h2000; What does a.addr read now?

    'h2000. b = a copied the reference, not the object, so a and b name the same object. The write through b is the write a sees.

    Assignment between handles copies the reference, not the object. After b = a there is one object with two names, and a write through either handle is visible through both.

    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
    class txn_l2;
      int unsigned addr;
    endclass
    
    module tb_l2_alias;
      int errors = 0;
      initial begin
        txn_l2 a;
        txn_l2 b;
        txn_l2 c;
        a = new();
        a.addr = 32'h1000;
        b = a;
        b.addr = 32'h2000;
        if (a.addr != 32'h2000) errors++;
        if (!(a == b)) errors++;
        c = new();
        c.addr = 32'h2000;
        if (a == c) errors++;
        if (a.addr != c.addr) errors++;
        if (errors == 0) $display("PASS: aliasing and handle identity compare as specified");
        else $fatal(1, "FAIL: aliasing and identity checks");
        $finish;
      end
    endmodule
    Terminal
    PASS: aliasing and handle identity compare as specified

    The testbench proves both halves. Handles a and b share one object, while c holds equal fields in a different object that == still rejects.

    Terminal
    PASS: aliasing and handle identity compare as specified
    One object, two names. Assignment between handles never copies an object.

    Identity and equality differ. Comparison with == tests the reference. Field-by-field equality needs a hand-written compare method.

    Null handles crash late

    A declared handle starts as null. Dereferencing it is a runtime fatal, and the compiler cannot catch it because nullness is a runtime question.

    The usual shape is a part of the testbench used without construction. One function builds the parts, another uses them, and the fatal lands far from the missing line.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // snippet
    class pair;
      comp left;
      comp right;
      function void build();
        left = new();
        // forgot: right = new();
      endfunction
      function void run();
        left.run();
        right.run(); // FATAL at runtime: right was never constructed
      endfunction
    endclass

    The guard below is runnable. It catches the missing construction before use, then constructs and proceeds.

    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
    class comp_l2;
      string name;
      function new(string name = "comp");
        this.name = name;
      endfunction
      function void run();
      endfunction
    endclass
    
    module tb_l2_null;
      int errors = 0;
      int caught = 0;
      initial begin
        comp_l2 drv;
        comp_l2 mon;
        drv = new("drv");
        if (mon == null) caught = 1;
        if (caught != 1) errors++;
        mon = new("mon");
        if (mon == null) errors++;
        drv.run();
        mon.run();
        if (errors == 0) $display("PASS: null handle caught by guard before use");
        else $fatal(1, "FAIL: null guard checks");
        $finish;
      end
    endmodule
    Terminal
    PASS: null handle caught by guard before use

    Reachability decides lifetime

    An object stays alive while any chain of references reaches it: a named handle, a queue entry, a mailbox slot, or a property of another live object. When the last reference drops, the object becomes collectable.

    Reclamation timing is implementation-dependent. The guarantee is only that a reachable object is never freed. There is no delete for class objects and no dangling handle.

    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
    class txn_life;
      int unsigned addr;
    endclass
    
    module tb_l2_life;
      int errors = 0;
      initial begin
        txn_life h;
        txn_life keep;
        txn_life t;
        h = new();
        h.addr = 32'hAAAA;
        keep = h;
        h = new();
        h.addr = 32'hBBBB;
        if (keep.addr != 32'hAAAA) errors++;
        if (h.addr != 32'hBBBB) errors++;
        if (h == keep) errors++;
        keep = null;
        for (int i = 0; i < 1000; i++) begin
          t = new();
          t.addr = i;
        end
        if (t.addr != 999) errors++;
        if (errors == 0) $display("PASS: reassignment drops one reference and old object stays intact");
        else $fatal(1, "FAIL: lifetime reassignment checks");
        $finish;
      end
    endmodule
    Terminal
    PASS: reassignment drops one reference and old object stays intact

    Reassignment never destroys an object. It drops one reference, and the old object survives while keep reaches it.

    Any reference keeps an object alive. Collection only happens when every arrow is gone.

    Common pitfall

    Using == to compare transaction contents passes only when both handles name the same object. Save handles in a list while the code that made them keeps changing the same object, and the saved history silently rewrites itself.

    Interview

    Likely question: a handle points at A, then is assigned B. What happens to A?

    • Immediately, nothing: the handle now names B.

    • A is collectable only if that handle was its last reference.

    • A survives intact while any other reference still reaches it.

    Check yourself After b = a, how many objects exist, and what does b.addr = 5 do to a.addr?

    One object exists. Both handles name it, so a.addr reads 5.

    Check yourself A sim dies with a null object access inside run. Where is the missing line usually?

    In build. A part was used without new. Guard the place where parts are connected with explicit null checks.

    Project step

    Make one pkt_txn object, then loop three times: change its payload and push the handle into a queue of expected packets. Print the queue. All three entries show the last payload, because the queue holds three handles to one object. The next lesson but one fixes this with clone().

    Remember

    • b = a copies the reference: one object, two names.

    • == on handles compares identity; equal contents need a compare method.

    • An object lives while any reference reaches it; there is no delete.

    Rule of thumb: if two handles can reach one object, a write through either is a write to both.

    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.

    When an unreachable object is freed is up to the simulator; the benches check only that a reachable object is never lost.

    Related topics

    • Class Declaration & Objects

    • Shallow vs Deep Copy

    Previous

    36 / 259

    Next: Constructors & super.new()

    Up next

    Up nextConstructors & super.new()

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Shallow vs Deep Copy
    • this, Scope & Nested Classes