Packages and the three ways to use them, compilation-unit scope and $unit, the $root top-level reference, module declaration enhancements including closing names and timeunit/timeprecision, nested modules for structural partitioning, and extern module declarations for separate compilation.
Verilog-2001 kept all declarations inside modules, with only global system tasks/functions outside. SystemVerilog adds several constructs that break this limitation and improve large-design reuse and separate compilation.
$unit.`timescale, bound to the module..name and .* shortcuts (covered in module instances).A package is a named scope at the outermost level of the source text — the same level as top-level modules. Items declared inside a package are shared among any number of compilation units, modules, interfaces, and programs that import or reference the package.
package ComplexPkg; typedef struct { real i, r; } Complex; function Complex add(Complex a, b); add.r = a.r + b.r; add.i = a.i + b.i; endfunction function Complex mul(Complex a, Complex b); mul.r = (a.r * b.r) - (a.i * b.i); mul.i = (a.r * b.i) + (a.i * b.r); endfunction endpackage : ComplexPkg
initial/always blocks are started, like module-level variables.Any package item can be accessed from any scope using PackageName::item without an import statement. The package must have been compiled, but no explicit import is required.
// Direct qualified reference — works everywhere, no import needed ComplexPkg::Complex c1, c2, cout; cout = ComplexPkg::mul(c1, c2); // In port declarations, parameter values, etc — all valid with :: // p::c always refers to c in package p, regardless of local scope
Explicit import brings specific identifiers from a package into the current scope, removing the need to qualify them with the package name on every use.
import ComplexPkg::Complex; // import only the type import ComplexPkg::add; // import only the function // Now can use without qualification: Complex a, b, sum; sum = add(a, b); // no ComplexPkg:: prefix needed // Restrictions: // — Illegal if the identifier is already declared in the same scope // — Illegal if the same identifier is already explicitly imported from another package // — Importing the same identifier from the same package multiple times is allowed
A wildcard import makes all package identifiers candidates for import. Each identifier is only actually imported when it is referenced and is not already defined in the scope.
import ComplexPkg::*; // ALL items in ComplexPkg are candidates // Identifiers are imported on demand — only when referenced // and not already declared/imported in scope Complex x; // triggered import of 'Complex' from ComplexPkg // A local declaration OVERRIDES an identifier from a wildcard import int add; // shadows ComplexPkg::add for direct references // Wildcard conflict: same name from two packages → undefined → error if referenced import p::*; import q::*; // Both p and q have 'c' → direct reference to c is an error // Use p::c or q::c to disambiguate // Triggering a wildcard import then making a conflicting explicit import → error module foo; import q::*; wire a = c; // forces import of q::c import p::c; // ERROR: q::c already imported into this scope endmodule
p::c) is always visible in any scope regardless of local declarations. It bypasses all search order rules. Use it when name conflicts would otherwise make a direct reference ambiguous.A compilation unit is a set of one or more source files compiled together. Each compilation unit has its own compilation-unit scope — an anonymous namespace that contains declarations placed outside all module/package/interface declarations within that unit.
'include directives expand into the compilation unit of the including file.$unit is the explicit name of the compilation-unit scope. It lets you unambiguously refer to declarations at the outermost level of a compilation unit when local names would shadow them. It uses the same :: scope resolution operator as packages.
bit b; // declared in the compilation-unit scope task foo; int b; // local b — shadows the outer b inside foo b = 5; // references the local int b b = 5 + $unit::b; // $unit::b explicitly references the compilation-unit bit b endtask // $unit cannot be used with import — it has no package name. // It is analogous to an anonymous package — shared within the unit, // invisible outside it, not portable across units.
$root is the root of the instantiation hierarchy. It provides an unambiguous absolute path to top-level instances, bypassing the precedence that local paths normally have over hierarchical paths.
// Absolute paths starting from the design root $root.A.B // item B within top instance A $root.A.B.C // item C within instance B within top instance A // Without $root: A.B.C is ambiguous if the current module also has // a local instance A that contains a B — local path wins. // With $root.A.B.C: always refers to the top-level path.
SV adds two enhancements to module declarations: closing names and timeunit/timeprecision declarations as a portable replacement for `timescale.
// Verilog-2001: no closing name module my_module(...); endmodule // SV: optional closing name — must match the opening name if given module my_module(...); endmodule : my_module
// File-order-independent time specification — bound to this module module fast_design; timeunit 100ps; // time unit = 100 picoseconds timeprecision 10fs; // precision = 10 femtoseconds // ... design code endmodule // Combined on one line (legal) timeunit 1ns; timeprecision 1ps;
`timescale directive is global and order-dependent — the last `timescale seen by the compiler applies to all subsequent modules until changed. timeunit/timeprecision are declarations inside the module, binding the time specification to that module regardless of file order. This eliminates the “who included what last” class of compile-order bugs.`timescale directive was previously active in the compilation unit — that value is used.A module can be declared inside another module’s body. The nested module’s name is in the outer module’s namespace, not the global namespace. This enables structural partitioning without ports, local library modules, and name hiding.
module dff_flat(input d, ck, pr, clr, output q, nq); wire q1, nq1, q2, nq2; nand g1b(nq1, d, clr, q1); nand g1a(q1, ck, nq2, nq1); nand g2b(nq2, ck, clr, q2); nand g2a(q2, nq1,pr, nq2); nand g3a(q, nq2,clr, nq); nand g3b(nq, q1, pr, q); endmodule
module dff_nested(input d, ck, pr, clr, output q, nq); wire q1, nq1, nq2; module ff1; // nested — shares outer scope nand g1b(nq1,d,clr,q1); nand g1a(q1,ck,nq2,nq1); endmodule ff1 i1(); module ff2; wire q2; // private to ff2 nand g2b(nq2,ck,clr,q2); nand g2a(q2,nq1,pr,nq2); endmodule ff2 i2(); module ff3; nand g3a(q,nq2,clr,nq); nand g3b(nq,q1,pr,q); endmodule ff3 i3(); endmodule
module part1(...); // Local and2 — only visible inside part1 module and2(input a, b, output z); assign z = a & b; endmodule module or2(input a, b, output z); assign z = a | b; endmodule and2 u1(...), u2(...); // instantiates LOCAL and2 — not any global and2 endmodule
An extern module declaration announces a module’s port list (and optional parameters) without providing the module body. This supports separate compilation — the module definition can be in a different compilation unit, compiled separately.
// extern declarations: ports/params without the body extern module m (a, b, c, d); extern module a #(parameter size = 8, parameter type TP = logic[7:0]) (input [size:0] a, output TP b); // With extern declarations, .* can be used as the port list module top (); wire [8:0] a; logic [7:0] b; m m (.*); // ports inferred from extern: (a,b,c,d) a a (.*); // ports inferred from extern: (a,b) endmodule // The module definitions (can be in separate files): module m (.*); // .* gets ports from the extern declaration input a, b, c; output d; // ... body ... endmodule module a (.*); // .* expands to (input[size:0] a, output TP b) // ... body ... endmodule
.* on both the module definition and its instantiation sites uses the ports from the extern declaration.| Method | Syntax | Scope needed | Notes |
|---|---|---|---|
| Qualified reference | Pkg::item | Any — no import needed | Always unambiguous; ignores local names |
| Explicit import | import Pkg::item; | Current scope | Illegal if item already locally declared or imported from another package |
| Wildcard import | import Pkg::*; | Current scope | Imports on demand; local declarations override; two wildcards with same name → error on use |
$unit::name — explicitly accesses the compilation-unit scope, bypassing local shadows.$root.path — absolute path from the top of the instantiation hierarchy, bypassing local path precedence.timeprecision declarations and `timescale precisions in the design.