Part 2 · OOP for Verification · Intermediate

Class Declaration & Objects

Properties, methods, new(), and the construction flow behind every transaction class.

Blueprint vs instance

A class declaration allocates no memory . It is a type definition — a blueprint listing properties (data) and methods (behavior). Memory exists only when you call new() at runtime, which builds an object on the simulator's heap. This is the fundamental contrast with a module: a module instance exists from elaboration to end of simulation; an object exists from its new() call until the last handle to it disappears.

Properties can be any type — packed/unpacked, rand or not, even handles to other classes. Methods are functions (zero simulation time) and tasks (may consume time, may block). The combination of randomizable data plus behavior in one unit is exactly what a transaction needs: fields the test randomizes, plus methods to print, compare, and copy itself.


A complete transaction class

systemverilog
class bus_txn;
  // -------- properties (per-object state) --------
  rand bit [31:0] addr;
  rand bit [31:0] data;
  rand bit        write;
  rand int unsigned len;
  string           name;

  constraint c_len { len inside {[1:16]}; }
  constraint c_align { addr[1:0] == 2'b00; }

  // -------- 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
systemverilog
module tb;
  initial begin
    bus_txn t;            // handle declared — value is null, NO object yet
    t = new("t0");        // object constructed; handle now points to it
    assert (t.randomize()) else $fatal(1, "randomize failed");
    t.print();

    repeat (10) begin
      bus_txn req = new("req");   // fresh object per iteration
      assert (req.randomize());
      req.print();
    end                            // old handles go out of scope each loop
  end
endmodule

Why one object per stimulus item

  • Each new() gives an independent object — randomizing one never disturbs another.

  • Reusing a single object and re-randomizing it corrupts anything downstream still holding that handle (mailbox, scoreboard).

  • Standard generator rule: construct, randomize, send — fresh object every iteration.


Object construction flow

When you call t = new(), the simulator performs a fixed sequence: allocate heap memory, set every property to its type default (or declared initializer), run the constructor body, then return the reference into the handle. Knowing this order explains why properties already have stable default values inside new(), and why constraints play no role at construction — they apply only when randomize() is called later.

diagram
OBJECT CONSTRUCTION FLOW

  bus_txn t;                 t ──► null
       │
  t = new("t0");
       │
       ├─ 1. allocate memory for one bus_txn on the heap
       │       ┌─────────────────────────────┐
       │       │ addr=x data=x write=x len=0 │  (type defaults /
       │       │ name=""                     │   declared initializers)
       │       └─────────────────────────────┘
       ├─ 2. run constructor body: this.name = "t0";
       │
       └─ 3. return reference ──► t ──► OBJECT @heap

  t.randomize();             constraints solved NOW, not at new()

Interview angle

  • 'What is the difference between a class and an object?' — type vs runtime instance; no memory until new().

  • 'Where do objects live?' — simulator-managed heap, not the static module hierarchy; no hierarchical path to an object.

  • 'Can a class contain an always block?' — no; classes hold tasks/functions started explicitly (e.g. fork), not structural processes.

Key takeaways

  • class = blueprint, new() = heap allocation + constructor, handle = the only way to reach the object.

  • Properties get type defaults before the constructor body runs; constraints apply only at randomize().

  • Generate one fresh object per stimulus item — never re-randomize an object already handed downstream.

Common pitfalls

  • Declaring a handle and calling methods without new() — null dereference, fatal at runtime not compile time.

  • Putting time-consuming structural behavior in a class like a module — classes have no always blocks.

  • Expecting constraints to apply at construction — they only constrain randomize() calls.

  • Sharing one txn object across loop iterations — downstream consumers see fields mutate underneath them.