SystemVerilog · Module 16
Package Declaration & Usage
SystemVerilog package — the named compilation scope that bundles parameters, types, functions, tasks, and classes for sharing across modules, interfaces, and classes. The :: scope-resolution operator, what is legal vs illegal inside a package, the function automatic discipline, and the canonical protocol-package and verification-utility patterns that all production VIPs (including UVM) are built on.
Module 16 · Page 16.1
A SystemVerilog package is a named compilation scope that bundles parameters, types, functions, tasks, and classes so they can be shared by any number of modules, interfaces, classes, and programs without re-declaration. It is not a hardware block — it has no ports, no always blocks, no instances — it is purely a compile-time namespace container. Every modular SystemVerilog codebase, every production protocol VIP, and every UVM environment is built on packages: get the package model wrong and the whole codebase becomes a maintenance trap of ``includecycles, redefinition errors, and silent static-locals bugs. This lesson covers the declaration syntax, the::scope-resolution operator, what is legal inside a package, thefunction automatic` discipline, and the canonical patterns (protocol package, verification utility package) that every senior engineer has memorised.
1. Engineering Problem — Why Packages Exist
Before SystemVerilog had packages, every module that needed a shared type or constant had two bad options:
- **``include
the same header file from every consumer** — text substitution at the point of inclusion. Identical type declarations appear in every compilation unit. Tools cannot tell that the twoaxi_ar_ttypedefs in modulesAandB` are meant to be the same type; a small drift between two copies becomes a silent semantic bug. Worse, including the same file from two compile units in the same scope triggers a redefinition error. - Repeat the declaration verbatim in every module — pure copy-paste. Maintenance becomes a search-and-replace exercise across the entire codebase; one missed file is a real-silicon bug.
The Verilog community lived with these workarounds for years. SystemVerilog (IEEE 1800, §26) replaced them with a proper language construct: a package is compiled once, lives in its own named namespace, and any consumer accesses its members through the unambiguous package_name::member form. There is exactly one definition of axi4_pkg::axi_ar_t in the entire elaboration — tools track it, consumers reference it, and a change to the package automatically propagates to every importer at the next compile.
Equally important: packages give SystemVerilog the modular-design foundation that classes alone cannot provide. Classes live inside packages; protocol constants live in packages; transaction types live in packages; UVM itself is shipped as a single package (uvm_pkg). Without packages, every multi-file SystemVerilog project would collapse into the same ``include` chaos the language was supposed to escape.
2. Mental Model — Package = Named Library
The picture every engineer carries:
A package is a library with a name on the door. Inside the library: any number of parameters, type aliases, functions, tasks, and classes. The door is the package name. Any consumer in the design — a module, an interface, a class, a program, another package — reaches in by writing
package_name::member. No imports are needed for this form; it always works as long as the package has been compiled. Theimportstatement (covered in 16.2) is a shorthand that lets a consumer omit thepackage_name::prefix.
Four invariants this picture preserves:
- The package is the only owner of its members. There is exactly one
axi4_pkg::burst_ein the entire elaboration. Two modules that referenceaxi4_pkg::INCRare guaranteed to be talking about the same enum literal of the same type — no copy-paste drift possible. - Packages have no hardware footprint. Synthesis tools elaborate package types and parameters into the consumers that use them; the package itself produces no gates, no flip-flops, no nets. A package is purely a compile-time namespace, exactly like a C++ namespace or a Java package.
- Packages are compiled before their consumers. This is a hard build-order constraint enforced by every simulator and synthesis tool. A file that references
axi4_pkg::axi_ar_tcannot be elaborated untilaxi4_pkg.svhas been compiled. (Module 16.4 covers this in detail.) - The scope-resolution operator
::is the universal access mechanism. It works from any context — module, interface, class, program, another package — and is unambiguous even when two packages export the same name. Use it explicitly even after a wildcard import when you want the reader to see exactly which package a name comes from.
3. Visual Explanation — Package Scope Diagram
The figure below shows the package as a named scope boundary, the items it owns, and the ::-qualified access pattern from a consumer module.
The :: operator is the explicit, unambiguous way to reach any package member. It works from any context — module, interface, class, program, or another package. The import statement (16.2) is the shorthand that lets you drop the package_name:: prefix.
Three properties of this picture worth carrying forward:
- The boundary is the package name. Two packages can declare identical member names (
pkg_a::txn_tandpkg_b::txn_t) and neither shadows the other — the::form keeps them distinct. - There is no
extern/forwardproblem for package contents. Anything inside a package is reachable by::immediately once the package is compiled — no forward declarations, no header/implementation split. - The arrow goes one way. Consumers reach into the package; the package never reaches out. A package never references a module or signal — that would couple it to a specific elaboration and destroy its reusability.
4. Syntax — Package Declaration Skeleton
A package body is wrapped in package ... endpackage. The trailing label after endpackage : is optional but strongly recommended — tools and linters use it to verify the block is closed correctly and to catch typos when the label is misspelled.
// ── Minimal package ──────────────────────────────────────────────
package my_pkg;
// declarations go here
endpackage : my_pkg // trailing label is optional but recommended
// ── Full anatomy of a real package ──────────────────────────────
package axi4_pkg;
// 1. Parameters / localparams
parameter int AXI_DATA_W = 64;
parameter int AXI_ADDR_W = 32;
localparam int AXI_STRB_W = AXI_DATA_W / 8;
// 2. Typedef — enum
typedef enum logic [1:0] {
FIXED = 2'b00,
INCR = 2'b01,
WRAP = 2'b10
} burst_type_e;
// 3. Typedef — struct
typedef struct packed {
logic [AXI_ADDR_W-1:0] addr;
logic [7:0] len;
burst_type_e burst;
} axi_ar_t;
// 4. Automatic function — pure utility, one stack frame per call
function automatic logic [3:0] byte_enables(input logic [2:0] size);
return (1 << (1 << size)) - 1;
endfunction
// 5. Class definition
class Axi4Transaction;
axi_ar_t ar_chan;
function new(); ar_chan = '0; endfunction
endclass
endpackage : axi4_pkgThe five categories above (parameter, typedef, function, task, class) are the only declarations a package body can contain. Anything procedural, anything that allocates hardware, and any module/interface instance is a compile error inside a package. The full restriction list:
5. What Can Go Inside a Package?
The LRM (IEEE 1800-2017, §26.2) gives the legal items. The table below covers every category you will encounter in practice.
| Category | Construct | Typical use |
|---|---|---|
| Parameters | parameter, localparam | Bus widths, depths, protocol constants shared across DUT and TB |
| Type aliases | typedef (enum, struct, union, class) | Shared data types — transaction structs, opcode enums, scoreboard types |
| Functions | function automatic | Pure utility functions — CRC computation, encoding, field extraction |
| Tasks | task automatic | Shared simulation helpers — delay routines, print formatting |
| Classes | class ... endclass | Reusable transaction objects, base classes, scoreboards |
| Nested imports | import other_pkg::*; | Pulling a dependency package's names into the current package scope |
| Text includes | `include "file.svh" | Splitting a large package across files while keeping a single compile unit |
5b. The :: Operator — Universal Access
Every item inside a package is reachable via the scope-resolution operator ::. You write package_name::member_name. No import is required for this form — it always works as long as the package has been compiled before the consumer.
// ── Package definition (compile this first) ──────────────────────
package apb_pkg;
parameter int APB_ADDR_W = 16;
parameter int APB_DATA_W = 32;
typedef enum logic {
APB_IDLE = 1'b0,
APB_SETUP = 1'b1
} apb_phase_e;
typedef struct packed {
logic [APB_ADDR_W-1:0] paddr;
logic [APB_DATA_W-1:0] pwdata;
logic pwrite;
logic psel;
logic penable;
} apb_txn_t;
function automatic logic [APB_DATA_W-1:0] mask_data(
input logic [APB_DATA_W-1:0] data,
input logic [3:0] strb
);
for (int i = 0; i < 4; i++)
if (!strb[i]) data[i*8 +: 8] = 8'h00;
return data;
endfunction
endpackage : apb_pkg
// ── Module consuming the package — no import, pure :: access ────
module apb_slave #(
parameter int ADDR_W = apb_pkg::APB_ADDR_W, // :: in parameter default
parameter int DATA_W = apb_pkg::APB_DATA_W
) (
input apb_pkg::apb_txn_t req, // :: in port type
input logic clk,
output logic [DATA_W-1:0] prdata,
output logic pready
);
apb_pkg::apb_phase_e phase; // :: in signal declaration
apb_pkg::apb_txn_t captured;
always_ff @(posedge clk) begin
if (req.psel && !req.penable) begin
phase <= apb_pkg::APB_SETUP;
captured <= req;
end else begin
phase <= apb_pkg::APB_IDLE;
end
end
// Call a package function directly with ::
assign prdata = apb_pkg::mask_data(captured.pwdata, 4'hF);
assign pready = (phase == apb_pkg::APB_SETUP);
endmoduleNotice every use site of the package:
- Parameter default —
apb_pkg::APB_ADDR_W - Port type —
apb_pkg::apb_txn_t - Internal signal type —
apb_pkg::apb_phase_e - Enum literal —
apb_pkg::APB_SETUP - Function call —
apb_pkg::mask_data(...)
The :: form is legal in every one of these positions and reads identically regardless of how deep you nest packages. The companion Class Scope Resolution page (Module 9.16) covers the related uses of :: for static class members, super::, and parameterised-class specialisations.
6. Waveform — Omitted (Architectural Lesson)
This lesson is architectural, not timing-specific. Packages produce no simulation events — they are pure compile-time constructs. Adding a waveform here would be misleading. The timing behaviour of the constructs inside a package (clocked logic that consumes package types, classes that schedule events) is covered in the relevant timing pages: program-block for the Reactive-region scheduling, clocking-blocks-deep-dive for the sampling timing of clocked package types. This section is intentionally omitted; the topic does not warrant it.
7. Synthesis View — Compile-Time Only
A package itself produces no hardware. Synthesis tools treat the package as a type and constant catalog: when a module references axi4_pkg::axi_ar_t, the synthesis tool elaborates the struct into the bit-vector signals that the consuming module needs, then forgets the package. There is no flip-flop, no net, no gate that traces back to the package declaration.
The members of a package, however, may or may not be synthesizable depending on what they are:
| Package member | Synthesizable? | Notes |
|---|---|---|
parameter / localparam | Yes | Resolved to a constant during elaboration |
typedef (enum, struct, union) | Yes | Elaborated into bit-vector types in the consumer |
function automatic (combinational) | Yes | Inlined into the consumer's combinational logic |
task automatic | No (sim-only) | Tasks may contain # delays, wait, @ — never synthesisable |
class | No (sim-only) | Classes are dynamic objects — verification only |
function automatic with #/wait | No | Same reason as tasks |
The takeaway: a package is universally synthesisable as a construct. Whether a given member synthesises depends on the standard synthesisable-subset rules, applied at the consumer's elaboration site. A protocol-constant package shared by DUT and testbench is a textbook example of safe dual-use: the DUT pulls in only the synthesisable parameters and types; the testbench additionally pulls in the classes and tasks.
8. Package vs Other Sharing Mechanisms
Three older mechanisms exist for sharing declarations in SystemVerilog. The decision matrix below is what every code review applies.
How it works: named language scope; compiled once; referenced with :: or import.
Namespace: isolated — pkg::name. Two packages with the same member name never collide.
Best for: types, parameters, functions shared across DUT + TB. Anything you want a name on.
Avoid when: never — always prefer this.
How it works: textual paste at the point of inclusion. Like C's #include. No language-level scope.
Namespace: pollutes the surrounding scope. Same identifier in two includers in the same compile unit is a redefinition error.
Best for: legacy code; tool-generated headers not yet ported to packages; splitting a single package across multiple files.
Avoid when: new code declaring shared types — use a package instead. The order of ``include` directives changes the semantics; trivially broken by refactoring.
How it works: global text macro — preprocessor-level substitution. No type, no scope, no namespace.
Namespace: global. A ``define WIDTH 32` in any file affects every later file in the compile unit.
Best for: conditional compilation flags (``ifdef SIMULATION`), tool-version guards, header-include guards.
Avoid when: declaring constants that have a type — use a parameter in a package instead. ``define` constants don't appear in the symbol table, don't show up in waveforms, and silently corrupt expressions through unexpected text expansion.
How it works: items declared outside any module at the top of a file — visible to the whole compilation unit ("$unit scope").
Namespace: file-level, no named prefix. Order-dependent on the file-list ordering.
Best for: almost nothing in modern code. Sometimes used as a quick-and-dirty cross-file constant during prototyping.
Avoid when: any shared code that ships. Different simulators handle $unit scope differently; the same source can produce different results between tools.
The rule that follows: prefer package for every named, typed, shared declaration; reserve `include for legacy and for splitting a package across files; reserve `define for conditional compilation only; avoid $unit entirely.
9. Real-World Patterns
The two canonical patterns every production verification environment uses.
9.1 Protocol Parameter Package
A shared protocol package consumed by both the DUT and the testbench. The same constants, types, and utility functions live in one file; the DUT pulls them in for synthesis, the testbench pulls them in for stimulus and checking.
// ── File: i2c_pkg.sv ──────────────────────────────────────────────
package i2c_pkg;
// Protocol constants
parameter int ADDR_BITS = 7;
parameter int DATA_BITS = 8;
parameter int SCL_FREQ_KHZ = 400;
// Derived localparam — computed once, used everywhere
localparam int FRAME_BITS = ADDR_BITS + 1 + DATA_BITS + 1; // addr+RW+data+ACK
// Opcode enum
typedef enum logic {
I2C_WRITE = 1'b0,
I2C_READ = 1'b1
} i2c_dir_e;
// Transfer descriptor
typedef struct packed {
logic [ADDR_BITS-1:0] addr;
i2c_dir_e dir;
logic [DATA_BITS-1:0] data;
} i2c_frame_t;
// Utility function — pack 7-bit addr + R/W into the on-wire byte
function automatic logic [7:0] addr_byte(
input logic [ADDR_BITS-1:0] a,
input i2c_dir_e dir
);
return {a, dir};
endfunction
endpackage : i2c_pkg
// ── DUT references the package ────────────────────────────────────
module i2c_master (
input logic clk, rst_n,
input i2c_pkg::i2c_frame_t req,
input logic req_valid,
output logic scl, sda
);
// Implementation uses i2c_pkg::ADDR_BITS, i2c_pkg::I2C_WRITE, etc.
endmodule
// ── Testbench references the SAME package ─────────────────────────
module tb_i2c;
i2c_pkg::i2c_frame_t stim;
initial begin
stim.addr = 7'h50;
stim.dir = i2c_pkg::I2C_WRITE;
stim.data = 8'hA5;
$display("Address byte: %02h", i2c_pkg::addr_byte(stim.addr, stim.dir));
end
endmoduleWhy this pattern matters: a change to ADDR_BITS or to addr_byte() is automatically reflected on both sides of the DUT-TB boundary. Without a package, you would have to remember to update both copies — a class of bug that has caused real-silicon respins.
9.2 Verification Utility Package
A shared utility package — logging, checking, summary reporting — consumed by every component of a testbench. This is the pattern UVM generalises into uvm_pkg (just with a much larger surface area).
// ── File: tb_utils_pkg.sv ─────────────────────────────────────────
package tb_utils_pkg;
// Severity type
typedef enum { SEV_INFO, SEV_WARN, SEV_ERROR, SEV_FATAL } sev_e;
// Deliberately shared counters — package-scope (not function-local)
int error_count = 0;
int check_count = 0;
// Log function — formats and prints
function automatic void log_msg(
input string tag,
input string msg,
input sev_e sev = SEV_INFO
);
string prefix;
case (sev)
SEV_INFO : prefix = "[INFO ]";
SEV_WARN : prefix = "[WARN ]";
SEV_ERROR : prefix = "[ERROR]";
SEV_FATAL : prefix = "[FATAL]";
endcase
$display("%0t %s [%s] %s", $time, prefix, tag, msg);
if (sev == SEV_FATAL) $fatal(1);
endfunction
// Checker — accumulates errors
function automatic void check_eq(
input string tag,
input longint got,
input longint exp
);
check_count++;
if (got !== exp) begin
error_count++;
log_msg(tag, $sformatf("MISMATCH got=%0h exp=%0h", got, exp), SEV_ERROR);
end
endfunction
// Final report — call from test end
function automatic void report_summary(input string test_name);
if (error_count == 0)
$display("[PASS] %s — %0d checks passed", test_name, check_count);
else
$display("[FAIL] %s — %0d/%0d checks failed",
test_name, error_count, check_count);
endfunction
endpackage : tb_utils_pkg
// ── Any testbench component uses it with :: ───────────────────────
module test_top;
initial begin
tb_utils_pkg::log_msg("TEST", "Starting reset sequence");
// apply_reset();
tb_utils_pkg::check_eq("DUT", 32'h0000_0001, 32'h0000_0001);
tb_utils_pkg::report_summary("smoke_test");
end
endmoduleThe key discipline this example shows: shared state (error_count, check_count) is at package scope, not function-local static. The intent is explicit and the reader can see the shared state at a glance. Functions are automatic so each call gets its own stack frame — the only shared part is the explicit counter.
10. Debug Lab — Six Package Bugs
The bugs every reviewer flags. Each follows the post-mortem pattern: symptom → root cause → fix → guardrail.
Package declaration & usage — bugs every engineer makes once
Symptom: Compile error — "initial / always not allowed in package" on the line that opens the procedural block.
Cause: A package is a compile-time namespace, not a simulation context. There are no simulation events at package scope — no time wheel to schedule initial or always against.
// ❌ COMPILE ERROR
package bad_pkg;
initial begin // not allowed in package
$display("hello");
end
endpackageFix: Move the procedural code to a module, program, or testbench. Keep only declarations in the package.
Guardrail: a package body should contain only parameter / localparam / typedef / function automatic / task automatic / class / nested import / ``include`. Any other top-level keyword is a red flag.
Symptom: Two concurrent testbench components calling the same package function get interleaved results. A counter inside the function does not reset between calls.
Cause: Package-scope functions default to static lifetime when the keyword is omitted. A single copy of all local variables is shared across every caller in the entire elaboration.
// ❌ BUG — silent corruption under concurrent calls
package bad_pkg2;
function int get_id(); // static by default — ONE copy of all locals
int cnt = 0; // BUG: cnt persists across all callers
return cnt++;
endfunction
endpackageFix: Write function automatic int get_id() — each call gets its own stack frame with a fresh cnt.
Guardrail: every function and task in a package must be automatic. Make this a lint rule; it costs nothing and catches a real class of bug.
Symptom: Compile error — "label 'utils_pkg' does not match package name 'util_pkg'". Usually a one-letter typo.
Cause: The trailing endpackage : <name> label must match the opening package name exactly — SystemVerilog identifiers are case-sensitive.
// ❌ COMPILE ERROR
package util_pkg;
parameter int MAX = 64;
endpackage : utils_pkg // label 'utils_pkg' != 'util_pkg'Fix: endpackage : util_pkg — match the opening name character-for-character.
Guardrail: always include the trailing label. The compile error is exactly what catches you when you rename the package and forget to update the label.
Symptom: Compile error — "net declarations not allowed inside package" on the wire or logic line.
Cause: Packages hold types and constants, not signal instances. A wire or logic declaration creates a hardware net — but a package has no module to host the net, no scope to drive it, no connectivity to receive it.
// ❌ COMPILE ERROR
package bad_pkg3;
wire clk; // net declarations not allowed
logic [7:0] reg_data; // signal-style variable also illegal
endpackageFix: Declare a typedef for the type, then have the consuming module instantiate the signal:
package good_pkg;
typedef logic [7:0] reg_data_t; // type alias is fine
endpackage
module dut;
good_pkg::reg_data_t reg_data; // signal lives in the module
endmoduleGuardrail: if you find yourself reaching for wire or signal-typed logic in a package, you are putting hardware in the wrong place. The package owns the type; the consumer owns the signal.
Symptom: Compile error — "module instance not allowed inside package". Often surfaces when someone tries to share a clock generator or a memory model via a package.
Cause: An instance ties a piece of hardware to a specific point in the elaboration hierarchy. Packages have no hierarchy — they are flat namespaces. There is no place in the design for the instance to live.
// ❌ COMPILE ERROR
package bad_pkg4;
clk_gen u_clk(); // module instance not allowed
endpackageFix: Instances belong in a parent module (or a bind construct for verification helpers). The package can still hold the parameters and types the instance needs, just not the instance itself.
Guardrail: "instance" and "package" are mutually exclusive concepts. If you need an instance, the right container is a module.
Symptom: Compile error — "package 'apb_Pkg' not found" despite apb_pkg.sv existing on the file list.
Cause: SystemVerilog identifiers are case-sensitive. apb_Pkg and apb_pkg are two different names — the compiler does not auto-correct.
// ❌ COMPILE ERROR
logic [31:0] data = apb_Pkg::APB_DATA_W; // 'apb_Pkg' vs 'apb_pkg'Fix: Match the exact case of the package name as declared. The convention is all-lowercase with underscores (apb_pkg, axi4_pkg, i2c_pkg) — pick this convention and stick to it across the codebase.
Guardrail: industry convention is one package per file, file name matches package name, all lowercase. Following this convention eliminates the entire class of case-mismatch errors.
11. Q & A
The questions that come up in code review and interviews.
12. Cross-References & What's Next
This lesson covered the foundational mechanics of a SystemVerilog package — declaration, the :: operator, what is legal inside, and the canonical protocol-package / utility-package patterns. The rest of Module 16 takes the package model to production depth.
- Next: 16.2 — import (Explicit & Wildcard) — the shorthand that lets you omit the
pkg_name::prefix, the difference between explicit (import pkg::name) and wildcard (import pkg::*), the name-resolution rules when two packages export the same name, and the in-port-list vs in-body placement choices. - 16.3 — Package-Level Parameters & Types — the canonical pattern for protocol constants (bus widths, depths, opcodes) 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, circular dependencies and how to break them with a shared base package, and common file-list patterns.
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. - typedef class — Forward Declarations (Module 9.15) — breaking circular references between mutually-dependent classes within one scope; 16.4 explains why this differs from breaking circular dependencies between packages.
- What UVM Is — and What It Is Not — UVM is shipped as the single
uvm_pkgpackage; without the package construct, UVM literally could not exist.
13. Quick Reference
The cheat sheet to keep open while writing your first package.
// ── Declaration skeleton ─────────────────────────────────────────
package pkg_name;
parameter int CONST = 42;
typedef enum { A, B } myenum_e;
typedef struct packed { logic [7:0] f; } mystruct_t;
function automatic int my_fn(input int x); return x*2; endfunction
class MyClass; /* ... */ endclass
endpackage : pkg_name // trailing label is optional but best practice
// ── Accessing members with :: (no import needed) ─────────────────
pkg_name::CONST // parameter
pkg_name::myenum_e // type
pkg_name::A // enum literal
pkg_name::mystruct_t // struct type
pkg_name::my_fn(3) // function call
// ── What IS allowed in a package ────────────────────────────────
// parameter / localparam
// typedef (enum, struct, union, class)
// function automatic / task automatic
// class definitions
// import other_pkg::*; (nested import — see 16.2)
// `include "file.svh"
// ── What is NOT allowed in a package ────────────────────────────
// initial / always / final blocks
// module / interface instances
// wire / logic signal declarations (only type aliases)
// port declarations
// generate constructs
// ── Eight rules to live by ──────────────────────────────────────
// 1. Packages are compile-time only — no hardware, no simulation events.
// 2. Compile packages BEFORE any file that references them.
// 3. Always use 'function automatic' — static functions share locals globally.
// 4. :: works from any context; import (wildcard or explicit) is a shorthand.
// 5. Package names are case-sensitive — pkg_name != Pkg_Name.
// 6. Trailing endpackage label must match the opening name exactly.
// 7. Prefer packages over `include for all shared type/param declarations.
// 8. One package per file, file name matches package name — industry convention.14. Summary
A SystemVerilog package is the named compilation scope that bundles parameters, types, functions, tasks, and classes for sharing across the entire design and verification hierarchy. It is purely a compile-time construct — no hardware, no simulation events, no instances — and that restriction is what makes it the only correct way to share typed declarations in modern SystemVerilog. The :: scope-resolution operator gives any consumer unambiguous access from any context (module, interface, class, program, another package); the import statement (covered in 16.2) is a shorthand on top of ::.
The discipline that follows from the model:
function automaticalways — static package functions share locals globally and silently corrupt concurrent simulations.- Trailing
endpackage : pkg_namelabel — costs nothing, catches typos at compile time. - One package per file, file name matches package name, all-lowercase identifiers — the industry convention that eliminates case-mismatch errors and makes file-list ordering predictable.
- Prefer
packageover`includeand`definefor every named, typed, shared declaration. The older mechanisms still have a place (legacy code; conditional compilation; splitting a large package across files), but they are not the default.
This page established the foundation. The next three pages of Module 16 build on it: import semantics (16.2), the canonical protocol-parameter and shared-type patterns (16.3), and the compilation-order discipline that keeps multi-package builds deterministic (16.4). By the end of the module you will have the package model that every production VIP and every UVM environment is built on.