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.
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 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.
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[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 writecompare looks at contents, while == on two handles asks whether they point at the same object. The next lesson is about that difference.
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.
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 |
Four objects are built in a loop, then mutated through one handle. The checks prove fresh construction gives independent objects.
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
endmodulePASS: 4 independently constructed txns hold distinct fieldsNone. Memory appears only when new runs. The declaration is a type.
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.
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.
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
endmoduleDATA 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 changeRun 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.