
简介一份面向硬件验证工程师、芯片设计师及半导体设计自动化从业者的Cadence Xceliumxrun数字仿真工具操作指南初学者和有经验用户均可参考。文档系统讲解Linux环境下xrun的安装检查、单步仿真以及compile、elaborate、sim三阶段分离执行等基础操作在此基础上进一步说明xcelium.d目录的作用、shm/db/fsdb三类波形文件的生成方式、覆盖率收集选项以及Gate Level Simulation中的参数调整与时序检查控制。这些内容可以帮助用户快速搭建仿真环境并掌握常用调试技巧。资源为单个PDF文档大小505KB内容精炼已有2050人学习下载。针对复杂工程中常见的隐式命名、license受限等报错文档还给出了-genhier、-licqueue、-helpargs等排查手段并介绍借助-helpall生成全部选项说明、访问官方支持网站检索错误码的方法能减少试错成本提升日常仿真与调试效率。1. 用 Xcelium 跑数字仿真为什么你的回归还停在“能跑”的阶段做数字验证的人对 Cadence Xcelium 都不会陌生命令行入口 xrun 几乎是每天都要敲的工具。但多数人用 xrun 的方式长期停留在“把 testbench 编过、把波形 dump 出来、看几个打印”的程度遇到 UPF 低功耗仿真、覆盖率收敛、形式化辅助分析这些高级场景就明显吃力。Xcelium 不是只能跑 RTL 仿真的黑匣子它真正值钱的地方在于用对编译选项和运行时选项可以让同一套设计在仿真速度、内存占用、调试效率三个维度上同时变好。这篇笔记就是围绕 xrun 的完整操作链路展开从最小可跑命令到大规模回归的调优、排错和高级用法写给每天要跟仿真器打交道的数字前端、验证和 SoC 集成工程师。2. xrun 的编译与运行模型先搞懂它的三步曲再谈高级功能2.1 xrun 为什么把“编译”和“仿真”拆成两个阶段Cadence Xcelium 的 xrun 本质上是一个前端封装脚本它把传统流程里的“解析、 elaboration、运行”三步合并成一条命令。但我建议你心里始终把它拆开看因为高级用法里大量选项是在某一阶段才生效的。解析阶段处理-f文件列表、宏定义和 include 路径elaboration 阶段做模块例化、参数传递和设计层次检查运行阶段才是真正的仿真执行。很多人遇到“明明语法没错但一跑就崩”的问题就是因为没分清错误发生在哪个阶段。常见做法是先用xrun -compile只做编译再用xrun -elaborate单独跑 elaboration最后xrun -R运行。这样分步做的好处是增量编译时不用每次都从头解析而且能精确定位是哪个阶段出的问题。我一般会把三步写进 Makefile 的不同 target而不是每次都敲一整条 xrun改动一个小模块时能省下大量时间。2.2 最小可跑命令与文件列表的写法先给一个你在本地就能验证的最小例子。假设有counter.v和tb_counter.sv你的 xrun 调用长这样xrun -sv tb_counter.sv counter.v \ -timescale 1ns/1ps \ -access rwc \ -top tb_counter \ -exit参数拆开看-sv打开 SystemVerilog 解析支持-timescale 1ns/1ps统一时基-access rwc是给波形 dump、force/release 提供读写权限-top指定顶层模块-exit让仿真跑完后自动退出。没有-access rwc你用$fsdbDumpvars或 xrun 内置的-input脚本去 force 内部信号时会直接报 no access 错误这是新手最常见的第一个坑。如果你用文件列表把上面两个文件写进files.fcounter.v tb_counter.sv然后xrun -f files.f -sv -access rwc -top tb_counter -exit。-f支持在文件列表里继续写define、-v、-y这类选项比在命令行堆参数清晰得多。注意-f里不要写-top之类的工程级选项顶层选择放在命令行更直观不然换 testbench 时容易改漏。2.3 elaboration 阶段的参数传递与层次选择Xcelium 里最常用的参数覆盖方式是在命令行用-param或直接通过defparam。但更推荐在-top指定后用-define配合 generate 来切配置。比如你要跑不同数据位宽的验证xrun -f files.f -sv -access rwc \ -define DW_128 \ -top tb_counter -exit代码里写成if (DW_128 1) ... else ...的 generate 块。相比在 testbench 里硬编码参数这种做法的好处是回归脚本里可以并行拉起多个不同配置的仿真每个 xrun 进程互不干扰。elaboration 阶段的顺序也很重要Xcelium 默认按文件列表顺序解析但模块定义和使用顺序不一致时加-relax可以降低对定义顺序的敏感度。前提是你确认设计里没有真正的依赖循环否则-relax会掩盖例化顺序错误。2.4 快慢两步走增量编译与-incr的边界增量编译是 xrun 最实用的日常选项。第一次全量编译后第二次运行加-incrXcelium 会只重编改动过的文件xrun -f files.f -sv -access rwc -top tb_counter -incr -exit-incr的边界要讲清楚它依赖文件时间戳做判断只对修改文件所在的编译单元做重解析跨文件的接口变化仍然会被捕获因为 elaboration 会重新做。但如果你改了-f文件里 include 的公共头文件并且头文件被几十个文件引用那这几十个文件都会重编增量带来的收益会明显下降。我一般会建议把公共宏和参数单独拆到一个小头文件减少这种“改一行全量编”的情况。3. 仿真控制与波形调试从$display进化到结构化调试3.1 使用-input脚本进行运行时控制而不是在 TB 里堆$finish很多验证工程师习惯在 testbench 里写#10000 $finish;来结束仿真。这在小型模块验证里没问题一旦跑到 SoC 级仿真时长不再固定这种写法非常浪费算力。更合理的方案是用 xrun 的-input选项传一个 Tcl 脚本动态决定 dump、force 和结束时机。# sim.tcl open 波形数据库名称 probe -create -database 波形数据库名称 -all -depth all -task -function -uvm -packed 1 run -time 10ms quit -fxrun -f files.f -sv -access rwc -top tb_counter \ -input sim.tcl -exit这段 Tcl 做了三件事打开波形数据库、全层次探测信号、跑 10ms 后退出。-depth all会把所有子模块的信号都记录下来适合刚上板调试时用但生成的波形文件会很大。等定位到具体模块后把-depth改成2或者用probe -create -database xx -design 某层次 -all做局部记录文件规模能降一个数量级。xrun 的 Tcl 控制是基于仿真内核的不是操作系统 shell所以quit -f必须放在最后不然仿真内核不会正确关闭数据库文件。3.2 波形 dump 的格式选择FSDB 还是 VCD 还是 SHMCadence 生态里 Xcelium 原生支持 SHM 格式用-input脚本里的probe写的就是 SHM。但如果你在用 Verdi 做调试FSDB 需要额外加载-fsdb相关支持常见做法是在-input脚本里调用$fsdbDumpvars系统任务。VCD 是通用格式兼容性好但体积最大只适合做后仿的少量信号交互。我给你的建议是日常调试用 FSDB回归验证用 SHM 做全量备份VCD 只在需要交给第三方工具时导出。-access rwc对 dump 的影响要专门提一下如果只加了r某些信号连接可能 dump 不完整加rwc能保证 probe 和 force 都可用代价是仿真时内存占用略高。对大规模 SoC 验证我会把-access rwc只在调试用例里用回归用例用-access r来换取吞吐。3.3 force/release 的层次引用与常见撞车用 xrun 的 Tcl 脚本做 force比在 testbench 里写force语句更灵活因为不需要重新编译# sim.tcl force -deposit /tb_counter/u_dut/count_signal 32h0 run -time 1us force -deposit /tb_counter/u_dut/count_signal 32hFFFF run -time 1us release -deposit /tb_counter/u_dut/count_signal-deposit表示把信号强制定为指定值并保留驱动能力不加-deposit的 force 更像“钉死”后续 design 里的正常驱动无法恢复。踩坑点在于force 的层次路径必须写到叶子信号如果你 force 到一个 net 型中间节点release 后可能会出现短暂的 X 态传播。另外一个常见撞车是 TB 里的$display还在按原来的值打印但信号已经被 force 改了对不上时间点。解决办法是 force 之后等一个 delta cycle 再采样通常 next 到下一拍再看结果。3.4 仿真不收敛时的第一反应不是加-timescale而是查初始值和 X 态传播仿真中报“不收敛”或出现大量 X 态是 xrun 使用中最消耗时间的问题。瞬态仿真不收敛常见于模拟电路但数字仿真里 X 态扩散大多源于三个地方寄存器没有复位、存储器模型没有初始化、force/release 导致多驱动冲突。先用-xrorsim或-xpropmode这类选项观察 X 态传播路径再用$assertoff临时关掉断言来缩小范围。提示遇到 X 态问题先跑xrun -R -input 检查脚本看波形里 X 是从哪个模块边界开始的。多数情况下问题并不是仿真器算错了而是 RTL 里某个模块在仿真开始前缺少初始化。用xrun -covtest系列功能之前先把这个 X 态链清干净。4. 低功耗与 UPF 仿真xrun 在功耗意图验证里的正确姿势4.1 UPF 文件如何进入 xrun-upf选项与 supply net 的声明低功耗设计验证现在已经是 SoC 验证的标准要求xrun 对 UPF 的支持通过-upf 文件名选项接入。一个典型命令xrun -f files.f -sv -access rwc \ -upf ../upf/top.upf \ -top tb_top \ -exit-upf会触发 Xcelium 进入低功耗仿真模式在 elaboration 阶段解析 UPF 的 power domain、isolation 和 retention 策略。你需要保证 UPF 文件里的 supply net 名称和 RTL 端口名严格一致否则仿真器会在 elaboration 时报“supply not found”且这个错误信息常常出现在几百行之后误导你以为是 RTL 语法问题。常见做法是把 UPF 文件也纳入版本管理并和一个专门的-define开关绑定比如-define LOW_POWER让同一个 RTL 既能跑功能仿真又能跑 UPF 仿真。这样做的意义在于普通功能回归不具备 power domain 切换能力UPF 仿真专门验证 isolation cell 和 retention 逻辑在 sleep/wakeup 时的时序行为。4.2 UPF 仿真最容易翻车的三件事第一件是忘记指定-retention策略。UPF 里写了set_retention但 RTL 里的 retention register 没有对应的供电控制逻辑xrun 在仿真时不会主动帮你做数据保持你会在退出 sleep 后读到未知值。第二件是 isolation 使能信号 polarity 写反。isolation -clamp_value 0意味着钳到 0但如果你后续的-isolation_sense high和实际控制信号不匹配跨 domain 信号会在一段时间内出现 X仿真不会报错但功能全乱。第三件是 power state table 缺失导致的 supply 冲突xrun 会打出power state conflict这时要回头检查 UPF 里add_power_state是否覆盖了所有休眠组合。注意UPF 仿真里不要用-access r草率跑完就收工。低功耗仿真必须 dump 波形回来对照 power domain 切换否则即使仿真 pass设计在真实芯片上也可能因为 isolation cell 时序不满足而功能失效。4.3 低频功耗仿真跑得慢怎么办UPF 仿真通常比普通功能仿真慢 15%30%因为仿真器要额外追踪 supply net 和 isolation cell 的状态。如果慢得离谱先确认是否把整个 testbench 都放在了 UPF 域里。正确的做法是只把设计 DUT 的 power intent 关联进 UPFtestbench 用-upf文件里单独定义的非电源域部分或者直接放在 UPF 外避免仿真器为 TB 逻辑做无谓的功耗状态追踪。另外set_scope在 UPF 里如果指到了 TB 顶层会把整个 TB 误判成功耗管理对象明显拖慢仿真。我在项目里会用-upf加-scoped_top tb_top.dut把范围限制住。5. 回归管理、覆盖率收敛与必踩坑排查清单5.1 用-covtest做覆盖率收敛的闭环Xcelium 的覆盖率功能通过编译和运行两个阶段的选项配合使用。编译阶段要加-covtest 用例名运行阶段用-covdbs指定覆盖率数据库文件路径。我建议每个回归用例一个独立目录而不是所有用例共享一个覆盖率库否则-merge时会出现误归并。xrun -f files.f -sv -access r \ -covtest test_counter \ -covdbs ./cov_db \ -top tb_counter -exit跑完后用xrun -covreport -covdbs ./cov_db或者 iccr 工具做覆盖率报告。覆盖率收敛不要只看行覆盖率重点看 toggle coverage 和 FSM coverage。行覆盖率 90% 以上但 toggle 低于 60% 很常见说明大量信号只在几种固定状态间翻转边界场景完全没跑到。我一般会把功能覆盖率收敛分两步第一步跑完直接看 unbound 的 bin 和 exclude 掉确实不需要测的项第二步再针对低覆盖率模块设计定向用例而不是盲目拉长随机种子。5.2 回归脚本里 xrun 与 job 并行-xs与-xml选项怎么配合大规模回归通常在 LSF 或者本地多核机器上并行拉 xrun 进程。Xcelium 本身不负责作业调度但它的-xs和-xml选项能影响多核并行仿真时的任务划分。-xs指定 runtime 模式下的额外选项-xml指定多核模式下的选项。对单核回归我会用-xrun -R配合 shell 的 并行对单用例多核仿真用xrun -xml -np 4之类的方式让单个用例内部并行分解。这里有个血泪经验不要对几十个用例统一开-xml。多核并行在单个用例内部有通信开销短用例开-np 4往往比单核还慢。我一般按用例的仿真时长分档超过小时的用例才考虑-xml -np 4短回归一律用进程级并行。此外并行跑 xrun 回归时要给每个进程指定独立的-cds_lib和-covdbs路径否则多个 xrun 进程会竞争同一个数据库文件轻则覆盖率丢失重则直接 crash。5.3 器件未定义和文件编译顺序问题数字仿真里报“器件未定义 / module not found”看起来像是缺文件但很多时候是编译顺序的问题。前面提到-relax可以缓解定义顺序依赖但如果缺失的是某个库里的标准单元模型比如用到工艺库里的AND2X1那-relax并不能解决。你需要加-v 工艺库文件或-y 库目录来指定。常见做法是把只读的库文件从设计文件列表里单独拆出来用-v指定避免回归脚本改动设计文件时误伤库路径。5.4 避坑排查清单5 个高频问题现象 1xrun报 “cannot open input file” 但文件路径明明对。原因多半是文件列表里用了相对路径而 xrun 当前工作目录不是脚本所在目录。解决所有路径改为相对回归根目录的绝对路径或者用-f文件里的//注释和incdir把 include 路径显式写全。现象 2仿真跑完打印了$finish但退出码不是 0。原因可能是存在未解决的 assertion 或者仿真器收到-exit时仍有 pending 的线程。解决在-inputTcl 脚本里显式调用quit -code 0或者用-nolog避免日志缓冲区写盘失败导致退出码异常。现象 3加了-access rwc以后仿真速度骤降。原因rwc 权限让仿真器为所有信号记录读写关系尤其是大型数组信号。解决改成-access r只在需要的层次上用 Tcl 的probe -create单独开读写避免全局权限开销。现象 4-incr编译后行为异常重新全量编译又正常。原因增量编译时某个头文件的依赖没有被正确识别典型发生在include路径用incdir且同一个头文件名存在于多个目录时。解决检查-f文件里是否重复定义了 include 路径把同名头文件收敛到唯一目录实在不行就全量重编。现象 5UPF 仿真中sleep 唤醒后数据错误但无任何告警。原因isolation cell 在 power domain 关闭期间没有正确钳制或者 retention register 的 restore 时序晚于功能时钟恢复。解决在波形里看 isolate 信号和时钟恢复点确认 restore 发生在时钟恢复之后、第一个功能采样沿之前必要时在 UPF 里调整-restore顺序或加约束。5.5 从 xrun 日志里快速定位失败点的三条命令遇到回归失败不要直接打开整个几十 MB 的日志翻。xrun 生成的日志里错误信息通常带Error前缀但有些文件里也会出现非致命错误比如 coverage 的Warning。我一般先跑grep -n Error\|Fatal xrun.log | head -50 grep -n UVM_ERROR\|UVM_FATAL xrun.log | tail -50第一条用于快速定位编译和 elaboration 阶段的问题第二条用于定位仿真运行阶段的 UVM 报错。如果日志里全是FSDB写入失败之类的问题优先看磁盘空间df -h .提示在回归脚本里给每条 xrun 加-log 用例名.log让日志文件名对应用例名。这样批量失败时能直接按名字搜索不会出现几十个xrun.log互相覆盖的惨案。6. 进阶技巧用-input脚本做自动复现、seed 管理和断言收敛验证到了这一章你已经可以把 xrun 用得比较顺了。剩下要提升的是验证效率的最后一公里自动化复现、seed 管理和断言收敛。xrun 里有一个被低估的功能-inputTcl 脚本不只是“结束仿真”用的它能做流程控制。比如在 UVM 环境里你想跑 1000 个不同种子来验证某个随机约束稳定性不需要循环启动 1000 次完整编译可以在一个仿真进程内多次重新随机# regress.tcl set seed 1 while {$seed 100} { xrun_uvm_reset xrun_uvm_set_seed $seed run -time 100us if {[xrun_uvm_test_done]} { 捕获仿真结果 } incr seed } quit -f这段脚本对每个种子复用同一个编译结果在仿真内核层面重新跑随机序列省掉了重复 elaboration 的时间。但注意xrun_uvm_reset需要 testbench 里正确实现 reset 流程且每次 reset 后所有寄存器状态必须回到初始化值否则后面的种子结果是污染的。我在实际项目里会要求 TB 把 reset 序列写成一个 task并且在 event 里触发配合-input脚本的循环才能真正把回归时间降下来。另外一个进阶用法是断言收敛验证。对于有大量 SVA 断言的设计回归跑完看pass/fail并不够还要看哪些断言一次都没触发过。Xcelium 提供断言覆盖率报告和普通覆盖率一样收集到-covdbs里。我习惯在回归末尾统一生成一份 assertion coverage 报告xrun -covreport -covdbs ./cov_db -type assertion通过这份报告你能直接看到哪些断言从未生效。很多验证组问题就在这里断言写了几百条实际上只有几十条被触达剩下的是“存在但没意义”的。把这类断言和普通覆盖率合并分析定向补激励收敛速度会明显加快。最后一件事是自动复现。随机仿真挂了要把出错的种子固定下来。xrun 的 seed 控制不像某些工具那么隐蔽在-input脚本里通过xrun_uvm_set_seed拿到当前失败种子后把它写回一个大文件或环境变量。下次回归前直接指定这个种子重跑不应该出现同一用例另一组随机通过的情况。如果重跑同一个 seed 两次结果不一致那就要检查是不是有绝对时间依赖或者外部文件读入这类问题在 xrun 里通常是因为$fopen读到了非确定性内容先把这类非确定性源清掉再做随机激励回归。我的个人习惯是在每个回归脚本头部留一个SEED_FILE变量在失败时自动记录当前 seed然后把这个 log 和波形文件归档。这个习惯救过我好几次——否则一个随机失败用例没法稳定复现你只能干瞪眼。希望这份 xrun 操作指南能帮你在 Cadence 数字仿真流程里少走几个来回把时间花在真正需要思考的功能点上。本文还有配套的精品资源点击获取