☰
Xcelium xrun 数字仿真实战:编译选项、覆盖率与UPF调优指南
2026/10/7 21:58:49 网站建设 项目流程

简介:一份面向硬件验证工程师、芯片设计师及半导体设计自动化从业者的Cadence Xcelium(xrun)数字仿真工具操作指南,初学者和有经验用户均可参考。文档系统讲解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.f:

counter.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 最实用的日常选项。第一次全量编译后,第二次运行加-incr,Xcelium 会只重编改动过的文件:

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 -f
xrun -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 还是 SHM

Cadence 生态里 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 32'h0 run -time 1us force -deposit /tb_counter/u_dut/count_signal 32'hFFFF 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 关联进 UPF,testbench 用-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 个高频问题

现象 1:xrun报 “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 路径,把同名头文件收敛到唯一目录;实在不行就全量重编。

现象 5:UPF 仿真中,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 数字仿真流程里少走几个来回,把时间花在真正需要思考的功能点上。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询