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:
** 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.
// ── 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
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| Layer | File | Compiled By | Key Rule |
|---|---|---|---|
| Interface | apb_if.sv | Direct — given to compiler as-is | Must be compiled before any package that uses virtual apb_if as a type |
| Package wrapper | apb_agent_pkg.sv | Direct — given to compiler | Imports uvm_pkg, then ``include`s all class files in dependency order |
| Class files | apb_driver.sv etc. | Indirect — via ``include` inside the package | Never 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.
// 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
endpackageCompilation Order — The Five Rules
These five rules cover every project structure you will encounter. Follow them in order and compilation errors disappear. STEP 1STEP 2STEP 3STEP 4STEP 5uvm_pkgUVM LibraryAlways firstInterfacesapb_if.svCompiled as modulesnot inside any packageAgent Pkgsapb_pkg.svimports uvm_pkgone per protocolEnv Packageenv_pkg.svimports agent pkgs+ uvm_pkgTest Pkgtest_pkg.svtb_top.sv(last)COMPILE ORDER →Left is compiled first. Each step can only reference names from steps to its left. 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 untiluvm_pkgis 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.svhas avirtual apb_ifmember, the compiler must have seenapb_ifbefore it compilesapb_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_pkgimportsapb_agent_pkgandaxi_agent_pkg. Both must be fully compiled beforemy_env_pkgis 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.
tb/
├── agents/
│ ├── apb_agent/
│ │ ├── apb_if.sv ← compiled as interface, NOT inside package
│ │ ├── apb_agent_pkg.sv ← package 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.sv ← imports 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.sv ← imports uvm_pkg only
│ └── dut_reg_block.sv
│
├── tests/
│ ├── test_pkg.sv ← imports my_env_pkg + reg_model_pkg + uvm_pkg
│ ├── base_test.sv
│ ├── smoke_test.sv
│ └── regression_test.sv
│
└── tb_top.sv ← top module: imports test_pkg, instantiates DUT + interfacesCircular 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.
// 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
endpackageFix 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.
// ── 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.
// 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.
// 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 Convention | Example |
|---|---|
| Class files | APB_SEQ_ITEM_SV (filename in uppercase, dots → underscores) |
| Package files | APB_AGENT_PKG_SV |
| Interface files | APB_IF_SV |
| Macro header files | UVM_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).
## 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// ── 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
endmoduleCode 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
// 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
// ── 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" errorsExample 3 — Verification: Compile Script with Dependency Checking
## 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 -allExample 4 — Tricky Corner Case: Circular Dependency Broken With typedef
// ── 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.
// ❌ 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.
// ❌ 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 oruvm_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.
// ================================================================
// 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 → PASSInterview 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:** ``includeinside 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 usingincludewhereimport` 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.
| Rule | Correct Pattern | Violation 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 scope | Always compile through the package; import in modules | Factory registration in wrong scope → type_id::create() returns null silently |
| One class per file | apb_txn.sv, apb_driver.sv separate | One change recompiles the whole file; diff/blame becomes unreadable |
| Package file names match their content | apb_pkg.sv defines package apb_pkg | Confusion in large teams; wrong file gets included in wrong project |
| Import, not include, at top level | import apb_pkg::* in tb_top.sv | Classes included at module scope; factory and config_db break |
| Encode dependency order in Makefile/filelist | Explicit vip_pkg → sb_pkg → env_pkg ordering | Random compilation order works locally, fails on different machine or CI |
| No circular package imports | Extract shared types into a base package | Unresolvable 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.
| Concept | Rule in One Line | What Breaks If Violated |
|---|---|---|
| Compilation Order | Compile dependencies before dependents — always | "identifier not found" at compile time |
| Include Guards | Every .sv file gets ifndef`…endif` | "class already declared" when included twice |
| Package Scope | Classes go in packages, never in modules directly | Factory, config_db, and overrides silently fail |
| Circular Dependencies | Extract shared types into a base package | Unresolvable compile loop; team blocked |
| Import vs Include | ``includebuilds packages;import` uses them | Double 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.