An include is a preprocessor directive: before compilation, the file text is pasted verbatim at the directive site. An import is a compiler construct: it makes names from an already-compiled package visible in the current scope.
The consequences diverge completely. Included text is re-parsed at every site and belongs to the surrounding scope. Imported names are compiled once and keep one identity: import a struct from a package into two hundred modules and there is still exactly one type.
Shared thing | Mechanism | Why |
|---|---|---|
Macros and conditional-compile blocks | `include | The preprocessor has no packages |
Types, parameters, functions, classes | Packages plus import | Textual sharing breaks type identity |
One large package split for readability | `include inside the package body | File-level readability with package-level identity |
Tool and legacy headers | `include | Same macro rule as any header |
Textual inclusion pastes at every site, so including one header twice re-declares everything in it. The guard idiom wraps the body in ifndef, define and endif so later inclusions expand to nothing.
Every header file gets a guard with no exceptions. Diamond-shaped include graphs appear within weeks, and the guard macro lives in the global preprocessor namespace, so name it after the full file path. Macros are preprocessor objects and cannot live in a package, which is why headers exist at all.
module tb_guarded_header;
timeunit 1ns; timeprecision 1ps;
// ---- emulated first `include "bus_hdr.svh" ----
`ifndef BUS_HDR_SVH
`define BUS_HDR_SVH
typedef struct packed {
logic [7:0] lo;
logic [7:0] hi;
} bus_hdr_t;
function automatic logic [7:0] hdr_sum(bus_hdr_t h);
return h.lo + h.hi;
endfunction
`endif
// ---- emulated second `include "bus_hdr.svh": same text, expands to nothing ----
`ifndef BUS_HDR_SVH
`define BUS_HDR_SVH
typedef struct packed {
logic [7:0] lo;
logic [7:0] hi;
} bus_hdr_t;
function automatic logic [7:0] hdr_sum(bus_hdr_t h);
return h.lo + h.hi;
endfunction
`endif
bus_hdr_t h;
int errors = 0;
initial begin
h.lo = 8'h11; h.hi = 8'h22; #1;
if (hdr_sum(h) !== 8'h33) errors++;
if (errors != 0) $fatal(1, "FAIL: guarded header names did not survive double inclusion");
$display("PASS: guarded header included twice declares one set of names");
$finish;
end
endmodulePASS: guarded header included twice declares one set of namesThe two pasted copies above stand in for two include lines naming one guarded header. The second copy vanishes in preprocessing, so the scope sees exactly one declaration set.
// bad-example: the same header pasted twice with no guard
module tb_unguarded_header;
typedef struct packed {
logic [7:0] lo;
logic [7:0] hi;
} bus_hdr_t;
typedef struct packed {
logic [7:0] lo;
logic [7:0] hi;
} bus_hdr_t; // re-declaration: no guard removed the duplicate
endmoduleThe third header would declare twice without protection. A guard named after its path makes the second expansion empty, so the diamond is harmless.
Production verification IP combines both mechanisms in one fixed idiom. Macros stay outside in a guarded header, and class files are included one by one inside the package body in dependency order.
The outside world then sees a clean namespace through one import. The inside stays navigable as one file per class, and every class compiles into the package single scope with identity preserved. The sketch below spans several files, so it is shown as structure rather than a checked listing.
// snippet: one package per VIP, class files included inside in dependency order
`include "my_vip_macros.svh" // macros: preprocessor layer, outside any package
package my_vip_pkg;
`include "my_vip_types.svh" // typedefs and enums
`include "my_vip_txn.svh" // class my_vip_txn
`include "my_vip_driver.svh" // class my_vip_driver, uses the transaction
`include "my_vip_monitor.svh"
`include "my_vip_agent.svh" // include order is dependency order
endpackage
// user side:
// `include "my_vip_macros.svh"
// import my_vip_pkg::*;There is no per-file import for classes. Inclusion inside the body compiles every class into the one package scope, which keeps a single type identity across the VIP.
The first inclusion defines the macro and empties the second header entirely. Guard names must carry the file path to stay unique.
Run with Verilator 5.048 (--binary --timing) on 2026-10-18; every bench printed the PASS line shown, with no warnings. Icarus 13 (-g2012) runs them too, with the same output.
The double-inclusion bench writes the two expanded copies inline, because a lesson listing is one file; the unguarded listing is a deliberately broken example.