4. 仿真控制与运行管理

编译通过只是把设计“翻译”成了可执行的仿真快照,真正让验证平台跑起来、跑得久、跑得可控的是 xmsim。本节围绕五个相互配合的控制面展开:启动选项决定仿真以什么模式进入、时间控制决定仿真跑多久、波形记录决定留下哪些可回看的证据、断点与交互调试决定出问题时如何停下来观察、多核加速决定在可接受时间内能否收敛。理解它们之间的配合关系,比单独记住某个开关更重要。

一、xmsim 的启动方式与关键运行选项

Xcelium 的典型流程是 xrun 一步完成编译与仿真,或先 xmelab 生成快照、再用 xmsim 反复运行。当需要多次以不同参数运行同一快照时,分离式流程更高效:编译一次,仿真多次。一个常见的启动命令形态如下:

xmsim -snapshot tb_top -timescale 1ns/1ps \
      -input run.tcl \
      -log xmsim.log \
      -sv_seed random \
      +TESTNAME=smoke_test \
      +UVM_VERBOSITY=UVM_LOW

其中 -snapshot 指定要运行的快照名;-input 指定启动后自动执行的 Tcl 脚本,用于加载波形、设置断点、启动 run;-log 把控制台输出同时写入日志文件,便于回归时批量检索;-sv_seed 控制 SystemVerilog 随机种子,固定种子可复现问题,随机种子用于探索覆盖;+TESTNAME 与 +UVM_VERBOSITY 是传给测试平台的 plusarg,用于选择用例和调整打印级别。这些选项的共同作用是:让同一次编译产物在不同运行配置下被复用,而不是每次改参数都重新编译。

二、仿真时间控制:run、时间精度与超时保护

仿真时间控制的核心是“跑多久”和“以什么步长推进”。在交互模式下,run 命令支持多种粒度:

run              # 一直运行到 $finish 或遇到断点
run 100ns        # 运行 100ns 仿真时间后暂停
run -absolute 2us  # 运行到绝对时刻 2us
run -step        # 单步推进一个时间片

时间精度由编译期的 -timescale 决定,例如 1ns/1ps 表示时间单位 1ns、精度 1ps。精度设置过粗会掩盖亚纳秒级竞争,过细则显著增加事件调度开销。工程上通常让 DUT 与 TB 使用一致的时间精度,避免跨模块延时被舍入。

长时间仿真必须有超时保护。可在 Tcl 脚本中设置看门狗:

# 若 30 分钟真实时间仍未结束,则强制停止并保存现场
set timeout 1800
after $timeout { puts "TIMEOUT: dumping state"; stop -save }
run

超时后不要直接杀掉进程,而应先 stop -save 保存当前状态,再检查是否卡在某个 wait、死循环或握手未完成。判断依据是:日志最后一条有效打印的时间戳、当前仿真时间、以及各关键信号的最后跳变时刻。

三、波形记录:从“全量 dump”到“按需记录”

波形是调试的主要证据,但全量记录会让波形文件迅速膨胀。合理的做法是分层记录:顶层接口信号全记,内部深层信号按需开启。常用控制方式包括:

  • 编译期用 -access +r 或 +rwc 开放信号可见性,否则运行时无法 probe 内部信号;
  • 运行期用 Tcl 的 database -open 打开波形库,probe 指定要记录的信号;
  • 用 +define+DUMP_LEVEL 在代码中条件编译 $dumpvars,控制记录深度;
  • 对长仿真采用分段记录,例如每 1ms 生成一个波形文件,避免单文件过大。
database -open waves -shm -into waves.shm
probe -create tb_top.dut -depth 3 -all -memories
run 1ms
database -close waves

记录范围要服务于调试目标:验证总线协议时重点记录 valid/ready/data 与握手时刻;验证状态机时重点记录状态寄存器与转移条件。记录过少会在出问题时无法回溯,记录过多则拖慢仿真并淹没关键跳变。

