1. 课程导入与工程环境搭建

芯片级回归验证不是把若干模块级测试简单堆叠,而是要在统一的编译、仿真、覆盖率与回归调度框架下,让成百上千个 UVM 用例可重复、可追踪地运行。Xcelium 作为 Cadence 的并行仿真器,配合 UVM 库与规范的目录结构,是搭建这条流水线的起点。本章先建立工具链的整体认知,再完成 UVM 库编译、目录结构设计与首个冒烟测试用例的运行。

Xcelium 工具链概览

Xcelium 的核心可执行程序是 xrun,它把编译、精化(elaboration)和仿真三个阶段封装为一条命令,同时保留 xmvlog、xmelab、xmsim 分步调用的能力。对于 UVM 工程,常用的关键选项包括:

  • -uvmhome <path>:指定 UVM 库根目录,xrun 会自动加入 UVM 源码路径与宏定义。
  • -sv / -sysv:启用 SystemVerilog 编译。
  • -timescale 1ns/1ps:统一时间单位与精度,避免不同文件默认值冲突。
  • -access +rwc:开放读写访问,便于波形与调试。
  • -coverage all:开启代码与功能覆盖率采集。
  • -seed <n>:指定随机种子,回归中用于复现失败用例。
  • -l <log>:输出编译与仿真日志。

需要区分的是:xrun 是“一站式”入口,适合日常开发与冒烟;而回归场景常把编译产物(snapshot)固化,用 xmsim -R 反复加载,避免每个用例重复编译,从而把回归时间集中在仿真本身。理解这一点,是后续设计目录结构与回归脚本的前提。

UVM 库编译流程

UVM 库有两种使用方式:一是直接引用 Xcelium 自带的 UVM 实现,二是编译用户指定版本的 UVM 源码。前者通过 -uvmhome 指向安装目录下的 tools/uvm 即可;后者需要显式编译 uvm_pkg.sv 并保证 UVM_NO_DPI 等宏与 DPI 库匹配。工程中更推荐固定 UVM 版本,避免工具升级导致行为漂移。

一个典型的编译命令如下:

xrun -sv -uvmhome $UVM_HOME \
     -timescale 1ns/1ps \
     -access +rwc \
     -coverage all \
     -f filelist.f \
     -top tb_top \
     -l compile.log

其中 filelist.f 按顺序列出接口、包、UVM 组件与顶层模块。编译顺序很关键:uvm_pkg 必须先于任何 import uvm_pkg::* 的文件;tb_top 中 run_test() 的调用必须在所有组件类定义之后。若出现 “class not found” 或 “package not imported” 类错误,优先检查 filelist 顺序与 -uvmhome 是否生效。

芯片级回归验证目录结构设计

芯片级回归的目录结构要同时满足三件事:源码可分层、编译产物可复用、回归结果可按用例与种子归档。下面给出一个可直接落地的结构示例:

proj/
├── rtl/                 # DUT RTL 与 filelist
├── tb/
│   ├── env/             # UVM env、agent、scoreboard
│   ├── seq/             # sequence 与 virtual sequence
│   ├── tests/           # 各测试用例
│   └── top/             # tb_top、接口绑定
├── sim/
│   ├── filelist.f
│   ├── Makefile
│   └── run_regress.sh
├── cov/                 # 覆盖率合并与报告
└── regress/
    ├── seeds/           # 种子列表
    └── results/         # 按 test/seed 归档日志与波形

设计要点:tb/ 只放可综合无关的验证代码;sim/ 集中编译脚本与 filelist,保证从任意路径调用都能定位源码;regress/results/ 以 <test>_<seed> 命名子目录,便于失败用例一键复现。覆盖率数据库建议统一输出到 cov/,回归结束后用 imc 合并,避免分散在各用例目录中难以汇总。

Xcelium UVM 芯片级回归工程结构 源码层 rtl/ DUT RTL + filelist tb/env/ env / agent / scoreboard tb/seq/ sequence / vseq tb/tests/ 各测试用例 tb/top/ tb_top + 接口绑定 UVM 库(-uvmhome) 编译与仿真层 sim/filelist.f 按依赖顺序列出源文件 xrun 编译 -sv -uvmhome -coverage snapshot 固化 避免重复编译 xmsim -R 仿真 +UVM_TESTNAME +ntb_random_seed run_regress.sh 遍历 test × seed 结果与归档层 regress/results/ <test>_<seed>/ sim.log UVM_ERROR / UVM_FATAL 统计 波形 .shm 失败用例调试 cov/ 覆盖率数据库合并 回归报告 通过率 / 失败清单

首个冒烟测试用例运行

冒烟测试的目标不是覆盖率,而是确认“编译—精化—仿真—报告”这条链路通畅。一个最小可运行的 UVM 用例包含三部分:继承自 uvm_test 的测试类、在 tb_top 中调用 run_test()、以及通过 +UVM_TESTNAME 选择用例。

class smoke_test extends uvm_test;
  `uvm_component_utils(smoke_test)
  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction
  task run_phase(uvm_phase phase);
    phase.raise_objection(this);
    `uvm_info("SMOKE", "smoke test started", UVM_LOW)
    #100ns;
    `uvm_info("SMOKE", "smoke test finished", UVM_LOW)
    phase.drop_objection(this);
  endtask
endclass

运行命令与判读方式:

xmsim -R -l smoke.log +UVM_TESTNAME=smoke_test +ntb_random_seed=1

运行结束后,在 smoke.log 中检查三项:是否出现 UVM_INFO ... SMOKE ... smoke test started 与 finished;UVM_ERROR 与 UVM_FATAL 计数是否为 0;仿真是否正常结束而非超时挂起。若 run_phase 中忘记 raise_objection,仿真会在 0 时刻直接结束,日志中看不到任何 SMOKE 信息,这是新手最常见的“仿真秒退”原因。冒烟通过后,再把该用例纳入 run_regress.sh 的用例列表,逐步扩展为完整回归集。

建议在 sim/Makefile 中把编译与仿真拆成 comp 和 run 两个目标:make comp 生成 snapshot,make run TEST=smoke_test SEED=1 只做仿真。这样回归时无需重复编译,单次仿真时间可显著下降。
不要把所有源文件都塞进一个 filelist 而不区分顺序。UVM 包、接口、DUT、tb_top 之间存在明确的编译依赖,顺序错误会直接导致精化失败。建议按“UVM 库 → 接口 → 包 → 组件 → 顶层”的顺序维护 filelist,并在注释中标明每段的作用。