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. Packages & Imports

Part 1 · Language Foundations · Intermediate

Packages & Imports

Declare a package once, reference it three ways, and re-publish names with export.

On this page

    Why this matters

    Two modules each had a header struct with identical fields, and the compiler refused to connect them: two definitions are two types. A package gives every user the same one.

    After this lesson you can: declare a package, reach its names with ::, an explicit import or a wildcard import, and re-publish names with export.

    What a package is

    A package is a named, compile-once container for declarations: typedefs, parameters, functions, tasks and classes. It holds no instances and no processes such as always or initial blocks. Variables declared in a package are static and shared by every importer, which makes them easy to misuse as globals.

    Every module, interface or class that references the package sees the same definitions. Two modules that both use bus_pkg::txn_t share one type, not two lookalikes, which is what lets them pass values of that type to each other.

    Three ways to reference a package

    The listing below reaches into bus_pkg three ways, in increasing order of blast radius. The scope operator names one item once. An explicit import pulls one name into the scope. A wildcard import makes every name a candidate for later use.

    Candidate is the operative word. A wildcard import binds nothing until a name is used, and a local declaration of the same name silently wins. That laziness is why wildcards feel harmless in small benches and become collision factories in large ones.

    Style

    Scope impact

    Use it in

    bus_pkg::txn_t

    One use site, zero collisions

    Shared headers, package bodies, vendor-facing code

    import bus_pkg::txn_t;

    One name bound, clash is a compile error

    Module and class files with few package dependencies

    import bus_pkg::*;

    Every name is a candidate, clash is silent

    Leaf scopes only: one testbench top, one test class

    Three rules keep names from colliding as a bench grows: prefix every name a package exports (pkt_hdr_t, PKT_DATA), wildcard-import only in leaf scopes such as one testbench top, and write pkg::name where two packages offer the same name. Namespace Hygiene at Scale is the later reading.

    Predict first A module does import bus_pkg::*; and also declares its own variable named IDLE, the same as a member of bus_pkg::cmd_e. Which IDLE does the module's code use?

    Its own, with no warning. A wildcard import only offers names; a local declaration of the same name wins silently.

    style_scope.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
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    package bus_pkg;
      // parameters: shared constants
      parameter int ADDR_W = 32;
      parameter int DATA_W = 64;
    
      // types: one definition, used everywhere
      typedef enum logic [1:0] { READ, WRITE, IDLE } cmd_e;
    
      typedef struct packed {
        cmd_e                cmd;
        logic [ADDR_W-1:0]   addr;
        logic [DATA_W-1:0]   data;
      } txn_t;
    
      // functions: pure utilities usable in checkers
      function automatic bit parity_of(txn_t t);
        return ^t.data;
      endfunction
    endpackage
    
    // Style 1: scope operator, no import, zero namespace impact.
    module style_scope (
      input  bus_pkg::txn_t t,
      output logic           par
    );
      assign par = bus_pkg::parity_of(t);
    endmodule
    
    // Style 2: explicit import, only the names listed.
    module style_explicit (
      input  bus_pkg::txn_t t,
      output logic           par
    );
      import bus_pkg::parity_of;
      assign par = parity_of(t);
    endmodule
    
    // Style 3: wildcard import, names resolve on demand in this scope.
    module style_wild (
      input  bus_pkg::txn_t t,
      output logic           par,
      output logic           is_read
    );
      import bus_pkg::*;
      assign par = parity_of(t);
      assign is_read = (t.cmd == READ);
    endmodule
    
    Terminal
    PASS: scope, explicit and wildcard references agree on both transactions

    Re-publishing names with export

    A package can export names it imported, presenting one aggregate facade. The common use is an umbrella package that gathers several protocol packages so users import once.

    Without the export, imported names stay inside the umbrella and never reach its importers. A wildcard export bus_pkg::* only carries names the umbrella actually imported, that is, referenced inside it, so an umbrella that uses nothing exports nothing. Export the public names explicitly. The testbench below proves the chain: it imports only vip_pkg yet uses the type and calls the parity function through it.

    design.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
    31
    32
    33
    package bus_pkg;
      parameter int DATA_W = 8;
    
      typedef struct packed {
        logic [DATA_W-1:0] data;
      } byte_txn_t;
    
      function automatic bit parity_of(byte_txn_t t);
        return ^t.data;
      endfunction
    endpackage
    
    package vip_pkg;
      import bus_pkg::*;
      export bus_pkg::byte_txn_t;   // re-publish by name: importers of vip_pkg see these
      export bus_pkg::parity_of;
    endpackage
    
    module tb_export;
    
      timeunit 1ns; timeprecision 1ps;
      import vip_pkg::*;
      byte_txn_t t;
      int errors = 0;
      initial begin
        t.data = 8'hF0; #1;
        if (vip_pkg::parity_of(t) !== ^t.data) errors++;
        if (t.data !== 8'hF0) errors++;
        if (errors != 0) $fatal(1, "FAIL: umbrella package did not re-publish the inner names");
        $display("PASS: umbrella package re-publishes the inner package names");
        $finish;
      end
    endmodule
    Terminal
    PASS: umbrella package re-publishes the inner package names

    Common mistakes

    Common pitfall

    Defining the same struct in two scopes by textual inclusion and expecting type compatibility. They are distinct types to the checker, even when every field matches. One package typedef is the only fix.

    Interview

    Likely questions: “package or include for shared types”, “what does a wildcard import actually bind”, and “what is export for”.

    • Package or include: compiled namespace with type identity against textual paste without it.

    • Wildcard import: binds nothing until use, and locals shadow it silently.

    • Export: re-publishes imported names so one umbrella package serves a whole VIP; a wildcard export carries only names the umbrella actually used.

    Check yourself Enum type cmd_e defines a member called IDLE. After import bus_pkg::*, what else becomes visible besides cmd_e?

    The member names READ, WRITE and IDLE become candidates too. An explicit import bus_pkg::cmd_e does not bring the literals: use bus_pkg::READ or import them by name. Unprefixed member names are a classic collision source, covered in the hygiene lesson.

    Check yourself A package imports another package but forgets export. What do downstream importers see?

    Only the outer package's own names. Imported names stop at the package boundary unless export passes them through.

    Project step

    Start pkt_pkg, the package every later PacketFIFO step imports: the kind_e enum and pkt_hdr_t struct from Data Types, MAX_LEN (8), a len_ok function, and a check_of function that XORs a queue of bytes. Check it on the Part 0 packet.

    One way to write it:

    design.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
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    package pkt_pkg;
      localparam int MAX_LEN = 8;                        // payload bytes per packet, 1 to 8
    
      typedef enum logic [1:0] {DATA = 2'd0, CTRL = 2'd1, ERR = 2'd2} kind_e;
    
      typedef struct packed {
        kind_e      kind;
        logic [1:0] dest;
        logic [3:0] len;
      } pkt_hdr_t;
    
      function automatic bit len_ok(pkt_hdr_t h);
        return h.len != 4'd0 && h.len <= 4'(MAX_LEN);
      endfunction
    
      // Check byte: the XOR of the header and every payload byte.
      function automatic logic [7:0] check_of(logic [7:0] bytes [$]);
        logic [7:0] c = 8'h00;
        foreach (bytes[i]) c ^= bytes[i];
        return c;
      endfunction
    endpackage
    
    module tb_pkt_pkg;
      import pkt_pkg::*;
      pkt_hdr_t   h;
      logic [7:0] bytes [$];
      int errors = 0;
      initial begin
        h.kind = DATA; h.dest = 2'd2; h.len = 4'd2;
        bytes = {h, 8'hA5, 8'h3C};
        $display("header %h, check byte %h", h, check_of(bytes));
        if (h !== 8'h22 || check_of(bytes) !== 8'hBB) errors++;
        if (!len_ok(h)) errors++;
        h.len = 4'd0;
        if (len_ok(h)) errors++;                         // len 0 is illegal
        h.len = 4'd9;
        if (len_ok(h)) errors++;                         // so is anything above 8
        if (errors == 0) $display("PASS: pkt_pkg gives header 22, check byte BB, and rejects len 0 and 9");
        else $fatal(1, "FAIL: %0d pkt_pkg checks disagreed", errors);
        $finish;
      end
    endmodule
    Terminal
    header 22, check byte bb
    PASS: pkt_pkg gives header 22, check byte BB, and rejects len 0 and 9

    Remember

    • A package holds declarations only: types, parameters, functions, tasks, classes.

    • pkg::name touches one use; import pkg::name binds one name; import pkg::* offers every name.

    • A local declaration silently beats a wildcard-imported name.

    Rule of thumb: a type shared by two files lives in one package.

    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 cannot run the export bench or the pkt_pkg bench (it stops on the queue argument); the three-styles bench runs on both.

    Related topics

    • Next: Compilation Units & Compile Order

    Previous

    30 / 363

    Next: Compilation Units & Compile Order

    Up next

    Up nextCompilation Units & Compile Order

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • typedef, parameter & localparam
    • `include vs import
    • Namespace Hygiene at Scale