☰
VCS覆盖率实战:URG报告未覆盖TC排查与Toggle收集优化策略
2026/9/28 14:09:20 网站建设 项目流程

1. 覆盖率报告里那些"看不见"的盲区是怎么来的

做数字验证的同行大概都有过这种体验:跑完一轮回归,URG报告一打开,行覆盖率98%、条件覆盖率95%,看着挺漂亮,但心里总不踏实——因为你知道那剩下的2%里,藏着最要命的东西。尤其是Toggle覆盖率,经常出现某个信号翻转次数为零,或者某个跨时钟域的信号只翻了一半,这种问题在仿真阶段不暴露,到了后仿或者硅后就变成"偶发性的诡异bug",查起来能耗掉你两周时间。

这篇内容就是围绕VCS覆盖率实战展开的,重点聊两件事:一是怎么高效排查那些没有被测试用例(TC)覆盖到的点,二是怎么优化Toggle收集策略,让覆盖率数据既有意义又不拖慢仿真速度。适合已经跑过完整回归、正在做覆盖率收敛的验证工程师,也适合刚接触VCS覆盖率、想搞清楚URG报告怎么读的入门者。我不会只贴命令,而是把每个操作背后的逻辑讲清楚,让你知道为什么要这么干,以及我踩过哪些坑。

先说一个反直觉的结论:覆盖率数字高不等于验证充分,覆盖率数字低也不一定代表有漏洞。关键在于你收集的覆盖率模型是否合理,以及你有没有真正理解那些未覆盖项背后的原因。很多人一看到未覆盖就急着加TC,结果加了一堆无效用例,仿真时间翻倍,覆盖率纹丝不动。问题出在哪儿?出在没有先做"未覆盖根因分析"。

2. 从URG报告定位未覆盖TC的完整排查链路

2.1 URG报告的正确打开方式

VCS跑完仿真后,会在simv.vdb目录下生成覆盖率数据库。很多人直接用urg -dir simv.vdb生成HTML报告就完事了,但其实URG有很多参数能帮你快速定位问题。我常用的组合是这样的:

urg -dir simv.vdb -format both -report urgReport -show tests -show ratios

这里几个参数的作用需要说清楚:

  • -format both:同时生成HTML和文本格式,文本格式方便用grep搜索,HTML适合整体浏览。
  • -show tests:在报告里显示每个测试用例对覆盖率的贡献,这个非常关键,能帮你快速判断是哪个TC覆盖了哪些点。
  • -show ratios:显示覆盖率比值而不是简单的百分比,有时候比值更能反映问题。

生成报告后,不要急着看总的覆盖率数字。先打开tests.html,按覆盖率贡献排序,看看哪些TC贡献最大,哪些TC几乎没贡献。我遇到过一种情况:某个TC跑了三个小时,结果对覆盖率贡献为零,原因是它的激励根本没打到DUT的有效路径上。这种TC就是典型的"无效用例",留着只会浪费回归时间。

2.2 用URG的exclude功能缩小排查范围

当你面对一个大型SoC项目,覆盖率数据库里可能有几十万个覆盖点,逐个看根本不现实。这时候要用URG的exclude功能,先把已知的、不关心的覆盖点排除掉。

urg -dir simv.vdb -exclude exclude_file.el -report urgReport_filtered

exclude文件的格式很简单,每行一个排除规则:

# 排除所有时钟门控单元的toggle toggle:top.u_core.u_clk_gating.* # 排除测试逻辑的覆盖点 line:top.u_dft.* # 排除某个已知不用的配置 cond:top.u_cfg.mode[3]

这里有个经验:exclude文件要版本化管理。我见过团队里有人随手改了exclude文件,把一些本该覆盖的点排除了,结果覆盖率虚高,流片后才发现问题。建议把exclude文件纳入Git管理,每次修改都要有review记录。

2.3 定位未覆盖TC的三步法

当你发现某个覆盖点没有被覆盖时,不要急着加TC。按下面三步走:

