
简介面向硬件验证工程师和芯片设计师整理了一份 Cadence 数字仿真工具 Xceliumxrun的完整操作指南。文档从 Linux 环境下的安装检查、单步仿真、多阶段分离仿真和常用选项入手随后深入讲解 xcelium.d 目录中 library 与 snapshot 的存放机制以及 shm、db、fsdb 三类波形文件的创建用途并系统介绍了覆盖率收集策略与门级仿真中的延时反标、时序检查调整等高级应用对于工程中常见的编译报错和 license 问题也给出了具体的排查思路和解决方向。资源以单个 PDF 文件交付压缩包约 505KB目前已有 2049 人学习。文档还整理了特殊场景下的扩展选项例如强制重新编译、放宽语法检查、排队等待 license 等同时提供基础 option 的速查表可帮助验证人员在项目规划、日常仿真和故障定位时减少重复试错提高工作效率适合从初学者到有一定经验的验证工程师作为工具书随时查阅。文档结构清晰覆盖了从基础配置到高级调试的完整流程是一份实用的 xrun 仿真参考。1. 从一条命令行说起Xcelium(xrun) 到底解决了数字验证里的什么麻烦在数字IC验证里真正让项目进度卡住的往往不是RTL写得多花而是仿真器怎么把几百个SystemVerilog和UVM文件按正确顺序装起来再在合理时间内跑出可复现的结果。Cadence Xcelium 的命令行入口 xrun就是把编译、elaborate、run、覆盖率收集和波形 dump 捏在一个命令里的那根绳子。第一次接触它的人容易把 xrun 当成一个“仿真开关”实际上它背后是一整套事件驱动仿真内核和对应的编译策略。这篇内容面向正在搭UVM环境、想从VCS或QuestaSim切过来、或者刚照着 Cadence 教程装完 Xcelium 还没跑通第一个用例的工程师我把常用参数和踩过的坑按可复现的顺序铺开。2. 跑通第一条 Xcelium 仿真从编译选项到波形 dump 的最小流程2.1 为什么数字仿真圈会选 Xcelium/xrun 而不是一把抓数字仿真工具不止 Cadence 一家Synopsys VCS、Mentor 家的 QuestaSim 都在常规选项里。选 Xcelium 最直接的动机往往不是单点性能而是 Cadence 生态的联动前端验证环境里常见的 IP 模型、内存模型、以及和 Virtuoso 模拟环境做混合仿真时的接口原生跟 Xcelium 走得最近。如果你的团队已经有 Cadence 全家桶xrun 是那个能少绕路的选择。从工具本身看xrun 作为 Xcelium 的命令行前端把传统仿真器拆散的三步——编译、例化、运行——合并成一条命令的默认行为。你可以只用一个 xrun 从头跑到尾不需要自己调 ncsim、irun 那一类遗留名。实际上 xrun 这个名字就是从 “x” 加 “run” 来的老用户从 Incisive 时代的 irun 切过来命令行习惯基本能平移。还有一点经常被忽略xrun 对 SystemVerilog 标准特性的支持粒度比较细尤其是覆盖率数据库和随机约束求解。后面章节会展开但选型阶段可以先记住这个结论如果验证平台以 UVM 为主、覆盖率要求写进 sign-off 流程Xcelium 的覆盖率工作流比某些竞品少一层格式转换。第一次跑 xrun 前先打开终端敲一句xrun -help它会按 compile、elaborate、run、coverage、debug 分组列出当前版本支持的选项。不同版本之间有细微差异这句帮助命令能让你少走很多弯路比照抄网上的旧参数靠谱得多。2.2 xrun 的最小三阶段compile、elaborate、run 各管什么虽然一条 xrun 命令能一口气跑完但理解内部三个阶段后面排错才不慌。第一个阶段是 compile。xrun 会逐个读取通过-f指到的文件列表按扩展名决定走 Verilog 编译器还是 SystemVerilog 编译器同时收集宏定义、include 目录、timescale 指令。这个阶段做完每个源文件都会变成中间表示有点像 C 语言里的.o文件但用的是仿真器的私有格式。第二个阶段是 elaborate。这一步把编译后的模块实例化展开 generate 块解析参数传递连接接口和端口。很多 UVM 环境跑挂不是挂在语法编译而是挂在 elaborate 阶段的层次连接错误比如某个 interface 没有被正确例化或者参数没传进去导致位宽不匹配。第三个阶段才是真正跑仿真。事件调度循环在这里启动仿真时间从 0 开始推进直到遇到$finish、显式停止或者达到-endtime指定的时间。xrun 的默认行为是三个阶段连做但你可以只跑前半段比如用xrun -c只编译快速查语法错误。排错时先看报错落在哪个阶段再决定要不要拆命令能省不少时间。一条真正能跑的最小命令长这样xrun \ -f rtl.f \ -f tb.f \ -top tb_top \ -timescale 1ns/1ps \ -access rwc \ -q \ -l sim_smoke.log-f rtl.f和-f tb.f分别引入 RTL 和测试平台的文件列表顺序由文件内部决定-top指定顶层模块elaborate 阶段从它开始展开-timescale 1ns/1ps是全局默认时间单位和精度如果代码里没有timescale 1ns/1ps这个值保证事件时间基准不至于乱-access rwc给仿真对象加 read/write/convert 访问权限后面做波形 dump 和内部信号观察都会用到-q压住启动横幅日志干净些-l sim_smoke.log把输出同时写进文件。-top是最容易被忽略的参数。有人以为仿真入口是 testbench 模块名随便写一个也能过。实际上-top同时影响 elaborate 和覆盖率层级写错会看到 “cannot find top” 或者莫名奇妙的空波形。另一个容易漏的是-access不开它默认权限只允许按端口访问dump 波形时想看内部寄存器就会看到一堆 x 或直接报 no access。2.3 把 -timescale 和 -access 用熟波形 dump 与仿真结束条件第一条仿真最好把波形也带出来这样能确认信号真的在跳。最通用的做法是用系统任务 dump VCD不依赖任何厂商格式。module tb_top; logic clk; logic rst_n; dut u_dut ( .clk (clk), .rst_n (rst_n) ); initial begin $dumpfile(sim_smoke.vcd); $dumpvars(0, tb_top); end initial begin clk 0; forever #5 clk ~clk; end endmodule$dumpvars(0, tb_top)里的 0 表示 dump 从该层次往下全部信号tb_top 是模块名而不是实例名在当前测试平台里它就是顶层自身。VCD 文件由仿真器在 run 阶段持续写入不需要额外加编译选项Xcelium 对标准 VCD 的支持不需要第三方工具。但 VCD 最大问题是文件体积增长极快跑一个稍完整的 UVM 环境几分钟就能写几个 GB。所以很多团队会把 VCD 换成 FSDB配合 Verdi 看波形。这时 testbench 里通常写$fsdbDumpfile和$fsdbDumpvars前提是 Xcelium 已经链接了 Verdi 的 PLI 库。这个依赖在 Cadence 的安装教程里会提到如果你只装了 Xcelium 本体$fsdbDumpfile会报 undefined system task。第一次复现时我建议先用 VCD 确认链路通再考虑 FSDB。还有一个容易被新手忽略的点仿真结束条件。很多第一次跑 xrun 的人会困惑为什么命令一直不退出。如果 testbench 里只有$display之类的打印没有$finish事件循环会空转。标准做法是在 initial 块里给一个#1000; $finish;或者在命令行加-endtime 1000ns。xrun 遇到$finish会把 final 块执行完再退出这里有时候也会卡住但至少你能区分是仿真没结束还是真死循环。3. 把 UVM 验证环境搬进 Xcelium常用 xrun 选项的完整参数清单3.1 编译 UVM 的两种方式-uvm 开关和手动编译 SV 库的区别UVM 不是仿真器内建语法它是一套基于 SystemVerilog 的类库。跑 UVM 环境之前要么把库源码编进仿真要么依赖仿真器提供的预编译版本。xrun 提供了-uvm开关让 Xcelium 自带的 UVM 库参与编译这是最省事的方式适合绝大多数项目。手动编译方式常见于两种场景一是项目锁定了某个 UVM 版本而仿真器自带的版本不匹配二是需要修改 UVM 源码来做打印或补丁。手动做法是在文件列表最前面把uvm_pkg.sv和uvm_macros.svh的路径加进去然后用普通 SV 编译流程处理。需要留意宏定义位置uvm_macros.svh只在真正用到 UVM 宏的源文件里包含如果在文件列表里重复 include会出现 redefinition 警告。两种方式不能混用。项目里如果文件列表有incdir$UVM_HOME/src同时又加了-uvm仿真器可能拿到两份 uvm_pkg轻则编译时间翻倍重则 type_id 查找出现诡异行为。判断一个环境是哪种用法看编译命令里有没有-uvm以及文件列表里有没有显式出现uvm_pkg.sv。实际项目里我会先把-uvm跑通如果编译时报 UVM 类库版本相关错误再切手动。下面这条命令是一个能跑的 UVM smoke 测试xrun \ -uvm \ -f uvm_tb.f \ -f rtl.f \ -top tb_top \ -timescale 1ns/1ps \ -access rwc \ UVM_VERBOSITYMEDIUM \ -l uvm_smoke.logUVM_VERBOSITYMEDIUM是运行时 plusarg通过命令行传给 UVM 的 report server控制打印详细度。初跑阶段用 MEDIUM 能看到uvm_info的主干信息又不会被 DEBUG 级别信息淹没。如果你只想看错误可以改成UVM_VERBOSITYNONE但排错时不建议错误上下文很关键。3.2 必调参数-f、-l、-seed、-q 和覆盖率入口多文件项目离不开-f。它后面跟一个文件列表里面每行写一个源文件路径支持注释。很多人把文件列表理解成“把所有文件平铺进去”实际 xrun 严格按行顺序编译package 必须在引用它的 module 之前出现否则编译阶段就报 “Cannot find package”。文件列表里incdir和宏定义也可以写格式是defineXXX比在命令行堆一长串更易维护。-l是日志文件开关xrun 所有 stdout/stderr 都会镜像到指定文件。跑长回归时务必给每个 seed 一个独立日志名否则多个仿真并发写同一个文件日志会交叉乱掉。常见做法是-l logs/sim_$(SEED).log脚本里用变量拼路径。-seed用来控制随机种子。Xcelium 在 run 阶段用这个种子初始化 SV 的随机数生成器。同一个 RTL、同一个 test 文件、同一个 seed理论上随机约束序列完全可复现。要复现某个失败用例最稳的做法是把它当时使用的 seed 原样记录在日志里xrun 默认会在日志开头打印当前 seed。-q是 quiet 模式只屏蔽启动 banner不影响仿真输出。很多 CI 脚本里会加-q配合-l让日志文件干净。但第一次调试最好不要加Xcelium 启动时打印的版本和 seed 信息在排查环境问题时很有用。覆盖率相关选项里最常用的是-covtest。它给本次覆盖率收集起一个测试名仿真结束后覆盖率数据会存放在cov_work目录下测试名就是目录名。如果你没加-covtestxrun 也可能收集覆盖率但目录名会被默认值占掉后面合并回归时容易覆盖。覆盖率配置需要更细控制时再加-covfile指定配置文件。3.3 调试场景下的 dump 门控与日志文件隔离每次手工开 dump 太慢更实用的做法是把 dump 命令放在单独的 initial 块里并用宏控制开关。ifdef DUMP_WAVE initial begin $dumpfile(wave.vcd); $dumpvars(0, tb_top); $dumpoff; #1000 $dumpon; end endif编译时用defineDUMP_WAVE打开这段代码。$dumpoff和$dumpon的组合可以控制只在感兴趣的时间窗口记录波形避免从头到尾全量记录。比如你知道 bug 出现在复位释放后可以在复位完成时刻调$dumpon前面一大段无用波形不落盘。日志分段方面xrun 没有内建按时间滚动的日志机制工程上通常靠回归脚本按 seed 或测试名分文件。常见脚本片段如下for seed in 1001 1002 1003; do xrun -f rtl.f -f tb.f -top tb_top -uvm \ -seed $seed -q -l log/run_$seed.log done这个循环里每次 xrun 都会重新编译效率不高后面增量编译会解决。这里想强调的是日志文件路径必须包含能区分运行实例的变量不然多线程触发回归脚本时日志互相踩定位问题等于从头再来。4. 高级功能实战覆盖率合并、增量编译与随机种子的正确用法4.1 功能覆盖率与行覆盖率-covtest 与 cov_work 的联动Xcelium 里覆盖率分好几种行覆盖率、翻转覆盖率、条件覆盖率以及 SystemVerilog 的 covergroup 功能覆盖率。前几种需要在编译和仿真时告知仿真器收集功能覆盖率只要 covergroup 实例化就会自动统计差别在于功能覆盖率属于语言标准不需要额外插件。行覆盖率这类结构性覆盖率的收集选项一般是在命令行加-covtest testname需要配置时再加-covfile。-covfile指向覆盖率配置文件里面可以用set_covergroup、set_line指令选择收集哪类覆盖率。很多团队默认全收集但全收集的仿真时间会明显变长尤其是翻转覆盖率在大型 SoC 验证里会拖慢回归。覆盖率合并发生在回归之后。Xcelium 的覆盖率数据落在cov_work/design/testname一个测试一个目录。合并时用安装目录下的覆盖率工具版本新一点叫xcrg老版本对应iccrg。基本思路是把多个测试的覆盖率目录合并成一个集合再生成可读报告。这里命令细节各版本差异很大动手前先执行xcrg -help看当前版本参数别直接抄网络上的旧命令。如果团队用脚本自动跑回归有一个容易踩的坑每个测试的-covtest名字如果都一样后跑的会覆盖先跑的结果。正确做法是测试名和 seed 绑定例如-covtest smoke_$(seed)最后合并时再聚合成最终覆盖率。4.2 增量编译为什么能省时间哪些文件适合放 -incremental回归里每次改动一点 RTL 就全部重编是验证流程里最浪费时间的环节。xrun 的增量编译选项可以把上一轮编译好的对象缓存下来下次只重编有变化的部分。我一般这样组织稳定的文件列表放一个 f 文件经常改动的文件放另一个 f 文件。增量编译只对稳定部分开频繁变动的文件反而不要放进去否则缓存命中率极低还要承担额外校验开销。xrun \ -f stable_rtl.f \ -f testbench.f \ -top tb_top \ -incremental obj_dir \ -q \ -l incr_run.logobj_dir是存放增量编译中间产物的目录。第一次执行时这个目录不存在也没关系xrun 会创建它之后再次执行如果源文件没有变化这部分编译会被跳过直接进入 elaborate。如果改了stable_rtl.f里的某个文件xrun 通过时间戳和内容校验发现差异仅重编该文件。增量编译不是万能的。接口定义、宏定义、参数文件这些影响范围大的内容一旦变化会连带着让大量依赖文件失效。你要是把全局宏所在的文件放进增量目录每次改宏都会引发连锁重编速度反而更慢。所以经验是只对行为独立、被依赖面小的模块开增量。另外覆盖率收集模式下增量编译的命中率也可能下降因为插桩逻辑要跟着覆盖率配置走。4.3 复现失败用例随机种子怎么管理才不靠运气数字仿真里最让人头疼的问题是“昨天跑挂了今天跑又过了”。绝大多数情况是随机约束没固定下来。UVM 环境的随机源有两层一层是 SystemVerilog 的$urandom和约束随机化一层是 UVM 工厂里各种 component 的随机化。只要种子一致整个序列应当可复现。在 xrun 命令行里-seed设置 SV 随机数生成器的初始值。但在 UVM 环境下很多团队更习惯用UVM_SEED12345这个 plusarg 会传给 UVM 内部的全局随机源。如果你的环境同时用了$urandom那么两者要一起固定。复现时先看失败日志开头有没有打印 seed没有的话下次跑之前先加上-seed和UVM_SEED。更靠谱的做法是把 seed 写进编译产物或者日志文件。回归脚本可以这样SEED${SEED:-$RANDOM} echo seed$SEED seed_info.txt xrun -f rtl.f -f tb.f -top tb_top -uvm \ -seed $SEED UVM_SEED$SEED \ -q -l logs/run_$SEED.log脚本里SEED默认取随机数生成后先落盘再传给 xrun。出问题后直接读seed_info.txt就能用同一个命令重新跑。这个习惯看起来简单但能把很多“偶发失败”变成“可复现失败”省下来的排错时间非常可观。5. 避坑与常见问题xrun 报错、波形丢失与仿真卡死的排查清单5.1 UVM 宏和包顺序导致的编译错误现象xrun 编译一批.sv文件时报uvm_pkg不存在或者uvm_object类型未定义。 原因最常见是文件列表顺序不对或者 includeuvm_macros.svh的时机早于uvm_pkg编译完成。UVM 宏展开依赖包内类型顺序错了编译器自然认不出来。 解决把 UVM 相关路径移到 filelist 最前面如果项目手动编译 UVM 库确认uvm_pkg.sv在第一个被编译。若用了-uvm开关就别再手动 includeuvm_macros.svh防止双份定义导致的奇怪错误。5.2 仿真卡死推进不了数字仿真里的“不收敛”根因现象仿真运行一段时间后日志不再前进CPU 持续占用波形在一个时间点反复震荡。 原因数字仿真器没有模拟仿真那种非物理的收敛步长但同样会遇到类似瞬态仿真不收敛的卡死场景。常见原因是组合逻辑环或者forever #5 clk ~clk;这类时钟翻转在时间精度配置不对时被舍入成 0 周期事件调度立刻陷入死循环。 解决先把 timescale 统一。用全局-timescale 1ns/1ps并检查 RTL 里有没有单独的timescale 1ns/10ps导致精度冲突。加入-endtime作为保险比如-endtime 100us让仿真到点强制退出。退出来之后再抓波形看震荡位置比在卡死现场猜要快。5.3 波形 dump 不出来或打开全是 x现象$dumpfile执行了VCD 文件也生成了但打开后信号全是 x或者是空的。 原因第一是层次写错$dumpvars(0, tb_top)里的层次名和实际模块名不一致。第二是-access权限不足VCD 虽然能记录端口但内部信号在默认权限下读不到。第三是 dump 的时间和仿真结束时间重合刚 dump 就 finish文件里自然只有一瞬间。 解决先确认 testbench 模块名和-top一致dump 层次用tb_top.u_dut这种明确路径再测。命令行加-access rwc并把$dumpvars放到仿真最开头保证从 0 时刻开始记录。5.4 License 和环境变量问题导致 xrun 启动失败现象xrun 一闪而过或者启动后提示找不到 license日志里出现 license feature 相关报错。 原因Cadence 工具的 license 管理依赖正确的环境变量最常见的是LM_LICENSE_FILE或CDS_LIC_FILE没指到 license 文件也有的是 license server 端口变了。这个问题在刚照着 Cadence 安装教程装完、还没配置环境变量的机器上尤其普遍。 解决用which xrun和xrun -version确认可执行文件路径正常。然后echo $CDS_LIC_FILE $LM_LICENSE_FILE看 license 配置是否指向有效服务器。如果 license server 正常再检查 hostname 和 mac 地址是否和 license 绑定一致。这一步比纠结代码更快。6. 把 Xcelium 用出效率的习惯从 Makefile 到回归脚本的验证技巧我自己在工作里最受益的习惯是把 xrun 的常用指令拆成 Makefile 的 target。编译一个目标叫 build增量运行叫 run覆盖率报告叫 cov。这样不管是本地调试还是 CI 触发命令都收敛在一个文件里换机器也不会因为命令行参数不同跑出两套结果。build: xrun -f rtl.f -f tb.f -top tb_top -uvm -q -l build.log run: build xrun -f rtl.f -f tb.f -top tb_top -uvm -seed $(SEED) -q -l logs/run_$(SEED).log真正让我少熬夜的是日志规范化日志文件名带测试名和 seed每次回归开始前用mkdir -p logs/$(DATE)隔离目录。另一个习惯是给xrun -help做版本笔记不同版本选项有增删遇到unrecognized option先查当前版本手册别因为一条旧命令浪费一晚上。我踩过最蠢的一次坑是把-q忘了50 个 seed 的日志文件全带 banner搜索关键词时被大量版本信息淹没。从那以后凡是进回归脚本的 xrun 必带-q但第一次调试我会故意去掉它等确认环境没问题再加上。希望这些习惯能帮你把 Xcelium 从“能跑”用到“好跑”也希望帮到你。本文还有配套的精品资源点击获取