::, is how SystemVerilog lets you say precisely which one you mean, instead of leaving it to guesswork or accidental shadowing.The scope resolution operator :: is used to refer to an identifier within the scope of a class, package, covergroup, coverpoint, cross, or typedef name. The left-hand side must be one of those scope names; the right-hand side is an identifier inside that scope, like a variable or method name.
7 min read | Intermediate Level
What You'll Learn
- What
::does, and what's legal on each side of it - How to define a class method's body outside the class declaration itself
- How
::accesses a class's static members without needing an object - How to reference a package's members directly, with or without importing the package
- How
::resolves a naming collision between an imported package and a local declaration
Why Is the Scope Resolution Operator Required?
Classes and other scopes can have identically-named identifiers, which can create a namespace collision if referred to without specifying which scope you mean. :: uniquely identifies a member or parameter of a given scope. It's also used to access a class's static variables and methods, parameters, and local parameters from outside the class, and to access public and protected members of a base class from within a derived class.
Defining an extern Function
A method can be declared inside a class with extern, and its actual body defined separately, outside the class, using :: to say which class it belongs to:
class ABC;
int data;
extern virtual function void display();
endclass
// Definition of an external function using scope
// resolution operator
function void ABC::display();
$display("data = 0x%0h", data);
endfunction
module tb;
initial begin
ABC abc = new();
abc.data = 32'hface_cafe;
abc.display();
end
endmodule
Output
ncsim> run data = 0xfacecafe ncsim: *W,RNQUIE: Simulation is complete. ncsim> exit
ABC::display() is what tells the compiler "this function body belongs to the display() method declared inside ABC," even though it's written outside the class entirely.
Quick Check
Q: What would happen if function void ABC::display();...endfunction were never written anywhere in the code?
Show Answer
A compile error. extern only declares that the method exists and what its signature looks like — the actual body still has to be provided somewhere, using :: to tie it back to the class.
Accessing Static Methods and Variables
A class's static members belong to the class itself, not to any one object, and :: lets you reach them without creating an instance first:
class ABC;
static int data;
static function void display();
$display("data = 0x%0h", data);
endfunction
endclass
module tb;
initial begin
ABC a1, a2;
// Assign to static variable before creating
// class objects, and display using class_type and
// scope resolution operator
ABC::data = 32'hface_cafe;
ABC::display();
a1 = new();
a2 = new();
$display ("a1.data=0x%0h a2.data=0x%0h", a1.data, a2.data);
end
endmodule
Output
ncsim> run data = 0xfacecafe a1.data=0xfacecafe a2.data=0xfacecafe ncsim: *W,RNQUIE: Simulation is complete. ncsim> exit
ABC::data is assigned before either a1 or a2 even exists, and both objects still see the same value once created — exactly what static means: one shared copy for the whole class, not one per object. See SystemVerilog Static Variables & Functions for more on this.
Quick Check
Q: If a third object, a3 = new(), were created right after this code, and then a1.data were changed to a new value, what would a3.data show?
Show Answer
The new value. Since data is static, a1.data, a2.data, and a3.data are all just different names for the exact same underlying storage — changing it through any one of them changes what all of them see.
Referencing Package Members
:: can also reach directly into a package, referencing a type or parameter declared there without importing the whole package first — the package just needs to be included in compilation:
package my_pkg;
typedef enum bit {FALSE, TRUE} e_bool;
endpackage
module tb;
bit val;
initial begin
// Refer to types that have been declared
// in a package. Note that package has to
// be included in compilation but not
// necessarily "imported"
val = my_pkg::TRUE;
$display("val = 0x%0h", val);
end
endmodule
Output
ncsim> run val = 0x1 ncsim: *W,RNQUIE: Simulation is complete. ncsim> exit
my_pkg::TRUE reaches directly into the package to grab its TRUE enum value (1, since it's the second value declared in FALSE, TRUE) without an import statement anywhere.
Avoiding Namespace Collisions
Things get more interesting when a locally-declared identifier shares a name with something already imported from a package:
package my_pkg;
typedef enum bit {FALSE, TRUE} e_bool;
endpackage
import my_pkg::*;
module tb;
typedef enum bit {TRUE, FALSE} e_bool;
initial begin
e_bool val;
// Be explicit and say that TRUE from my_pkg
// should be assigned to val
val = my_pkg::TRUE;
$display("val = 0x%0h", val);
// TRUE from current scope will be assigned to
// val
val = TRUE;
$display("val = 0x%0h", val);
end
endmodule
Output
ncsim> run val = 0x1 val = 0x0 ncsim: *W,RNQUIE: Simulation is complete. ncsim> exit
Notice the local e_bool reverses the order: TRUE, FALSE instead of the package's FALSE, TRUE — so the module's own TRUE is 0, while the package's TRUE is 1. val = my_pkg::TRUE explicitly forces the package's version, giving 0x1. val = TRUE, with no qualification, resolves to the module's own local declaration instead — giving 0x0 — because a local declaration takes precedence over a wildcard-imported (import my_pkg::*;) one, even though both are visible in the same scope.
Quick Check
Q: If the module's local typedef were removed entirely, what would val = TRUE; (unqualified) resolve to?
Show Answer
The package's TRUE (value 1). With no local declaration to shadow it, the wildcard-imported my_pkg::* version becomes the only TRUE visible in that scope, so the unqualified reference resolves to it.
Where Does This Matter in Practice?
Large verification environments commonly share types, parameters, and enums across many files through packages, often pulled in with a wildcard import for convenience. Relying on that convenience everywhere means a locally-declared identifier with the same name can silently shadow the package's version with no warning — explicit :: qualification is the way to be certain which one is actually being used when it matters.
Common Beginner Mistakes
Mistake #1: Assuming a wildcard import behaves exactly like a local declaration
As the namespace collision example shows, a local declaration silently wins over a wildcard-imported identifier with the same name. Why? This can produce a value that looks wrong at first glance if you forgot a local declaration existed and expected the package's version to be used instead — explicit :: removes the ambiguity entirely.
Mistake #2: Forgetting to actually define an extern-declared method
extern only declares a method's signature inside the class — it doesn't provide a body. Why? If ClassName::method_name(...) is never written anywhere, the method has no implementation, and you'll get a compile error rather than a working (if empty) function.
Mistake #3: Thinking :: is only for static members
:: shows up in several different contexts — defining an extern method's body, accessing static members, reaching into a package, and accessing base-class members from a derived class. Why? Treating it as purely a "static member" operator misses how often it's actually used just to disambiguate which scope an identifier belongs to.
Practice Exercise
Challenge: Trace a Different Collision
Given package p; typedef enum bit [1:0] {RED, GREEN, BLUE, YELLOW} e_color; endpackage, imported with import p::*;, and a module that locally declares typedef enum bit [1:0] {YELLOW, BLUE, GREEN, RED} e_color;, what would e_color c; c = RED; (unqualified) evaluate to, and what would c = p::RED; evaluate to?
Hint
Work out each enum's ordinal values separately first — the package's declaration order, and the module's local declaration order — then apply the same shadowing rule from the article.
Solution
c = RED; gives 3, using the local declaration (YELLOW=0, BLUE=1, GREEN=2, RED=3), since the local typedef shadows the wildcard import. c = p::RED; gives 0, explicitly forcing the package's own ordering (RED=0, GREEN=1, BLUE=2, YELLOW=3) regardless of what's declared locally.
Summary
In this tutorial, you learned:
::resolves an identifier to a specific scope — a class, package, covergroup, coverpoint, cross, or typedef name on its left-hand side- An
extern-declared method's body is defined outside the class usingClassName::method_name - A class's
staticmembers are accessed withClassName::member_name, without needing an object instance - A package's members can be referenced directly with
package_name::member_name, even without importing the package - A local declaration silently shadows a same-named identifier pulled in through a wildcard
import— explicit::qualification removes that ambiguity
Continuing the Advanced Features Chapter
Continue with SystemVerilog 'extern' for a deeper look at declaring methods outside a class body.