3 minute read

Simulation Event Scheduling

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.


→ 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