Skip to content

UVM

Package Structure & Compilation

Standard VIP layout, multi-agent directory structure, eliminating circular dependencies.

UVM Fundamentals · Module 3

Why Compilation Order Matters

The SystemVerilog compiler processes files in the order you supply them. When it encounters a class name it has not yet seen, it throws an error and stops. This makes compilation order a hard constraint, not a preference.

In a single-file testbench, the constraint is obvious — put uvm_pkg first, then your code. In a production project with multiple agents, a shared environment, a register model, and dozens of test files, wrong compilation order produces failures that look like this:

Simulator output — typical compilation order errors
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
** Error: apb_driver.sv(12): Identifier 'apb_seq_item' has not been declared.
** Error: my_env.sv(8):  Identifier 'apb_agent_pkg' is not a package.
** Error: my_env.sv(31): 'apb_agent' has not been declared.
** Error: test_pkg.sv(4): Package 'my_env_pkg' is not defined.
   Compilation aborted after 4 error(s).
 
// Root cause: files were compiled in alphabetical order.
// apb_driver.sv compiled before apb_seq_item.sv was available.
// my_env.sv compiled before apb_agent_pkg.sv was imported.

Every one of these errors would be fixed by compiling files in the correct order. None of them indicate a bug in the code itself. Understanding package structure eliminates this entire class of errors permanently.

SystemVerilog Packages — A Focused Refresher

A SystemVerilog package is a named compilation unit. Everything declared inside it — classes, typedefs, functions, parameters — lives in that package's namespace. To use those names elsewhere, you import the package.

SystemVerilog — Package declaration and import
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── apb_agent_pkg.sv ──────────────────────────────────────────
package apb_agent_pkg;
    `include "uvm_macros.svh"
    import uvm_pkg::*;       // everything in uvm_pkg is now visible inside this package
 
    `include "apb_seq_item.sv"   // classes are `include'd, not compiled separately
    `include "apb_sequence.sv"
    `include "apb_driver.sv"
    `include "apb_monitor.sv"
    `include "apb_agent.sv"
endpackage
 
// ── my_env_pkg.sv — importing the agent package ───────────────
package my_env_pkg;
    `include "uvm_macros.svh"
    import uvm_pkg::*;
    import apb_agent_pkg::*;  // apb_seq_item, apb_driver etc. now visible here
 
    `include "my_scoreboard.sv"
    `include "my_env.sv"
endpackage

What Belongs Inside a Package

Classes, typedef, parameters, functions, tasks, enums, and structs. Everything that defines types and behaviour.

What Does NOT Belong Inside

interface declarations, module instantiations, and clock/reset generators. These are module-scope constructs — they compile outside any package.

Wildcard vs. Explicit Import

import pkg::* imports all names (standard in UVM). import pkg::my_class imports one name. For UVM packages, wildcard import is the convention — there is no ambiguity risk.

The Standard VIP Package Layout

Every production VIP at Tier-1 companies follows a consistent internal layout. It has one package file that acts as a wrapper, individual class files that are ``include`d into it, and one interface file that sits outside the package entirely.

The Three Layers of a VIP Directory

Shell — APB VIP directory layout
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
apb_agent/
├── apb_if.sv Interface file compiled as a standalone SV module, NOT inside any package
├── apb_agent_pkg.sv Package wrapper the ONLY file the compiler is given directly
├── apb_seq_item.sv `include'd by apb_agent_pkg.sv (not compiled directly)
├── apb_sequence.sv       ← `include'd by apb_agent_pkg.sv
├── apb_driver.sv         ← `include'd by apb_agent_pkg.sv
├── apb_monitor.sv        ← `include'd by apb_agent_pkg.sv
└── apb_agent.sv `include'd by apb_agent_pkg.sv
LayerFileCompiled ByKey Rule
Interfaceapb_if.svDirect — given to compiler as-isMust be compiled before any package that uses virtual apb_if as a type
Package wrapperapb_agent_pkg.svDirect — given to compilerImports uvm_pkg, then ``include`s all class files in dependency order
Class filesapb_driver.sv etc.Indirect — via ``include` inside the packageNever compile these directly. They have no package keyword — they rely on the wrapper's context

Inside the Package Wrapper

The package wrapper contains only two things: imports from dependencies, and include`s for every class file. The includeorder within the package must also respect dependencies —apb_seq_item.svmust come beforeapb_driver.sv` because the driver references the seq_item type.

SystemVerilog — Complete apb_agent_pkg.sv
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// apb_agent_pkg.sv — the package wrapper
package apb_agent_pkg;
 
    // ── Step 1: Pull in UVM ────────────────────────────────────
    `include "uvm_macros.svh"
    import uvm_pkg::*;
 
    // ── Step 2: Include class files in dependency order ────────
    // seq_item first — driver and monitor both reference this type
    `include "apb_seq_item.sv"    // extends uvm_sequence_item
    `include "apb_sequence.sv"    // extends uvm_sequence #(apb_seq_item)
    `include "apb_driver.sv"     // extends uvm_driver   #(apb_seq_item)
    `include "apb_monitor.sv"    // extends uvm_monitor
    `include "apb_agent.sv"      // extends uvm_agent — references all of the above
 
endpackage

Compilation Order — The Five Rules

These five rules cover every project structure you will encounter. Follow them in order and compilation errors disappear.

UVM compilation order flow diagram
UVM compilation order flow diagram

Figure 1 — UVM project compilation order. Each stage can only reference types declared in stages to its left. Reversing any two stages causes "identifier not declared" errors.

  • 01 Compile uvm_pkg first — always, without exception Every UVM class inherits from something in uvm_pkg. Nothing in your project can compile until uvm_pkg is loaded. Most simulators load it automatically, but make it explicit in your compile script.
  • 02 Compile interface files before any package that uses them as types Interfaces are not packages — they compile in module scope. If apb_driver.sv has a virtual apb_if member, the compiler must have seen apb_if before it compiles apb_agent_pkg.
  • 03 Compile agent packages in dependency order — leaf packages first If your AXI agent uses types from a common bus package, compile the common bus package before the AXI agent package. Map the dependency graph and always compile the deepest node first.
  • 04 Compile the environment package after all agent packages it imports my_env_pkg imports apb_agent_pkg and axi_agent_pkg. Both must be fully compiled before my_env_pkg is given to the compiler.
  • 05 Compile the top module last The top module (tb_top.sv) imports test packages, instantiates interfaces, and connects to the DUT. It references every other construct in the project — so it compiles last.

Multi-Agent Project Directory Structure

A project with two protocol agents (APB and AHB), a shared environment, a register model, and a test suite follows this canonical layout. This structure is used — with minor naming variations — at every major semiconductor company.

Shell — Production UVM project directory layout
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
tb/
├── agents/
│   ├── apb_agent/
│   │   ├── apb_if.svcompiled as interface, NOT inside package
│   │   ├── apb_agent_pkg.svpackage wrapper, `includes all classes below
│   │   ├── apb_seq_item.sv
│   │   ├── apb_sequence.sv
│   │   ├── apb_driver.sv
│   │   ├── apb_monitor.sv
│   │   └── apb_agent.sv
│   └── ahb_agent/
│       ├── ahb_if.sv
│       ├── ahb_agent_pkg.sv
│       └── ...

