Packages and Scope
System Tasks and Compiler Directives covered `include — textually pasting one file's contents into another at compile time. That works, but it has a real downside for shared declarations: if the same `included file defines a typedef or parameter and gets included into ten different files, every one of those ten compiles its own separate copy of the same declaration, and there's no namespace protecting against two unrelated included files happening to declare the same name. SystemVerilog's package fixes both problems with a real, singly-compiled, namespaced unit.
Declaring a package
package frame_pkg;
typedef enum logic [1:0] {
IDLE = 2'b00,
START = 2'b01,
DATA = 2'b10,
STOP = 2'b11
} frame_state_e;
typedef logic [7:0] byte_t;
parameter int FRAME_WIDTH = 8;
endpackage
This is the frame_state_e enum from Data Types and the byte_t typedef from the same page, now declared once, inside a named package, instead of repeated (or `included) into every file that needs them.
Using a package: import
module frame_receiver
import frame_pkg::*;
(
input logic clk,
input logic rst,
output byte_t data_out
);
frame_state_e state, next_state;
// ...
endmodule
import frame_pkg::*; pulls every declaration from frame_pkg into scope for this module — frame_state_e and byte_t are now usable exactly as if declared locally. The :: (scope resolution) operator can also reference one specific item without wildcard-importing the whole package (frame_pkg::FRAME_WIDTH), which is worth reaching for when only one or two names are actually needed and pulling in everything risks a name collision with something else already in scope.
Unlike `include, a package is compiled exactly once, and its declarations are genuinely shared (not copy-pasted per file) — every file that imports frame_pkg refers to the same frame_state_e type, which matters the moment two modules need to pass a frame_state_e value between them (a port or function argument of that type only type-checks correctly if both sides agree it's the same declared type, not two textually-identical-but-separately-compiled copies).
$unit: the compilation-unit scope
Declarations written outside any module, package, or interface — directly at the top of a file — live in a special implicit scope called $unit, shared across every file compiled together in the same compilation unit. This is what lets, e.g., a `define macro or a file-scope parameter declared outside any module be visible from other files in the same build without an explicit import. In practice, $unit-scope declarations are used sparingly (mostly for things that genuinely need to be global, like a small number of build-wide constants) — a real package with an explicit import is almost always the better-organized choice for anything meant to be shared deliberately, since it makes the dependency visible at the point of use instead of relying on implicit compilation-unit ordering.
When names collide: import precedence
A wildcard import pkg::*; doesn't necessarily win over everything else in scope — SystemVerilog resolves a name in a fixed priority order:
- A local declaration inside the current module/interface/class always wins, even over a wildcard-imported name of the same identifier.
- An explicit, single-item import (
import frame_pkg::byte_t;) takes priority over a wildcard import of the same package or a different one. - A wildcard import (
import frame_pkg::*;) is resolved last, and only for names not already settled by the first two rules.
package a_pkg; typedef logic [7:0] byte_t; endpackage
package b_pkg; typedef logic [7:0] byte_t; endpackage
module m
import a_pkg::*;
import b_pkg::byte_t; // explicit — wins over a_pkg's wildcard-imported byte_t
(
byte_t data // resolves to b_pkg::byte_t, not a_pkg::byte_t
);
endmodule
Without b_pkg's explicit import taking precedence, two same-named wildcard imports (a_pkg::* and, hypothetically, b_pkg::*) would actually be a genuine ambiguity error rather than a silent pick — the explicit-import rule exists precisely so a designer can resolve that ambiguity deliberately instead of the compiler guessing. One more subtlety: a wildcard import doesn't eagerly pull in every name in the package the moment import pkg::*; is parsed — only names actually referenced later in that scope get resolved against it, which is why two modules can wildcard-import the same two colliding-name packages without issue, as long as neither module actually references the colliding name itself.
Importing a package doesn't chain through automatically
package a_pkg;
import b_pkg::*; // a_pkg uses b_pkg internally...
typedef logic [7:0] a_byte_t;
endpackage
module m
import a_pkg::*; // ...but this does NOT also bring b_pkg's names into m
(
a_byte_t data // fine — a_byte_t is a_pkg's own declaration
// b_pkg's declarations are still not visible here at all
);
endmodule
If a_pkg itself imports b_pkg, that import is not transitive — a module that only imports a_pkg does not automatically gain access to b_pkg's declarations too, even though a_pkg uses them internally. To deliberately make b_pkg's names visible to anyone importing a_pkg, a_pkg has to explicitly re-export them with export b_pkg::*; (or export specific items by name) — without that explicit export, each package's imports stay private to itself, the same "make the dependency visible at the point of use" philosophy the rest of this page already favors over implicit visibility.
Why this matters for larger codebases
`include | package | |
|---|---|---|
| Compiled | Once per file that includes it (re-parsed each time) | Once, total |
| Namespacing | None — a plain textual paste | Declarations live under the package's name, accessed via :: or import |
| Type identity across files | Two includes of the same typedef are two separate copies to the compiler (usually harmless, but can bite type-matching in edge cases) | Genuinely the same type everywhere it's imported |
`include still has its place (pasting in a set of macros, for instance) — but for typedefs, parameters, and functions meant to be shared consistently across a real testbench or design, package is the modern, correct tool, and is exactly what UVM's own base classes are packaged in (import uvm_pkg::*;, already used unexplained in every code example throughout the UVM topic — this is what that line actually does).
What's next
Section B closes out the procedural and modular vocabulary. Section C moves to SystemVerilog's biggest addition over plain Verilog: real object-oriented programming, starting with class itself.