四、断点与交互调试:让仿真在关键条件处停下

断点分为时间断点、信号断点和条件断点。时间断点用于在指定时刻暂停;信号断点用于在信号跳变时暂停;条件断点则把暂停条件写成表达式,只在满足业务条件时停下,避免在无关跳变上反复中断。

stop -time 500ns
stop -condition {tb_top.dut.state == 3'b101 && tb_top.dut.err_flag}
stop -signal tb_top.dut.req -edge rising

停下之后,交互调试的价值才真正体现。常用命令包括:examine 查看信号当前值,deposit 强制赋值以验证假设,step 单步推进,where 查看调用栈,trace 追踪驱动来源。一个典型排障路径是:条件断点命中 → examine 检查输入与状态 → 判断是激励未到位还是 DUT 响应错误 → 用 deposit 临时修正输入验证后续逻辑 → 记录结论后继续 run。

xmsim 仿真控制与运行管理信号链 启动与选项 -snapshot / -input -sv_seed / +TESTNAME -log / +UVM_VERBOSITY 时间控制 run / run 100ns -timescale 1ns/1ps 超时看门狗 after 波形记录 database -open probe -depth / -memories 分段 dump 控制体积 断点与交互调试 stop -time / -signal / -condition examine / deposit / step where / trace 定位驱动源 多核仿真加速 -mce 多核编译与仿真 | -parallel 并行求解 按模块/分区拆分,减少单核事件调度压力 加速比受分区均衡度与跨核通信量制约 调试结论 反馈优化 加速配置 回写启动 结果判读 $finish 正常结束 超时/断点命中 日志时间戳比对 波形关键跳变核对

五、多核仿真加速:把事件调度压力分摊出去

当设计规模增大、单核仿真时间不可接受时,可启用多核仿真。Xcelium 提供多核编译与多核仿真能力,典型选项包括 -mce(多核引擎)与并行求解相关开关。其基本思路是把设计按模块或分区拆分到多个核上,每个核负责一部分事件的调度与求值,核间通过同步点交换必要数据。

加速效果并非线性。假设单核运行 1000ns 需要 100 秒,理想四核可降到 25 秒,但实际受三方面制约:一是分区是否均衡,若某个核承担了大部分活跃逻辑,其他核会空等;二是跨核通信量,握手频繁的模块被拆到不同核会引入同步开销;三是仿真本身的串行部分,如全局时钟、复位、共享总线仲裁,难以并行。因此工程上通常先做分区评估,再逐步增加核数,观察加速比是否进入收益递减区间。

一个可参考的配置形态如下:

xmsim -snapshot tb_top -mce 4 \
      -parallel \
      -input run.tcl \
      -log xmsim_mce.log

启用后应重点观察:日志中是否报告分区信息与核间同步次数;相同激励下仿真结果是否与单核一致;加速比是否达到预期。若结果不一致,优先检查跨核同步点附近的竞争条件,而不是直接怀疑设计逻辑。

六、把五个控制面串成一次可复现的运行

实际工作中,这五项能力通常组合使用。一个可复现的排障流程是:先用固定 -sv_seed 和 +TESTNAME 复现失败;用 run 配合超时看门狗定位卡死区间;在可疑时刻设置条件断点,命中后用 examine 与 trace 找到错误驱动源;确认问题后开启针对性波形记录,保留证据;最后在回归中启用多核加速,缩短整体收敛时间。每一步的输入、观察点和判断依据都不同,但目标一致:让仿真从“能跑”变成“可控、可查、可复现”。

建议把常用的 run、stop、probe、database 命令固化到 run.tcl 中,配合 -input 自动加载。这样每次启动 xmsim 都进入一致的调试环境,减少手工输入带来的差异。
波形记录与多核加速都会显著增加内存与磁盘开销。长时间回归前应确认波形分段策略与磁盘余量;多核模式下若出现结果不一致,应先关闭多核复测,确认是否为并行同步引入的问题,再决定是否继续使用该配置。