SystemVerilog · Module 16
import — Explicit & Wildcard
SystemVerilog import — the shorthand for using package members without the pkg_name:: prefix. The two forms behave very differently: explicit (import pkg::item) verifies the name at the import line and errors on conflict with locals; wildcard (import pkg::*) registers a deferred search path and silently shadows under local declarations. Module-header vs body placement, nested-import non-export, name-resolution priority, and the UVM-style three-tier import pattern.
Module 16 · Page 16.2
import is a shorthand that lets a consumer use package members without writing pkg_name:: every time. There are two forms — explicit (import pkg::item) and wildcard (import pkg::*) — and they behave very differently under name conflicts, typos, and ambiguity. Equally important: an import statement must appear in the module header (not the body) if any package type appears in the port or parameter list, because port declarations are parsed before the body. Get the form right and import is a clean readability win; get it wrong and you create silent shadowing bugs, latent ambiguity errors, or unnecessary scope pollution that breaks elaboration in unrelated files.
1. Engineering Problem — Why import Exists
In Package Declaration & Usage (16.1) every package member was accessed with the :: operator — apb_pkg::apb_txn_t, apb_pkg::APB_DATA_W, and so on. That always works, but it becomes verbose when a module uses many items from the same package. A driver that uses ten items from axi4_pkg would have axi4_pkg:: written ten times — noise that obscures the actual logic.
import is the shorthand for that exact case. It tells the compiler: "for this scope, when you see a bare name you can't resolve locally, look inside this package." After the import, apb_txn_t resolves to the same thing as apb_pkg::apb_txn_t — the compiler does the lookup for you.
Critically, import does not copy code. It is purely a compile-time name-resolution hint. The package remains the single source of truth — import only adjusts what the compiler considers "in scope" for that consumer. This is a different model from `include (which physically pastes file contents) and from `define (which substitutes text before the compiler sees the code). import is a language-level construct that the compiler tracks symbolically.
The two forms then differ in what they bring into scope and how aggressively they bring it. Knowing which to use, and where to place the import statement, is what separates clean from fragile code.
2. Mental Model — Explicit = Name Copy; Wildcard = Search Path
The picture every engineer carries:
An explicit import (
import pkg::item) copies exactly one name into the current scope, checked at the import line. If the name doesn't exist in the package, the error fires at theimportstatement — not deep inside the module. The compiler verifies eagerly. A wildcard import (import pkg::*) does not eagerly inject every name from the package. It registers the package as a fallback search path. When the compiler sees an unresolved bare name, it searches the wildcard-imported packages on demand. If exactly one provides the name, resolution succeeds. If two provide it, you get an ambiguity error — but only at the point of use, not at the import statements.
Four invariants this picture preserves:
importadds a search path; it does not duplicate code. A million consumers canimport pkg::*and there is still exactly one definition ofpkg::axi_ar_tin the elaboration.- Imports are scope-specific. An
importinside moduleAis visible only inside moduleA. It does not leak to moduleBeven if they live in the same file. (The single exception is$unit-scope import, which leaks across the whole file — and is why nobody does it.) - Explicit catches typos at the import line; wildcard catches conflicts at point of use. This asymmetry is the entire engineering distinction between the two forms. Pick explicit when you want eager checking; pick wildcard when you want the convenience of "let me reach the whole package."
- Local declarations always win over imports, silently for wildcards and as a hard error for explicit. A
localparam WIDTH = 64quietly hides a wildcard-importedWIDTH = 32with no warning — a real, silent bug. The same local conflicting with animport pkg::WIDTHis a compile error. This asymmetry is the single most surprising rule for newcomers.
3. Visual Explanation — Name Resolution Order
The figure below shows the four-level priority order the compiler uses when resolving an unqualified name.
The key asymmetry every engineer must internalise: local vs wildcard → silent shadow (no error); local vs explicit → compile error; explicit vs wildcard → explicit wins silently; wildcard vs wildcard → compile error, but only at the point of use.
The wildcard search itself is deferred — it does not happen at the import pkg::* statement. The compiler records "treat pkg as a search candidate" and moves on. When a bare name is encountered later, the compiler walks the recorded candidates in declaration order and applies the rules above. This is why two wildcard imports of packages that share a name do not error at the import statements; they only error at the line that actually uses the ambiguous bare name.
4. Syntax — Two Forms
// ── Explicit — exactly one identifier into scope ───────────────────
import pkg_name::item_name; // brings in one name; verified now
// ── Wildcard — register package as fallback search path ────────────
import pkg_name::*; // names resolved on demandBoth forms are statements (not declarations) and can appear in several places:
| Location | Allowed? | Purpose |
|---|---|---|
Module / interface / program header (before #( or () | Yes | Required when package types appear in port or parameter declarations |
| Module / interface / program / class body | Yes | Standard placement when port types are plain logic and the package is only used inside |
Inside another package (nested import) | Yes | Lets one package depend on another's types — but the import does not re-export |
At $unit scope (top of file, outside any module) | Legal but discouraged | Pollutes every compilation unit in the file — unintended coupling |
The placement choice is rarely cosmetic — it determines whether the package types are visible to the port-list parser, and whether the import leaks beyond the intended module.
5. Code — Explicit, Wildcard, and Placement
5.1 Explicit Import — Verified at the Import Line
// ── Package ────────────────────────────────────────────────────────
package uart_pkg;
parameter int BAUD_DIV = 868; // 100 MHz / 115200
parameter int DATA_BITS = 8;
typedef enum logic [1:0] { NONE=0, ODD=1, EVEN=2 } parity_e;
typedef struct packed {
logic [DATA_BITS-1:0] data;
parity_e parity;
} uart_frame_t;
endpackage : uart_pkg
// ── Explicit import — only the named items enter scope ─────────────
module uart_tx;
import uart_pkg::parity_e; // ONLY this type is imported
import uart_pkg::uart_frame_t; // and this struct
// uart_pkg::BAUD_DIV still needs pkg:: prefix — it was NOT imported
parity_e par_mode; // OK — explicitly imported
uart_frame_t tx_frame; // OK — explicitly imported
localparam int DIV = uart_pkg::BAUD_DIV; // still needs :: (not imported)
initial begin
par_mode = EVEN; // OK — parity_e literals also enter scope
tx_frame.data = 8'hA5;
tx_frame.parity = par_mode;
end
endmodule
// ── Compile-time safety: explicit import verifies the name exists ──
module bad_module;
import uart_pkg::baud_div; // COMPILE ERROR: 'baud_div' does not exist
// (correct name is BAUD_DIV — case-sensitive)
endmoduleThe typo on the last import line fires immediately. That early catch is the key advantage of explicit import — a misspelled identifier never makes it past the import statement to corrupt downstream resolution.
Note also that importing an enum-typed name (uart_pkg::parity_e) brings in the type and its literals (NONE, ODD, EVEN) — the literals enter scope as part of the enum definition.
5.2 Wildcard Import — Deferred Search, Latent Ambiguity
// ── Two packages that share a name ─────────────────────────────────
package bus_a_pkg;
typedef enum { RD, WR } cmd_e; // 'cmd_e' defined here
parameter int WIDTH = 32;
endpackage
package bus_b_pkg;
typedef enum { GET, PUT } cmd_e; // 'cmd_e' ALSO defined here
parameter int DEPTH = 16;
endpackage
// ── Wildcard import — unambiguous names resolve cleanly ────────────
module consumer_ok;
import bus_a_pkg::*;
import bus_b_pkg::*;
logic [WIDTH-1:0] data_a; // OK: WIDTH only in bus_a_pkg
logic [DEPTH-1:0] addr_b; // OK: DEPTH only in bus_b_pkg
endmodule
// ── Ambiguous wildcard: using the shared name causes an error ──────
module consumer_bad;
import bus_a_pkg::*;
import bus_b_pkg::*;
bus_a_pkg::cmd_e my_cmd; // OK — explicit :: resolves ambiguity
cmd_e bad_cmd; // COMPILE ERROR — ambiguous: found in both
endmodule
// ── Local declaration silently shadows wildcard ────────────────────
module consumer_shadow;
import bus_a_pkg::*; // WIDTH = 32 available via wildcard
localparam int WIDTH = 64; // LOCAL declaration hides the wildcard
logic [WIDTH-1:0] data; // resolves to 64 (local) — NOT 32 (pkg)
endmodule
// ── Explicit import wins over wildcard for same name ───────────────
module consumer_explicit_wins;
import bus_a_pkg::*;
import bus_b_pkg::cmd_e; // explicit import of bus_b_pkg::cmd_e
cmd_e my_cmd; // resolved to bus_b_pkg::cmd_e
// explicit wins over wildcard, silently
endmoduleThe third example is the silent-shadow trap: the local WIDTH = 64 overrides the package's WIDTH = 32 without any diagnostic. If the intent was to use the package value, the bug compiles cleanly and produces wrong hardware. The mitigation is discipline: never name a local identical to a wildcard-imported one, or use pkg::WIDTH explicitly when you mean the package value.
5.3 Module Header Import — Required for Typed Ports
Port and parameter declarations are parsed before the module body. If you need a package type in a port declaration, the import must appear in the module header, between the module name and the #( or (.
// ── Package ────────────────────────────────────────────────────────
package axi_pkg;
parameter int DATA_W = 64;
typedef struct packed {
logic [DATA_W-1:0] wdata;
logic [7:0] wstrb;
logic wlast;
} axi_w_t;
endpackage : axi_pkg
// ── WRONG: import inside body — too late for port list ─────────────
module bad_dut (
input axi_w_t wch, // COMPILE ERROR: axi_w_t not yet visible
output logic wready
);
import axi_pkg::*; // too late — port list was already parsed
endmodule
// ── CORRECT: import in module header — before the port list ────────
module good_dut
import axi_pkg::*; // ← header import, before #() or ()
(
input axi_w_t wch, // OK — axi_w_t is now visible
input logic clk,
output logic wready
);
axi_w_t captured; // also visible in the body
always_ff @(posedge clk)
if (wch.wlast) captured <= wch;
endmodule
// ── Header import + parameter defaults using pkg type ──────────────
module parameterised_dut
import axi_pkg::*;
#(
parameter int W = DATA_W // DATA_W from axi_pkg — visible via header import
) (
input axi_w_t wch,
output logic [W-1:0] rdata
);
endmodule
// ── Multiple header imports — wildcard + explicit mixed ────────────
module multi_import_dut
import axi_pkg::*;
import apb_pkg::apb_txn_t;
(
input axi_w_t axi_in,
input apb_txn_t apb_in
);
endmoduleThe pattern module name import pkg::*; #(...) (...); is the canonical header-import form. The same form is legal for interfaces (interface name import pkg::*; (...);) and programs.
5.4 Nested Import — Visible Inside, Not Re-exported
// ── Base package ───────────────────────────────────────────────────
package base_types_pkg;
typedef logic [7:0] byte_t;
typedef logic [31:0] word_t;
endpackage
// ── Higher-level package depends on base_types_pkg ─────────────────
package protocol_pkg;
import base_types_pkg::*; // nested import — byte_t/word_t visible HERE
typedef struct packed {
byte_t opcode; // resolved via nested import
word_t payload;
} msg_t;
parameter int MSG_SIZE = $bits(msg_t);
endpackage
// ── Consumer of protocol_pkg — nested import is NOT re-exported ────
module msg_handler
import protocol_pkg::*;
(
input protocol_pkg::msg_t req,
output logic ack
);
// msg_t is visible — imported from protocol_pkg
// byte_t is NOT automatically visible — nested imports do not propagate
base_types_pkg::byte_t op = req.opcode; // :: still works
// To make byte_t bare-name available here, add:
// import base_types_pkg::byte_t;
endmoduleThe non-re-export rule is the most-asked LRM clarification in interviews. The nested import base_types_pkg::* inside protocol_pkg makes the base names visible only during the compilation of protocol_pkg itself. A consumer that imports protocol_pkg does not transitively gain access to base_types_pkg — each consumer must import what it needs directly. (Module 16.4 covers the dependency-graph implications.)
5.5 Import in Interface and Class
// ── Interface header import — same rule as module ──────────────────
interface apb_if
import apb_pkg::*; // header import — pkg types usable in signals
(
input logic pclk, presetn
);
apb_pkg::apb_txn_t txn; // struct type from the package
logic psel, penable, pready;
modport master (output txn, psel, penable, input pready, pclk, presetn);
modport slave (input txn, psel, penable, output pready);
endinterface
// ── Class body import ──────────────────────────────────────────────
class ApbMonitor;
import apb_pkg::apb_txn_t; // explicit import inside class
import apb_pkg::APB_DATA_W;
apb_txn_t observed_q[$]; // OK — apb_txn_t resolved via import
function void capture(apb_txn_t t);
observed_q.push_back(t);
$display("[MON] paddr=%0h pwdata=%0h", t.paddr, t.pwdata);
endfunction
endclassInterfaces and programs follow the same header-vs-body rule as modules. Classes are simpler: a class has no port list, so any body-position import is sufficient.
6. Waveform — Omitted (Compile-Time Only)
This lesson is architectural, not timing-specific. import is a compile-time name-resolution mechanism — it produces no simulation events, schedules no transitions, and has no waveform behaviour. The timing behaviour of the code that consumes imported types (clocked logic, classes, programs) is covered in the relevant timing pages: program-block, clocking-blocks-deep-dive. This section is intentionally omitted; the topic does not warrant it.
7. Synthesis View — Transparent at Elaboration
import is fully transparent to synthesis. The synthesis tool resolves every reference through the same priority chain (local → explicit import → wildcard import → $unit) and then elaborates the resolved type/parameter/function into the consuming module. The import statement itself produces no gates, no nets, no flip-flops.
The only thing the synthesis tool cares about is whether the referenced item is synthesisable. A wildcard import of a package that contains class definitions and tasks is fine — the synthesis tool simply ignores the non-synthesisable members and only uses the parameters, types, and function automatic declarations that the consumer actually references. The package itself is never "synthesised"; only the resolved consumer-side references are.
8. Explicit vs Wildcard — Side by Side
| Aspect | Explicit import pkg::item | Wildcard import pkg::* |
|---|---|---|
| What it imports | Exactly one named identifier | Registers the whole package as a fallback search path |
| When names resolved | Immediately at the import statement | On demand — only when an unresolved name is used |
| Typo detection | Error at the import line | No check at import time; errors only at point of use |
| Conflict with local scope | Compile error if local has same name | Local silently wins (shadows the package name) |
| Conflict with another import (same name) | Error if same name explicitly imported twice | Error only if the ambiguous bare name is actually referenced |
| Readability | Reader sees exactly which items come from which package | Any bare name might come from any wildcard pkg — less traceable |
| Best used when | Importing 1–3 known items; safety-critical code; team standards require traceability | A module uses many items from one package (UVM env, protocol VIP); the package was designed for wildcard import |
The industry convention that follows:
- Use explicit import when you only need 1–3 items from a package, or when team review must trace every unqualified name back to its source. Explicit is the safer default for new code.
- Use wildcard import when you use a large portion of a package's contents (common for UVM base-class packages —
import uvm_pkg::*;is the canonical form). Keep the number of wildcard-imported packages per module small to reduce ambiguity risk. - Fall back to
::for the few items that remain ambiguous after wildcard imports — mixing::andimportin the same module is perfectly legal and often the cleanest solution.
9. Real-World Pattern — UVM-Style Header Import
Every UVM environment uses the same import pattern in every component. It is worth memorising as the canonical example.
// ── UVM-style component package ────────────────────────────────────
package my_agent_pkg;
`include "uvm_macros.svh"
import uvm_pkg::*; // wildcard: UVM base classes
import my_protocol_pkg::*; // wildcard: protocol types
import my_config_pkg::cfg_t; // explicit: just the one config type
class my_driver extends uvm_driver #(my_txn);
`uvm_component_utils(my_driver)
cfg_t cfg;
// ...
endclass
class my_monitor extends uvm_monitor;
`uvm_component_utils(my_monitor)
// ...
endclass
endpackageThe three-import pattern is intentional:
import uvm_pkg::*;— wildcard because the component usesuvm_driver,uvm_monitor,uvm_sequence_item,uvm_analysis_port, etc. A dozen identifiers per file; wildcard is the only reasonable choice.import my_protocol_pkg::*;— wildcard because the component uses the protocol's transaction type, opcode enum, parameter constants, and helper functions. Same reasoning.import my_config_pkg::cfg_t;— explicit because the component only uses thecfg_ttype. Importing the entiremy_config_pkgwould register one more wildcard candidate and increase ambiguity risk for no readability gain.
This three-tier pattern (UVM wildcard + protocol wildcard + targeted explicit) is what every senior UVM engineer writes by reflex.
10. Debug Lab — Six import Bugs
The bugs every code reviewer flags.
import — bugs every engineer makes once
Symptom: Compile error — "apb_txn_t is not a type" on the port declaration line, even though the package is on the file list and the body import looks correct.
Cause: Port and parameter declarations are parsed before the module body. An import that lives inside the body has not been seen yet when the port-list parser tries to resolve the type name.
// ❌ COMPILE ERROR
module bad1 (input apb_pkg::apb_txn_t req); // port list parsed first
import apb_pkg::apb_txn_t; // too late — ports already parsed
endmoduleFix: Move the import to the module header, before the #( or (:
module good
import apb_pkg::*; // header import
(
input apb_txn_t req // now visible
);
endmoduleGuardrail: any package type in a port or parameter list requires a header import. Body imports are fine only when the package types are used exclusively inside the body.
Symptom: An engineer adds import some_pkg::*; and expects the whole package surface to be "ready" — but later writes a localparam with the same name as a package constant and is surprised when the local value is silently used.
Cause: Wildcard import does not eagerly inject every name. It registers the package as a deferred search candidate. Local declarations take priority and shadow wildcard-imported names silently — no error, no warning.
import uart_pkg::*; // BAUD_DIV available via wildcard
localparam int BAUD_DIV = 1000; // silently hides uart_pkg::BAUD_DIV
// All consumers see BAUD_DIV = 1000, not the pkg valueFix: Either rename the local parameter, or use the package value explicitly when you mean the package value:
localparam int DIV = uart_pkg::BAUD_DIV; // explicit — unambiguousGuardrail: never name a localparam / parameter / localvar identical to a name your wildcard imports might provide. If you can't avoid the collision, use pkg::name explicitly at every use site.
Symptom: Two wildcard imports compile fine for months. Then someone adds a new use site of a bare name that happens to exist in both packages, and the build breaks far from the imports.
Cause: Wildcard ambiguity is latent — it only fires at the point where the bare ambiguous name is actually referenced. The two import pkg::*; statements themselves never error.
import bus_a_pkg::*;
import bus_b_pkg::*;
// No error here — ambiguity is latent
// ... 200 lines later ...
txn_t my_txn; // COMPILE ERROR: txn_t in both pkgsFix: at the use site, either qualify explicitly (bus_a_pkg::txn_t) or add a targeted explicit import (import bus_a_pkg::txn_t;) to disambiguate.
Guardrail: when reviewing code that has more than one wildcard import, scan for any bare names that might exist in multiple packages — even if today's build passes. The error is one new typedef away.
Symptom: Compile error — "baudRate is not a member of uart_pkg" at the import statement.
Cause: Explicit imports are verified eagerly. The name must exist in the package at the time the import is parsed.
import uart_pkg::baudRate; // ERROR: 'baudRate' does not exist
// (correct name: BAUD_DIV — case-sensitive)Fix: match the exact case of the package member. SystemVerilog identifiers are case-sensitive.
Guardrail: this is actually the desired behaviour — explicit import catches typos at the import line, before they can pollute downstream resolution. If you find this annoying, you are reaching for the wildcard form when explicit's safety is exactly what you wanted.
Symptom: A consumer imports protocol_pkg::* and expects to be able to use bare names from base_types_pkg (which protocol_pkg itself imports). The compiler errors on the base-type names.
Cause: Nested imports are visible only inside the package that does the import — they are not re-exported to the package's consumers. Each consumer must import what it needs directly.
package base_types_pkg;
typedef logic [7:0] byte_t;
endpackage
package protocol_pkg;
import base_types_pkg::*; // byte_t visible inside this pkg only
typedef struct packed { byte_t op; } msg_t;
endpackage
module consumer
import protocol_pkg::*; // msg_t visible
(
input msg_t req
);
byte_t raw; // ERROR: byte_t not visible here
endmoduleFix: import the base package directly in the consumer:
module consumer
import protocol_pkg::*;
import base_types_pkg::byte_t; // add the base import explicitly
(
input msg_t req
);
byte_t raw; // OK
endmoduleGuardrail: think of each package's import set as private. If a consumer needs a transitively-imported name, the consumer must import it itself. Module 16.4 covers the dependency-graph patterns this implies.
Symptom: An import placed at the top of a file (outside any module) silently affects every module declared later in that file — including modules that were never meant to know about the package. Strange ambiguity errors appear in unrelated modules.
Cause: An import at $unit scope (file level, outside any module) is visible to every compilation unit defined later in the file. This is rarely the intended scope and produces cross-module coupling that breaks the encapsulation packages are meant to provide.
// ❌ scope pollution
import apb_pkg::*; // $unit scope — affects every module below
module foo; /* ... */ endmodule // foo sees apb_pkg names
module bar; /* ... */ endmodule // so does bar — unintendedFix: always place imports inside the module header or body where they are actually needed:
module foo
import apb_pkg::*;
(...);
endmodule
module bar; // bar gets no apb_pkg names — clean
// ...
endmoduleGuardrail: there is essentially no legitimate use for a $unit-scope import in production code. If you find one, treat it as a refactor target.
11. Q & A
The questions that come up in code review and interviews.
12. Cross-References & What's Next
This lesson covered the two import forms, their behaviour under name conflicts, the placement rules (header vs body), and the production conventions for choosing between them.
- Previous: 16.1 — Package Declaration & Usage — the foundation: what a package is, the
::operator, what is legal inside. - Next: 16.3 — Package-Level Parameters & Types — the canonical pattern for protocol constants and shared transaction types; how parameter packages flow through parameterised modules and parameterised classes.
- 16.4 — Package Dependencies & Compilation Order — the build-order discipline that keeps multi-package projects deterministic; the dependency graph as a DAG and how to break circular dependencies with a shared base package.
Related material elsewhere in the curriculum:
- Class Scope Resolution (
::) (Module 9.16) — the same::operator applied to static class members,super, and parameterised-class specialisations. - What UVM Is — and What It Is Not — every UVM component uses the three-tier
importpattern from §9; UVM is shipped asuvm_pkgand wildcard-imported into every environment.
13. Quick Reference
// ── Syntax ─────────────────────────────────────────────────────────
import pkg_name::item_name; // explicit: exactly one identifier (checked now)
import pkg_name::*; // wildcard: pkg registered as fallback search path
// ── Placement ──────────────────────────────────────────────────────
module my_mod
import pkg::*; // ← HEADER import — required when pkg types in ports
#(parameter int W = pkg::DATA_W)
(input pkg::txn_t port_in, ...);
import pkg2::item; // body import — fine when only used in the body
endmodule
// ── Name resolution priority (highest → lowest) ────────────────────
// 1. Local declarations (always win — silently shadow wildcards)
// 2. Explicit imports (win over wildcards — no error if same name)
// 3. Wildcard imports (deferred search; ambiguity error if 2 wildcards match)
// 4. $unit scope (lowest — avoid putting imports here)
// ── Explicit vs Wildcard summary ───────────────────────────────────
// Explicit: one name, checked immediately, typos caught at import line
// Wildcard: whole pkg as search path, names resolved on demand, ambiguity latent
// ── Eight rules to live by ─────────────────────────────────────────
// 1. Import does NOT copy code — it adds a name-resolution search path only.
// 2. Wildcard import does NOT eagerly make all names visible.
// 3. Two wildcards conflict ONLY when an ambiguous bare name is actually used.
// 4. Local declaration silently shadows wildcard — no error, potential bug.
// 5. Nested package imports are NOT re-exported to the package's consumers.
// 6. Always use header import when pkg types appear in port or parameter lists.
// 7. Never import at $unit scope — pollutes every module in the file.
// 8. When ambiguity exists, use pkg:: to qualify — mixing :: and import is legal.14. Summary
import is a compile-time name-resolution shorthand that lets a consumer use package members without writing the pkg_name:: prefix. The two forms are not interchangeable: explicit (import pkg::item) verifies the name eagerly at the import line and errors on conflict with locals; wildcard (import pkg::*) registers the package as a deferred search path, resolves names on demand, and silently shadows under local declarations. The eager-vs-deferred distinction is the entire engineering difference between the two forms.
The disciplines that follow:
- Place imports in the module header when any package type appears in the port or parameter list — body imports are too late for the port-list parser.
- Default to explicit imports for small, known item sets; switch to wildcard when the consumer uses a large portion of the package (UVM, protocol VIPs).
- Never import at
$unitscope — file-level imports pollute every module declared later in the file. - Nested imports are not re-exported — every consumer must import what it needs directly. This non-transitive rule is what keeps each package's public surface explicit.
- When ambiguity exists, use
::— mixing explicit qualification with imports is perfectly legal and often the clearest solution.
The UVM-style three-tier pattern (import uvm_pkg::*; import my_protocol_pkg::*; import my_config_pkg::cfg_t;) is the canonical reference every senior verification engineer writes by reflex. The next two pages of Module 16 build on this: the canonical protocol-parameter and shared-type patterns (16.3), and the compilation-order discipline that keeps multi-package builds deterministic (16.4).