Part 2 · OOP for Verification · Intermediate
this, Scope & Nested Classes
this for disambiguation, class scope resolution, hierarchical class scope, and typedef-class forward declarations.
this — the current object's handle
this is a built-in handle, available inside non-static methods, that points at the object the method was invoked on. Its everyday job is disambiguation : when a method argument or local variable shadows a class property, the bare name refers to the local, and this.name reaches the property. The canonical place this matters is constructors, where arguments are conventionally named after the properties they initialize.
class agent;
string name;
int id;
function new(string name, int id);
// 'name' alone = the ARGUMENT (innermost scope wins)
this.name = name; // property ← argument
this.id = id;
endfunction
function agent get_self();
return this; // hand out a reference to myself
endfunction
function void register(registry r);
r.add(this); // pass myself to another object
endfunction
endclassBeyond disambiguation
return this — fluent/builder-style APIs and self-registration patterns.
Passing this to callbacks or registries so peers can call back into the object.
this is illegal in static methods — there is no current object to refer to.
Class scope and the :: resolution operator
Name lookup inside a method walks outward: method locals, then class properties (including inherited ones), then the enclosing scope (package, module, or $unit). The class scope resolution operator :: lets you name things that live inside a class's scope from outside it — static members, typedefs, enums, and parameterized-class specializations. Classes are real scopes: a typedef or enum declared inside one does not leak out.
class bus_pkg_types;
typedef enum {READ, WRITE, IDLE} kind_e;
typedef bit [31:0] addr_t;
static int unsigned txn_count = 0;
endclass
// Reaching INTO a class scope with ::
bus_pkg_types::kind_e k = bus_pkg_types::READ;
bus_pkg_types::addr_t a = 32'h4000;
$display("%0d txns", bus_pkg_types::txn_count);
class base;
function void report();
$display("base report");
endfunction
endclass
class child extends base;
function void report();
super.report(); // nearest parent's version
base::report(); // explicit scope — same thing here,
// but works at any depth
$display("child report");
endfunction
endclassNAME LOOKUP from inside child::report()
identifier 'x' used in method body
│
├─ 1. method locals / arguments (innermost)
├─ 2. child's properties & methods
├─ 3. base's properties & methods (inheritance chain)
└─ 4. enclosing scope: package / module / $unit
Overrides:
this.x → force property lookup (skip locals)
super.x → start lookup at the parent class
Cls::x → exact class scope, also reaches statics from outsideNested classes and typedef-class forward declarations
Two classes that reference each other — a driver holding a handle to its agent, the agent holding a handle to the driver — create a chicken-and-egg compile problem: whichever is compiled first refers to a type that does not exist yet. The fix is a forward declaration : typedef class agent; tells the compiler 'agent is a class type, details later', which is enough to declare handles. Classes may also be declared inside other classes (nested), giving helper types a private home, though verification code usually prefers packages for organization.
typedef class agent; // forward declaration — breaks the cycle
class driver;
agent parent; // legal: compiler knows agent is a class
function new(agent parent);
this.parent = parent;
endfunction
endclass
class agent;
driver drv;
function void build();
drv = new(this); // give child a back-reference to me
endfunction
endclass
// Nested class: helper scoped inside its only user
class scoreboard;
class entry; // scoreboard::entry from outside
bus_txn txn;
time t_in;
endclass
entry pending[$];
endclassInterview angle
'When must you use this?' — when a local/argument shadows a property; otherwise it is optional style.
'What is typedef class for?' — forward declaration to resolve mutually referencing classes.
'Difference between super.f() and base::f()?' — super starts at the immediate parent; :: names an exact class scope and also reaches statics.
Key takeaways
this is the current object's handle — required only when names shadow, useful for self-registration.
Lookup goes locals → class → base classes → enclosing scope; this/super/:: override the starting point.
:: reaches into class scopes from outside — statics, typedefs, enums.
typedef class Name; forward-declares a class so mutually referencing handles compile.
Common pitfalls
Constructor argument silently shadowing a property — assignment goes argument-to-argument, property stays default.
Using this inside a static method — compile error; no current object exists.
Mutually referencing classes without typedef class — 'undefined type' errors that move when you reorder files.
Expecting a typedef declared inside a class to be visible outside without the Class:: prefix.