第一步:确认覆盖点是否合理。有些覆盖点是工具自动生成的,可能根本不需要覆盖。比如一个复位信号的toggle,如果设计上复位只在初始化时拉一次,那toggle覆盖率低是正常的。这时候应该用exclude排除,而不是强行加TC去翻转它。

第二步:确认激励是否到达。用Verdi打开波形,找到对应的信号,看看仿真期间它有没有被激活过。如果波形里信号根本没动,说明激励没打到。这时候要检查你的sequence或者testbench的约束是不是太严了。

第三步:确认覆盖点是否可达。有些覆盖点在当前配置下就是不可达的,比如某个只在特定模式下拉高的信号,而你的TC根本没配置那个模式。这时候要么加配置,要么用exclude排除。

我通常会用这样一个表格来跟踪未覆盖项的处理状态:

覆盖点类型未覆盖原因处理方式负责人状态
top.u_dma.ch_prio[2]toggle激励未打到高优先级通道新增TC张三进行中
top.u_cpu.irq_mask[7]toggle该中断源未启用exclude李四已完成
top.u_mem.addr[31:28]toggle地址空间未使用exclude王五已完成

这个表格看起来简单,但能帮你把"为什么没覆盖"和"怎么处理"分开,避免一上来就盲目加TC。

2.4 用覆盖率数据库合并来定位回归盲区

VCS支持把多次仿真的覆盖率数据库合并,这个功能在排查未覆盖TC时特别有用:

urg -dir simv1.vdb simv2.vdb simv3.vdb -dbname merged.vdb

合并之后,你可以看到哪些覆盖点是所有TC都没覆盖的,哪些是部分TC覆盖的。我一般会做两次合并:一次是全回归的合并,一次是只选"应该覆盖该模块"的TC合并。两次结果的差集,就是真正的盲区。

这里有个坑:合并数据库时要注意仿真配置是否一致。如果两次仿真的编译选项不同(比如一次开了assertion,一次没开),合并后的覆盖率数据可能会失真。建议在Makefile里固定覆盖率编译选项,避免人为失误。

3. Toggle覆盖率收集的优化策略与参数调优

3.1 Toggle覆盖率为什么这么"重"

Toggle覆盖率是VCS覆盖率里最耗资源的一项。原因很简单:它要记录每个信号在仿真期间的翻转情况,包括上升沿、下降沿、以及是否翻转到0和1。对于一个有几十万信号的设计,这个数据量是巨大的。

我做过一个对比测试:同一个DUT,关闭Toggle收集时仿真速度是1.2kHz,打开后降到0.8kHz,慢了33%。如果再加上条件覆盖率和FSM覆盖率,速度可能只有原来的一半。所以Toggle收集必须优化,不能无脑全开。

3.2 用+ntb_random_seed和+rad控制Toggle收集范围

VCS提供了几个编译和运行时选项来控制Toggle收集:

# 编译时指定只收集特定层次的toggle vcs -cm_tgl portsonly -cm_tgl_portsonly top.u_core

portsonly的意思是只收集模块端口的toggle,不收集内部信号的。这个选项在模块级验证时特别有用,因为内部信号的toggle往往可以通过端口推断出来。

另一个常用的是+rad选项,它可以让VCS在运行时动态决定是否收集某个信号的toggle:

simv +rad +cm_tgl

+rad的原理是基于活动性分析,如果某个信号在仿真期间很少活动,VCS会自动降低它的收集优先级。这个选项对仿真速度的提升很明显,但要注意:它可能会漏掉一些低频但关键的翻转。所以我在做最终覆盖率签核时,会关掉+rad重新跑一遍,确保数据完整。

3.3 Toggle rate的合理设置

Toggle rate是指信号在单位时间内的翻转次数。VCS允许你设置一个阈值,只有超过这个阈值的信号才会被详细记录:

simv +cm_tgl +tgl_rate=100

这个设置的意思是:只有翻转次数超过100的信号才记录详细toggle信息。对于低频信号,只记录是否翻转,不记录翻转次数。

这个参数怎么设?我的经验是:先跑一轮不设阈值的仿真,看看toggle rate的分布。如果大部分信号的翻转次数都在1000以上,那阈值设100就没问题。如果有些关键信号翻转次数只有个位数,那阈值就要调低,或者把这些信号加入白名单。

