Two-package benches never collide. Forty-package SoC benches with three vendors fail in three escalating ways: ambiguity errors the compiler reports, silent shadowing it does not, and latent breakage that arrives with a vendor next release.
Enum members make all three worse. Every member name lands in the importing namespace beside the type, and every protocol package on earth defines an IDLE. Unprefixed members are a guaranteed future clash.
// bad-example: two wildcards, one shared name
package axi_pkg;
typedef enum logic [1:0] { AXI_IDLE, AXI_MOVE, AXI_DONE, AXI_ERR } status_e;
endpackage
package pcie_pkg;
typedef enum logic [1:0] { PCIE_IDLE, PCIE_MOVE, PCIE_DONE, PCIE_ERR } status_e;
endpackage
module tb_clash;
import axi_pkg::*;
import pcie_pkg::*;
status_e s; // ambiguous: both packages define status_e
endmoduleMature codebases converge on one import discipline. Package headers and shared files never wildcard-import: they use explicit imports or fully qualified references, because anything a package imports shapes resolution for everything compiled inside it.
Leaf scopes may wildcard-import: one test class, one testbench top, the inside of one module. The blast radius ends at the scope closing keyword. Where two packages both provide a needed name, qualify at the use site instead of importing either.
package axi_pkg;
typedef enum logic [1:0] { AXI_IDLE, AXI_MOVE, AXI_DONE, AXI_ERR } axi_status_e;
typedef struct packed {
logic [31:0] addr;
axi_status_e st;
} axi_txn_t;
function automatic bit axi_ok(axi_status_e s);
return (s == AXI_DONE);
endfunction
endpackage
package pcie_pkg;
typedef enum logic [1:0] { PCIE_IDLE, PCIE_MOVE, PCIE_DONE, PCIE_ERR } pcie_status_e;
typedef struct packed {
logic [31:0] addr;
pcie_status_e st;
} pcie_txn_t;
function automatic bit pcie_ok(pcie_status_e s);
return (s == PCIE_DONE);
endfunction
endpackage
// Shared code: explicit imports only, qualified rare references.
package soc_env_pkg;
import axi_pkg::axi_txn_t;
import pcie_pkg::pcie_txn_t;
function automatic bit pair_ok(axi_pkg::axi_status_e a, pcie_pkg::pcie_status_e p);
return (axi_pkg::axi_ok(a) && pcie_pkg::pcie_ok(p));
endfunction
endpackage
module tb_hygiene;
timeunit 1ns; timeprecision 1ps;
import soc_env_pkg::*; // leaf scope: the blast radius ends here
axi_pkg::axi_txn_t at;
pcie_pkg::pcie_txn_t pt;
int errors = 0;
initial begin
at.addr = 32'h1000; at.st = axi_pkg::AXI_DONE;
pt.addr = 32'h2000; pt.st = pcie_pkg::PCIE_DONE; #1;
if (pair_ok(at.st, pt.st) !== 1'b1) errors++;
pt.st = pcie_pkg::PCIE_MOVE; #1;
if (pair_ok(at.st, pt.st) !== 1'b0) errors++;
if (at.addr !== 32'h1000) errors++;
if (pt.addr !== 32'h2000) errors++;
if (errors != 0) $fatal(1, "FAIL: protocol names collided or crossed in the shared environment");
$display("PASS: explicit imports and prefixed names keep two protocols side by side");
$finish;
end
endmodulePASS: explicit imports and prefixed names keep two protocols side by sideA vendor release can add a name that collides with the other package or with local code. The wildcard turns their addition into your ambiguity error. Explicit imports are immune.
Every name a package exports earns a short package tag: axi_txn_t, AXI_IDLE, pcie_cfg_t. Collisions turn from likely into improbable, and readers can place each name instantly.
Related constants can also hide inside a class scope as static members (classes come in the OOP part), referenced as axi_consts::IDLE. Class members never leak into importer namespaces, which makes the class an airtight constant container. Either habit works; mixing bare global names does not.
The unprefixed one collides with every other bare IDLE in the bench. Prefixing moves the name out of the shared pool of short words.
Commercial VIP shows the pattern to copy. Each VIP ships exactly one public package with prefixed names, one guarded macro header, and one filelist stub that solves compile order for the integrator.
The contract runs both ways. The vendor adds names but never repurposes published ones, since it cannot fix the importing code it would break. Your in-house VIP deserves the same courtesy the moment a second team imports it: public names are an API.
Who | Ships or writes | Promises |
|---|---|---|
Vendor | One public package with prefixed names ( | Never rename or repurpose a published name; only add |
Integrator |
| Never import vendor internals or rely on unprefixed names |
Yes. The blast radius ends at the class closing keyword, which is exactly what the leaf-only rule permits.
Qualify at the use site: axi_pkg::txn_t costs one line and no aliasing machinery. Import neither bare name.
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 two-wildcard listing is a deliberately broken example and is not run.