Cadence Xcelium (xrun) 命令行仿真完全指南:从编译到覆盖率
简介面向硬件验证工程师、芯片设计师与半导体设计自动化从业者的 Cadence Xcelium 数字仿真工具操作指南重点讲解 xrun 在仿真中的基础命令与高级特性。资源以单个 PDF 文档形式提供压缩包大小约 505KB内容精炼但知识点密度高目前已有 2050 人学习下载。文档从环境安装与版本检查入手依次说明单步仿真、多阶段分离执行、常用编译选项及仿真中间目录的结构与作用进阶部分则涵盖三类主流波形文件的生成、覆盖率数据收集策略、门级仿真时的时序反标与检查关闭方法并针对常见报错和许可证失效问题给出系统化排查思路同时介绍了快速检索选项与错误信息的辅助命令。借助这份指南读者可快速上手该工具链在项目规划、日常验证或故障定位中提升效率与正确性适合从初学者到有一定经验的工程师按需查阅。1. Cadence数字仿真工具Xcelium(xrun)先搞定命令入口再谈高级功能Cadence数字仿真工具Xcelium(xrun)一上手最常见的翻车姿势就是把 xrun 当成 VCS 的换皮同样的 filelist、同样的-sverilog、同样的 UVM 编译参数换台机器一跑就报错报错信息还看不懂。Xcelium 是 Cadence 主推的数字仿真器xrun 是它的统一命令行入口完整工作流是“编译生成快照 → 运行快照 → 调试波形”每一步的中间产物都和 VCS 的simv不一样有 snapshot 文件、有xcelium.d缓存目录、有 SimVision 的 ASDB 波形库。这篇笔记写给要拿 xrun 跑通 SV/UVM 验证、UPF 低功耗仿真和覆盖率回归的人内容全部围绕可以直接抄的命令与参数读完能自己搭起最小工程再往里填 IP 和测试用例。适合数字验证工程师、做前端仿真的在校生以及从模拟转数字、第一次碰 xrun 的混合信号工程师。2. 理解 xrun 的编译-仿真模型三段式命令行与四个高频参数2.1 一体模式与快照xrun 不是“编译加运行”两个命令第一次用 xrun 的人最容易懵VCS 里vlogan编译、vcs链接、./simv运行三个阶段清清楚楚Questasim 里vlog、vsim也是两个独立工具。xrun 默认走的是“先编译、后仿真”的一体流程——一条xrun命令会完成 RTL 解析、elaboration、生成可执行快照、然后立即把仿真跑起来。如果只想要编译结果就得加-c或-compile参数如果只想运行已经生成好的快照直接执行快照文件即可。这个“快照”是 Xcelium 和所有 Cadence 数字流程的关键。快照就是把编译和 elaboration 的结果序列化成一个可执行文件后续回归测试可以直接启动它跳过前端解析。常见做法是# 编译阶段生成 sim_top 这个快照 xrun -f filelist.f -sv -uvm -timescale 1ns/1ps -access rwc -o sim_top # 运行阶段每次case直接跑快照 ./sim_top -q -exit UVM_TESTNAMEsar_adc_base_test第一行只编译不仿真第二行只跑仿真实例。同一份设计跑上百个用例时只编译一次后面全部走快照启动时间会从分钟级降到秒级。注意如果改了 RTL 或者 package 定义必须回到第一行重新生成快照否则跑的是“昨天的代码”。2.2 高频参数速查-timescale / -sv / -uvm / -accessxrun 参数非常多但新手期真正决定生死的就那么几个。下表是每个新环境我都先确认一遍的参数参数作用常见坑-timescale 1ns/1ps设置全局时间单位和精度不写时文件又缺 timescale仿真时间会变成无单位整数-sv按 SystemVerilog 编译.sv/.v文件不写时遇到class、interface直接语法报错-uvm加载 xrun 自带 UVM 库并完成预编译不写时uvm_pkg找不到报错和真实设计错误混在一起-access rwc打开读、写、连接权限不加时$shm_probe和force可能完全无效参数顺序没有硬性要求但我的习惯是编译类参数放在文件列表前面运行类参数放在快照后面这样别人读脚本时一眼能分清阶段。-access rwc是其中最容易被忽略的一个它能让你在调试阶段对信号做force也能让波形 dump 权限足够缺了它后面开波形一片黑排查起来非常绕。2.3 大工程组织-f 文件清单与编译顺序的隐藏规则真实项目不可能手动把几百个文件敲在命令行里。xrun 用-f指定文件清单清单内还可以嵌套子清单。这里有一个隐藏规则xrun 基本按文件在清单中出现的顺序编译被例化的模块、被 import 的 package 必须先于引用方出现。# filelist.f —— xrun 会按从上到下的顺序处理 -f ../ip/rtl_ip_list.f # 子文件清单可以嵌套 ../rtl/top.sv # 顶层先编译但子模块必须在它之前 ../rtl/sar_adc_clkdiv.sv # 被例化的模块放这里 ../tb/tb_pkg.sv # package 先于使用它的 module ../tb/tb_top.sv # testbench 最后 defineSIM_MODE # 可以用 define 传宏 -incdir ../tb # include 目录用 -incdir不是 -I这段清单里../ip/rtl_ip_list.f是子清单里面同样要求“被依赖的在前”。-incdir是 xrun 自己风格的 include 路径写法如果你习惯写-I ../tb在这里不生效。文件顺序错误时报错信息往往是“can‘t find design unit”或“Unknown module type”误导你以为是文件缺失实际上只是顺序问题。3. 最小工程跑通RTL、testbench 与 make 脚本一次到位3.1 示例设计给 SAR ADC 控制逻辑配一个分频计数器流程验证不需要复杂设计一个可综合的同步计数器足够。这个示例完全可以挂在 8 位同步 SAR ADC 的数字控制逻辑里用于给转换状态机产生节拍计数器到load_value时拉高count_done状态机据此进入下一次逐次逼近。// sar_adc_clkdiv.sv —— 可综合的模 load_value 同步计数器 module sar_adc_clkdiv ( input logic clk, input logic rst_n, input logic en, input logic [3:0] load_value, output logic [3:0] cnt, output logic count_done ); always_ff (posedge clk or negedge rst_n) begin if (!rst_n) cnt 4‘h0; else if (en) begin if (cnt load_value) cnt 4’h0; else cnt cnt 1‘b1; end end assign count_done en (cnt load_value); endmodule信号含义很简单load_value是计数目标en高电平时每个时钟周期加一到目标后清零并输出一个周期的count_done高电平。用这个设计跑 xrun 的目的不是为了验证计数器本身而是让编译、仿真、波形、退出码这四个环节在一个小工程里完整闭环。等换成真正的 SAR ADC 控制状态机时流程完全一致。3.2 testbenchSV 风格端口连接与 $shm_probe 波形 dumptestbench 用 SystemVerilog 写tb_pkg这个空 package 是为以后放 UVM sequence、driver 预留的位置。用.*做端口连接前提是 tb 里声明的信号名和 DUT 端口名完全一致小写模块名别写错。timescale 1ns/1ps package tb_pkg; // 预留后续的 UVM sequence / driver 定义放在这里 endpackage module tb_top; import tb_pkg::*; logic clk 0; logic rst_n 0; logic en 1; logic [3:0] load_value 4‘d7; logic [3:0] cnt; logic count_done; sar_adc_clkdiv dut (.*); initial begin $shm_open(waves.shm); // 打开波形库 $shm_probe(tb_top, ASDB); // 抓取 tb_top 下所有信号 end always #5 clk ~clk; // 10ns 周期 initial begin repeat (2) (posedge clk); // 复位释放 rst_n 1; repeat (20) (posedge clk); // 跑 20 拍 $display(“TEST PASSED, cnt%0d”, cnt); $finish; end initial begin $monitor($time,, “cnt%0d count_done%0b”, cnt, count_done); end endmodule$shm_open和$shm_probe是 Cadence 仿真器自带的波形接口Xcelium 后台落地的是 ASDB 波形库但函数名沿用了老的 SHM 叫法。$shm_probe(tb_top, ASDB)表示抓取整个tb_top层级调试阶段够用如果只关心 DUT可以缩小范围写成tb_top.dut。$monitor会在信号变化时打印一行是终端快速确认行为的低成本手段。3.3 编译运行一行命令与 make 脚本把清理动作固定下来把上面两个文件和 filelist 放进同一个目录编译运行可以分两步走xrun -f filelist.f -timescale 1ns/1ps -sv -uvm -access rwc -o sim_top ./sim_top -q -exit第一行生成sim_top可执行快照第二行运行它。-q抑制仿真器启动 banner-exit保证仿真结束后进程自动退出不会停在交互环境里等输入。不加-exit时如果脚本里用了管道容易卡在交互提示符上。把这两个动作写进 make 脚本是让整个验证环境可复现的关键一步TARGET sim_top SRCS filelist.f sim: xrun -f $(SRCS) -timescale 1ns/1ps -sv -uvm -access rwc -o $(TARGET) -q ./$(TARGET) -q -exit clean: rm -rf xcelium.d $(TARGET) waves.shm cov_work *.logclean目标里的xcelium.d是 xrun 的编译缓存目录老版本叫INCA_libs。跑一次make clean相当于把编译中间产物、快照、波形库全清掉是从“旧状态”回到“裸环境”的最快方式。后面排雷章节会反复用这个动作。4. 增量编译与快照复用把回归从分钟级压到秒级4.1 什么时候用 -incremental、什么时候必须 clean拿到一个较大的工程后每次改一行 RTL 都全量编译很痛苦。xrun 提供增量编译基本思路是复用之前的 elaboration 结果只重新处理变化的文件。# 小步调试只增量编译避免全量 xrun -f filelist.f -incremental -sv -uvm -timescale 1ns/1ps -access rwc -o sim_top # 正式回归前清理后全量重建 make clean xrun -f filelist.f -sv -uvm -timescale 1ns/1ps -access rwc -o sim_top增量编译对“只改了 tb 里的$display、只加了断言”这类改动非常快。但有三类改动我强烈建议直接全量重建改动了 package 里的类定义、改动了模块端口列表、改动了宏定义。增量编译检查文件时间戳和依赖关系但不总是可靠尤其是 package 内容变化后依赖它的 class 可能仍在使用旧布局。我自己的习惯是白天小步调试用-incremental晚上投正式回归前一定make clean再全量编译一次。这个动作花不了几分钟但能排除掉大量“代码改了但结果没变”的玄学问题。4.2 用 -input 脚本把波形和断点从命令行交出去波形 dump 不一定要写死在 testbench 里。xrun 支持-input指定一个 Tcl 脚本在仿真运行时控制波形抓取和运行流程。好处是同一个快照有人想开波形有人不想开不需要改代码重新编译。# run.tcl —— 等效于 SimVision 里手动打开波形库并加信号 database -open waves -shm -into waves.shm probe -create -database waves -depth all tb_top.dut.cnt tb_top.dut.count_done run -all exit这份脚本执行的动作是打开waves.shm波形库抓取cnt和count_done两个信号跑完所有仿真时间后退出。跑回归时用-input来控制波形比在 tb 里写死$shm_probe灵活得多每个 testcase 想单独决定开不开波形只需要在命令行决定要不要加-input run.tcl。# 不带波形跑 ./sim_top -q -exit # 带波形跑 ./sim_top -q -exit -input run.tcl这套命令等效于 SimVision GUI 里的 Database → Open 和 Add Signals具体参数在不同版本间略有差异但逻辑一致。项目里维护一份run.tcl比让每个工程师手工开波形更能保证 debug 效率。4.3 回归脚本里的退出码、随机种子与并发真正跑回归时不是手动敲./sim_top而是写一个循环脚本把多组随机种子和 UVM test 串起来。Xcelium 里最常见的随机种子参数是ntb_random_seed它直接传给 UVM 的随机化机制。#!/bin/bash for seed in 1 2 3 4 5; do ./sim_top -q -exit \ UVM_TESTNAMEsar_adc_base_test \ ntb_random_seed$seed \ -log run_$seed.log if grep -q “TEST PASSED” run_$seed.log; then echo “seed $seed ok” else echo “seed $seed FAILED” fi done注意判断成功条件时不要只依赖进程退出码。xrun 在某些版本下$finish(1)会让退出码非零但仿真本身可能正常结束反过来仿真崩溃时退出码也不统一。用 grep 日志里的“TEST PASSED”做判据比裸看$?稳定得多。这就是“仿真日志比退出码真实”的一线经验。5. xrun 常见问题排查五个把新手卡到深夜的报错与解法5.1 报“can’t find design unit”不是文件缺失是编译顺序和库路径问题现象仿真器报ERROR: cant find design unit sar_adc_clkdiv但你明明把文件加进 filelist 了。原因最常见是 filelist 顺序不对testbench 出现在 RTL 前面或者 IP 的预编译库路径没加-y库目录没有指向正确的.v库。这对应于模拟仿真里“器件未定义”的报错本质都是单元没进编译视野。解决先检查 filelist把被例化的模块放在例化方之前再检查有没有漏-y库路径最后用make clean清掉缓存重新编译。不要在旧缓存上反复试缓存里的旧编译信息会让问题更难定位。5.2 -timescale 缺失所有 #10 都变成没有单位的时间现象tb 里写#10波形上 eda 的时间也显示10但整个仿真时间轴和时钟周期对不上行为时快时慢。原因文件里没有timescale命令行又没传-timescale仿真器把所有延时当成无单位整数处理。解决在 xrun 命令行统一加-timescale 1ns/1ps。如果显式在每个.sv文件头写 timescale文件内声明会覆盖命令行建议全工程统一用命令行参数不要混用混用以后跨文件调用延时会出现意想不到的量级偏差。时间单位不一致是仿真行为“不收敛”的最隐蔽来源之一。5.3 增量编译后“代码改了但行为没变”现象改了 RTL 里的 always 块重跑-incremental波形和日志还是老结果。原因增量编译对依赖关系判断过于保守或者上一次编译留下的缓存已经损坏更常见的是快照还是旧的你实际跑的是旧快照。解决先确认命令行里有没有-o把快照名写混多个工程共用一个快照名时后编译的会覆盖先前的跑的人根本不知道自己在跑哪个版本。出现可疑情况别硬猜直接make clean再全量编译一次。增量编译是效率工具不是正确性保证在回归投递阶段必须用干净环境验证。5.4 UVM_TESTNAME 传了但 test 空跑日志只有 UVM_INFO现象命令行传了UVM_TESTNAMEsar_adc_base_test仿真正常结束但日志里既没有 sequence 执行也没有 test body 里的打印。原因类名拼写不一致或者 UVM 库版本和-uvm加载的库不匹配或者 sequence 只是在 test 里被实例化但start没有被调用。另一种隐蔽情况是run_test()写成了run_test(xxx_test)字符串硬编码会覆盖命令行参数。解决在 test 的build_phase里加一行$display(test name %s, get_type_name())先确认跑的是不是目标 test。命令行参数统一用UVM_TESTNAME包名::类名的完整形式并且去掉run_test()里的硬编码字符串。文件列表里把 UVM sequence 定义文件放在 tb 之前这个顺序问题在 2.3 章已经强调过。5.5 波形文件打开一片黑信号栏里什么都没有现象SimVision 打开waves.shm波形窗口存在但所有信号都是空的。原因编译时没有-access rwc$shm_probe没有权限抓到信号或者 probe 调用发生在仿真已经开始之后或者仿真进程被 kill -9波形库没有正常 flush。解决编译参数补上-access rwc把$shm_open和$shm_probe放在 initial 块的第一行仿真结束前不要强杀进程让它正常走到$finish。如果你用了-input run.tcl确认 Tcl 脚本里的database -open路径没有中文或空格Cadence 波形库对路径字符集比较敏感这是很多人开黑屏时完全想不到的坑。6. 高级功能这样用才顺手UPF 低功耗、覆盖率收集与多核回归6.1 UPF 最少步骤先跑通再谈复杂隔离低功耗验证是 xrun 区别于普通 Verilog 仿真的一个重要卖点很多用户搜 xrun upf其实就是想搞清楚低功耗仿真怎么开。先看一个通用 UPF 片段set_design_top sar_dut create_power_domain PD_TOP create_power_domain PD_SAR -elements {dut/sar_adc_clkdiv} create_supply_port VDD_SAR set_related_supply -power VDD_SAR -domain PD_SAR这段 UPF 用通用语法描述了电源域划分PD_SAR只包含sar_adc_clkdiv这一个子模块并为它声明独立的供电端口。实际项目里还会加 isolation 和 retention 策略但最简流程是先把电源域跑通。xrun -f filelist.f -sv -uvm -timescale 1ns/1ps -access rwc -upf top.upf -o sim_upf ./sim_upf -q -exit-upf让 xrun 读取 UPF 文件并在 elaboration 阶段检查电源域声明是否和 RTL 层次一致。如果你的 UPF 里引用了不存在的层次仿真器会直接报错。先跑通最小 UPF再逐步加 isolation cell 和 retention register排查范围会小很多。6.2 覆盖率-coverage all 一行开启数据落在 cov_work覆盖率收集是验证完整性的硬指标。xrun 的-coverage all一行打开所有默认覆盖率维度配合-covtest命名测试用例数据会自动落在cov_work目录下。覆盖率维度关注点什么时候重点看行覆盖每行代码是否执行过功能验证初期分支覆盖if/case 每个分支是否走到状态机、仲裁逻辑表达式覆盖组合逻辑中短路条件是否覆盖复杂门控逻辑toggle 覆盖信号是否发生过 0→1、1→0寄存器、总线交接断言覆盖并发断言和序列是否命中协议验证xrun -f filelist.f -sv -uvm -timescale 1ns/1ps -access rwc -coverage all -covtest sar_adc_basic -o sim_cov ./sim_cov -q -exit -covtest sar_adc_basic imc -session cov_work/sar_adc_basic -covtest给这次覆盖率收集起名字第二次跑同一个名字时数据会合并。回归结束用 Cadence 的 IMC 工具打开cov_work下的会话可以看哪些分支缺口大、要不要补用例。开 coverage 会拖慢仿真所以调试阶段别开投回归前再统一开。6.3 回归调度才是充分利用多核的正道很多人在 xrun 里找多核加速选项但回归提速最直接的方式是让多个快照进程并行跑不同随机种子。#!/bin/bash for seed in {1..32}; do ./sim_top -q -exit \ UVM_TESTNAMEsar_adc_base_test \ ntb_random_seed$seed \ -log run_$seed.log if (( seed % 8 0 )); then wait # 每8个一组避免一次拉起太多进程 fi done wait脚本把 32 个仿真进程按 8 个一组投入后台每一组跑完wait再放下一组。这里有个真实教训xrun 每个进程都要占用 license 的仿真 feature并不是开 32 个进程就快 32 倍license 不够时进程会排队日志里全是等待信息。把并发数控制在 8 左右通常是最稳的折中。我自己在 UPF 上吃过亏第一次投低功耗回归忘了在 UPF 里写 isolation 策略仿真报告全部通过但后仿真的功耗数据对不上白白浪费两天。后来养成了习惯——任何 UPF 改动先做一次干净编译再用-input run.tcl拉波形确认 isolation cell 是否真正插入。这套最小流程能帮你少走这点弯路希望帮到你。本文还有配套的精品资源点击获取