Classes, objects and handles

Learning objectives
  • Declare a class with properties, a constructor and methods
  • Distinguish a handle from the object it refers to
  • Explain null handles and garbage collection
  • Implement shallow and deep copies
  • Use static members and this correctly
1 Objective→ 2 Learn→ 3 Example→ 4 Practice→ 5 Verify→ 6 Continue
How to study this lessonUnderstand the idea first. Use the example to see the behaviour, practise where provided, then make sure you can explain the result before continuing.

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?

Engineering practice

Practice: Classes, objects and handles

Task→Run / reason→Check result→Explain why

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.