Without a package, every file that needs a shared type declares its own copy. The copies compile, and then drift. Below, a later edit swapped two fields in one copy. Both structs are still 32 bits, so a 32-bit bus between the two blocks connects with no warning, and the checker reads tag where the parser wrote length.
// snippet: before: two files, two private copies of one header layout
// parser.sv
typedef struct packed { logic [7:0] kind; logic [7:0] length; logic [15:0] tag; } header_t;
// checker.sv (edited later: length and tag swapped)
typedef struct packed { logic [7:0] kind; logic [15:0] tag; logic [7:0] length; } header_t;A package holds the one definition. Every block imports it, so a change to the layout reaches all of them at once.
package packet_pkg;
parameter int DATA_W = 32;
typedef enum logic [1:0] {IDLE, HEADER, PAYLOAD, ERROR} state_t;
typedef struct packed {
logic [7:0] kind;
logic [7:0] length;
logic [15:0] tag;
} header_t;
function automatic logic parity32(logic [31:0] data);
return ^data;
endfunction
endpackage
module consumer import packet_pkg::*; (
input header_t header_i,
output state_t state_o
);
// ...
endmoduleRead the package top to bottom:
DATA_W is a shared constant, so every block agrees on the data width.
state_t is a named 2-bit enum; waveforms and reviews show IDLE rather than 0.
header_t is the one packed layout: kind in bits 31:24, length in 23:16, tag in 15:0.
parity32 is a pure helper: its result depends only on its argument, so it builds the same XOR tree wherever it is called.
The import sits in the module header, before the port list, so header_t and state_t are visible for the port types. The consumer body is left out; it is a declaration sketch.
A package must be compiled before any file that imports it. Here is what two simulators print when consumer.sv comes first on the command line:
$ iverilog -g2012 consumer.sv packet_pkg.sv
consumer.sv:1: syntax error
I give up.
$ verilator --lint-only consumer.sv packet_pkg.sv
%Error: consumer.sv:2:9: Reference to 'header_t' before declaration
%Error: consumer.sv:3:10: Reference to 'state_t' before declarationThe first message names neither the package nor the order, which is why compile-order bugs waste time. With packet_pkg.sv first, both tools accept the same two files. Put the order in the file list or build script, never in someone's memory.
Read the arrows as "must be compiled before". Any top-down order of this tree is a legal compile order. If two packages need each other's definitions, the graph has a cycle and no order works: move the shared definitions down into the base package.
Compile packages before modules that import them.
Prefer explicit pkg::type in widely shared headers when wildcard imports could collide.
Keep packages free of module hierarchy and run-time state.
Avoid circular package imports; move common definitions into a lower-level package.
Two packages can export the same name, making resolution ambiguous or silently dependent on scope. Qualified names document ownership.
None of the compile steps: the bus between the blocks is just 32 bits. Only a test that checks field values does. One package definition removes the second copy, so the mismatch cannot happen.
Compile-order messages from Icarus Verilog 13.0 and Verilator 5.048 (2026-10-17), with consumer.sv listed before packet_pkg.sv, trimmed to the error lines. With the package first, Verilator lints the pair cleanly.