Part 1 · Language Foundations · Intermediate
Packages & Imports
Declaring packages, wildcard vs explicit import, the :: scope operator, export, and a protocol package of types, params, and functions.
What a package is
A package is a named, compile-once container for declarations : typedefs, parameters, functions, tasks, and classes. It contains no hardware — no instances, no always blocks, no nets driven by anything. The simulator elaborates the package a single time, and every module, interface, or class that references it sees the same definitions. This is what makes shared types possible: when two modules both use bus_pkg::txn_t, the compiler knows they are the same type, not two structurally identical strangers. Two structs defined separately by textual inclusion into different scopes are different types to the type checker, even if every field matches — a distinction that bites hard with virtual interfaces and mailbox parameterization.
package bus_pkg;
// parameters: shared constants
parameter int ADDR_W = 32;
parameter int DATA_W = 64;
// types: one definition, used everywhere
typedef enum logic [1:0] { READ, WRITE, IDLE } cmd_e;
typedef struct packed {
cmd_e cmd;
logic [ADDR_W-1:0] addr;
logic [DATA_W-1:0] data;
} txn_t;
// functions: pure utilities usable in constraints and checkers
function automatic logic [7:0] parity_of(txn_t t);
return ^t.data;
endfunction
endpackageThree ways to reference package contents
You can reach into a package three ways, in increasing order of blast radius. The scope operator bus_pkg::txn_t names one item, once, with zero namespace impact — ideal in headers and shared code. An explicit import import bus_pkg::txn_t; pulls one name into the current scope so it can be used bare. A wildcard import import bus_pkg::*; makes every name in the package a candidate — note the word candidate: a wildcard import does not actually import anything until a name is used, and a local declaration of the same name silently wins. That laziness is why wildcard imports feel harmless in small testbenches and become collision factories in large ones.
// 1. Scope operator — no import at all, fully qualified
module checker_m (input bus_pkg::txn_t t);
logic [7:0] par = bus_pkg::parity_of(t);
endmodule
// 2. Explicit import — only the names you ask for
module driver_m;
import bus_pkg::txn_t;
import bus_pkg::cmd_e;
txn_t current;
endmodule
// 3. Wildcard import — every name becomes visible on demand
module tb_top;
import bus_pkg::*;
txn_t t; // resolves to bus_pkg::txn_t
cmd_e c = READ; // enum members come along too
endmoduleexport — re-publishing names
A package can export names it imported, presenting an aggregate facade. The common use is an umbrella package: vip_pkg imports and exports bus_pkg and cfg_pkg so users import one package instead of three. Without the export, names imported into vip_pkg are visible inside it but do not pass through to its importers.
package vip_pkg;
import bus_pkg::*;
export bus_pkg::*; // re-publish: importers of vip_pkg see bus_pkg names
import cfg_pkg::*;
export cfg_pkg::*;
// plus vip_pkg's own declarations...
endpackageChoosing a reference style
REFERENCE STYLE BLAST RADIUS
bus_pkg::txn_t import bus_pkg::txn_t; import bus_pkg::*;
─────────────── ────────────────────── ──────────────────
one use site one name in scope ALL names candidate
zero collisions collision = compile err collision = SILENT
verbose moderate terse
│ │ │
▼ ▼ ▼
shared headers, module/class files leaf scopes only:
package bodies, with few package deps tb_top, test classes
vendor-facing codeInterview angle
"Why use a package instead of `include?" — compiled namespace with type identity vs textual paste; see the include-vs-import lesson.
"What does import pkg::* actually do?" — makes names candidates for resolution; nothing is bound until used, and local names shadow it.
"What is export for?" — umbrella packages that re-publish imported names to their own importers.
Key takeaways
Packages hold declarations only — compiled once, giving shared type identity across the environment.
pkg::name has zero namespace impact; explicit import binds one name; wildcard import only makes names candidates.
Local declarations silently shadow wildcard-imported names — the root of most import surprises.
export re-publishes imported names, enabling one umbrella package per VIP.
Common pitfalls
Defining the same struct in two scopes via `include and expecting type compatibility — they are distinct types.
Wildcard-importing two packages that both define clk_period_t — works until someone references it, then ambiguity error.
Forgetting that enum member names (READ, WRITE) enter the namespace along with the enum type.
Importing inside a package without export and expecting downstream visibility — imports do not chain.