Sometimes the compiler errors out because a class variable is used before the declaration of the class itself. This happens when two classes need a handle to each other — the classic chicken-and-egg puzzle. It occurs because the compiler processes the first class it encounters, and finds a reference to a second class that hasn't been declared yet.
9 min read | Intermediate Level
What You'll Learn
- Why two classes referencing each other creates a "used before declared" compile error
- How a
typedefforward declaration resolves it - How to build a complete example where two classes actually hold and use handles to each other
- How the same forward-declaration technique applies to parameterized classes
The Problem: A Chicken-and-Egg Reference
class ABC;
DEF def; // Error: DEF has not been declared yet
endclass
class DEF;
ABC abc;
endclass
Output
file: typdef-class.sv
DEF def;
|
ncvlog: *E,NOIPRT (typedef-class.sv,2|5): Unrecognized declaration 'DEF' could be an unsupported keyword, a spelling mistake or missing instance port list '()' [SystemVerilog].
ABC references DEF before DEF has been declared anywhere in the file — the compiler has no way to know DEF is a class at this point.
The Fix: Forward Declaration With typedef
A forward declaration using typedef tells the compiler that a class definition will be found later in the same file:
typedef class DEF; // Inform compiler that DEF might be
// used before actual class definition
class ABC;
DEF def; // Okay: Compiler knows that DEF
// declaration will come later
endclass
class DEF;
ABC abc;
endclass
By using typedef, DEF is declared to be of type class, which is later confirmed once its actual definition appears. It isn't necessary to specify that DEF is of type class in the typedef statement:
typedef DEF; // Legal
class ABC;
DEF def;
endclass
Quick Check
Q: Would typedef class DEF; still work if it were placed after class ABC instead of before it?
Show Answer
No. The compiler processes a file top to bottom, and ABC references DEF while being read — the forward declaration has to appear before the first place DEF is actually referenced, not just before DEF's own full definition.
A Complete Working Example
Here's the forward-declaration pattern actually put to use — ABC holds a handle to a DEF object and reads a value from it:
typedef class DEF;
class ABC;
DEF def;
int val = 10;
function new (DEF d);
def = d;
endfunction
function void show ();
$display ("ABC.val=%0d, DEF.val=%0d", val, def.val);
endfunction
endclass
class DEF;
ABC abc;
int val = 20;
endclass
module tb;
initial begin
DEF d = new ();
ABC a = new (d);
a.show ();
end
endmodule
This prints ABC.val=10, DEF.val=20. d (a DEF object) is constructed first, then passed into ABC's constructor, which stores the handle. a.show() then reads its own val directly and def.val through the stored DEF handle — exactly the kind of cross-referencing the forward declaration exists to make possible.
Quick Check
Q: What would happen if ABC a = new(d); were changed to ABC a = new(null); instead?
Show Answer
The code would still compile fine, but a.show() would fail at run time with a null pointer dereference when it tries to read def.val — def would still be null, with no actual DEF object behind it. See SystemVerilog Class Handle for more on this exact failure mode.
Forward-Declaring a Parameterized Class
A typedef forward declaration also works for a class with a parameterized port list:
typedef XYZ;
module top;
XYZ #(8'h3f, real) xyz0; // positional parameter override
XYZ #(.ADDR(8'h60), .T(real)) xyz1; // named parameter override
endmodule
class XYZ #(parameter ADDR = 8'h00, type T = int);
endclass
Exactly like the non-parameterized case, XYZ is used (with parameter overrides) inside top before its actual definition appears later in the file.
Where Is This Used?
In verification environments, a driver and a monitor — or a sequencer and a scoreboard — sometimes need mutual handles to each other for tighter coordination, without one component being defined strictly "before" the other in the file. A typedef forward declaration is exactly what makes that legal, regardless of which class happens to be written first.
Common Beginner Mistakes
Mistake #1: Forgetting the forward declaration entirely
As the first example shows, referencing a class before it's declared anywhere is always a compile-time error. Why? Two mutually-referencing classes can't simply be reordered to fix this — whichever one is written first will always reference the other before it's been declared, which is exactly the case a forward declaration solves.
Mistake #2: Placing the typedef after the reference that needs it
As the first Quick Check shows, the forward declaration has to come before its first use, not just somewhere earlier in the file. Why? The compiler processes declarations in order, so a typedef placed too late doesn't help the reference that appears before it.
Mistake #3: Assuming typedef alone is a complete class definition
typedef class DEF; is only a promise that a real class definition will appear later — it isn't a substitute for one. Why? If the actual class DEF; ... endclass definition is never provided anywhere in the same compilation scope, that's still a compile error, just a different one.
Mistake #4: Assuming a null handle behaves like a valid, empty object
As the second Quick Check shows, passing null where a real object is expected compiles fine but fails at run time the moment a member is accessed through it. Why? A forward-declared type still follows all the same handle rules as any other class — it's still null until something actually constructs an object and assigns it.
Practice Exercise
Challenge: A Driver and Monitor That Reference Each Other
Using the same forward-declaration pattern, write two classes, Driver and Monitor, each holding a handle to the other. Give Driver a property tx_count and Monitor a property rx_count, and write a Monitor method that prints both counts by reading its own rx_count and the connected Driver's tx_count.
Hint
Follow the exact same structure as the ABC/DEF example — forward-declare whichever class is referenced first, then have one constructor accept and store a handle to the other.
Solution
typedef class Driver;
class Monitor;
Driver drv;
int rx_count = 5;
function new (Driver d);
drv = d;
endfunction
function void report ();
$display ("rx_count=%0d, tx_count=%0d", rx_count, drv.tx_count);
endfunction
endclass
class Driver;
Monitor mon;
int tx_count = 7;
endclass
module tb;
initial begin
Driver d = new ();
Monitor m = new (d);
m.report ();
end
endmodule
This prints rx_count=5, tx_count=7 — the same cross-referencing pattern as ABC/DEF, just with names that reflect how this shows up in an actual testbench.
Summary
In this tutorial, you learned:
- Two classes referencing each other creates a "used before declared" compile error, since the compiler processes declarations in order
typedef class ClassName;forward-declares a class, telling the compiler its full definition will appear later in the same file- The forward declaration must appear before the class's first reference, not just somewhere earlier in the file
- The
classkeyword in thetypedefstatement is optional —typedef ClassName;works too - The same technique applies to parameterized classes, letting them be referenced (with parameter overrides) before their full definition appears