上周帮同事调一个APB桥接模块,现象很诡异:连续访问时第二个周期的pready会提前一拍拉高,协议上这是不允许的。同事已经把RTL翻了两遍,怀疑是状态机的复位逻辑出了问题,但就是看不出具体是哪一行。我在Verdi里把pready选中,顺着驱动链一路往上追,从组合逻辑到状态寄存器再到异步复位分支,前后不到十分钟就定位到一处优先级写反的case语句。这个过程让我很感慨:很多验证工程师遇到信号行为异常时,第一反应是“读代码”,但在复杂RTL里线性阅读效率太低,正确做法是先让工具把“谁驱动了这个信号”这层关系画出来,再顺着画出来的逻辑锥去读代码。
这篇内容围绕Verdi的信号追踪能力展开,会讲清楚nTrace和nWave怎么联动、定位驱动信号有哪些核心操作、哪些快捷键真正高频实用,还会用一个实际排障案例完整走一遍“从波形异常到根因定位”的链路。适合刚接触Verdi的数字IC设计/验证工程师,也适合那些已经会看波形但没系统用过逻辑追踪功能的同行。
1. 为什么说Verdi是数字芯片调试的“信号追踪”主力
1.1 Verdi到底解决了什么问题
先理解Verdi在整个流程里的定位。仿真器如VCS、Questa、Xcelium负责“跑仿真”,产生波形文件;而Verdi负责“看波形、看结构、找根因”。这两个环节是分开的。如果只用仿真器自带的波形工具看信号,你能做的只是把信号拖出来、放大、对比,本质上还是在用眼睛线性地扫。而Verdi的核心价值在于:它在加载波形的同时,还读了编译后的设计层级结构,把波形中的每个信号和RTL源码中的具体位置、逻辑连接关系建立了映射,因此你能从任意一个信号出发,向上去找它的驱动源,向下去看它影响哪些负载。
这个能力在调试中非常关键。RTL里一个信号可能是某个always块里多个条件综合出来的,也可能是好几个模块共同驱动的结果。人脑沿着代码去推逻辑关系,一次只能处理一条路径;而Verdi是把整张逻辑网表展开给你看,路径之间的关系一目了然。打个比方,阅读RTL排查信号异常就像在没有地图的陌生城市里找一家店,你得一条街一条街地扫;用Verdi追踪驱动信号则是开了导航,直接告诉你从当前位置到目标店要走哪条路。
1.2 FSDB是Verdi追踪链路的信息底座
Verdi原生支持FSDB格式波形。FSDB是Synopsys定义的二进制波形格式,相比VCD这种纯文本格式,文件体积小很多、加载速度快很多,而且内部保存了信号名与设计层级结构、RTL行号的映射关系。有了这层映射,nTrace才能在你选中一个信号时,立刻跳转到这个信号被定义的源码位置,也才能在逻辑锥追踪时准确还原出信号之间的连线关系。
实际项目中常用的dump写法是这样:
initial begin $fsdbDumpfile("tb_top.fsdb"); $fsdbDumpvars(0, tb_top, "+all"); end第一行指定FSDB文件名,第二行表示dump整个tb_top层以下所有信号。第三个参数“+all”会额外记录一些Verdi追踪需要的信息,比如信号方向、端口连接关系等。如果只写$fsdbDumpvars(0, tb_top)也能跑,但某些追踪功能可能因为信息不足用不了,所以我一般都会加“+all”。
另外,现在VCS和Verdi的联合仿真已经做得非常顺,用vcs -kdb编译一遍,仿真结束后不仅拿到fsdb波形,还会生成kdb.elab++目录,Verdi打开这个目录后可以加载到宏展开、参数解析之后的真实代码视图,追踪精度会更高。这个我后面单独讲。
1.3 nTrace与nWave各司其职
Verdi启动后最常用的两个窗口是nWave和nTrace。nWave是波形窗口,负责显示信号随时间变化的数值;nTrace是逻辑/源码窗口,负责显示RTL源代码、实例层级和信号间的逻辑连接关系。很多初学者把大量时间花在nWave里放大、缩小、量测,但真正高效的做法是:在nWave里发现异常,切到nTrace分析驱动关系,再回到nWave验证猜测。
这套“波形-结构”双窗口联动叫“反标(back-annotation)”。你在nWave里选中一个信号,nTrace自动跳到该信号对应的源码位置并高亮;反过来,在nTrace里选中一个信号,nWave里对应的波形曲线也会被高亮。这个双向联动是Verdi信号追踪体验的灵魂,后续所有操作都建立在这套联动机制之上。
2. nWave与nTrace联动的底层逻辑:先看现象,再找源头
2.1 双窗口联动必须先打开的几个选项
有些新装的环境里,联动功能默认没有完全打开,导致nWave选了信号,nTrace没反应。我一般在打开波形后的第一件事就是检查联动配置:在Verdi主界面菜单栏依次找到Tools -> Preference -> General,确认“Link nTrace and nWave selection”这类联动选项是勾选状态。不同版本菜单位置略有差别,本质上就是让两个窗口的选中事件互相通信。
还有一种更高效的联动方式,不是选中信号,而是选中一段“时间”。在nWave里用鼠标拖选一个时间范围后,右键选择“Trace Time Range”之类的功能,Verdi会把这个时间窗内的信号跳变关系在nTrace里展示出来。这对定位瞬态毛刺、建立时间违例之类的时序问题特别有用。
2.2 从波形反推驱动的标准路径
我自己的标准操作路径是这样的:nWave里发现某信号在某个时刻行为异常,先单击选中这个信号,然后切到nTrace窗口,此时信号已经在源码里被高亮。紧接着按快捷键或者右键选择Trace Cone或Get Drivers,Verdi会打开一个新的逻辑追踪视图,把这个信号的所有上游驱动源按照逻辑锥形式画出来。
这里要理解“驱动源”的含义。对寄存器类型信号来说,它的驱动源是某个always块或者initial块;对wire类型信号来说,驱动源是连续赋值语句或者模块输出端口;如果一个信号在设计中存在多个驱动点,比如两个always块同时给同一个reg赋值,那么Verdi会把多个驱动源都列出来,这正好是我们在时序检查里最怕遇到的多驱动问题。
2.3 先在nWave里确认“现象边界”
追踪之前必须先确认故障发生在哪个时间段。我见过不少同事不做时间切片,直接把整个仿真时长的驱动锥全部展开,结果一个信号的上游逻辑有几十个节点,信息量太大根本看不过来。正确的做法是先在nWave里把异常区间缩到最小,比如你要查第二个周期pready为什么会提前拉高,那就在第二个周期的边界前后各留一点点余量,选出一小段窗口,再去展开驱动锥。
这样做的好处有两个:一是Verdi在做逻辑锥追踪时可以结合时间信息做“动态追踪”,只显示在这个时间段内真正发生跳变或真正参与逻辑运算的路径,排除大量无关分支;二是你自己在看展开后的逻辑锥时不会迷路,始终知道当前关注的是哪个时间点附近的行为。
3. 定位驱动信号的七种核心追法
3.1 双击信号看Schematic视图
最直观的追法:在nTrace源码视图里双击某个信号名,Verdi会打开Schematic视图,把该信号和与它直接相连的驱动、负载以门级/模块级图形显示出来。如果你只想搞清楚“这个信号是从哪个模块哪个端口来的”,双击最快。
Schematic视图里,输入在左、输出在右,连线会标注信号名。你可以继续双击连线末端的新信号,一层一层往上跳。这种方式非常符合人类视觉习惯,缺点是当逻辑路径很深时视图会变得很宽。处理方法是用Schematic视图自带的收展功能,只展开当前关注的路径,不要一次性全部展开。
3.2 Get Drivers:从信号反推驱动源
如果双击之后你发现多层路径太深,直接右键信号名,在菜单里找Get Drivers或者Trace Cone,Verdi会为这个信号单独生成一个“驱动锥”视图,只保留所有上游路径,不显示负载方向。
这里注意,Verdi生成的驱动锥一般会给你两个选择:Combination Cone和Sequential Cone。通俗讲,前者只看组合逻辑路径,止步于第一个寄存器;后者把寄存器也展开,延续到更上游的寄存器/输入端口。调试时先看组合锥,确认组合逻辑本身没问题,再扩展成时序锥,层层递进。切忌一上来就把整个时序锥全部展开,路径里会混入大量历史状态相关的分支,干扰判断。
3.3 扇入扇出分析:不要只盯上游
“驱动信号”并不只是线性地往上游找。调试中经常出现这种情况:你找到了驱动源,逻辑看起来也没毛病,但输出就是不对。这时候问题可能出在另一个输入的扇入路径上。比如某个组合逻辑的表达式是a && b,a的来源没问题,b的来源被别的逻辑干扰了,只看主驱动路径是发现不了的。
所以Verdi的Trace Fan-in和Trace Fan-out功能非常实用。Fan-in是列出某个信号的所有输入来源;Fan-out是列出这个信号驱动了哪些后续逻辑。排查时我会先用Fan-in确认全部输入源,再到nWave里对比这些输入源在异常时刻的跳变情况,基本能快速锁定是哪条输入路径引入了错误值。
3.4 跨层次追踪:信号穿过子模块之后
实际IP内部信号往往经过多级层次:顶层信号进入子模块A,子模块A内部逻辑处理后输出到子模块B,再经过B的处理才到达目标信号。这种跨层次追踪里,最关键的操作是“进入实例下游”。
在nTrace源码窗口里,信号名旁边如果有一个类似实例符号的图标,说明这个信号在这个层次是端口,可以继续下钻。选中后按快捷键或者右键选择“Go to Source/Instance”,Verdi会跳转到对应端口在子模块内部的声明位置,接着再双击端口名,就能看到它在子模块内部的驱动逻辑。重复这个过程,就能完整跨越多层模块,从头到尾追踪一条驱动链。
有个细节:跨层次追踪时,nTrace窗口左侧的Hierarchy树会同步变化,当前你正处在哪个实例下会高亮显示。如果发现层次跳到了完全不同的模块,多半是你点错了同名信号。RTL里不同模块下同名信号非常多,建议追踪时时刻留意左侧层级树的路径。
3.5 用波形里的“Time Slicing”辅助追踪
这是比较进阶的玩法。在nWave里用鼠标选中一段异常时间区域,右键在菜单里找到“Trace Selected Time Range”或类似选项,Verdi会把这段时间内涉及到的信号活动映射到nTrace逻辑锥中,自动过滤掉没有在被选时间区域内发生跳变的路径。
这个功能对毛刺类问题极其有用。比如你发现某个信号在时钟上升沿附近出现了一个几百皮秒的Glitch,肉眼很难判断是哪个输入变化引起的。用时间切片选中毛刺区间,Verdi只会保留在这个区间内真正翻转的信号路径,通常一眼就能看到是某个异步输入信号在这个时刻发生了跳变,进而找到根因。
3.6 总线信号按位拆开追踪
总线信号在Verdi里的追踪有个常见误区:直接选整个总线信号,比如data_bus[31:0],追踪出来的逻辑锥会非常庞大,因为每一位可能来自不同的驱动逻辑。我建议先根据波形判断异常出现在哪一位,比如发现第7位异常,就在nWave里把data_bus[7]单独拖出来确认,再用这一位作为起点去做驱动追踪。
如果总线比较宽,位索引看着混乱,可以在nWave的信号列表里右键选择“Bus Bit Expansion”,把总线展开成32位单独曲线,然后双击异常的那一位。这样追踪路径会窄很多,定位速度明显加快。
3.7 用TCL Command窗口批量追
最后一种方式适合有批量需求的场景。Verdi的Command窗口支持TCL脚本控制,你可以用脚本一次性导出多条信号的驱动信息,或者生成多个信号的上游逻辑列表。比如有一百个信号需要检查是否存在多驱动问题,手工一个一个点会浪费大量时间,写几行TCL循环就能批量完成。
不过这里不打算给具体命令的原因是不同版本的Verdi TCL API命名有差异,而且很多命令需要结合具体设计。我的建议是先用GUI交互方式把事情跑通,再把敲过的命令从Command窗口的History里拷贝出来,做成脚本反复使用,这是最稳妥的学习路径。
4. 快捷键大全:一套真正的高频操作表
4.1 nWave与nTrace的高频快捷键总表
下面这张表是我在多个项目里用下来真正高频的按键,至少覆盖90%的日常操作。不同版本、不同操作系统下个别按键会有差异,但总体稳定性很高。
| 功能分类 | 快捷键 | 说明 |
|---|---|---|
| nWave视图缩放 | Z | 放大波形 |
| nWave视图缩放 | Shift+Z | 缩小波形 |
| nWave视图平移 | 鼠标滚轮 | 纵向滚动多条信号 |
| nWave视图平移 | Shift+鼠标滚轮 | 左右平移时间轴 |
| nWave光标跳转 | Ctrl+G | 弹出跳转对话框,输入具体时间 |
| nWave信号搜索 | Ctrl+F | 在信号列表里快速搜索信号名 |
| nWave跳转边沿 | 方向键左右 | 光标移动到信号下一个/上一个跳变沿 |
| nWave跳到边界 | Home / End | 跳到仿真起点/终点 |
| nTrace源码查找 | Ctrl+F | 当前文件内搜索文本 |
| nTrace全局搜索 | Ctrl+Shift+F | 在打开的设计范围内搜信号名/实例名 |
| nTrace查找下一个 | F3 | 重复上一次查找 |
| nTrace查找上一个 | Shift+F3 | 向上查找 |
| nTrace跳转信号源码 | 双击信号 | 打开Schematic视图或跳转到信号定义位置 |
| nTrace跳转实例层级 | Enter | 进入当前实例的子层级 |
| nTrace返回上层 | Backspace或Esc | 退出当前实例,返回上一层 |
| nTrace追踪历史回退 | Alt+左方向 | 回到上一次浏览位置 |
| nTrace追踪历史前进 | Alt+右方向 | 前进到下一次浏览位置 |
| nTrace信号使用点下一个 | F | 跳到该信号下一个被引用的位置 |
| nTrace信号使用点上一个 | Shift+F | 跳到该信号上一个被引用的位置 |
| Schematic视图放大 | Ctrl+鼠标滚轮或+ | 放大逻辑图 |
| Schematic视图缩小 | 减号键 | 缩小逻辑图 |
| Schematic适合窗口 | Ctrl+0 | 一键将视图缩放到适配窗口 |
| 通用撤销 | Ctrl+Z | 撤销上一步操作 |
| 通用重做 | Ctrl+Y | 重做被撤销的操作 |
| 切换最近窗口 | Ctrl+Tab | 在nWave、nTrace、Schematic之间快速切换 |
这张表建议收藏。实际调试时最常用的组合是:nWave里Ctrl+F找信号,Ctrl+G跳时间,双击信号跳到源码,再按F/Shift+F在源码引用位置间快速移动。这套流程熟练后,从发现异常到定位驱动逻辑通常只需要一两分钟。
4.2 通过.novas.rc自定义键位
Verdi允许通过配置文件重新映射快捷键。默认配置文件在用户主目录下的.novas.rc,没有就自己创建一个。Verdi读取该文件时会按里面的keybind语句修改按键绑定。
比如你想把“Trace Up”和“Trace Down”绑定到F2和F3,思路是打开.novas.rc,找到keybind相关段落,参考默认键位写法把F2和F3映射到对应的追踪动作。不同版本支持的指令名略有不同,具体语法以安装目录docs下的KeyBinding文档为准,但思路都一样。
我建议不要改默认键位,而是把自定义键位添加到现有配置里。因为多台服务器共用环境时,默认键位是大家通用的,你改动了某些默认键位,换台机器就得重新适应,反而降低效率。我自己通常只新增几个额外的组合键,比如把Alt+向上方向键绑定为“回到上一个追踪节点”,其他全部保留默认。
4.3 快捷键使用的三个避坑提醒
第一个坑:方向键在nWave里跳变沿的粒度问题。默认情况下方向键跳的是“光标前后最近的跳变沿”,但如果你同时选中了多条信号,跳变沿会按所有选中信号的集合来计算,经常跳到一个你并不关心的边沿。所以想精确跳转时,最好只选中目标信号一条曲线。
第二个坑:F和Shift+F在nTrace里跳转的是“当前信号的下一个引用点”,不是“下一行代码”。很多刚上手的人按F发现光标从模块端口跳到几百行之后的一个比较器里,以为是Bug,其实这是Verdi在帮你跳转到该信号在代码中被使用的下一个位置,用于快速遍历信号的全部影响点。
第三个坑:在Schematic视图里,方向键和放大缩小键的作用范围有点“上下文相关”。有时候按加号不是放大,而是展开某个模块的端口列表;有时候按Esc键会关闭当前Schematic而不是返回上一层。遇到这种情况不要硬记按键,直接看当前窗口底部或侧边的工具栏提示,Verdi会把当前上下文可用的操作列出来。
5. 实战复盘:一个握手信号被错误拉高的完整定位链路
5.1 故障现象与第一步剪枝
下面用一个简化但完全贴近真实项目经历的案例来串一遍整个追踪流程。
假设有一个APB从机接口模块,协议要求从机在地址译码成功后的下一个时钟周期拉高pready,表示数据准备好。仿真发现pready在第二个访问周期时被提前了半个周期拉高,波形上看就是高电平比预期早了一个delta cycle。这在协议检查器里会被报成违例。
我在nWave里先找到pready信号,单独拖出来,选中异常那一小段区间。因为异常发生在第二个访问周期,我先把时间光标定位到该周期,然后切到nTrace,信号已经被高亮在源码中。右键选择Trace Cone,先看Combination Cone,也就是组合逻辑路径。
展开后的逻辑锥显示,pready由一个assign语句驱动,逻辑大致是:
assign pready = (state == S_WAIT) && addr_ok && !bus_busy;三个输入中,addr_ok和bus_busy在波形上看都是稳定值,只有state可疑。于是第二步,向上追踪state的驱动源。
5.2 第二次剪枝:锁定状态寄存器的更新逻辑
展开state的驱动锥后,看到它由时序逻辑驱动,标准的两段式状态机写法。state寄存器在时钟上升沿更新,并且存在一个异步复位分支。nTrace会把三个主要路径都标出来:复位路径、状态跳转组合逻辑路径、以及寄存器本身的时钟驱动。
我先看组合逻辑路径,因为pready提前拉高发生在时钟边沿附近,大概率是组合逻辑里某个跳转条件提前满足了。继续展开状态跳转组合逻辑,发现跳转逻辑里有这样一段:
always @(*) begin case (state) S_IDLE: next_state = (apb_enable) ? S_WAIT : S_IDLE; S_WAIT: next_state = data_valid ? S_DONE : S_WAIT; S_DONE: next_state = S_IDLE; default: next_state = S_IDLE; endcase end看起来没有直接问题。但nTrace里的一个细节引起了我的注意:逻辑锥中data_valid信号的来源路径上有一个下拉箭头,表示它的驱动链还有更深层级。于是我继续追data_valid。
5.3 第三次剪枝:问题藏在数据通路的状态标志里
data_valid的驱动逻辑在一个子模块里,该子模块负责数据通路的状态标志产生。选中data_valid,再次Trace Cone,这次我直接把时间范围限制在异常区间的几十纳秒内。
展开后发现data_valid由内部计数器count的值比较产生,而count的更新逻辑里出现了一个容易被忽略的分支:
always @(posedge clk or negedge rst_n) begin if (!rst_n) count <= 4'd0; else if (clear_flag) count <= 4'd0; else if (en_count) count <= count + 1'b1; end注意优先级:clear_flag的优先级高于en_count。问题就在这里——clear_flag信号在这个访问周期内,因为上游某个握手信号被提前撤销,导致它的有效时间只维持了半个周期。如果clear_flag的有效沿比en_count的有效沿早到来,count会先被清零,但紧接着en_count又加一,count从0变1,data_valid就会提前一个时钟周期拉高,pready也跟着提前。
5.4 根因确认和修复建议
到这里,问题链路已经完整:某个握手信号提前撤销,导致clear_flag提前失效,count清零后立刻被加一,data_valid被提前拉高,进而pready提前拉高。整个过程用Verdi从pready追到data_valid,再追到clear_flag,总共三次Trace Cone,耗时约十几分钟。
修复方向就非常明确了:要么让clear_flag维持至少一个完整时钟周期,要么调整计数条件的优先级,把en_count的判断放在clear_flag之前。具体选哪种取决于设计意图,但至少调试侧已经把根因路径定位得清清楚楚,不用再靠猜。
这个案例想说明的是,Verdi信号追踪并不是一次性把整棵逻辑树展示给你,而是需要结合你的判断一步步剪枝。每一次Trace Cone都是在问“这个信号是从哪里来的”,当前一个追问得到答案后,再把答案里的可疑信号作为新的起点继续追问。周而复始,直到找到最初的问题来源。
6. 信号追踪过程中的常见坑与对应技巧
6.1 X态传播:源头也是X,追到一半断了
调试中最高频的坑就是X态传播。你一路追驱动源,追到一个多路选择器的输出,发现输出是X。再展开选择器的输入,发现两个数据输入端一个为0一个为1,而选择端select为X,导致输出X。到了这一步,问题已经从“现象信号”转移到了“select信号为什么是X”,还需要继续往上追。
Verdi在处理X态时有一个非常实用的功能:在nWave里可以把X和Z值用独立颜色高亮。配置方法是在nWave的波形颜色设置里,把X态设为亮红色、Z态设为亮蓝色。这样做的好处是,当你同时观察几十条信号的时序关系时,一眼就能扫出哪条信号在哪个时间段处于非0非1的可疑状态。
还有一个经验:X态传播往往不是单条路径,而是多条路径交织。遇到X态时,不光要追目标信号的驱动源,还要逆着时间往回找“X态第一次出现的时刻”,找出X态到底是在哪一级逻辑被引入的。这个“第一次出现点”才是真正的根因位置。
6.2 多驱动冲突:Verdi列出多个Driver怎么办
RTL中很少会故意让两个always块驱动同一个reg,但遇到inout端口、三态总线、或者经过某种自动生成代码时,多驱动问题就会出现。Verdi在追踪这种信号时会把所有驱动源都列出来,有时是两三个,有时甚至更多。
处理多驱动的第一原则:不要试图同时分析所有驱动源,先在nWave里把所有驱动源信号全部拖出来,观察在故障时间点哪个驱动源的输出发生了异常跳变。绝大多数情况下,多驱动冲突都表现为某一时刻一个驱动源处于高阻态,另一个驱动源在正常驱动,但高阻态的信号通过内部上拉/下拉影响到了总线电平。
如果nWave里实在区分不出来,第二个方法是在nTrace里逐个关闭驱动源的显示,只保留一个,隔离分析。Verdi的Schematic视图支持filter功能,可以把不关注的驱动路径暂时隐藏,只留当前怀疑的那个路径。
6.3 综合后网表追踪:名字被优化改掉了
做综合后仿真时,经常遇到RTL里的信号名在网表里已经不存在了,因为综合工具把冗余逻辑优化掉了,或者把多个信号合并成一个。这时候再用RTL里的信号名去搜索,结果自然是空的。
解决办法是在综合时保留用于调试的信息。以DC综合为例,编译时加上-debug相关选项,或者使用compile_ultra -no_autoungroup之类的方式保留层次和信号名。另外,也可以配合guidance database使用,Verdi可以读取综合工具生成的guidance文件,自动把网表信号名映射回RTL信号名。
如果是VCS+Verdi联合仿真的流程,强烈建议在VCS编译时加-kdb选项,这样Verdi加载网表后仍能看到比较完整的原始RTL结构信息,追踪体验会好非常多。我见过一些项目为了省编译时间不加-kdb,结果综合后仿真时信号追踪基本不可用,最后花在定位上的时间远超省下的编译时间。
6.4 dump信号不全,追踪链断裂
追踪有时候会在某一级突然断掉,错误提示类似“Signal not found in FSDB”。这种八成不是Verdi的问题,而是仿真时FSDB根本没dump到这条信号。
原因通常是dump层次太浅。$fsdbDumpvars(0, tb_top)里的0表示dump tb_top以下所有层次的信号,但某些情况下,比如tb_top下面挂了别的验证IP,或者使用了SystemVerilog interface里动态创建的信号,默认dump选项可能覆盖不到。解决办法是结合具体仿真环境调整dump范围,或者在目标模块里单独加一条$fsdbDumpvars(0, module_instance),把该模块下的信号强制dump进同一个FSDB文件。
另外,某些Verdi版本在打开FSDB时默认做了“信号归类”,一些信号被归到“Power/Ground”或“Assertion”类别里,列表里不直接显示。如果搜索明明输入了正确的信号名却找不到,试着在信号列表的Filter区域清除掉分类过滤,再搜一次。
6.5 跨时钟域信号追踪要留个心眼
跨时钟域(CDC)信号是追踪里的另一大难题。一个信号在clk_a域产生,经过同步器打两拍后进入clk_b域,你在clk_b域看到它,直接往上游追,会追到同步器的输出寄存器。到这里,逻辑链路没有断,但你很容易误以为问题出在同步器本身,而忽视了真正的源头在clk_a域的某个模块。
我的习惯是,凡是追踪路径中遇到两个时钟驱动的逻辑,就停下来先确认这个信号是不是异步信号。方法很简单,在nTrace里看同步器前面一级寄存器的时钟是什么,如果和当前逻辑的时钟不同,就要考虑是不是跨域问题。这时再去追clk_a域里真正的源信号,同时还要检查一下clk_b域有没有配置正确的异步约束。Verdi虽然有CDC相关的专门工具,但在信号追踪时保持这个警惕性可以帮你少走很多弯路。
7. 个人使用习惯与额外建议
前面聊了很多操作层面的东西,最后分享几个我自己在实际项目中养成的使用习惯,不一定适用于所有团队,但至少帮我节省过大量排查时间。
第一个习惯是每次追踪都用“时间切片”而不是全时间轴展开。包括我自己在内,很多工程师看到驱动锥的第一反应是全部展开,觉得这样信息最全。但信息全不等于信息有用,没有时间约束的展开会让几十条无关路径混进来,反而把真正关键的路径淹没了。先选时间窗口,再做追踪,相当于让Verdi当你的助手帮你过滤掉与故障时刻无关的逻辑。
第二个习惯是追踪过程中善用标记。Verdi支持在源码窗口和波形窗口添加书签标记,在nWave里也可以给特殊时间点添加注释。我一般每定位到一条关键路径,就在nWave里加一个标记,写明“这是第X次剪枝的起点”。这样做的好处是,如果追踪半小时后发现自己走错了方向,可以快速回到之前的分支点重新开始,不用从头再来。
第三个习惯是不要只用一种视图。Schematic视图适合看整体结构,Source视图适合读细节代码,Hierarchy视图适合确认当前所在的模块层级。三者结合使用,效率远高于只盯一个视图。很多人在Schematic里迷路了还不知道切回源码窗口看具体代码,结果在一个很宽的逻辑图里反复横跳,浪费大量时间。
第四个习惯是追踪前先确认设计代码有没有改动。这个坑我踩过不止一次:花半小时追一个信号,最后发现这个信号在最新版代码里已经改了逻辑,而波形是老版本仿真产生的。Verdi的nTrace窗口加载的是当前打开的设计代码,如果你用Verdi打开代码时版本和仿真时不一致,追踪到的路径很可能是错的。所以每次打开Verdi前,先确认用的文件列表和代码版本和仿真时刻一致,这一步花不了几秒钟,但能省下后面可能浪费的几小时。
Verdi信号追踪说到底就是一个逐步收敛的过程。先从宏观上找到异常信号,再用驱动关系逐级向上缩小范围,每次只追一步,每步都验证一次,最后总能收敛到根因。这套方法不需要太多花哨技巧,关键是养成“让结构和工具替你缩小范围”的习惯,而不是一头扎进代码细节里去徒手翻逻辑。希望这篇内容能帮你在下次遇到难啃的Bug时,少走一些弯路。