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

  • Start Here6
  • SystemVerilog Language Foundations35
    • SystemVerilog Data Types
    • Arrays: Packed, Dynamic, Queues & Associative
    • Procedural Code & Scheduling
    • Operators, Expressions & Width Rules
    • Packages, Typedefs & Compilation
      • Packages & Imports
      • Compilation Units & Compile Order
      • typedef, parameter & localparam
      • `include vs import
      • Namespace Hygiene at Scale
    • Interfaces, Modports & Clocking Blocks
  • Object-Oriented SystemVerilog for Verification35
  • Constraint Randomization — Interview-Critical Deep Dive66
  • SystemVerilog Assertions — Interview-Critical Deep Dive63
  • Functional Coverage in SystemVerilog28
  • Testbench Architecture in SystemVerilog38
  • Advanced Language & Integration30
  • Senior & Interview Preparation34
  • Principal-Level Verification28
Loading lesson

‹Packages, Typedefs & Compilation
  1. Topics
  2. SystemVerilog
  3. SystemVerilog Language Foundations
  4. Packages, Typedefs & Compilation
  5. Compilation Units & Compile Order

Part 1 · Language Foundations · Intermediate

Compilation Units & Compile Order

Compilation-unit scopes, filelist order, classic undefined-type errors and time scales.

On this page

    Why this matters

    A bench compiled on one simulator and failed with undefined types on another, with no code changed. The two tools grouped the files into compilation units differently.

    After this lesson you can: explain $unit and why sharing through it is not portable, order a filelist by dependencies, and read the classic compile errors.

    One unit or many

    A compilation unit is the set of source files the compiler processes together as one scope-resolution group. Declarations at file level, outside any module or package, land in the compilation-unit scope called $unit.

    The grouping policy belongs to the tool. Some simulators treat one command line as a single unit, others treat each file as its own unit, so a typedef at the top of file A is visible to file B in one mode and invisible in the other.

    Whether files share $unit depends on how the tool groups them; packages do not depend on it.

    Packages escape this trap. Once compiled, a package lives in a library visible to every later compilation regardless of unit boundaries. The portable rule is short: declare nothing shared in $unit, and put every shared declaration in a package.

    Compile order is a dependency graph

    A definition must be compiled before its first use. A module that imports bus_pkg needs bus_pkg compiled already, either earlier in the same file list or in an earlier compilation into the same library.

    Module instantiation is more forgiving because tools defer binding to elaboration. Packages, typedefs and classes resolve at compile time, so their order is strict. Real projects encode the order in a filelist: packages first, then interfaces, then RTL and testbench files.

    Diagram
    CORRECT FILELIST (tb.f): packages first, dependency order within them
    
        src/common_pkg.sv     // lowest-level package
        src/bus_pkg.sv        // imports common_pkg, so it follows it
        src/bus_if.sv         // interface uses bus_pkg types
        src/dut.sv
        tb/tb_pkg.sv
        tb/tb.sv              // testbench top comes last
    design.sv
    1
    2
    3
    4
    5
    // bad-example: tb.sv compiled before its package
    module tb_wrong_order;
      import bus_pkg::*;   // package bus_pkg was never compiled: not found
      txn_t t;             // undefined type follows from the missing package
    endmodule
    Predict first a_pkg imports b_pkg, and b_pkg imports a_pkg. In what order should the filelist list them?

    No order works: each needs the other compiled first. Move the shared names into a third package that both import.

    The sketch above is the classic failure: the importer compiled before the package. Reversing the filelist order fixes it, and the checked listing below shows the healthy direction in one file.

    worker.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    package common_pkg;
      parameter int WIDTH = 8;
      typedef logic [WIDTH-1:0] data_t;
    endpackage
    
    // The package above is compiled first, so this module resolves cleanly.
    module worker (
      input  common_pkg::data_t d,
      output common_pkg::data_t q
    );
      assign q = d + 1'b1;
    endmodule
    
    module tb_order;
    
      timeunit 1ns; timeprecision 1ps;
      import common_pkg::*;
      data_t stim, got;
      worker u_w (.d(stim), .q(got));
      int errors = 0;
      initial begin
        stim = 8'h41; #1;
        if (got !== 8'h42) errors++;
        stim = 8'hFF; #1;
        if (got !== 8'h00) errors++;
        if (errors != 0) $fatal(1, "FAIL: package import did not carry widths and types");
        $display("PASS: package compiled before its importer carries widths and types");
        $finish;
      end
    endmodule
    Terminal
    PASS: package compiled before its importer carries widths and types

    Reading the classic error messages

    Four messages cover most compilation failures. Each names a precise structural cause, and each has a precise fix.

    Message

    Cause

    Fix

    undefined type txn_t

    Typedef sat in another unit, or its package compiled later

    Move the typedef into a package and fix filelist order

    package bus_pkg not found

    Package source appears after its importer

    List packages before their users in the filelist

    txn_t is not compatible with txn_t

    Two same-named types from textual inclusion into different scopes

    Keep one package definition and import it on both sides

    Works in one simulator, fails in another

    Code depends on single-unit $unit visibility

    Remove all shared file-level declarations

    Check yourself A bench compiles with one vendor tool and fails with undefined types under another. No code changed. What is the likely cause?

    The code shares declarations through $unit, and the two tools group files into compilation units differently. Moving the declarations into a package removes the tool dependence.

    Time scales without file-order traps

    A `timescale 1ns/1ps directive sets the time unit and precision for the modules compiled after it in the same run. Moving a file changes which modules it covers, which is the same file-order trap as $unit sharing.

    A timeunit and timeprecision pair declared inside a module or package travels with that source instead. Prefer the in-source form in packages, and keep bare directives out of shared headers.

    tick.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // snippet: directive position matters, in-source timeunit travels with the code
    `timescale 1ns/1ps   // covers only modules compiled after this line
    
    module tick (
      input  logic clk,
      output logic done
    );
      timeunit 1ns;        // part of this module, independent of file order
      timeprecision 1ps;
      // ...
      assign done = 1'b0;
    endmodule
    Check yourself A shared header sets `timescale for the whole bench. Why is that fragile?

    The directive covers whatever compiles after it, so adding or reordering files changes timing silently. A timeunit declaration inside each package is immune to ordering.

    Common mistakes

    Common pitfall

    Sharing a typedef through $unit because it works today in single-unit mode. It breaks the day someone compiles per-file or switches simulator. Nothing shared belongs at file level.

    Interview

    Likely questions: “what is $unit”, “why does this type resolve in one tool but not another”, and “how do you fix package not found”.

    • $unit: the compilation-unit scope for file-level declarations, with tool-dependent visibility.

    • One tool only: single-unit against separate compilation defaults differ between tools.

    • Package not found: reorder the filelist so packages compile before importers.

    Check yourself Recompiling a base package forces a full-environment rebuild. How do you keep that cheap?

    Keep packages small, stable and few. One package per coherent concern limits the blast radius of each edit.

    Remember

    • File-level declarations land in $unit, and tools group files into units differently.

    • A package must be compiled before anything that imports it.

    • timeunit inside a module travels with it; `timescale depends on file order.

    Rule of thumb: nothing shared lives at file level.

    How this was checked

    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 wrong-order listing is a deliberately broken example and is not run.

    Related topics

    • Next: typedef, parameter & localparam

    Previous

    31 / 363

    Next: typedef, parameter & localparam

    Up next

    Up nexttypedef, parameter & localparam

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • `include vs import
    • Namespace Hygiene at Scale