做芯片验证这些年,覆盖率分析一直是回邮件、写报告、找领导签字时绕不开的一道坎。很多时候我们花在仿真的时间并不少,测试用例也跑了不少,但一问到"这版覆盖率多少?哪些逻辑没被踩到?"就有点心虚。VCS作为数字IC前端验证用得最广的仿真器之一,自带的覆盖率收集能力其实非常成熟,配合Verdi和DVE这两套GUI,基本覆盖了从"跑完回归看数字"到"定位某个具体未覆盖分支"的全流程。这篇东西就围绕我在实际项目里用VCS做覆盖率分析的完整操作,从最基础的编译选项到后面的数据合并且,把能落地的细节一次讲透,给正在被覆盖率报告折磨的同行一个可以直接抄的作业。
1. 覆盖率分析的整体思路
1.1 为什么必须做覆盖率分析
芯片验证做得到不到位,不能光靠"我觉得测了很多"来证明。设计代码里可能有一百个分支,你的用例可能只走到了六十个,剩下四十个也许藏着bug也许只是冗余逻辑,但你没跑到就是没跑到,投片前这就是风险。代码覆盖率分析就是用来量化这件事的工具:每一行代码有没有执行过、每一个条件分支有没有翻转、每一条状态机路径有没有跳进去,全部用数字告诉你。
我用VCS做覆盖率分析时,最常见的做法是收集行覆盖率、条件覆盖率、分支覆盖率、状态机覆盖率、翻转覆盖率这几类。不同的覆盖率类型对应不同的验证盲区,比如行覆盖率只能告诉你这一行代码被执行了,但一个if(a && b)这样的条件,行覆盖率不会告诉你a为真b为假的情况有没有测过,所以还得配合条件覆盖率来看。在Subsystem和SoC级别的验证环境里,覆盖率报告往往是验证完备性的最重要依据,手头没有一份成型的覆盖率报告,项目评审的时候根本说不过去。
1.2 VCS覆盖率工具链的架构
VCS做覆盖率分析用的是内置的覆盖率引擎,不需要额外安装单独的覆盖率工具,但需要配合GUI或者命令行工具来查看和分析。整体链路是这样的:编译的时候加覆盖率开关,仿真的时候动态收集覆盖率数据,数据落盘到指定目录,最后再用Verdi或者DVE打开数据做可视化分析,或者用urg命令生成报告。
Verdi和DVE这两套GUI在我的实际使用中各有各的长处。Verdi是现在主流的选择,启动快、界面现代化、代码窗口联动好用,特别是FSM状态机覆盖率可以直接状态转移图上标红标绿,非常适合排查状态机问题。DVE是老牌的覆盖率分析工具,虽然界面看起来比较朴素,但在大规模回归数据的汇总、层级结构遍历这些场景下依然非常稳定,很多老项目里还是习惯了DVE的操作方式。两个工具读取的是同一套覆盖率数据库,所以不存在谁的数据更准的问题,只是操作习惯和使用场景的差别。
提示:VCS和Verdi是同一家公司工具链,仿真时收集的覆盖率数据可以同时被Verdi和DVE读取,不用做任何格式转换。这点在实际项目里省了不少事。
2. 仿真前的参数准备:覆盖率不是白来的
2.1 编译阶段必须加的选项
覆盖率收集的第一步是在编译阶段告诉VCS要收集哪些类型的覆盖率。我见过不少同事第一次接触覆盖率时,跑完仿真发现simv.cm目录是空的,或者是压根没有这个目录,原因基本都是在编译和运行阶段漏了参数。
编译阶段最核心的选项是-cm,后面跟要收集的覆盖率种类。平时用得最多的是下面几种:
vcs -sverilog -full64 \ -cm line+cond+branch+tgl+fsm \ -cm_name test_top \ -cm_dir ./cov_dir/test_top.cm \ -f filelist.f \ -l vcs_compile.logline:行覆盖率,代码的每一行是否被执行cond:条件覆盖率,条件表达式里各种逻辑组合是否被覆盖branch:分支覆盖率,if/else、case等分支是否都走过了tgl:翻转覆盖率,信号是否发生了0到1、1到0的翻转fsm:状态机覆盖率,状态机状态是否都进入过、转移路径是否完整
这里要注意,-cm后面跟的覆盖率种类在编译阶段就要定好,仿真阶段可以缩小范围但不能扩大范围。我自己的习惯是直接把line+cond+branch+tgl+fsm全开了,虽然仿真时间会有一些额外开销,但总比后面发现某类覆盖率没收集要重新跑回归强得多。仿真时间的开销,实测下来大概是15%到30%左右的额外运行时间,但换来的是覆盖率数据的完整性,这个代价完全值得。
-cm_name是给这组覆盖率数据起个名字。这个参数在merge多个覆盖目录时特别有用,每一组用例的覆盖率数据源都能从名字上区分出来。-cm_dir指定覆盖率数据的落盘目录,建议每个测试用例或者每个种子单独建一个目录,方便后面做merge。
2.2 仿真阶段的数据落盘
编译通过之后,运行仿真的时候还需要再加一次-cm参数,告诉仿真器在这个用例的运行过程中要收集覆盖率数据:
./simv -cm line+cond+branch+tgl+fsm \ -cm_name test_case_001 \ -cm_dir ./cov_dir/test_case_001.cm \ -l simv_test_case_001.log仿真跑完以后,VCS会在当前目录下生成一个simv.cm目录,里面是本次运行收集到的覆盖率原始数据。如果指定了-cm_dir,数据就会写到自定义路径下。这里有个坑:simv.cm这个目录是增量累加的,下一次仿真如果不清理,新数据会和旧数据混在一起,导致覆盖率明显虚高。我一般是写一个清理脚本,在每次回归开始前把simv.cm目录删掉,保证每次回归的数据都是干净的。
有些场景下,跑的是带SDF延迟的后仿真,VCS同样支持覆盖率收集。但后仿真速度慢、时间开销大,而且很多内部节点在门级网表里已经不叫原来的名字了,覆盖率数据对应的源代码位置依然可以映射回RTL代码,不过分析和定位的粒度会比前仿稍微粗糙一些。后仿覆盖率我建议重点看行覆盖率和翻转覆盖率,条件覆盖率在后仿里的参考价值相对有限。
2.3 一个容易被忽略的细节:rst_db
VCS在收集覆盖率时,默认会把顶层模块的名字记录在覆盖率数据里。如果编译时没有设置-cm_top,有些情况下覆盖率报告里会出现奇怪的顶层名字,特别是当验证环境的testbench顶层和DUT顶层不一致时。我通常用-cm_top显式指定DUT的顶层模块名,这样在Verdi和DVE里看覆盖率层次结构的时候,直接就能从DUT开始往下钻,不用在一堆testbench代码的覆盖率上浪费时间。
再看一个实际项目的Makefile片段,这套配置我用了很多年,稳定可靠:
COV_CMD = -cm line+cond+branch+tgl+fsm COV_DIR = $(COV_ROOT)/$(TC_NAME).cm compile: vcs -sverilog -full64 \ $(COV_CMD) \ -cm_top $(DUT_TOP) \ -cm_name $(TC_NAME) \ -cm_dir $(COV_DIR) \ -f $(FILELIST) \ -l compile.log run: ./simv \ $(COV_CMD) \ -cm_name $(TC_NAME) \ -cm_dir $(COV_DIR) \ -l $(TC_NAME).log这样每个测试用例跑完,覆盖率数据都独立存在$(TC_NAME).cm目录下。后面无论是用Verdi单独看某个用例的盲点,还是用urg把所有用例的覆盖率合并成一份总报告,操作起来都很顺手。
3. Verdi查看覆盖率实操
3.1 启动Verdi并加载覆盖率数据
Verdi加载覆盖率数据有两种典型方式。第一种是在启动时就指定覆盖率文件,直接进入覆盖率分析界面:
verdi -cov -covdir ./cov_dir/test_case_001.cm -f filelist.f &第二种是先打开Verdi的源码界面,再通过菜单Tools -> Coverage Analysis加载覆盖率数据。我个人更推荐第一种,一步到位。启动之后Verdi会打开覆盖率分析窗口,左侧是覆盖率类型的页签,包括Line、Condition、Branch、Toggle、FSM等,右侧是覆盖率汇总面板,显示每一个模块的覆盖率百分比。
这里要注意,Verdi打开覆盖率数据的时候需要源代码文件。如果编译时用的文件列表路径和当前路径不一致,Verdi可能会找不到源文件,需要在启动时通过-f filelist.f指定源代码文件列表。我自己就吃过这个亏:从别人手里接手一个验证环境,直接把覆盖率文件拷过来用Verdi打开,结果源码路径全乱了,所有覆盖率都定位不到代码行。后来养成了习惯,覆盖率数据文件和源代码文件列表放在一起拷贝,打开的时候两个一起指定。
3.2 核心操作:从覆盖盲区定位到源头逻辑
Verdi覆盖率分析窗口里最有价值的功能,是在源代码窗口里直接显示覆盖率彩色标注。点击任意一个模块,源代码窗口会在每行代码前面用红绿黄色块标注覆盖率状态:绿色表示这一行已经被执行覆盖,红色表示从未执行过,黄色表示部分覆盖(比如条件分支只走了一半)。这样定位未覆盖逻辑的速度非常快,不用对着表格数字猜代码位置。
比如你看某个模块条件覆盖率只有70%,点击Condition页签,源代码里出现红色标注的条件表达式,鼠标移上去能看到具体是哪几种逻辑组合没有测到。如果是if (a && b)这样的条件,Verdi会告诉你覆盖到了"a真b真"、"a真b假"这些组合中的哪些,还没覆盖到哪些。结合这个信息再回去看测试用例的激励约束,往往很快就能发现是因为某个信号的取值范围受限,导致特定逻辑分支根本进不去。
FSM覆盖率这块是Verdi的强项。点开FSM页签,Verdi会以图形化状态转移图的形式展示状态机的状态和跳转关系。状态用圆圈表示,转移用箭头表示,已经覆盖到的状态和转移是绿色,没覆盖到的是红色。对于状态机这种"跑没跑到某个状态"一目了然的需求,这个图比看代码有效率得多。我在实际项目中靠这个功能抓出过好几次时钟复位期间状态机被异常复位到某个非法状态却没被测试用例观察到的问题。
3.3 Verdi的层级树和过滤技巧
在Chip级别验证中,整个设计的模块层次非常深,覆盖率分析如果从顶层一个个点下去,效率很低。Verdi的覆盖率窗口左侧有一个层次树,默认按设计层次展开。我通常在层次树的过滤框里输入模块名关键字,快速跳到关心的模块;如果是子模块的所有实例都要看,直接在过滤框里输入模块名加通配符,能一次列出所有实例的覆盖率数据做横向对比。
排障场景下,我经常用到一个对比操作:跑一组用例之前先记录覆盖率偏低的一组模块,跑完回归后直接按"覆盖率增量"排序,看看哪些模块在这次回归里覆盖率没有增长。这些模块就是测试盲区,需要补充激励。Verdi覆盖率窗口支持按模块、按覆盖率类型的多维排序,灵活用的话,一份庞大的覆盖率报告很快就能提炼出"接下来要在哪里努力"的结论。
4. DVE查看覆盖率实操
4.1 DVE的启动方式和界面布局
DVE作为传统的VCS配套GUI工具,至今在很多老项目和客户的参考流程里依然占有一席之地。如果你所在的团队已经习惯用Verdi,可以跳过这一章,但如果你手上只有DVE环境,下面的操作流程可以直接照用。
DVE启动加载覆盖率数据的命令是:
dve -cov -covdir ./cov_dir/test_case_001.cm &启动后DVE会打开覆盖率分析主窗口。界面风格比较朴素,但功能分区很清晰:左侧是设计层次树,右侧是覆盖率详情列表,顶部是菜单和工具条。DVE的覆盖率汇总视图默认会以表格形式展示当前设计所有模块的行覆盖率、条件覆盖率、分支覆盖率、翻转覆盖率等,按模块层级分组,每一行都可以展开,一直到具体的实例和信号。
4.2 从汇总到深挖:DVE的操作路径
DVE看覆盖率最直接的操作是分级下钻。在层次树里点击一个模块,右侧表格显示这个模块的覆盖率汇总;双击某个指标,DVE会自动跳到对应的源码窗口,并用颜色标注未覆盖的代码行。这个联动效果和Verdi的体验是类似的,只是视觉呈现更老派一些,对于看惯了现代IDE界面的人来说会有个习惯过程,但用顺手了以后一样能快速定位问题。
DVE有一个比较实用的特性是Coverage Group View。这个视图把所有同一类型的数据(比如所有模块的条件覆盖率)聚合到一个表格里,按百分比从低到高排序,一眼就能看出哪些模块覆盖率拖了后腿。对于回归结束后要统计挂起风险模块的场景,我习惯直接在Coverage Group View里按最低覆盖率排序,把排名靠后的几个模块列入重点补充测试清单。
4.3 Verdi和DVE如何选择
在我工作过的几个项目里,Verdi和DVE的选择往往取决于团队习惯和项目历史。新项目基本都切到了Verdi,用户体验和功能都更占优;但有一些存量IP验证环境和客户交付流程里,DVE依然是标准配置。两个工具读取同一套VCS覆盖率数据库,分析结果理论上是一致的,所以选择哪个更多是看个人熟练度和团队标准,没必要因为工具不同而纠结数据差异。
真正要注意的是复现环境:当你拿到一个别人的覆盖率数据,打开之前先确认对方用的VCS版本和你要用的GUI版本是否差距过大。跨大版本打开覆盖率数据偶尔会出现兼容性警告,但一般不影响查看。如果遇到数据完全打不开的情况,优先考虑用urg命令把数据导出成文本或网页报告,这招在应急场景下很管用。
5. merge技巧:多场景覆盖率数据这样合并不踩坑
5.1 为什么需要merge,何时需要merge
覆盖率分析的最终目标是评估整个验证计划下设计代码被覆盖的完整程度。单个测试用例跑出来的覆盖率再高也只是局部视角,真正要交付的是一份所有用例合并之后的总体覆盖率报告。Merge就是把多个独立的覆盖率数据库合并成一份综合数据库,反映的是所有测试用例叠加之后的总覆盖率。
什么时候必须merge?最典型的是约束随机验证:同一个测试用例用不同的随机种子跑几十次,每次跑出来的覆盖组合都不同,最终要合并成一份数据看总覆盖率。跨功能场景的合并也一样,比如一个模块的复位测试、正常功能测试、异常路径测试分别由不同用例覆盖,回归跑完后必须合起来看总体的功能覆盖率盲区。SoC级验证里还有IP级和系统级的合并需求,IP验证的覆盖率数据要汇到SoC级验证报告里,也需要通过merge来完成。
5.2 urg命令实战
VCS提供的merge工具就是urg命令,全称是Unified Report Generator。它既可以用来生成可读的报告,也可以用来合并覆盖率数据并输出一个新的合并后的数据库。基础用法很简单:
urg -dir ./cov_dir/test_case_001.cm \ -dir ./cov_dir/test_case_002.cm \ -dir ./cov_dir/test_case_003.cm \ -dbname merged.cm \ -report merged_report执行后,urg会把这三个目录的覆盖率数据合并,合并结果写入merged.cm目录,同时生成一份文本报告在merged_report目录里。之后用Verdi或DVE打开merged.cm就能看到合并后的总覆盖率。
当用例数量很多的时候,命令行一个个写-dir参数不现实。urg支持用-f参数指定一个文件列表,文件里每行写一个覆盖率数据目录:
urg -f cov_dir_list.txt -dbname merged.cm -report merged_report这里cov_dir_list.txt的内容格式就是每一行一个目录路径。我在实际回归环境里都是自动生成这个文件:回归结束后脚本扫描整个回归目录,把所有.cm目录路径写进列表文件,然后调用urg做合并。整个流程完全不需要人工介入,覆盖率总报告在回归结束后的几分钟内就能自动生成。
5.3 merge时的关键注意事项
Merge看起来简单,实际操作中有几个关键细节踩过坑之后我才特别注意。第一个是时间戳问题。VCS编译生成的仿真可执行文件如果每次编译时间不同,生成的覆盖率数据里会带有编译时间戳信息。合并不同时间编译产生的覆盖率数据时,urg有时候会报警告或者直接跳过不兼容的数据。解决办法是在做多轮回归比较时保持同一个编译版本,不要中间重新编译。如果确实需要重新编译,那编译完成后所有用例都要重新跑一遍回归,否则新旧数据不能混着merge。
第二个是覆盖率数据目录的命名和管理。我见过有人把所有用例的覆盖率都指定到同一个-cm_dir,结果每个用例跑完都把之前的数据累加进去了,最终的merge完全失去了意义。正确做法是每一个用例或者每一组随机种子使用独立的目录,在Makefile或者回归脚本里用变量区分,这样可以保证merge的每一份数据都是单一用例的原始结果。
第三个是针对随机验证场景的merge策略。约束随机验证跑多个种子时,有时候你关心的不是"所有种子合并后的总覆盖率",而是"单一种子能达到的最高覆盖率"以及"几个种子分别的差异覆盖"。这种情况下我会分两层merge:第一层把同一个测试用例的所有种子merge成一份"用例级覆盖率";第二层把所有用例级覆盖率再merge成"回归总覆盖率"。这样做的好处是既能看整体达标情况,又能定位某个测试用例覆盖贡献的基数,避免出现"总覆盖率达标了但其实某个用例对总覆盖率完全没贡献"的水分数据。下面是一个帮助理解merge组合方式的表格:
| merge维度 | 输入数据 | 输出用途 | 典型场景 |
|---|---|---|---|
| 种子级merge | 同一用例不同随机种子 | 反映该用例稳定的覆盖率能力 | 约束随机验证,统计单一用例覆盖贡献 |
| 用例级merge | 同一回归内所有用例 | 反映本次回归的总覆盖率 | 项目节点回归,交付前的覆盖率评估 |
| 跨回归merge | 多轮回归的覆盖率数据 | 反映多个回归叠加后的覆盖率 | 长时间验证周期,多批次测试数据汇总 |
5.4 merge后的报告解析
urg生成的报告里有几个关键指标要会看。Line Coverage和Condition Coverage是最常被领导问到的两个数字,一个是代码行执行覆盖,一个是条件逻辑组合覆盖。Branch Coverage和Toggle Coverage用于评估控制流和信号翻转的完备性。FSM Coverage在状态机比较多的设计里是不可或缺的,如果这个数字偏低,意味着状态机还有很多状态跳转没有被实际激励到,这种情况往往需要针对状态机增加定向测试用例。
报告里的Total Coverage是各类覆盖率的加权综合,不同的覆盖率类型权重默认相同,也可以自己定义权重。但我想强调的是,Total Coverage这个数字只能作为宏观参考,真正找问题的时候还是要逐个模块逐个覆盖率类型去钻,不要只盯着一个综合数字看。
6. 常见问题与排查技巧实录
6.1 仿真结束却没有覆盖率数据
这是我被问过最多的问题:命令行加了-cm参数,仿真也正常跑完退出了,但是目录下没有simv.cm,或者有目录但里面是空的。排查思路很简单,先看编译时候有没有加-cm,再确认仿真的可执行文件是不是最新编译的。如果编译时没加-cm参数,仿真时即使加了也不会生成覆盖率数据。
还有一种不太容易发现的情况:仿真在某个用例里面通过系统任务$finish或者$fatal结束,覆盖率数据可能没有及时flush。正常跑完的仿真会在结束阶段自动把覆盖率数据写盘,但如果是被外部机制强行杀掉的进程,最后的覆盖率数据往往会丢失。解决办法有两个:一是尽量让用例正常退出;二是在代码或者命令行层面设置周期性的覆盖率数据保存。VCS提供-cm_log参数来记录覆盖率数据写盘日志,出现了数据不完整的情况可以看日志定位是哪个阶段出了问题。
6.2 Merge时的时间和版本错配
不同编译版本甚至不同VCS版本生成的覆盖率数据在merge时可能不兼容。urg会报类似timestamp mismatch或者version mismatch的警告。遇到这个问题不要硬merge,先检查哪些目录是同一个编译版本生成的,把能合并的先合并,不能合并的单独保留并在报告中注明原因,这是最稳妥的处理方式。
6.3 仿真后处理Memory初始化
对验证环境而言,覆盖率分析和memory初始化是两个相对独立的话题,但做后仿真时经常要一起处理。跑带时序的后仿真,通常需要做memory初始化避免仿真开始时的X态传播导致覆盖率收集异常。VCS处理memory初始化的常见做法是使用$readmemh或者$readmemb系统任务,在后仿测试代码里把memory内容加载到指定数组里。如果memory是综合后的RAM模型,初值可能要通过force或者deposit的方式初始化。我的经验是,初始化步骤越早做越好,否则仿真开始阶段就会有大量的X态出现在覆盖率结果里,影响行覆盖率和条件覆盖率的准确度。
有些人会用编译选项-xprop来处理X态传播问题,但针对覆盖率分析,我建议重点关注翻转覆盖率是否因为X态导致大量误报。翻转覆盖率的原理是记录信号从0到1、从1到0的变化,如果信号一开始就是X,那翻转计数往往会被跳过,这类信号在覆盖率报告里会长期是"未覆盖"状态。排查这类问题时先确认仿真开始时信号有没有被正确初始化,再决定要不要关注这些翻转覆盖率数据。
6.4 后仿Memory初始化的实操技巧
后仿阶段要让memory初始化,常见的手段是把RTL里的memory替换成带初始化能力的仿真模型,或者在测试代码里直接对memory区域做deposit。实际项目里我比较常用的是VCS的-cm结合+define+MEM_INIT条件编译的方式:在RTL或者仿真模型里预留初始化分支,仿真时通过宏定义打开。这个做法的好处是可以灵活控制哪些仿真阶段做初始化、哪些阶段做真正的功能仿真,不至于把初始化逻辑带到正式仿真里影响事务时序。
在system verilog的interface或者module里加一个初始化块,用#0的方式在0时刻deposit所有memory单元,对几百KB量级的memory,仿真启动的额外开销很小,但能有效避免后仿初期出现海量X态导致的覆盖率失真。初始化的数据是否可以复用$readmemh加载的hex文件,取决于你手头有没有对应的初始化文件,没有的话全写0也是可以的,主要目的是让信号脱离X态。
6.5 Verdi和DVE都打不开覆盖率数据
这种情况下我习惯先跑一下urg试试,因为urg的兼容性要比GUI工具更强一些,如果urg能正常生成报告,那数据本身没有坏,问题出在GUI工具的版本或者环境上。urg跑出来如果是空的,那说明数据文件确实有问题,这时候检查simv.cm目录下面的具体文件,看simv.cm目录里有没有完整的header数据和具体的覆盖率数据文件。覆盖率数据文件一般比较大,如果发现某些文件是0字节,大概率是仿真被中途强杀了,需要重新跑相应用例。
6.6 覆盖率一直卡在某个百分比上不去
这种情况在项目后期非常常见。就是因为该覆盖的逻辑在现有测试架构下很难被激励到,不是你用例数量不够,而是激励约束限制了输入的多样性。我排查这种"覆盖率平台期"的标准做法是:用Verdi打开当前覆盖率数据,按模块排序找到覆盖率最低的几个模块,逐个点进源码看红色标注的逻辑,分析这些逻辑在什么输入条件下才可能执行,然后检查现有用例的约束是否屏蔽了这些输入条件。大多数情况下是约束过死,解约束之后重新跑回归就有明显改善。
这里放一个我常用的排查路径表,帮助快速定位不同覆盖率类型偏低的原因:
| 现象 | 可能原因 | 优先排查项 |
|---|---|---|
| 行覆盖率偏低 | 大量代码分支未被激励到 | 检查测试用例的输入约束是否限制了合法输入的取值范围 |
| 条件覆盖率偏低 | if/else条件组合未覆盖全 | 查看Verdi标注的具体条件组合缺口 |
| 翻转覆盖率偏低 | 信号长期保持恒定值 | 检查复位逻辑和初始化逻辑,确认信号是否从未被驱动 |
| FSM覆盖率偏低 | 状态机某些状态或转移未进 | 用Verdi的FSM视图定位红色状态,设计对应场景激励 |
| 总覆盖率卡住不涨 | 激励空间受限或测试目标固化 | 解约束、新增边界场景、考虑加入定向用例 |
写在最后的实操心得
覆盖率分析做到最后,你会发现真正值钱的不只是会敲那几条命令,而是拿到一份覆盖率报告后知道下一步该干什么。VCS的覆盖率收集、Verdi的图形化定位、DVE的老牌稳定、urg的高效合并,这四样东西组合起来已经能覆盖绝大多数验证项目的覆盖率交付需求。我个人在实际项目里最受益的一个习惯是写一套自动化的覆盖率回归脚本:编译、跑用例、按用例收集独立覆盖率目录、自动merge、自动生成报告、自动邮件通知结果。整个流程里我只需要在第二天早上看报告,然后根据覆盖率盲区去补充和调整用例。希望这篇东西能帮你把覆盖率分析的整个链路理顺,少踩几个我当年踩过的坑。