3.4 用覆盖率分组来管理Toggle收集

大型项目里,我建议按功能模块把Toggle覆盖率分组管理。VCS支持在编译时用-cm_hier指定覆盖率层次:

vcs -cm_hier cm_hier.cfg

cm_hier.cfg文件的内容大概是这样:

+tree top.u_core 0 +tree top.u_dma 1 +tree top.u_periph 1 +tree top.u_dft 0

这里的0和1表示收集级别,0是不收集,1是收集。这样你可以把DFT逻辑、测试逻辑的Toggle关掉,只收集功能逻辑的,既省时间又让报告更干净。

我通常会维护两份cm_hier.cfg:一份是"全收集"用于最终签核,一份是"精简收集"用于日常回归。日常回归用精简版,速度能快40%左右。

4. 那些年我在覆盖率收敛上踩过的坑

4.1 坑一:覆盖率数据库损坏导致数据丢失

这个坑我踩过两次,都是因为仿真异常终止(比如磁盘满了或者被kill了),导致simv.vdb目录损坏。URG打开时报错"database corrupted",之前跑的几个小时全白费。

解决方案:在仿真脚本里加一个保护机制,每次仿真结束后先检查simv.vdb是否完整,再决定是否删除旧的数据库。我现在的做法是:

# 仿真结束后检查数据库 if [ -d simv.vdb ]; then urg -dir simv.vdb -format text -report /dev/null > /dev/null 2>&1 if [ $? -eq 0 ]; then echo "Coverage database is valid" # 备份到带时间戳的目录 cp -r simv.vdb simv.vdb.$(date +%Y%m%d_%H%M%S) else echo "Coverage database is corrupted, skipping backup" fi fi

这个检查虽然多花几秒钟,但能避免数据丢失的风险。

4.2 坑二:exclude文件写错导致覆盖率虚高

前面提过exclude文件要版本化,这里再补充一个细节:exclude文件的通配符要小心使用。我见过有人写了toggle:top.u_core.*,本意是排除u_core下某个子模块,结果把整个u_core的toggle都排除了,覆盖率直接从95%跳到99%,但实际上是漏掉了大量未覆盖点。

解决方案:exclude文件写完后,用URG的-show excluded选项检查一下到底排除了哪些点:

urg -dir simv.vdb -show excluded -report exclude_check

这个报告会列出所有被排除的覆盖点,你可以逐个确认是否合理。我一般会把这个报告发给模块负责人review,确认无误后再纳入正式流程。

4.3 坑三:Toggle收集导致仿真超时

有一次跑一个大型SoC的回归,开了全量Toggle收集,结果单个TC的仿真时间从2小时涨到6小时,整个回归跑了两天还没跑完。后来发现是某个时钟分频器的内部计数器,每个周期都在翻转,Toggle数据量巨大。

解决方案:对于这种高频翻转的信号,用+tgl_rate设置阈值,或者直接用cm_hier.cfg把它排除。另外,VCS还有一个-cm_tgl_max选项可以限制单个信号的toggle记录条数:

simv +cm_tgl +tgl_max=10000

这个选项的意思是:单个信号最多记录10000次翻转,超过后只记录统计信息。对于高频信号,这个限制能大幅减少数据量。

4.4 坑四:合并数据库时配置不一致

前面提过配置不一致的问题,这里展开说一下具体表现。有一次我把模块级验证的数据库和系统级验证的数据库合并,结果发现某些覆盖点在模块级是覆盖的,在系统级是未覆盖的,合并后变成了"部分覆盖",导致报告看起来很奇怪。

根本原因:模块级验证时,某些配置信号被固定了,而系统级验证时这些信号是动态变化的。合并后,VCS无法正确判断这些覆盖点的状态。

解决方案:合并数据库前,先确认两次仿真的编译选项、覆盖率模型、exclude文件是否一致。如果不一致,要么统一配置后重跑,要么分开报告,不要强行合并。

5. 覆盖率签核前的最后一道检查

5.1 用URG的assertion和FSM报告交叉验证