├── env/
│   ├── my_env_pkg.svimports uvm_pkg + apb_agent_pkg + ahb_agent_pkg
│   ├── my_scoreboard.sv           ← `include'd by my_env_pkg.sv
│   ├── my_coverage.sv
│   └── my_env.sv

├── reg_model/
│   ├── reg_model_pkg.svimports uvm_pkg only
│   └── dut_reg_block.sv

├── tests/
│   ├── test_pkg.svimports my_env_pkg + reg_model_pkg + uvm_pkg
│   ├── base_test.sv
│   ├── smoke_test.sv
│   └── regression_test.sv

└── tb_top.svtop module: imports test_pkg, instantiates DUT + interfaces

Circular Dependencies — The Deadlock and How to Break It

A circular dependency occurs when Package A imports Package B, and Package B also imports Package A. The compiler cannot resolve this — it needs A to compile B, and B to compile A. It is an unbreakable deadlock.

SystemVerilog — Circular dependency (breaks compilation)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// PROBLEM — This causes a compilation deadlock:
 
package apb_agent_pkg;
    import my_env_pkg::*;    // ← references my_env_pkg
    // ...
endpackage
 
package my_env_pkg;
    import apb_agent_pkg::*;  // ← references apb_agent_pkg
    // ...                     // Deadlock: each needs the other compiled first
endpackage

Fix 1: Shared Types Package

Extract the shared type into a third package that both can import. This is the cleanest long-term solution and is used in most large projects.

SystemVerilog — Shared types package breaks the cycle
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── Step 1: Extract shared types into a leaf package ──────────
package apb_types_pkg;
    import uvm_pkg::*;
    `include "apb_seq_item.sv"   // the shared type that both packages needed
endpackage
 
// ── Step 2: Both packages import the shared types package ──────
package apb_agent_pkg;
    import uvm_pkg::*;
    import apb_types_pkg::*;   // apb_seq_item now available
    // ... driver, monitor, agent
endpackage
 
package my_env_pkg;
    import uvm_pkg::*;
    import apb_types_pkg::*;   // apb_seq_item now available here too
    import apb_agent_pkg::*;   // no circular dependency — apb_agent_pkg does NOT import my_env_pkg
    // ... scoreboard, env
endpackage
 
// Compile order: apb_types_pkg → apb_agent_pkg → my_env_pkg ✓

Fix 2: typedef class (Forward Declaration)

When you only need to declare a handle of a type (not access its members), a forward declaration removes the need for the full package import.

SystemVerilog — Forward declaration with typedef class
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// If pkg_a only needs a handle of a type from pkg_b (not the full type):
package pkg_a;
    import uvm_pkg::*;
 
    // Forward-declare the class — no import of pkg_b needed
    typedef class my_scoreboard;   // tells compiler "this type exists"
 
    class my_agent extends uvm_agent;
        my_scoreboard scb_h;   // handle OK — we declared it above
        // scb_h.check() would FAIL — we cannot access members of a forward-declared type
    endclass
endpackage
 
// Use typedef class when:
// - You only store a handle (no method calls on it in this package)
// - The full type will be connected later (in connect_phase or from outside)

Include Guards — Preventing Double-Inclusion

When two package wrappers both ``include` the same file, the file gets compiled twice. In the best case, you get a "redefiniton" error. In the worst case — when the file has no declarations, just macros — the double-inclusion silently changes behaviour. Include guards prevent this.

SystemVerilog — Include guard pattern
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// apb_seq_item.sv — every class file should start with a guard
 
`ifndef APB_SEQ_ITEM_SV     // if this macro is NOT defined yet...
`define APB_SEQ_ITEM_SV     // ...define it and process the file content
 
class apb_seq_item extends uvm_sequence_item;
    `uvm_object_utils(apb_seq_item)
    rand bit [31:0] addr;
    rand bit [7:0]  data;
    rand bit         write;
    function new(string name = "apb_seq_item");
        super.new(name);
    endfunction
endclass
 
`endif   // APB_SEQ_ITEM_SV
 
// If `include "apb_seq_item.sv" appears a second time anywhere,
// APB_SEQ_ITEM_SV is already defined, so the `ifndef block is skipped entirely.
// The class is NOT redeclared. No error.
Guard Macro Naming ConventionExample
Class filesAPB_SEQ_ITEM_SV (filename in uppercase, dots → underscores)
Package filesAPB_AGENT_PKG_SV
Interface filesAPB_IF_SV
Macro header filesUVM_MACROS_SVH (note: already guarded inside uvm_macros.svh)

