SV10. User-Defined Packages in SystemVerilog
If you work with SystemVerilog, you know the headaches that can come from improper scope management. In this wiki, I cover:
- Why you should avoid $unit scope and use packages instead
- The proper contents of synthesizable packages
- Three methods for importing packages (wildcard, explicit, and direct)
- Best practices for maintaining clean, portable code
Video Tutorial
Watch this comprehensive video explanation of Packages:
Understanding $unit
Scope in SystemVerilog
What is $unit
Scope?
In Verilog or SystemVerilog, the $unit
scope refers to declarations placed outside of any design unit—before any module
, package
, or interface
declaration.
The Problem with $unit
Scope
The primary issue with $unit
scope is tool dependency. Different tools handle $unit
scope inconsistently:
- Some tools allow
$unit
scope declarations to be shared between several files- This creates compile order dependencies
- Files containing the declarations must be compiled first before they can be used
- Other tools restrict
$unit
scope declarations to only be valid within the same file
This inconsistency makes code using $unit
scope less portable and more prone to errors when switching between simulation or synthesis tools.
Recommended Solution
→ Do NOT use $unit
scope. Use packages for sharing declarations.
Example
Problematic ($unit
scope):
// In $unit scope (problematic)
typedef enum {RED, GREEN, BLUE} color_t;
module my_module();
color_t my_color;
// ...
endmodule
Correct (using package):
package color_pkg;
typedef enum {RED, GREEN, BLUE} color_t;
endpackage
module my_module();
import color_pkg::*; // Or use explicit imports
color_t my_color;
// ...
endmodule
Packages
A package is a declaration space for sharing definitions across multiple design units in SystemVerilog.
Packages solve the issues associated with $unit
scope by providing a standardized way to share declarations with consistent behavior across different tools.
Synthesizable Package Contents
A synthesizable package can include:
Parameter and localparam
package my_constants_pkg;
parameter INT_WIDTH = 32;
localparam MAX_COUNT = 1024;
endpackage
Typedef
package my_types_pkg;
typedef enum {IDLE, ACTIVE, ERROR} state_t;
typedef struct {
logic [7:0] addr;
logic [31:0] data;
} mem_access_t;
endpackage
Automatic task and function definitions
Tasks and functions within packages must be declared as automatic
to be synthesizable.
package my_functions_pkg;
automatic function int log2(int value);
int result = 0;
...
return result;
endfunction
endpackage
Import and export
package derived_pkg;
import base_pkg::*; // Import all definitions from base_pkg
endpackage
Time unit
package timing_pkg;
timeunit 1ns;
timeprecision 1ps;
endpackage
Package Import Methods in SystemVerilog
Suppose you have the following package:
package p1;
typedef logic [31:0] uint32_t;
localparam WIDTH = 32;
typedef enum logic [1:0] {IDLE,BUSY,DONE} state_t;
typedef struct {
logic [WIDTH-1:0] data;
state_t state;
} data_t;
endpackage : p1
1. Wildcard Import (*
)
Imports all declarations from a package.
import my_package::*;
Characteristics:
- Imports all declarations from the package
- Declarations become available only if they are not already defined in the module
- Convenient for quick development but can lead to namespace pollution
Example:
module vecmul
import p1::*;
(
input uint32_t a,
input uint32_t b,
output uint32_t c,
inout logic o
);
data_t data;
always_comb begin
case (data.state)
IDLE: begin ... end
BUSY: begin ... end
DONE: begin ... end
endcase
end
endmodule
2. Explicit Import
Specifically name package elements to import.
import my_package::specific_element;
Characteristics:
- Makes it obvious which elements are imported
- Reduces namespace pollution
- Note: Enum elements must be imported separately
Example:
module vecmul
import p1::uint32_t;
import p1::state_t;
import p1::data_t;
(
input uint32_t a,
input uint32_t b,
output uint32_t c,
inout logic o
);
// Import each enum value
import p1::IDLE;
import p1::BUSY;
import p1::DONE;
data_t data;
always_comb begin
case (data.state)
IDLE: begin ... end
BUSY: begin ... end
DONE: begin ... end
endcase
end
endmodule
3. Direct Import
Use when the same name is defined in multiple packages; explicitly indicate which definition is used.
package_name::element_name
Characteristics:
- Resolves naming conflicts across packages
- No import statement needed
- Clear indication of source package
Example:
module vecmul
(
input p1::uint32_t a,
input p1::uint32_t b,
output p1::uint32_t c,
inout logic o
);
p1::data_t data;
always_comb begin
case (data.state)
p1::IDLE: begin ... end
p1::BUSY: begin ... end
p1::DONE: begin ... end
endcase
end
endmodule
Best Practices
- Prefer explicit imports over wildcard imports for better code clarity
- Use direct imports for naming conflicts
- When using enums from packages, import both the enum type and the enum values
- Import statements must be declared after the module name, not before