ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Verdi波形调试SVA断言失败:从log到根因定位

Verdi波形调试SVA断言失败:从log到根因定位 SV 断言跑 fail 的时候仿真器吐出来的那行 log 大概是这行里最不体贴人的东西它只会告诉你某时刻某条 assertion failed至于到底是 antecedent 里哪个信号没起来、还是 consequent 晚了一个周期、还是干脆被 disable iff 掐掉了一个字都不说。做验证这些年我遇到过太多次这种场面——设计同事站在你工位旁边等结论你盯着 log 发呆。后来我的习惯就固定下来了断言一 fail先别改断言、先别怀疑 RTL直接开 Verdi 把波形拉出来用 assertion 视图把那次 attempt 的每级条件摊平了看八成的问题五分钟内能定位。这篇讲的就是用 Verdi 观察 assertion 波形做 debug 的完整路数编译期要埋哪些开关、波形 dump 有什么坑、断言窗口怎么用、采样点为什么总跟你直觉反着来、以及几类特别容易骗人的假失败。适合正在写 SVA 的验证工程师也适合被断言追着跑的设计同事刚入行的朋友照着走一遍基本能建立起自己的一套排查顺序。1. 断言报错只给一行 log为什么还得开波形1.1 断言的三态里log 只告诉你一种很多人对断言的理解停留在过了/没过两态实际是三种pass、fail还有最阴的vacuous success空成功。第三种的意思是property 的前提条件从头到尾就没成立过于是整条断言被判定为成功但它其实什么都没验。log 里它和真正的 pass 长得一模一样你只有在波形上看到 antecedent 那一段从头到尾是灰的才能意识到这条断言在你的测试用例里压根没被激活。而真正的 faillog 给的信息也就到时间点为止。举例一条req |- ##[1:3] ack失败可能是 req 起来了但 ack 三个周期都没来也可能是 req 起来的那拍本身就带着 X。这两种情况 RTL 侧的修法完全不同一个查握手逻辑一个查复位或初值。log 分辨不了波形可以把 req、ack、相关的 valid/ready 全部加到 nWave 里对着失败时刻往前推几个周期看原因基本是明摆着的。所以我的判断标准很粗暴能被 log 直接解释清楚的断言失败通常不是真问题比如断言写错了、约束写错了。凡是需要想一下的直接上波形别硬扛。1.2 Verdi 里跟断言沾边的几个入口Verdi 提供断言调试能力的地方不止一处摸清楚各自能干什么比记菜单路径重要。我常用的有三块一是 nWave 里的 Assertion 视图它以状态条的方式把每条断言在时间轴上的 pass/fail 画出来双击失败点直接跳转二是断言失败列表窗口把所有失败的 attempt 按时间排好适合快速扫一遍到底炸了几条、集中在什么时间段三是 related signals 功能能把某条断言引用到的信号自动抓出来加进波形省得你手动一个个找。这三块配合起来用流程很顺先用失败列表看全局确定哪条断言在什么时候开始炸跳到 nWave 的对应时刻看断言状态条确认是第一次失败还是持续失败再把 related signals 拉出来逐个周期核对条件。我一般会把这三步当成固定的肌肉记忆不跳步。提示断言视图里看不到状态条和状态条全是灰的是两种完全不同的故障前者是信息没进波形库后者多半是 vacuous。排查方向完全不同别混着查。2. 编译和仿真阶段就要埋好的伏笔2.1 编译选项让断言信息进得了 KDB断言调试失败八成的原因不在波形窗口里而在更早的编译阶段。VCS 默认并不会把所有断言细节塞进调试数据库你必须在编译命令行上明确要求。我的标准配置大概是这样vcs -full64 -sverilog \ -debug_accessall \ -kdb \ -assert svaext \ -assert enable_diag \ -f filelist.f \ -l comp.log逐条说下为什么。-debug_accessall打开完整的调试访问权限新版本里它通常已经隐含生成 KDB 数据库但我还是习惯显式再写一个-kdb图个稳——不同版本对隐含行为的处理有差异显式写不会有副作用隐式依赖却可能在某次升级后失效。-assert svaext是打开 SVA 的语言扩展支持很多写法比如更复杂的序列、局部变量没它是编不过或者行为不一致的。-assert enable_diag是关键中的关键它让 VCS 在编译时收集断言的诊断信息并写进 KDB没有这个开关你在 Verdi 里点开断言视图大概率是空的或者只能看到最粗粒度的结果。还有个容易被忽略的点如果断言是用bind的方式挂到 RTL 上的KDB 里依然能收到它但断言的层次路径会挂在被绑定的目标模块下。找断言的时候别一直盯着 tb 的层次找往下翻到 RTL 层次里看看。2.2 波形 dump为什么你总看不到成功的断言这块是我踩坑最多的地方。默认情况下FSDB 里记录的断言信息是不完整的尤其是那些成功的 attempt很多人的波形里压根没有。原因在于SVA 信息要不要落盘、成功的那次要不要落盘是两个独立的开关。在 testbench 的 initial 块里基础的 dump 长这样initial begin $fsdbDumpfile(wave.fsdb); $fsdbDumpvars(0, tb_top, all); $fsdbDumpSVA(0, tb_top); end$fsdbDumpvars负责常规信号$fsdbDumpSVA负责断言本身的信息。少写后面那句你在 nWave 里就找不到断言视图的入口再怎么翻 View 菜单也是白搭。然后还有运行期开关。命令行走./simv fsdbsva_success fsdbregion -l sim.logfsdbsva_success的作用是让那些成功的 attempt 也落到波形里。不开它你只能看到失败的 attempt于是这条断言到底有没有被触发过这个问题就永远回答不了——vacuous success 也正因此变得极具迷惑性。开了它antecent 命中但 consequent 还没判定的那些中间状态也能看到调试体验完全是两个档次。代价是波形文件会明显变大断言越多、成功次数越多涨得越夸张。所以我通常做法是日常跑回归不开定位问题的时候单独重编重跑一个带fsdbsva_success的版本。别为了省事全局打开回归的磁盘空间会教你做人。阶段开关作用不开会怎样编译-kdb生成 Verdi 调试数据库打不开源码/断言关联编译-assert enable_diag收集断言诊断信息断言视图为空编译-assert svaext启用 SVA 扩展复杂序列编不过仿真$fsdbDumpSVA把断言信息写入 FSDB波形里没有断言仿真fsdbsva_success记录成功的 attempt无法判断 vacuous2.3 断言本身的写法决定了它好不好调断言的可调试性很大程度上在写的时候就已经定死了。我踩过的坑包括一条断言里塞了五六个信号的条件失败之后根本不知道该先看哪个断言没有名字波形里全是assert_xxx_unnamed_1234对不上号还有把 reset 相关逻辑全写在disable iff里结果复位期间什么都不判问题反而被藏起来。我的习惯是每条断言给一个有意义的 label比如a_axi_awvalid_stable_until_ready波形里一眼能认出来一条断言只验一件事宁可拆成三条也不要揉成一条disable iff里只放真正必须屏蔽的条件别为了不报错而滥用。这些习惯在写的时候多花五分钟调的时候能省半小时。3. 实操从失败点一路追到根因3.1 打开波形先把失败清单捋一遍拿到一个断言失败的 case我的第一步不是打开波形找那条断言的信号而是先看全局。启动命令通常是verdi -dbdir simv.daidir/kdb -ssf wave.fsdb -nologo -dbdir指向编译生成的 KDB-ssf打开波形文件。两个都给上Verdi 才能把源码、断言和波形关联起来只给波形文件你能看信号但断言视图和源码跳转都会缺。进去之后先打开断言失败列表扫一遍。这一步的目的不是修问题是判断问题的分布是某一条断言零星炸还是短时间内多条一起炸是集中在仿真刚开始的复位附近还是集中在某个事务密集的窗口。前者多半是断言或约束的问题后者更可能是 RTL 在特定场景下的真实缺陷。分类做完了再决定往下怎么查比一头扎进波形里翻信号效率高得多。3.2 拆一条真实的失败握手超时怎么查假设失败的是这么一条断言property p_req_ack; (posedge clk) disable iff (!rst_n) req |- ##[1:3] ack; endproperty a_req_ack: assert property (p_req_ack) else $error([%0t] req without ack within 3 cycles, $time);失败时刻你可以从失败列表直接拿到。跳到那个时间点之后第一件事是看 antecedentreq在失败那一拍的采样值到底是不是 1。注意这里有个特别容易搞错的地方——断言的采样发生在时钟沿之前的 preponed 区域也就是说波形上时钟上升沿那个时刻对应的采样值取的是上升沿之前那个稳定值不是上升沿之后随着组合逻辑变化的新值。如果req在采样点确实是 1那就往后数三个周期逐个看ack。这里还有个细节##[1:3]的意思是 1 到 3 个周期内任意一个周期ack为 1 都算过而不是第 3 个周期必须为 1。如果你发现 ack 在第 4 个周期才起来那就是纯粹的性能/时序问题跟断言没关系去看 RTL 的响应逻辑。如果 ack 一直没起来但有ack_pending之类的内部信号起来了那问题就在输出的最后一级寄存或者握手协议上。我把这类排查整理成一张对照表平时直接照着看波形现象大概率原因下一步看什么req 采样值为 0 但你觉得该是 1采样点理解错了看时钟沿前一拍的稳定值req 为 X复位或初值没清干净追 rst_n 和寄存器初值ack 晚于窗口才起来响应路径延迟过长看 RTL 内部状态机ack 一直为 0握手逻辑未触发看仲裁、fifo 满等阻塞条件断言从未被触发全灰vacuous success确认 req 是否真的产生过3.3 时序对齐和采样点最反直觉的那部分$sampled、$rose、$stable这几个系统函数的行为是断言调试里最容易让人怀疑工具出错的地方。拿$rose(sig)举例它判断的是当前采样点上 sig 为 1 且上一个采样点为 0采样点依然遵循 preponed 规则。所以你在波形上明明看到信号在时钟沿之后才从 0 变 1$rose却在那一拍就返回了真这不是工具错了是你和工具对当前时刻的定义不一样。另一个高频坑是disable iff。它在条件成立时会直接终止断言既不报 pass 也不报 fail。如果你的波形里某条断言在某段时间突然消失别急着怀疑 dump 出了问题先看看那段时间 reset 是不是被拉低了、或者某个 disable 条件是不是被触发了。我见过好几次新人花了半天查断言为什么丢了最后发现是复位释放的时刻和自己想的不一致。注意断言调试时永远保持工具是对的、我的理解可能错了这个前提。断言采样规则和普通信号的时序直觉是两套体系先把规则对齐再谈谁有问题。4. 常见问题速查与排查技巧4.1 波形里压根看不到断言这是最高频的一类问题按下面的顺序查基本能覆盖先确认编译有没有-assert enable_diag和-kdb再确认 testbench 有没有调$fsdbDumpSVA接着确认启动 Verdi 时有没有带-dbdir。这三步是必查项缺一个都会导致断言视图空着。如果这三步都齐了还是看不到那就要考虑是不是断言根本没被编译进去——比如用了条件编译宏把它排除掉了或者断言所在的bind文件没被加进 filelist。这种情况有个很直接的验证办法故意在断言里插一句$display看仿真 log 里有没有打印。没有打印说明它压根没生效跟波形 dump 一点关系都没有。4.2 那几类特别会骗人的假失败有几类失败的成因和表象差得很远我单独拎出来说。第一类是 X 态传播有时候断言失败不是因为逻辑错而是因为某个控制信号在某段时间是 X导致整个条件表达式求值结果不可预测。这类失败在波形上表现为大量信号同时变红特征很明显修的方向是查复位和初始化不是改断言。第二类是位宽和符号问题。比如你比较的两个信号一个有符号一个无符号或者一个是 8 位一个是 16 位仿真器会按规则做隐式扩展结果和你的预期不一致。这类问题在断言里特别隐蔽因为断言本身写得看起来完全正确。我的做法是凡是涉及比较的断言两边位宽和符号显式写清楚宁可多写几个$signed、$unsigned。第三类是时钟域。如果断言里引用的信号来自不同时钟域采样点就会变得非常混乱。这类问题我一般的处理是断言只在单一时钟域内写跨域的检查交给专门的同步器或 CDC 检查工具不要在一条断言里混着来。4.3 几个能明显提效的小习惯第一个习惯是保存波形视图。把常用的信号组、断言视图的布局存成.rc或者视图文件下次打开直接加载省掉每次重新拖信号的两三分钟。调一个复杂 bug 的时候你会反复开这个波形这个习惯省下的时间很可观。第二个习惯是用 Tcl 脚本做批处理。Verdi 支持 Tcl很多重复性的操作加信号、找断言、跳到指定时刻都可以脚本化。我现在习惯把一个 case 的常规操作写成一个脚本打开波形时直接 source很快就能把现场铺好。第三个习惯是从失败时刻往回倒着看。人的直觉是顺着时间往前追但断言的失败是在某个时刻被判定的原因通常在这之前的一到两个周期。养成从失败时刻开始、一个周期一个周期往回推的习惯比从仿真开头顺着看快得多。5. 把断言调试往后延伸一点5.1 cover property 和 assert 在波形上的差别cover property和assert property在波形里的表现完全不同很多人会混。assert 有明确的 pass/fail 状态失败会报错cover 只记录这个序列有没有被命中过命中就是一次覆盖点没命中也不会报错。所以你在断言视图里看到的那些从来没变过状态的条目很可能就是 cover 而不是 assert。这个区别在调试时很有用如果某条 assert 一直是灰的vacuous我会顺手在旁边加一条同样条件的 cover跑一次看它到底有没有被命中过。cover 命中了但 assert 还是灰的说明问题出在 consequent 的判定上cover 也没命中说明前提条件本身在这个用例里就没成立问题是激励不够不是设计有问题。这个手法我用了很多年判断是激励问题还是设计问题几乎百试百灵。5.2 断言调试和覆盖率、后仿的关系功能覆盖率里通常会有一类专门统计断言命中的覆盖组它会告诉你哪些断言在整个回归里从来没被触发过。这批零命中的断言是很有价值的线索——要么是断言写得不对要么是激励根本没覆盖到对应的场景。我会定期把零命中的断言列表拉出来逐条看波形确认原因这个动作比闷头加测试用例更有针对性。另外提一句后仿。断言在后仿里的行为和前仿会有差异主要来自延迟信息的引入原来零延迟的组合逻辑现在有了延时antecedent 的采样点可能因此错开一个周期导致一批前仿好好的、后仿全炸的假失败。遇到这种情况先别改 RTL去核对断言的时间窗口是不是卡得太死通常把##[1:3]这类窗口放宽一点就能解决。5.3 一个我自己的取舍最后说说我个人的取舍我不是所有断言都开fsdbsva_success。日常回归只保留失败信息波形文件能小一大截一旦某个 case 要调我单独跑一个带全量断言信息的版本针对性分析。调试版本的编译和仿真时间会明显变长波形文件也大用它做全量回归是自找麻烦。把它当成一把专用工具而不是默认配置心态会顺很多。提示断言调试的现场信息很值钱尤其是一次难复现的失败。定位到根因之后顺手把当时的波形视图和排查结论记一笔下次同类问题能直接复用。这套流程我用了很多年从最早只会盯着 log 发呆到现在基本能做到断言一 fail 就有明确的排查路径中间踩过的坑大部分都写在上面了。真正难缠的从来不是断言本身而是那些看起来没问题、实际采样点和你想象不一样的时序细节——把规则对齐剩下的都是体力活。
返回列表