Quick Reference — A Real Compilation Script

This is the compile command sequence for a two-agent project on a typical simulator. Adapt the flags for your tool (vlog for Questa, vcs for VCS, xvlog for Xcelium).

Makefile — Compile script for a two-agent UVM project (Questa/VCS style)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
# compile.sh — run in order, top to bottom
 
# ── STEP 1: UVM library (simulator usually handles this automatically) ──
# vlog -sv -work work $UVM_HOME/src/uvm_pkg.sv
 
# ── STEP 2: SystemVerilog interfaces ─────────────────────────────────────
vlog -sv tb/agents/apb_agent/apb_if.sv
vlog -sv tb/agents/ahb_agent/ahb_if.sv
vlog -sv tb/dut/dut_if.sv         # DUT interface if separate
 
# ── STEP 3: Agent packages ───────────────────────────────────────────────
# Supply the package wrapper — NOT the individual class files
vlog -sv tb/agents/apb_agent/apb_agent_pkg.sv
vlog -sv tb/agents/ahb_agent/ahb_agent_pkg.sv
 
# ── STEP 4: Register model package ──────────────────────────────────────
vlog -sv tb/reg_model/reg_model_pkg.sv
 
# ── STEP 5: Environment package ─────────────────────────────────────────
# Imports uvm_pkg + apb_agent_pkg + ahb_agent_pkg + reg_model_pkg
vlog -sv tb/env/my_env_pkg.sv
 
# ── STEP 6: Test package ─────────────────────────────────────────────────
# Imports uvm_pkg + my_env_pkg
vlog -sv tb/tests/test_pkg.sv
 
# ── STEP 7: DUT RTL ──────────────────────────────────────────────────────
vlog -sv rtl/my_dut.sv
 
# ── STEP 8: Top module (very last) ──────────────────────────────────────
# Imports test_pkg, instantiates DUT and interfaces, calls run_test()
vlog -sv tb/tb_top.sv
 
# ── Simulate ─────────────────────────────────────────────────────────────
vsim -sv_seed random -GEN_SEED=0 \
    +UVM_TESTNAME=smoke_test \
    +UVM_VERBOSITY=UVM_MEDIUM \
    work.tb_top
SystemVerilog — Package dependency summary (all at a glance)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── apb_agent_pkg.sv ──────────────────────────────────────────
package apb_agent_pkg;
    `include "uvm_macros.svh"
    import uvm_pkg::*;
    `include "apb_seq_item.sv"  // seq_item before driver
    `include "apb_sequence.sv"
    `include "apb_driver.sv"
    `include "apb_monitor.sv"
    `include "apb_agent.sv"     // agent last — references all above
endpackage
 
// ── my_env_pkg.sv ─────────────────────────────────────────────
package my_env_pkg;
    `include "uvm_macros.svh"
    import uvm_pkg::*;
    import apb_agent_pkg::*;  // provides apb_seq_item, apb_agent etc.
    `include "my_scoreboard.sv"
    `include "my_env.sv"        // env last — creates agents, scoreboard
endpackage
 
// ── test_pkg.sv ───────────────────────────────────────────────
package test_pkg;
    `include "uvm_macros.svh"
    import uvm_pkg::*;
    import my_env_pkg::*;    // everything from env and agent packages cascades in
    `include "base_test.sv"
    `include "smoke_test.sv"
endpackage
 
// ── tb_top.sv — NOT a package ─────────────────────────────────
module tb_top;
    `include "uvm_macros.svh"
    import uvm_pkg::*;
    import test_pkg::*;
 
    apb_if    apb_vif();              // interface instances
    my_dut    dut(.apb(apb_vif));     // DUT connection
 
    initial begin
        uvm_config_db#(virtual apb_if)::set(null, "uvm_test_top.*", "vif", apb_vif);
        run_test();  // test name from +UVM_TESTNAME plusarg
    end
endmodule

Code Examples — Building a Real Package Structure

Abstract rules about compilation order become real the moment you try to compile a 12-file VIP with circular dependencies and watch the simulator throw "identifier not found" on the third file. These examples show the exact patterns production teams use — and the ones that silently break.

Example 1 — Beginner: Minimal Compilable VIP Package

SystemVerilog — Beginner: Minimal Three-File VIP Package
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// File 1: apb_txn.sv — Transaction (no UVM dependency other than uvm_pkg)
// Compile first — everything else depends on it
class apb_txn extends uvm_sequence_item;
    `uvm_object_utils(apb_txn)
    rand bit[31:0] addr;
    rand bit[31:0] data;
    rand bit        write;
    function new(string name="apb_txn"); super.new(name); endfunction
endclass
 
// File 2: apb_driver.sv — Driver depends on apb_txn
// Must compile after apb_txn.sv
class apb_driver extends uvm_driver#(apb_txn);
    `uvm_component_utils(apb_driver)
    function new(string n, uvm_component p); super.new(n,p); endfunction
    task run_phase(uvm_phase phase);
        apb_txn txn;
        forever begin
            seq_item_port.get_next_item(txn);
            `uvm_info("DRV",$sformatf("Driving %s",txn.convert2string()),UVM_MEDIUM)
            seq_item_port.item_done();
        end
    endtask
endclass
 
