Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • RTL Foundations36
  • Combinational Design25
  • Sequential Design27
  • FSM & Control27
  • Timing & Clocking39
  • Reset & CDC34
  • Memory & Buses57
  • Advanced RTL28
    • Parameterization and Generate Hub
    • Top-Level Integration Hub
    • Synthesis Intent Hub
    • Style and Static Checks Hub
    • Assertions in RTL Hub
    • Formal Verification Basics Hub
    • Reusable SystemVerilog for RTL
      • Packages and Compile Order
      • Packed Structs and Unions
      • Parameter Types and Guards
      • Functions and Tasks
      • Interfaces, Modports, and Portability
      • Compile-Order and Package Errors, Decoded
      • Mini Project: a Reusable FIFO IP
  • Senior & Interview76
  • Glossary and Notation
Loading lesson

‹Reusable SystemVerilog for RTL
  1. Topics
  2. RTL Design
  3. Advanced RTL
  4. Reusable SystemVerilog for RTL
  5. Packages and Compile Order

Part 8 · Advanced · Advanced

Packages and Compile Order

Centralize shared types and constants while keeping dependencies acyclic and build order explicit.

On this page

    Why this matters

    Two private copies of one struct drift apart and still connect cleanly, and a package compiled after its importer stops the build with a message that does not name the cause. Ownership and order have to be designed.

    After this lesson you can: replace duplicated type definitions with one package, order packages before their importers, and read a compile-order error.

    Why a package: one definition, not two copies

    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.

    design.sv
    1
    2
    3
    4
    5
    // 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.

    consumer.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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
    );
      // ...
    endmodule

    Read 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.

    Compile order: packages first

    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:

    Diagram
    $ 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 declaration

    The 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.

    Draw the dependency graph

    Dependency graph: base_pkg feeds packet_pkg and csr_pkg; packet_pkg feeds the parser and consumer modules; csr_pkg feeds csr_block.
    Packages form a tree from a low-level base package down to the modules; compile from the top of the tree down.

    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.

    Rules

    • 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.

    Common pitfall

    A package is not a dumping ground for every project constant. Separate stable interface types from block-local implementation constants so unrelated changes do not force broad recompilation.

    Check yourself Why can a wildcard import become risky in a large integration?

    Two packages can export the same name, making resolution ambiguous or silently dependent on scope. Qualified names document ownership.

    Check yourself Two copies of header_t differ only in field order, and both are 32 bits. Which tool catches the mismatch?

    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.

    Remember

    • One definition in a package; every block imports it, so a layout change reaches all of them.

    • Compile packages before the modules that import them, and avoid circular imports.

    • Prefer explicit pkg::type in widely shared headers, because wildcard imports can collide.

    Rule of thumb: move common definitions into a lower-level package instead of importing in a circle.

    How this was checked

    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.

    Keep going

    • Code lab: Package functions

    • Interview: run a mock interview on this topic

    Related topics

    • Complete packages course

    • Packed structs and unions

    • Compile-order and package errors, decoded

    Previous

    267 / 350

    Next: Packed Structs and Unions

    Up next

    Up nextPacked Structs and Unions

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Parameter Types and Guards
    • Functions and Tasks
    • Interfaces, Modports, and Portability