Classes, objects and handles
- Declare a class with properties, a constructor and methods
- Distinguish a handle from the object it refers to
- Explain
nullhandles and garbage collection - Implement shallow and deep copies
- Use
staticmembers andthiscorrectly
Explanation
Why classes in a testbench
Verilog testbenches pass data through module ports and global variables. A class bundles data (properties) with the operations on that data (methods) and can be created as many times as needed at run time. Transactions, drivers, monitors and scoreboards are all written as classes.
class packet;
bit [7:0] addr;
bit [31:0] data;
static int count = 0; // shared by all packet objects
int id;
function new(bit [7:0] addr = 0, bit [31:0] data = 0);
this.addr = addr; // 'this' selects the property, not the argument
this.data = data;
id = count++;
endfunction
function string convert2string();
return $sformatf("#%0d addr=%h data=%h", id, addr, data);
endfunction
endclass
Handles and objects
A class variable is a handle: a reference to an object, not the object itself.
packet p1, p2;
initial begin
p1 = new(8'h10, 32'h1234); // object A created
p2 = p1; // p2 refers to the same object A
p2.data = 32'hFFFF; // p1.data is now FFFF too
p1 = null; // A still exists through p2
p2 = null; // no handles left: A is reclaimed automatically
end
| Code | Objects created | Effect |
|---|---|---|
packet p; |
0 | p is null |
p = new(); |
1 | p refers to a new object |
q = p; |
0 | Both handles refer to one object |
q = new p; |
1 | Shallow copy of p |
Calling a method or reading a property through a null handle is a fatal run-time error. Guard with if (p != null) where a handle may be empty.
Shallow and deep copy
new p copies every property of p into a new object. Properties that are themselves handles are copied as handles, so the two objects share the nested object. That is a shallow copy. A deep copy also creates new nested objects; write it yourself.
class header;
bit [3:0] kind;
endclass
class frame;
header hdr = new();
bit [7:0] len;
function frame copy(); // deep copy
copy = new();
copy.len = len;
copy.hdr.kind = hdr.kind; // new header, value copied
endfunction
endclass
After f2 = new f1;, changing f2.hdr.kind also changes f1.hdr.kind. After f2 = f1.copy();, it does not.
static members
A static property has one copy shared by every object of the class, which suits counters and configuration. A static method can be called through the class name (packet::count) without an object and can only access static members.
Methods, tasks and automatic lifetime
Class methods are automatic: each call has its own local variables. Use function for zero-time computation and task for anything that waits on time or events, such as driving a bus. Methods can be declared in the class with extern and defined outside with function packet::name(...).
Tool note: Classes require Verilator 5.x. Icarus 12 does not support SystemVerilog classes.
Worked example
With the packet class above, creating three packets prints IDs 0, 1 and 2 because count is static and incremented in every constructor call. If count were not static, every object would have its own count starting at 0 and every ID would be 0, which is the classic symptom of forgetting static.
Check yourself
What does p2 = p1 copy? Why does new p produce a shallow copy? When does an object become eligible for reclamation? Why must a method that waits for a clock edge be a task rather than a function?
Practice: Classes, objects and handles
Write a transaction class with address, data and a bit write flag, a constructor with default arguments, convert2string() and a static instance counter. Add a nested timestamp class handle and implement copy() as a deep copy. Demonstrate the difference between t2 = t1, t2 = new t1 and t2 = t1.copy() by modifying t2 and printing t1.
Practice work is non-certified and is not stored by ASIC Campus.