// File 3: apb_pkg.sv — Package wrapper (compile LAST, after all files above)
package apb_pkg;
    `include "uvm_macros.svh"
    import uvm_pkg::*;
    `include "apb_txn.sv"       // ← transaction first
    `include "apb_driver.sv"    // ← driver second (depends on txn)
endpackage
 
// Compile command (Questa):
// vlog -sv -L uvm_1_2 apb_pkg.sv
// ↑ One command. The package handles the include order internally.

Example 2 — Intermediate: Multi-Package Environment with Dependencies

SystemVerilog — Intermediate: Layered Package Dependencies
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── Layer 1: VIP package (no env dependencies) ────────────────────
package apb_vip_pkg;
    `include "uvm_macros.svh"
    import uvm_pkg::*;
    `include "apb_txn.sv"
    `include "apb_seq_lib.sv"
    `include "apb_driver.sv"
    `include "apb_monitor.sv"
    `include "apb_agent.sv"
endpackage
 
// ── Layer 2: Scoreboard package (depends on VIP for transaction type) ─
package apb_sb_pkg;
    `include "uvm_macros.svh"
    import uvm_pkg::*;
    import apb_vip_pkg::*;    // ← depends on VIP for apb_txn
    `include "apb_scoreboard.sv"
endpackage
 
// ── Layer 3: Environment package (depends on both above) ──────────────
package apb_env_pkg;
    `include "uvm_macros.svh"
    import uvm_pkg::*;
    import apb_vip_pkg::*;
    import apb_sb_pkg::*;
    `include "apb_env.sv"
    `include "apb_base_test.sv"
endpackage
 
// ── Makefile compile order (each layer compiled independently) ─────────
// make:
//   vlog -sv -L uvm_1_2 apb_vip_pkg.sv   # Layer 1
//   vlog -sv -L uvm_1_2 apb_sb_pkg.sv    # Layer 2 (imports VIP)
//   vlog -sv -L uvm_1_2 apb_env_pkg.sv   # Layer 3 (imports both)
//   vlog -sv -L uvm_1_2 tb_top.sv        # Top module
// ↑ Reverse this order and you get "identifier not found" errors

Example 3 — Verification: Compile Script with Dependency Checking

Shell/Make — Production Compile Script with Explicit Order
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
# Makefile — production compile script for SoC APB testbench
# Demonstrates explicit dependency tracking
 
# Tool settings
VLOG    = vlog -sv -timescale 1ns/1ps
UVM_LIB = -L uvm_1_2
VLOG_OPTS = $(UVM_LIB) +define+UVM_NO_DEPRECATED
 
# Step 1: UVM (provided by simulator — always first)
# Step 2: APB VIP package (no env dependencies)
apb_vip.done: src/vip/apb_pkg.sv
	$(VLOG) $(VLOG_OPTS) $< && touch $@
 
# Step 3: Scoreboard (needs VIP for transaction type)
apb_sb.done: src/env/apb_sb_pkg.sv apb_vip.done
	$(VLOG) $(VLOG_OPTS) $< && touch $@
 
# Step 4: Environment (needs VIP + scoreboard)
apb_env.done: src/env/apb_env_pkg.sv apb_vip.done apb_sb.done
	$(VLOG) $(VLOG_OPTS) $< && touch $@
 
# Step 5: Tests (need everything above)
tests.done: src/tests/test_pkg.sv apb_env.done
	$(VLOG) $(VLOG_OPTS) $< && touch $@
 
# Step 6: Top module (instantiates DUT + interface)
tb.done: tb/tb_top.sv tests.done
	$(VLOG) $(VLOG_OPTS) $< && touch $@
 
compile: tb.done
	@echo "Compilation complete"
 
clean:
	rm -f *.done work/_* && vdel -all

Example 4 — Tricky Corner Case: Circular Dependency Broken With typedef

SystemVerilog — Tricky: Breaking Circular Deps with typedef class
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── Problem: Driver needs Sequencer, Sequencer needs Driver type ───
// This creates a circular dependency that breaks compilation
 
