ARTICLE DETAIL

资讯详情

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

VCS HVP层级化验证计划:打通覆盖率收敛与功能验证

VCS HVP层级化验证计划:打通覆盖率收敛与功能验证 第一次把覆盖率报告投到评审会上被问了一句这个 87% 到底对应哪些功能点、剩下 13% 是谁的责任当场语塞——那是我刚开始接触 VCS 覆盖率流程时最狼狈的一次。后来才明白VCS 采集出来的 line/cond/fsm/tgl/branch 这些数字回答的是代码被跑到了多少而不是功能被验证到什么程度。这两件事之间的桥就是 HVPHierarchical Verification Plan层级化验证计划。所谓 VCS hvp planner我个人的理解是用 Verdi/VCS 这套工具链把验证计划本身变成结构化、可追踪、能和覆盖率数据挂钩的活文档而不是一张躺在共享盘里三个月没人更新的 Excel。这篇内容偏向数字 IC 验证方向适合正在用 VCS 跑仿真、已经开始被覆盖率收敛问题折磨的验证工程师也适合想搞清楚覆盖率与验证计划怎么打通的朋友。1. 先把 HVP 在 VCS 覆盖率体系里放对位置1.1 覆盖率数据库跑完之后真正缺的是什么VCS 的覆盖率流程本身并不复杂编译时打开采集开关仿真时把数据写进数据库最后用 URG 合并出一份报告。这套流程跑通以后你会拿到一堆以百分数呈现的数字颗粒度细到某一行 RTL、某个条件分支的某个操作数组合、某个状态机的某条跳转。问题恰恰出在这个细字上——数字太底层了它不知道自己在验证计划里对应哪一条需求。一个典型的场景某个模块条件覆盖率卡在 92% 上不去你打开报告一看是十几个分散在不同 always 块里的条件表达式没被完整触发。这十几个点里有的属于正常功能路径有的属于异常保护逻辑有的根本是冗余代码。报告不会告诉你哪些该管、哪些可以豁免更不会告诉你这些点分别是哪个测试用例该负责的。于是每周的覆盖率评审就变成了逐行读 RTL 的体力活标出几十行已确认无法覆盖下次回归又冒出来新的。HVP 要解决的就是这一层信息断层。它把验证计划这个本来只存在于文档里的人造物搬进工具链变成一个可以被工具解析、可以被版本管理、可以自动和覆盖率数据库做关联的实体。你在计划树上写的每个节点都能挂上具体的覆盖率条目和测试用例工具读一遍数据库就能告诉你每个节点当前是什么状态。1.2 HVP 的实质一份能被工具读懂的计划文本HVP 全称 Hierarchical Verification Plan关键词在 hierarchical 上。它不是一张平铺的清单而是一棵有父子关系的树顶层是模块或者大功能域往下是特性、子特性最底层的叶子节点才是真正可执行、可判定的条目。这个结构和我们写验证计划时的思维方式是对得上的只不过以前是用 Word 的多级标题来体现现在是工具里真实的树形结构。从文件角度看HVP 通常以一个独立文件的形式存在内容偏结构化很多版本里是类 XML 的文本格式这意味着它可以进版本库。这一点在我看来价值极高计划变更留下 diff谁在什么时候把一个节点从已验证改回进行中、为什么改都留痕。相比一份靠邮件来回传递的 Excel可控性完全不是一个量级。需要说清楚的是各家的叫法和入口在不同版本里会有差别有的版本把这块功能放在 Verdi 的覆盖率视图下有的通过命令行选项拉起计划视图参数名也换过几轮。我的习惯是上手先跑一遍-help把当前版本里实际存在的选项抄到自己的笔记里别直接照抄三年前论坛上的帖子命令。1.3 和 Excel 版验证计划表的真实差异很多人会问计划表用 Excel 维护得好好的为什么要换工具。我在两个项目上分别用过两种方式差异可以列成下面这张表。对比维度Excel 计划表HVP 计划树与覆盖率的关系手工把百分比抄进去工具读取数据库自动回填状态变更历史靠文件名加日期容易乱文件进版本库diff 可查更新成本每次回归后人工更新易滞后跑一条命令就能刷新团队协作同一份文件多人编辑易冲突结构化文本冲突范围小且可合并与波形/报告的联动无靠人肉对照可以从节点直接跳到覆盖率视图适合的阶段项目早期需求梳理中后期覆盖率收敛与签核真正让我下决心推 HVP 的是某次 tapeout 前的覆盖率评审。Excel 表上写着某特性已验证但去查覆盖率数据库对应 covergroup 的 cross bin 只覆盖了 60%只是当时更新表格的人抄错了版本号。这种错误在人工维护的流程里几乎无法杜绝而工具回填天然不会出现。1.4 什么阶段用它最划算我的建议是不要一上来就强推。项目启动初期需求还在变验证计划本身一天改三版这时候用工具建树反而是负担一张白板加一份文档更快。等 DUT 的接口和主要特性稳定下来、覆盖率流程能稳定产出数据库了再把计划往 HVP 里搬效率收益最明显。如果是从零开始的新项目可以一开始就建但节点粒度不要切太细允许它跟着需求长。2. 从覆盖率采集到计划回填的完整链路2.1 编译期的覆盖率开关与数据库命名约定HVP 要工作前提是覆盖率数据库得稳定产出。而数据库最容易出问题的地方就是命名混乱——每个人用自己的目录、自己的名字最后合并的时候谁也说不清哪个 vdb 是哪个测试跑出来的。我固定下来的做法是编译阶段只产生一份 compile 数据库运行阶段每个测试一份独立的运行库目录结构按测试名分。命令大致是这样# 编译阶段打开需要的覆盖率类型固定编译库路径 vcs -full64 -sverilog -debug_accessall \ -cm linecondfsmtglbranch \ -cm_dir ./cov/simv.vdb \ -cm_hier ./cfg/cov_hier.cfg \ -f ./flist/filelist.f \ -o ./sim/simv # 运行阶段每个测试单独一个库用 -cm_name 打标签 ./sim/simv -cm linecondfsmtglbranch \ -cm_dir ./cov/run/smoke_001.vdb \ -cm_name smoke_001 \ -l ./log/smoke_001.log这里有几个细节值得展开。-cm后面的类型不是开得越多越好cond和branch在大规模设计上会让数据库和运行时间都明显膨胀如果当前阶段主要关注功能覆盖可以考虑先只开linetgl加功能覆盖率等收敛后期再补齐。-cm_name这个标签很重要它会写进数据库里URG 合并后的报告能按测试名拆分你的计划节点绑定测试时才有可读的标识。-cm_hier指向的层级配置文件用来限定采集范围格式大致如下# cov_hier.cfg 示意 tree tb_top.u_dut.u_core -node tb_top.u_dut.u_core.u_mem_model -module tb_top.u_dut.u_core.u_legacy_block tree tb_top.u_dut.u_arbtree表示纳入-node和-module表示排除。为什么一定要写这个文件典型的 DUT 里总会有一些第三方 IP、存储器模型、或者纯粹的流水线打拍逻辑把这些纳入采集只会让报告充满永远无法覆盖的项评审时白白消耗注意力。我一般会给每个项目维护一份这样的配置文件进版本库改的时候在提交信息里写明原因。2.2 用 URG 做合并与报告为回填准备数据一堆分散的 vdb 没法直接喂给计划树得先合并。合并命令本身很简单urg -dir ./cov/run/smoke_001.vdb ./cov/run/featureA.vdb \ -dbname ./cov/merged/merged.vdb \ -format both \ -report ./cov/merged/urgReport \ -parallel-parallel在测试数量多的时候能省不少时间值得加上。-format both会同时生成文本和 HTML 两套报告文本的报告方便脚本解析HTML 的方便人看。合并这一步最常踩的坑是数据库版本不一致。如果某次回归用了修改过编译选项的 simv产生的 vdb 结构和别的库对不上URG 会报错或者静默丢弃一部分数据。我的处理方式是在回归脚本里记录每次编译的 simv 指纹比如文件的 md5和一个版本号只有指纹一致的库才允许合并到同一个 merged.vdb 里。这个约束听起来苛刻但能省掉大量报告数字莫名其妙的排查时间。2.3 计划树的节点怎么搭一个可落地的层次结构HVP 文件本身不建议手搓尤其不要手搓后再手工维护那样还不如用 Excel。正确姿势是在工具里建节点、连线、填属性让工具自己写文件。但你在动手之前得先想清楚树长什么样。我通常用三层验证计划结构示意 ├── 数据通路 │ ├── 仲裁优先级的边界组合 [已验证] 关联 cond cross bin │ ├── 背压连续 N 拍的场景 [进行中] 关联 fsm 跳转 covergroup │ └── 复位期间的请求丢弃 [未开始] 关联 line assert ├── 控制通路 │ ├── 配置寄存器的读写回环 [已验证] │ └── 非法配置的拒绝路径 [进行中] └── 异常处理 ├── 超时保护触发 [未开始] └── 错误状态的自恢复 [未开始]第一层是功能域第二层是具体特性第三层一般就不往下切了直接在特性节点上挂覆盖率条目。切得太深会让维护成本陡增而工具能提供的价值并不会随深度线性增长。节点上我固定填四类信息名字用动词短语比如背压连续 N 拍的场景而不是背压、描述一句话说清判定标准、责任人具体到人不写组名、关联项覆盖率条目、测试名、相关 bug 编号。判定标准这一栏最容易被省也最要命——如果一条计划节点的状态定义是模糊的那它永远会在进行中里挂着。2.4 覆盖率回填把数据库和计划树对上回填的本质是让工具拿覆盖率数据库去匹配计划节点上挂的条目。匹配的键通常是 RTL 的层级路径、covergroup 实例名、cross 名之类。这里有个很现实的约束RTL 层级路径一旦因为代码重构发生变化历史挂接就会失效回填时表现为某些节点突然变成未覆盖。应对办法有两个。一是挂接时尽量挂在 covergroup/cross 这种位于验证环境的条目上而不是 RTL 内部路径验证环境的稳定性通常高于 RTL 内部结构。二是把 RTL 路径挂接的范围限定在模块的某个固定层级以下避免路径前缀因为例化层次调整而整体漂移。回填完成后你会得到每个节点的状态和实际覆盖率数值的对照。不要只看状态数值也要看一个标着已验证但覆盖率只有 82% 的节点比一个标着进行中但覆盖率 100% 的节点危险得多。3. 层级怎么切才不白干3.1 按 DUT 结构切还是按功能特性切这是建计划树时第一个要做的决定也是我最开始纠结最久的地方。按 DUT 结构切树形和 RTL 层次一一对应路径好挂、责任人好分但缺点也很明显同一个功能特性如果横跨几个模块你会在好几个分支下重复描述它覆盖率条目也会被拆得七零八落。按功能特性切语义清晰、评审时一目了然但对验证环境的结构依赖更重需要你事先把 covergroup 的组织方式也按功能域对齐。我最后采用的是混合方式第一层按功能域比如数据通路、配置、中断、异常第二层开始按特性细分RTL 层级信息不作为组织维度只在挂接覆盖率条目时体现。这样评审时讲的是功能回填时匹配的是代码两边都不别扭。3.2 叶子节点的粒度细到什么程度算合适粒度太粗节点状态没有信息量数据通路已验证这种话没人敢签字。粒度太细节点的数量会爆炸维护成本压过收益。我摸索出来的参考线是一个叶子节点应当对应一个可以被单个或有数几个测试用例判定通过与否的特性判定点并且它挂接的覆盖率条目数量控制在个位数。举几个例子感受一下。仲裁器功能正常太粗了这不是判定点是一句口号。仲裁器在多个请求同优先级时按轮询顺序授权就合适它能挂一个 covergroup 的 cross也能明确定义通过标准。仲裁器所有可能的优先级组合又太细了那更像是一个覆盖率模型而不是一条计划条目。这个尺度感只能靠项目里磨我从第一版建树到比较顺手大概调整了三轮。3.3 节点和测试用例的绑定别把映射做死计划节点和测试名之间是多对多的关系一个节点可能要靠好几个测试才覆盖得全一个测试也可能同时为多个节点提供覆盖。所以映射一定要做成多对多别指望一条计划对一个测试这种整齐的结构。还有一个实践上的细节不要在节点里写死测试的完整路径或者编号写测试在回归列表里的逻辑名即可。回归脚本换了执行环境、测试被重命名的时候只改一处映射就够。我见过有人把带绝对路径的测试名写进去一次目录迁移就让整棵树的绑定关系全断重建花了两天。3.4 状态字段该定义到什么程度状态字段看似简单实则是最容易引发扯皮的地方。我用的四态定义是这样的状态判定标准谁能改未开始无任何测试针对性构造激励特性责任人进行中有测试在跑但覆盖率条目存在未命中项特性责任人待复核覆盖率条目全部命中等待他人确认特性责任人提交复核人确认已验证复核通过且连续若干次回归保持稳定复核人关键在于已验证和待复核的拆分。这两者合并成一个状态结果就是自己给自己签字覆盖率数字漂亮但没人真正检查过用例质量。加一道复核多花的时间非常有限但能挡掉相当一部分数字达标、功能没测的情况。至于连续若干次回归保持稳定这条是为了防止覆盖率抖动——有些测试存在随机性或者时序依赖某一次跑过了不代表稳定覆盖。4. 实测中最容易翻车的几个地方4.1 数据库不完整回填结果比实际好看这是最危险的一类问题因为它给你的是偏乐观的结论。常见成因有三种回归跑挂了没发现对应的 vdb 是残缺的合并时用了不同编译版本的库被静默丢弃-cm_hier配置在某个节点上误排除了一部分层级导致那部分永远显示为已覆盖因为它压根没被采集。排查的思路是拿数据库里的测试清单和回归计划清单做对账。URG 的文本报告里会列出参与合并的每个测试把这份清单和实际提交的回归列表 diff 一遍少一个都别放过。另外每次合并后记录 merged.vdb 里的实例总数和覆盖率采集范围和上一次对比出现明显跳变就停下来查原因别直接拿去汇报。4.2 层级路径写错映射静默失效挂接覆盖率条目时写错路径工具一般不会大声报警最多是在回填时把这个条目算作未命中。于是你看到的现象是覆盖率一直上不去但真正的原因是路径根本对不上。这种问题在几十个节点的树上靠肉眼找非常痛苦。我的做法是每次改完挂接关系先跑一次回填重点看两件事新增节点有没有立刻从未开始变成有数值的状态覆盖率条目里有没有零命中的项。如果某个节点刚挂上条目、相关测试也跑过了状态却纹丝不动八成就是路径问题。另外可以养成习惯挂接优先选用工具里的选择器从数据库里挑条目而不是手打字符串能挡掉绝大部分拼写错误。4.3 分支与条件覆盖率的数字收敛、功能没收条件覆盖率的数值是可以通过构造极端激励快速刷上去的比如把某些操作数组合硬凑出来。数字到了 100%但你其实并没有验证这条路径上的功能行为是否正确。这类刷覆盖率在紧张的项目后期特别常见。防堵手段是把功能覆盖率和结构覆盖率交叉看一个叶子节点如果结构覆盖率满了、功能覆盖率还空着说明激励构造出来了但检查没跟上这时候该补的是断言和参考模型比对不是继续加激励。反过来功能覆盖率满了但结构覆盖率有明显缺口可能是 DUT 里存在死代码或者被配置屏蔽的逻辑需要判断是否豁免。两种情况的处理方向完全不同只看单一指标会走错路。4.4 多人协作下的计划树合并冲突计划文件进版本库之后多人同时编辑必然产生冲突。好在结构化文本的冲突范围通常局限在单个节点块内比 Excel 二进制文件的整文件冲突好处理得多。我固定下来几条纪律按功能域分工一个人负责的子树不交叉提交前先在本地跑一次合并回填确认没有语法或关联性破坏节点的新增和删除尽量单独成一次提交不和其他修改混在一起。4.5 工具版本升级带来的入口与参数变化这类工具链的选项在不同版本之间会调整我遇到过升级之后原来的命令行选项报错、图形入口挪位置的情况。比较稳妥的习惯是把自己的常用命令写成一个脚本脚本里对关键选项加注释说明用途升级后先跑脚本验证报错就去查该版本的帮助信息改完更新注释。这样升级的代价是一次性排查而不是每次用的时候现场猜。5. 和 Verdi 配合把未覆盖的点落到波形上5.1 从计划节点反查到具体的覆盖率视图计划树本身只告诉你哪条没过接下来要定位为什么没过。这一步我是用 Verdi 的覆盖率视图来接的把合并后的数据库加载进去从报告里的未命中条目标识出发直接跳到对应的 RTL 或者 covergroup 定义处看它需要什么条件才能命中。# 加载合并后的覆盖率数据库做分析 verdi -cov -covdir ./cov/merged/merged.vdb # 代码与波形联动调试 verdi -ssf ./wave/smoke_001.fsdb -nologo 两条链路配合起来效率提升很明显覆盖率视图负责告诉你哪个 bin 是空的波形负责告诉你仿真里到底发生了什么。缺了前者你不知道该看哪段波形缺了后者你只知道缺了什么不知道怎么写激励补上。5.2 用波形反推未被触发的激励条件具体做法是在覆盖率视图里选中一个未命中的条件项或 cross bin查看它期望的属性组合然后回到波形上找相关信号看这些信号的取值组合在仿真里出现过哪些、缺哪些。这一步特别需要耐心但经常能发现一些有意思的问题比如某个条件分支需要信号 A 为高且 B 为低同时出现而现有的测试序列里这两个信号天然互斥根本构不出这个组合——这时候问题就上升到了设计或者验证环境层面不是简单加激励能解决的。我还用这招发现过几次覆盖率模型的定义和 RTL 实现不一致的情况覆盖率模型里写的是三拍连续背压RTL 实际实现只判断了两拍。这类问题如果只盯数字很可能被当成暂时覆盖不到挂在那里直到流片后才暴露。5.3 大规模回归下的存储与耗时控制覆盖率数据库很占空间一个中等规模项目的完整回归vdb 目录能到几十甚至上百 GB。几个能明显省资源的手段跑完就把原始 vdb 压缩归档只保留合并后的库-cm_hier严格排除掉不需要采集的层级在不需要细粒度条件的阶段用较粗的采集类型合并时用-parallel。另外建议给覆盖率目录单独挂一块盘别和波形、日志挤在一起否则清理的时候容易误删。6. 几条我在项目里固定下来的习惯说几条踩过坑之后不再动摇的做法。第一计划树的节点责任人一律写到具体的人不写组名——写组名的节点三个月后没人认领。第二每次覆盖率评审之前一定重新回填一次不允许用上次的数据哪怕只多跑了一个测试因为覆盖率评审会上扯数据版本的问题最浪费时间。第三节点从进行中改到待复核的时候顺手在提交信息里写清楚是哪次回归、哪个数据库几个月后回溯的时候这一行信息能救命。第四别追求一次性把树建完美第一版粗糙没关系重要的是让它跟着项目持续更新一棵三个月没动过的计划树状态比 Excel 还不可信。最后分享一个真实的小教训。我曾经在一个项目上把计划树建得非常细致节点数接近三百个结果中期需求变更一半节点的描述和判定标准都需要重写维护成本高到没人愿意碰最后整棵树废掉重来。后来我改成先粗后细初期只建到特性层等这一层基本稳定、覆盖率流程跑顺了再对重点特性往下加判定点。这个节奏下计划树是跟着验证进度一起长起来的而不是变成一个一开始就要还清的技术债。
返回列表