覆盖率签核不能只看数字,还要看质量。我通常会用URG生成几份交叉报告:

# 生成assertion覆盖率报告 urg -dir simv.vdb -report assertion_report -show assertion # 生成FSM覆盖率报告 urg -dir simv.vdb -report fsm_report -show fsm

Assertion覆盖率能告诉你哪些断言被激活过,哪些从未触发。如果一个断言从未触发,要么是激励没打到,要么是断言写错了。FSM覆盖率能告诉你状态机的所有状态和跳转是否都覆盖了,未覆盖的跳转往往是bug的高发区。

5.2 用Verdi的覆盖率浏览器做最终确认

URG报告是静态的,Verdi的覆盖率浏览器可以让你在波形上直接看覆盖情况。我一般会这样做:

verdi -cov -covdir simv.vdb

打开后,在覆盖率浏览器里找到未覆盖的点,右键选择"Trace to Waveform",Verdi会自动跳转到对应的信号和时刻。这个功能在排查"为什么没覆盖"时特别高效,比对着报告猜要快得多。

5.3 覆盖率签核清单

最后分享一个我用了多年的签核清单,每次覆盖率收敛到最后一轮时,逐项检查:

检查项检查方法通过标准
行覆盖率URG报告>95%
条件覆盖率URG报告>90%
Toggle覆盖率URG报告>85%
FSM覆盖率URG报告100%状态和跳转
Assertion覆盖率URG报告100%激活
exclude文件review人工确认所有排除项有记录
数据库完整性URG打开无报错无corrupted提示
配置一致性对比编译选项与签核配置一致

这个清单看起来简单,但能帮你避免90%的覆盖率签核事故。我见过太多团队在最后一刻发现exclude文件写错了,或者数据库损坏了,导致签核延期。

5.4 一个容易被忽略的细节:覆盖率数据的版本管理

覆盖率数据库和代码一样,需要版本管理。我建议每次签核的覆盖率数据库都打上标签,和对应的RTL版本、TC版本关联起来。这样当流片后发现问题时,可以回溯到当时的覆盖率数据,看看是哪个环节漏掉了。

具体做法是在Makefile里加一个目标:

coverage_tag: @echo "Tagging coverage database with version $(VERSION)" cp -r simv.vdb coverage_$(VERSION)_$(shell date +%Y%m%d) @echo "Coverage database tagged as coverage_$(VERSION)_$(shell date +%Y%m%d)"

这个操作只需要几秒钟,但能在关键时刻救你一命。

6. 写在最后的一些个人体会

覆盖率收敛这件事,说到底是一个"平衡"的艺术。你要在覆盖率数字、仿真时间、验证质量之间找到平衡点。我见过有人为了追求100%的Toggle覆盖率,加了无数TC,结果仿真时间翻了三倍,但真正发现的bug还不如之前多。也见过有人为了省时间,把Toggle收集关掉,结果流片后才发现一个跨时钟域的握手信号从来没翻转过。

我的经验是:覆盖率是手段,不是目的。URG报告上的数字只是参考,真正重要的是你有没有理解每个未覆盖项背后的原因。如果一个未覆盖项你能说清楚"为什么不覆盖"和"为什么不需要覆盖",那它就是合理的。如果你说不清楚,那就老老实实加TC或者改激励。

另外,Toggle覆盖率的优化没有银弹。+rad、+tgl_rate、cm_hier.cfg这些工具都有各自的适用场景,你需要根据项目的特点去组合使用。我通常会在项目初期就建立一套覆盖率收集的规范,包括编译选项、exclude规则、分组策略,然后在整个项目周期里持续优化。这样到了签核阶段,就不会手忙脚乱。

最后再分享一个小技巧:定期用URG的-show tests功能检查每个TC的覆盖率贡献。如果某个TC的贡献长期为零,要么是TC写错了,要么是它覆盖的功能已经被其他TC覆盖了。及时清理这些无效TC,能让你的回归效率提升不少。我在最近一个项目里,通过这个方式砍掉了30%的冗余TC,回归时间从8小时降到5小时,覆盖率数字一点没掉。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询