// ── Solution 1: Forward declaration with typedef class ─────────────
package apb_pkg;
    import uvm_pkg::*;
    `include "uvm_macros.svh"
 
    // Forward declare — tells compiler "this class exists" without full definition
    typedef class apb_driver;
 
    class apb_sequencer extends uvm_sequencer#(apb_txn);
        `uvm_component_utils(apb_sequencer)
        apb_driver driver_h;   // ← OK: forward decl allows handle declaration
        function new(string n, uvm_component p); super.new(n,p); endfunction
    endclass
 
    class apb_driver extends uvm_driver#(apb_txn);
        `uvm_component_utils(apb_driver)
        apb_sequencer seqr_h;  // ← full definition now available
        function new(string n, uvm_component p); super.new(n,p); endfunction
    endclass
endpackage
 
// ── Solution 2 (preferred): Restructure to eliminate the coupling ──
// Sequencer should not hold a handle to the driver.
// Pass shared data through config_db or a shared config object.
// Circular deps in design = architectural problem, not a compile problem.

Common Bugs and Debugging — Package Compilation Failures That Waste Days

Package and compilation order bugs are uniquely frustrating because the error messages are often misleading — the compiler points at the wrong line, or gives a generic "identifier not found" with no hint about which include was missing. These are the exact patterns to recognize.

⚠️ Bug 1 — Including a File Directly Instead of Through Its Package

Symptom: The class compiles in isolation but type_id::create() silently returns null at runtime. Factory overrides don't work. get_type_name() returns "uvm_component" instead of your class name.

Root cause: When you include` a class file directly in tb_top.sv without going through the package, the class is compiled in a different scope. The factory registration (from uvm_component_utils`) runs in the wrong scope and the lookup fails at runtime.

SystemVerilog — Bug 1: Direct Include vs Package
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ❌ WRONG — including class files directly in top module
module tb_top;
    import uvm_pkg::*;
    `include "uvm_macros.svh"
    `include "apb_txn.sv"      // ← compiled in module scope
    `include "apb_driver.sv"   // ← also module scope
    // Factory registration happens in module scope, not package scope
    // Result: type_id::create() silently returns null
    initial run_test("apb_test");
endmodule
 
// ✓ CORRECT — compile through the package, import in top module
// apb_pkg.sv compiled first (via Makefile/filelist)
module tb_top;
    import uvm_pkg::*;
    import apb_pkg::*;          // ← bring package into scope
    `include "uvm_macros.svh"
    initial run_test("apb_test");
endmodule
 
// Debug check: if get_type_name() returns "uvm_component" or "uvm_sequence_item"
// instead of your class name → you have a scope/include order problem

⚠️ Bug 2 — Missing Include Guard Causes Double-Definition Errors

Symptom: Compiler error: "class apb_txn already declared in this scope" or "redefinition of apb_txn". Happens when two packages both include the same file, or a file is included both directly and through a package.

Root cause: No ifndef` guard around the class definition. Every include` re-inserts the full text, causing duplicate declarations.

SystemVerilog — Bug 2: Missing Include Guard
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ❌ WRONG — apb_txn.sv without include guard
class apb_txn extends uvm_sequence_item;
    // ... class body
endclass
// If two packages both `include "apb_txn.sv" → compiler error
 
// ✓ CORRECT — every file must have an include guard
`ifndef APB_TXN_SV
`define APB_TXN_SV
 
class apb_txn extends uvm_sequence_item;
    `uvm_object_utils(apb_txn)
    rand bit[31:0] addr;
    rand bit[31:0] data;
    rand bit        write;
    function new(string name="apb_txn"); super.new(name); endfunction
endclass
 
`endif // APB_TXN_SV
// Now safe to include from multiple locations — second include is a no-op

🔍 Debug Insight — Diagnosing Compilation Order Failures

  • Error "identifier 'apb_txn' not found" at line 3 of apb_driver.sv → apb_txn compiled after apb_driver. Fix: move apb_txn.sv earlier in filelist or `include earlier in the package.
  • Error "class apb_agent already defined" → missing include guard on apb_agent.sv. Two packages or two filelists both including it.
  • Runtime: type_id::create() returns null → class included in wrong scope (module vs package). Move to package.
  • get_type_name() returns base class name → uvm_component_utils or uvm_object_utils macro missing from the class.

Ready-to-Run: Complete Package Structure Demo

This is a complete, multi-file project demonstrating correct package structure. Every file has an include guard. The package respects compilation order. The top module imports correctly. You can compile and simulate this as-is.

SystemVerilog — pkg_demo.sv (Single-File Demo, All-in-One)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ================================================================
// pkg_demo.sv — Package Structure Demonstration
// Single file for easy testing; real projects split into multiple files
//
// Compile (Questa):
//   vlog -sv -L uvm_1_2 pkg_demo.sv
//   vsim -c -L uvm_1_2 pkg_demo_top -do "run -all; quit"
//
// Compile (VCS):
//   vcs -sverilog -ntb_opts uvm-1.2 pkg_demo.sv && ./simv
//
// Compile (Xcelium):
//   xrun -sv -uvm pkg_demo.sv
// ================================================================
 
`include "uvm_macros.svh"
import uvm_pkg::*;
 
// ── Simulating apb_txn.sv content (with include guard) ────────────
`ifndef APB_TXN_SV
`define APB_TXN_SV
class apb_txn extends uvm_sequence_item;
    `uvm_object_utils_begin(apb_txn)
        `uvm_field_int(addr, UVM_ALL_ON)
        `uvm_field_int(data, UVM_ALL_ON)
        `uvm_field_int(write, UVM_ALL_ON)
    `uvm_object_utils_end
    rand bit[31:0] addr;
    rand bit[31:0] data;
    rand bit        write;
    constraint c_aligned { addr[1:0] == 2'b00; }
    function new(string name="apb_txn"); super.new(name); endfunction
    function string convert2string();
        return $sformatf("%s addr=%0h data=%0h", write?"WR":"RD", addr, data);
    endfunction
endclass
`endif
 
// ── Simulating apb_seq.sv content ─────────────────────────────────
`ifndef APB_SEQ_SV
`define APB_SEQ_SV
class apb_write_seq extends uvm_sequence#(apb_txn);
    `uvm_object_utils(apb_write_seq)
    int num_txns = 3;
    function new(string n="apb_write_seq"); super.new(n); endfunction
    task body();
        apb_txn txn;
        repeat(num_txns) begin
            txn = apb_txn::type_id::create("txn");
            start_item(txn);
            void'(txn.randomize() with {write==1;});
            finish_item(txn);
        end
    endtask
endclass
`endif
 
// ── Simulating apb_driver.sv content ──────────────────────────────
`ifndef APB_DRV_SV
`define APB_DRV_SV
class apb_driver extends uvm_driver#(apb_txn);
    `uvm_component_utils(apb_driver)
    function new(string n, uvm_component p); super.new(n,p); endfunction
    task run_phase(uvm_phase phase);
        apb_txn txn;
        forever begin
            seq_item_port.get_next_item(txn);
            `uvm_info("DRV",txn.convert2string(),UVM_LOW)
            #5ns;
            seq_item_port.item_done();
        end
    endtask
endclass
`endif
 
// ── Test: uses all classes above ───────────────────────────────────
class pkg_demo_test extends uvm_test;
    `uvm_component_utils(pkg_demo_test)
    apb_driver                   drv;
    uvm_sequencer#(apb_txn)     seqr;
    function new(string n, uvm_component p); super.new(n,p); endfunction
 
    function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        drv  = apb_driver::type_id::create("drv",  this);
        seqr = uvm_sequencer#(apb_txn)::type_id::create("seqr", this);
        `uvm_info("TEST", $sformatf("driver type: %s", drv.get_type_name()), UVM_NONE)
    endfunction
 
    function void connect_phase(uvm_phase phase);
        super.connect_phase(phase);
        drv.seq_item_port.connect(seqr.seq_item_export);
    endfunction
 
    task run_phase(uvm_phase phase);
        apb_write_seq seq;
        phase.raise_objection(this);
        seq = apb_write_seq::type_id::create("seq");
        seq.num_txns = 3;
        seq.start(seqr);
        phase.drop_objection(this);
    endtask
endclass
 
module pkg_demo_top;
    initial run_test("pkg_demo_test");
endmodule
 
// Expected Output:
// UVM_INFO: driver type: apb_driver   ← factory resolved correctly
// UVM_INFO [DRV] WR addr=8c data=f3a2b1c0
// UVM_INFO [DRV] WR addr=14 data=726a1eb3
// UVM_INFO [DRV] WR addr=20 data=9d4f2380
// UVM_INFO: UVM_ERROR: 0  UVM_FATAL: 0  → PASS

The Dependency Graph Is the Compile Order

Compile order is not a convention to memorise. It is a topological sort of the dependency graph, and once you can draw the graph the order is the only thing it can be.

Dependency graph for a two-agent UVM testbench showing interfaces, agent packages, register model, environment package, test package and top module, with arrows indicating compile dependenciesuvm_pkgsupplied by the simulatorInterfacesapb_if.sv · ahb_if.sv — compileFIRSTapb_agent_pkgno env dependenciesahb_agent_pkgno env dependenciesreg_model_pkgindependent of agentsmy_env_pkgimports both agents + regmodeltest_pkgimports the environmenttb_topinstantiates DUT — compileLASTvif typeuvm_pkgseq_itemreg blockmy_envtest_pkg12
Figure — the dependency DAG for a two-agent testbench, and the compile order it forces. An arrow means provides to: the package at the head cannot compile until the package at the tail exists in the work library. Interfaces come first because packages name them as virtual interface types; the top module comes last because it depends on everything. Any valid compile order is a topological sort of this graph, which is why nothing has to be memorised once the graph is drawn.

Two properties of the graph do all the work.

It must be acyclic. A cycle is not a difficult ordering — it is the absence of any ordering, which is why the circular-dependency section above is about restructuring rather than about finding the right sequence.

Arrows point from provider to consumer, and compilation follows the arrows. Interfaces sit at the source because packages name them as virtual apb_if types, and a virtual interface type must exist before a package that declares one can elaborate. The top module sits at the sink because it depends on everything.

Executable Failure — Four Compile Errors and What the Compiler Actually Saw

Compile errors in UVM package structures are famously unhelpful, because the message names the symptom line and the cause is usually in a different file. Each case below is reproducible, and the point is the gap between the two.

SystemVerilog — case 1: macro file not included
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// apb_agent_pkg.sv
package apb_agent_pkg;
    import uvm_pkg::*;              // ← uvm_pkg imported, macros NOT included
    `include "apb_seq_item.sv"
endpackage
 
// apb_seq_item.sv
class apb_seq_item extends uvm_sequence_item;
    `uvm_object_utils(apb_seq_item)   // ← error reported HERE
    ...
endclass

Error: Unknown macro or compiler directive: 'uvm_object_utils'

The error names the class file; the cause is the missing `include "uvm_macros.svh" in the package file. import and `include are not alternativesimport uvm_pkg::* makes type names visible, while macros are text substitutions resolved by the preprocessor and require the header. Every UVM package needs both, and the include must come first.

SystemVerilog — case 2: class referenced before its package is imported
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// my_env_pkg.sv
package my_env_pkg;
    `include "uvm_macros.svh"
    import uvm_pkg::*;
    `include "my_scoreboard.sv"     // ← uses apb_seq_item
    import apb_agent_pkg::*;        // ← imported AFTER the include
endpackage

Error: 'apb_seq_item' is not declared

Imports and includes are processed in source order. The scoreboard's text is pulled in and compiled before the import that makes apb_seq_item visible. All imports go at the top of the package, before any `include — the ordering inside the wrapper is as real as the ordering of files on the compile line.

SystemVerilog — case 3: the same .svh pulled into two packages
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Both packages `include the same file:
package apb_agent_pkg;
    `include "common_types.svh"     // defines apb_mode_e
endpackage
 
package apb_test_pkg;
    `include "common_types.svh"     // the SAME text, compiled again
    import apb_agent_pkg::*;        // now two distinct apb_mode_e types exist
endpackage

Error: Type 'apb_mode_e' is ambiguous — or a type mismatch on an apparently identical assignment

This one is worse than a plain error, because it sometimes compiles. `include copies text; a type defined inside two packages is two different types with the same name. An include guard does not help — guards prevent double inclusion within one compilation unit, not across separate packages. Shared types belong in one package that both others import.

SystemVerilog — case 4: a genuine cycle
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
package apb_agent_pkg;
    import my_env_pkg::*;      // agent needs the env's config type
endpackage
 
package my_env_pkg;
    import apb_agent_pkg::*;   // env needs the agent's transaction
endpackage

Error: Package 'my_env_pkg' not found — whichever one you compile first

No compile order exists. The fix is structural, not sequential: hoist the shared declarations into a third package both import, or break the direction so the agent never depends on the environment. An agent that needs to know about its environment is usually a design problem before it is a compile problem — it means the agent is not independently reusable, which was the reason for packaging it separately.

1

A VIP compiled standalone and failed in the integration build with an error in a file nobody had touched

PKG-INCLUDE-ORDER
Symptom

An APB VIP compiled cleanly in its own regression for months. When a second team pulled it into an SoC testbench, compilation failed with an unknown-macro error inside apb_driver.sv — a file neither team had modified.

The VIP owners could not reproduce it. The integrators could not compile without it.

Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// apb_agent_pkg.sv — as shipped
package apb_agent_pkg;
    import uvm_pkg::*;
    `include "apb_seq_item.sv"
    `include "apb_driver.sv"
endpackage

The package never included uvm_macros.svh.

Diagnostic Evidence

The two builds differed in one line of their compile scripts. The VIP's own regression compiled a local test file before the package:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
vlog -sv tb/apb_smoke_test.sv      # ← this file had `include "uvm_macros.svh"
vlog -sv tb/apb_agent_pkg.sv

The SoC build compiled the package first. Adding -v to the failing compile and comparing preprocessor output showed uvm_object_utils defined in one run and undefined in the other, with identical package source.

Root Cause

`include and `define operate on the compilation unit, and many simulators treat a set of files given on one command line as a single unit. The smoke test's include of uvm_macros.svh therefore leaked its definitions into the package that followed it.

The VIP had a latent missing include that its own build order accidentally satisfied. Nothing about the VIP was correct — it was compiling by coincidence, and the coincidence did not survive being integrated into a build with a different file order.

This is the general hazard with compilation-unit scope: a file's correctness can depend on what was compiled beside it, so a package can be simultaneously broken and passing.

Fix

Make every package self-sufficient. The first two lines of any UVM package wrapper, in this order:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
package apb_agent_pkg;
    `include "uvm_macros.svh"   // preprocessor: macro definitions
    import uvm_pkg::*;          // compiler: type names
    `include "apb_seq_item.sv"
    `include "apb_driver.sv"
endpackage

The guard against recurrence is a single-package compile check in CI: compile each package on its own command line, in isolation, with nothing else in the compilation unit. Any package that only builds in company has an accidental dependency, and this is the one test that finds it while it is still cheap.

It is worth being explicit about why both lines are needed and neither substitutes for the other. `include is preprocessor text substitution and supplies the macros; import is a compiler-visible namespace operation and supplies the type names. A package that imports without including gets types and no macros — exactly this failure.

2

Two packages agreed on a type name and the compiler refused to accept the assignment

PKG-DUPLICATE-TYPE
Symptom

A scoreboard assigned a mode enum received from the agent into its own variable of what appeared to be the same type. The compiler rejected it:

Error: Cannot assign 'apb_agent_pkg::apb_mode_e' to 'env_pkg::apb_mode_e'

Both names resolved to the same source file, and grep found exactly one definition of apb_mode_e in the repository.

Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// common_types.svh — ONE file, with a guard
`ifndef COMMON_TYPES_SVH
`define COMMON_TYPES_SVH
    typedef enum bit [1:0] { APB_IDLE, APB_SETUP, APB_ACCESS } apb_mode_e;
`endif
 
// apb_agent_pkg.sv
package apb_agent_pkg;
    `include "common_types.svh"
endpackage
 
// env_pkg.sv
package env_pkg;
    `include "common_types.svh"     // same text, second package
    import apb_agent_pkg::*;
endpackage
Diagnostic Evidence

The error message itself was the evidence, once it was read carefully: the two type names were package-qualified differently. apb_agent_pkg::apb_mode_e and env_pkg::apb_mode_e are distinct fully-qualified names, so the compiler was reporting exactly what was true.

The include guard was present and working. It had simply never been relevant.

Root Cause

`include copies text. Including the same file into two packages creates two independent declarations, each scoped to its own package, and SystemVerilog treats distinct type declarations as distinct types even when they are textually identical.

Include guards prevent a file being pulled twice into the same compilation unit. They have no visibility across separate packages, so the guard was defending against a different problem than the one occurring.

The team's mental model — "the guard means it is only defined once" — was the actual defect. The guard means it is only defined once per unit.

Fix

Give shared declarations one owning package and import it everywhere:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// apb_types_pkg.sv — the single owner
package apb_types_pkg;
    typedef enum bit [1:0] { APB_IDLE, APB_SETUP, APB_ACCESS } apb_mode_e;
endpackage
 
// Both consumers import rather than include
package apb_agent_pkg;
    import apb_types_pkg::*;
endpackage
 
package env_pkg;
    import apb_types_pkg::*;
    import apb_agent_pkg::*;
endpackage

Now one declaration exists and both packages refer to it, so the assignment is between a type and itself.

The rule this generalises to is worth stating as policy: `include is for class implementations that belong to exactly one package; import is for anything shared between packages. A .svh of shared typedefs, parameters or enums that is included in more than one place is a latent instance of this bug, whether or not it has surfaced yet — which makes "grep for .svh files included by two packages" a productive one-off audit on any established codebase.

Questions Engineers Actually Ask

Because classes must live inside a package, and a file compiled on its own command line is not inside anything.

apb_driver.sv has no package keyword — it is a class body, meant to be textually inserted into a package wrapper. Compiling it directly puts the class in the compilation-unit scope instead, where the rest of the package cannot see it and where it collides with anything else of the same name.

So the compile script only ever names package wrappers and interfaces, never individual class files. If your build lists class files, either they are not really package members or the build is producing something other than what the packaging intended.

They operate at different stages and neither substitutes for the other.

`include is a preprocessor directive: it copies file text in before the compiler sees it. That is how macro definitions arrive, which is why uvm_macros.svh must be included and cannot be imported.

import is a compiler operation on namespaces: it makes names that already exist in a compiled package visible in the current scope. It does not recompile anything and it does not bring in macros.

Every UVM package needs both, in this order: `include "uvm_macros.svh" for the macros, then import uvm_pkg::* for the types. Case 1 in the section above is what a missing include looks like from the error log.

Where This Is Specified

  • IEEE 1800-2023 §26 — Packages. Package declaration, import semantics, wildcard versus explicit import, and the scoping of names declared inside a package.
  • IEEE 1800-2023 §26.3 — Referencing data in packages. Package-qualified names, and why identical declarations in two packages are distinct types.
  • IEEE 1800-2023 §22.4 — `include. Preprocessor text substitution, and its distinction from namespace import.
  • IEEE 1800-2023 §3.12 — Compilation and elaboration; compilation units. The $unit scope, and the rule that makes `define and `include effects depend on which files share a compilation unit.
  • IEEE 1800-2023 §22.5 — `define. Macro definition scope, which is what allows a macro to leak from one file into another compiled beside it.
  • IEEE 1800.2-2020 (UVM) — uvm_pkg and uvm_macros.svh. The library package that supplies UVM types, and the separate header that supplies the macros.

Interview Questions — From Fresher to Principal

Beginner Level

  • Q1 What is the purpose of a UVM package and why is it important for compilation? Answer: A UVM package groups related class definitions into a named compilation unit. It's important because: (1) it creates a namespace that prevents class name collisions across multiple VIPs, (2) it allows the simulator to compile all related classes in one step with the correct internal ordering, and (3) it enables other packages to import the classes with a single import pkg_name::* statement rather than including individual files. Without packages, large projects quickly suffer from circular include dependencies and class redefinition errors.
  • Q2 What is an include guard and when do you need one? Answer: An include guard is a preprocessor pattern using ``ifndef / define / endif` that prevents a file from being compiled more than once. You need it on every single class file in your testbench — without exception. When two packages include the same file, or when a file is included both through a package and directly in a top module, the compiler sees the class definition twice and throws a "class already declared" error. Include guards are the standard fix.

Intermediate Level

  • Q3 Explain the compilation order for a VIP with transaction, sequence, driver, monitor, and agent classes. Answer: The rule is: compile dependencies before dependents. Correct order: (1) Transaction — no class dependencies beyond uvm_sequence_item; (2) Sequence — depends on transaction; (3) Driver — depends on transaction; (4) Monitor — depends on transaction; (5) Agent — depends on driver, monitor, and sequencer. Inside the package, `include files in this exact order. Reversing any step gives "identifier not found" errors pointing at the wrong line.
  • Q4 **What is the difference between include inside a package and import package::*?** **Answer:** ``include inside a package physically inserts the file's contents at that location during preprocessing — the included class becomes part of the package's namespace. import package::* makes names from an already-compiled package visible in the current scope without including any source code. include` is for building the package; `import` is for using it. A common mistake is using includewhereimport` should be used, which leads to double-definition errors.

Senior / Principal Level

  • Q5 Your scoreboard needs the DUT's register model (which is in a separate package), but the register model also uses your scoreboard's reference model class for expected value prediction. How do you resolve this circular dependency? Answer: This is a real architectural problem seen on complex SoC projects. Solutions in order of preference: (1) Refactor — pull the shared "reference model" class into a third package that both the scoreboard and register model packages import. (2) Interface/virtual class — define a virtual base class or interface for the prediction behavior in a common package; both sides depend on the interface, not each other's full implementation. (3) typedef forward declaration — allows declaring a handle to a class before its full definition, breaking the compile-time dependency while preserving the runtime link. Option 3 is a workaround; options 1 and 2 fix the underlying design problem.

Best Practices — What Gets Enforced in Real Code Review

Package structure is one of those areas where discipline early saves weeks later. Every practice below exists because a project violated it and paid the price in compilation failures, regression timeouts, or non-reusable VIPs.

RuleCorrect PatternViolation Consequence
Include guard on every file``ifndef NAME_SV / define NAME_SV / ... / endif`"class X already declared" errors when two compilation units include the same file
Never `include classes in module scopeAlways compile through the package; import in modulesFactory registration in wrong scope → type_id::create() returns null silently
One class per fileapb_txn.sv, apb_driver.sv separateOne change recompiles the whole file; diff/blame becomes unreadable
Package file names match their contentapb_pkg.sv defines package apb_pkgConfusion in large teams; wrong file gets included in wrong project
Import, not include, at top levelimport apb_pkg::* in tb_top.svClasses included at module scope; factory and config_db break
Encode dependency order in Makefile/filelistExplicit vip_pkg → sb_pkg → env_pkg orderingRandom compilation order works locally, fails on different machine or CI
No circular package importsExtract shared types into a base packageUnresolvable compile dependency; teams get blocked for days

💡 Pro Tip — The Filelist Approach for Large Projects

On projects with 50+ files, maintain a filelist (often called flist or vf) that declares every source file in the correct compilation order. Every simulator supports this. The filelist becomes the single source of truth for your project's compile order — and it can be version-controlled, reviewed, and updated independently of the source files themselves. If you change the dependency structure between packages, update the filelist. Compilation failures then become instantly obvious rather than mysterious.

Summary — Why Getting This Right Saves Projects

Package structure isn't glamorous. Nobody writes blog posts about it. But in every large SoC project, it's one of the first things that breaks when someone adds a new VIP without understanding the dependency chain, or when a fresher copies a class file into the top module and spends two days wondering why the factory returns null.

ConceptRule in One LineWhat Breaks If Violated
Compilation OrderCompile dependencies before dependents — always"identifier not found" at compile time
Include GuardsEvery .sv file gets ifndef`…endif`"class already declared" when included twice
Package ScopeClasses go in packages, never in modules directlyFactory, config_db, and overrides silently fail
Circular DependenciesExtract shared types into a base packageUnresolvable compile loop; team blocked
Import vs Include``includebuilds packages;import` uses themDouble definitions and scope confusion
  • 1 The package is the compilation unit. One package per VIP layer (transaction, agent, environment, test). Every file in the package has an include guard. The package handles internal ordering. External users just import.
  • 2 Dependency order is not optional. If package B imports package A, package A must be compiled first. This must be encoded explicitly in your Makefile or filelist — never rely on the simulator to figure it out.
  • 3 Circular dependencies are architecture bugs. If you find yourself reaching for typedef forward declarations or workarounds to break a compile cycle, stop and fix the design. The two packages are too tightly coupled. Extract the shared type and the problem disappears.

📌 The Test of a Well-Structured Project

A well-structured UVM project should pass this test: can any package in the project be compiled in complete isolation (with only uvm_pkg as a dependency) and replaced with an updated version without touching any other package's source files? If yes — you have correct package separation. If changing one file requires touching five others to fix compilation — you have a dependency architecture problem.

Continue learning