
1. 项目概述在Batch Mode下捕获仿真波形在数字电路设计尤其是ASIC和FPGA验证流程中仿真波形是我们调试和分析设计行为的“眼睛”。对于使用Cadence NC-Verilog/Xcelium的工程师来说交互模式Interactive Mode下通过GUI添加信号、运行仿真、查看波形是常规操作。然而当我们需要进行大规模的回归测试、长时间的后仿或者在无图形界面的服务器/计算节点上运行任务时Batch Mode批处理模式就成了唯一的选择。在这种模式下仿真器从头到尾自动执行没有人工干预的环节那么如何确保关键的波形数据被自动保存下来以供后续离线分析呢这就是我们今天要解决的核心问题配置NC-Verilog在Batch Mode下自动保存指定信号的仿真波形到数据库文件。很多工程师初次接触Batch Mode仿真时会遇到一个尴尬的局面仿真成功跑完了日志也没有报错但就是找不到波形文件无法进行时序和逻辑的深入分析。手动编写一个TCL脚本在仿真启动时自动执行“探针Probe”命令是解决这个问题的标准且高效的方法。本文将从一个资深验证工程师的角度详细拆解如何通过一个精心编写的startup.tcl脚本实现对仿真波形的灵活、自动化捕获。无论你是正在搭建自动化验证环境还是需要为某个特定测试保存深度调试信息这套方法都能直接拿来复用。2. 核心思路与脚本设计解析在Batch Mode下让NC-Verilog保存波形其核心思路是利用仿真器提供的初始化和预加载机制。NC-Verilog支持在启动时通过ncinput选项加载一个TCL脚本这个脚本会在仿真内核初始化之后、开始运行run之前自动执行。我们可以在这个脚本里放置所有需要在仿真前配置好的命令其中最关键的就是probe命令。2.1probe命令的角色与工作原理probe命令是Cadence仿真器中用于创建信号数据库的瑞士军刀。你可以把它理解为一个“示波器探头”的自动化配置工具。在交互模式下你在波形查看器SimVision里用鼠标点选信号后台其实就是生成了对应的probe命令。在Batch Mode下我们通过TCL脚本提前写好这些“探头”的配置告诉仿真器“请你在运行过程中持续记录这些信号的变化并把数据存到指定的文件里。”这个命令的强大之处在于它的高度可配置性。它不仅仅能决定记录哪些信号-all或指定层次还能决定记录数据的格式-shm,-vcd,-evcd、数据库的名称-database、以及记录的层次深度-depth。理解每个参数的含义和适用场景是写出高效、实用脚本的前提。2.2 脚本设计的关键考量点设计startup.tcl脚本时我们需要权衡几个关键因素信号范围与深度是记录整个测试平台TB的所有信号还是只记录核心设计模块DUT的信号记录的层次结构要深入到什么程度过深的记录会导致波形文件SHM数据库体积爆炸式增长严重影响仿真性能和磁盘I/O过浅则可能丢失关键的子模块内部状态不利于调试。数据格式选择SHM、VCD、EVCD各有优劣。SHM是Cadence原生的高性能二进制格式文件小、读写快但只能用SimVision或Cadence工具查看。VCD是IEEE标准的文本格式通用性强任何支持VCD的工具都能读但文件巨大生成和加载都很慢。EVCD是VCD的扩展支持更多的数据类型。运行控制脚本的结尾必须是run命令吗不一定。probe命令配置的是数据记录器而run是启动仿真运行的命令。在更复杂的脚本中我们可以在probe之后进行其他设置比如设置断点stop、或执行一段自定义初始化TCL代码最后再调用run。对于最简单的“保存波形并跑完”这个需求probe后直接接run是最直接的。3. 脚本编写与参数详解让我们从一个最基础、最常用的脚本实例开始然后逐步分解每个参数并探讨如何根据实际项目需求进行定制。3.1 基础脚本实例创建一个纯文本文件命名为startup.tcl内容如下# startup.tcl - 自动保存仿真波形 probe -create -shm -database my_waveform -all -depth 2 run这个脚本做了两件事创建-create一个波形探针使用SHM格式-shm将数据保存到名为my_waveform.shm的数据库-database my_waveform记录所有-all信号的波形记录层次深度为2-depth 2。执行仿真运行run直到遇到$finish系统任务或达到指定的时间。3.2 核心参数深度解析3.2.1 信号选择参数-allvs. 指定路径-all这是一个“省心但可能昂贵”的选项。它会让仿真器记录当前仿真环境中所有可见的信号。在大型设计中这会产生海量数据。适用场景小型模块验证、或初期调试时不确定问题出在哪里需要全局视野。指定信号路径这是生产环境推荐的做法。你可以使用通配符来精确定位需要关注的信号集极大地减少数据量。# 只记录测试平台顶层信号 probe -create -shm -database my_waveform tb.* -depth 0 # 记录DUT顶层及其下一级子模块的所有信号 probe -create -shm -database my_waveform dut.* -depth 1 # 混合记录同时添加特定关键信号 probe -create -shm -database my_waveform dut.* sys_clk sys_rst_n tb.monitor.err_count -depth 1实操心得我通常会为不同的测试场景准备不同的startup.tcl脚本。例如wave_full.tcl用于记录所有信号进行深度调试wave_dut_only.tcl用于回归测试时只记录DUT接口和内部关键状态wave_cover.tcl则专门记录与功能覆盖点相关的信号。通过Makefile或脚本根据仿真目标动态选择加载实现灵活性和效率的平衡。3.2.2 数据库格式参数-shm,-vcd,-evcd-shm(Shared Memory)默认且最常用的格式。仿真器将波形数据写入一个二进制的SHM数据库文件.shm目录。它的优势是速度快、文件相对紧凑并且与SimVision工具链无缝集成支持反向标注back-annotation、信号值查询等高级调试功能。绝大多数情况下你应该首选-shm。-vcd(Value Change Dump)生成一个文本格式的.vcd文件。它的唯一优势是通用性可以被GTKWave、Synopsys DVE等其他厂商的波形查看器打开。致命缺点是性能文本解析和写入速度慢文件体积通常是SHM格式的10倍甚至100倍以上。仅在需要与其他非Cadence工具链交换波形数据时使用。-evcd(Extended VCD)VCD的增强版支持reg和wire以外的更多数据类型。优缺点与VCD类似。注意-shm、-vcd、-evcd是互斥的选项一次probe只能选择一种格式。如果你需要同时生成SHM和VCD必须写两条probe命令。3.2.3 层次深度参数-depth N这个参数控制信号记录的“纵向”深度。-depth 0只记录你指定的路径本身的信号。例如probe -create tb.dut.* -depth 0只会记录tb.dut这一层下的所有信号如tb.dut.clk,tb.dut.data但不会记录tb.dut.u_submodule子模块内部的任何信号。-depth 1记录指定路径及其下一级子模块的所有信号。这是最常用的设置因为它既能抓住模块接口和关键内部寄存器又不会让数据量失控。例如对于tb.dut深度1会记录tb.dut.*以及tb.dut.u_ram.*,tb.dut.u_ctrl.*等直接子模块的信号。-depth 2或更高记录更深层次。随着深度增加信号数量呈指数级增长。务必谨慎使用通常只在追踪一个非常具体的、深埋于层次结构中的bug时才临时启用。省略-depth如果不指定仿真器使用默认深度1。所以在我们最初的例子中-depth 2是可以省略的效果等同于-depth 1。但显式地写出来是一个好习惯可以让脚本的意图更清晰。3.2.4 数据库名称参数-database NAME这个参数指定生成的波形数据库文件的基本名。对于SHM格式最终会生成一个名为NAME.shm的目录。对于VCD格式会生成NAME.vcd文件。省略-database如果不指定NC-Verilog会使用一个默认的数据库名通常是ncsim.shmSHM格式或ncsim.vcdVCD格式。在自动化环境中强烈建议显式指定一个有意义的名称例如包含测试用例名、时间戳或种子号-database test_alu_randseed12345这样可以避免多次仿真输出相互覆盖便于归档和追溯。4. 完整实操流程与集成方法掌握了脚本的编写方法后我们需要将其集成到实际的仿真流程中。下面是一个从环境准备到执行分析的完整闭环流程。4.1 环境准备与脚本创建首先在你的仿真工作目录下使用任意文本编辑器如Vim, VSCode, gedit创建startup.tcl文件。根据你的调试需求选择上述参数组合编写脚本内容。一个针对中等规模DUT的推荐配置如下# 文件名wave_dut_debug.tcl # 描述记录DUT及其子模块信号用于功能调试 probe -create -shm -database waves/${TESTNAME}_${SEED} dut.* -depth 1 probe -create -shm -database waves/${TESTNAME}_${SEED} tb.clk tb.rst_n tb.start tb.done tb.error -depth 0 probe -create -shm -database waves/${TESTNAME}_${SEED} tb.monitor.* -depth 0 run这个脚本做了几件聪明的事使用了变量${TESTNAME}和${SEED}来动态生成数据库名这需要你在调用NC-Verilog前定义这些TCL变量或者通过环境变量传递。将波形数据库统一输出到waves/子目录下保持工作区整洁。分层记录对DUT记录深度1对TB顶层的关键控制信号只记录自身深度0对特定的监控模块记录其所有信号。4.2 启动仿真并加载脚本在终端中使用ncverilog或xrunXcelium的启动命令命令启动仿真并通过ncinput选项指定你的TCL脚本。# 基本用法 ncverilog ncinputstartup.tcl -f filelist.f defineSIMULATION top_tb.v # 更实际的例子包含常见选项 xrun \ -f ../rtl/filelist.f \ -top top_tb \ -access rwc \ -input wave_dut_debug.tcl \ definePOST_SIM \ UVM_TESTNAMEmy_test \ -svseed random \ -l simulation.log命令解释-input wave_dut_debug.tcl 这是ncinput的另一种等效写法告诉仿真器在初始化后执行该TCL脚本。-access rwc 确保对设计文件的读写权限这对于probe命令正常工作有时是必要的。-l simulation.log 将仿真器的标准输出和错误重定向到日志文件便于批处理运行后查看结果。4.3 仿真执行与输出确认仿真启动后你会在终端或日志文件中看到仿真进度。如果脚本加载成功通常会有类似以下的输出ncsim source wave_dut_debug.tcl Creating shm database waves/test_alu_randseed12345 Probing signals... ... ncsim run仿真结束后检查当前目录或waves/目录下是否生成了test_alu_randseed12345.shm文件夹及其内部的一系列数据文件。4.4 波形查看与分析生成SHM数据库后你可以使用Cadence的SimVision工具进行离线查看和分析。# 打开SimVision并加载指定的波形数据库 simvision waves/test_alu_randseed12345.shm 在SimVision中你可以像在交互仿真中一样添加信号到波形窗口进行测量、标记、查找等所有调试操作。这就是Batch Mode仿真的价值所在将计算密集型的仿真过程与图形化的调试分析过程解耦。你可以在高性能服务器上无头headless运行大量夜间回归测试第二天早上再在本地工作站上加载感兴趣的用例波形进行详细分析。5. 高级技巧与避坑指南在实际工程应用中仅仅掌握基础命令是不够的。下面分享一些能显著提升效率和可靠性的高级技巧以及我踩过的一些“坑”。5.1 动态脚本生成与集成在复杂的UVM或基于Makefile的验证环境中硬编码的startup.tcl可能不够灵活。我常用的方法是使用脚本如Perl、Python或Shell动态生成TCL文件。示例通过Makefile集成TEST_NAME : alu_random_test SEED : $(shell echo $$RANDOM) WAVE_DB : waves/$(TEST_NAME)_seed$(SEED).shm sim_wave: mkdir -p waves echo probe -create -shm -database $(WAVE_DB) dut.* -depth 1 dynamic_wave.tcl echo probe -create -shm -database $(WAVE_DB) tb.clock tb.reset tb.scoreboard.* -depth 0 dynamic_wave.tcl echo run dynamic_wave.tcl xrun -f filelist.f -top tb -input dynamic_wave.tcl TESTNAME$(TEST_NAME) SVSEED$(SEED)这样每次运行make sim_wave都会生成一个包含随机种子和测试名的唯一波形数据库完美避免了文件覆盖。5.2 性能优化与磁盘空间管理波形记录是仿真性能的主要瓶颈之一。以下是一些优化策略按需记录切忌贪婪这是最重要的原则。永远不要在生产环境的回归测试中使用-all。仔细定义你的调试信号集合。使用-depth明智地裁剪大部分bug通过模块接口和一级子模块的内部状态就能定位。将-depth设为1作为默认值。分时段记录有时bug只出现在仿真的特定阶段。你可以使用TCL的when命令在特定时间点才开始记录。# 在仿真时间100ns后开始记录波形 when {now 100ns} { probe -create -shm -database late_wave dut.counter -depth 0 } run 200ns定期清理SHM数据库是目录直接rm -rf即可。建议在自动化脚本中加入归档和清理逻辑例如只保留最近一周的波形或者只保留失败的测试用例波形。5.3 常见问题排查实录问题1仿真运行了但没有生成任何波形文件。可能原因1probe命令语法错误或路径不存在。仿真器在source TCL脚本时遇到错误可能静默失败或报错在日志中。排查方法仔细检查仿真日志文件simulation.log搜索“Error”、“Warning”以及你的TCL脚本文件名。确保信号路径在仿真环境中存在。可能原因2脚本中没有run命令。probe只是设置了记录器没有run仿真不会执行。确保脚本最后有run、run 1ms或类似的执行命令。可能原因3数据库文件路径权限问题。如果指定了像waves/这样的子目录确保该目录存在且可写。可以在TCL脚本开头加入file mkdir waves来创建目录。问题2波形文件生成了但SimVision打开后看不到预期的信号。可能原因1-depth设置过浅。如果你指定了tb.dut.* -depth 0那么你只能看到tb.dut这一层的信号子模块tb.dut.u_core内部的信号不会被记录。尝试增加深度或直接指定子模块路径。可能原因2信号在仿真期间从未发生变化。对于常量或初始值后一直保持不变的信号有些视图设置下可能默认不显示。在SimVision中尝试使用“Add All Signals in Region”功能。可能原因3信号被优化掉了。如果某个信号在代码中未被“使用”例如只被驱动但它的值不影响任何其他逻辑或输出综合或仿真工具可能会将其优化掉。在RTL代码中为该信号添加(* keep *)或/* syn_keep */等属性具体语法取决于工具防止其被优化。问题3仿真速度因为记录波形而变得极慢。立即行动检查你的probe命令。你是否使用了-all-depth是否设置得过大比如5或99信号列表是否包含了像tb.*这样庞大的总线优化策略立即将记录范围缩小到最小的可疑模块。使用-depth 1。考虑是否真的需要记录整个测试平台的波形也许只记录DUT和比对器checker的信号就足够了。问题4需要生成VCD给第三方工具但文件太大。妥协方案VCD文件庞大是无法根本解决的缺点。可以尝试以下方法缓解只记录最关键、最少的信号。缩短记录的时间范围例如只记录出错前后的一段仿真时间。考虑使用压缩工具对生成的.vcd文件进行压缩如gzip在需要时再解压查看。不过这增加了操作步骤。评估是否真的必须使用VCD。如果团队内部都使用Cadence工具链极力说服大家使用SHM格式可以节省大量时间和存储空间。掌握在Batch Mode下保存波形的技能是数字设计验证工程师迈向高效自动化工作流的关键一步。它让你摆脱了对图形界面的依赖使大规模验证、持续集成和远程计算成为可能。从今天起为你每一个重要的测试用例配上定制的波形记录脚本吧。