验证方法论

第 1 章 · 共 15 章

一页看懂覆盖率:Functional 与 Code Coverage 的区别

分清 Functional Coverage(你声明自己关心什么)和 Code Coverage(工具从 RTL 自动统计出什么)——这是一道常见的面试题,也是整个模块的立足点——在讲任何 covergroup 语法之前先把这件事说清楚。

uvmuvm-advanced 两条赛道检查的都是正确性:观察到的 transaction 是否符合预期。两者都从没衡量过彻底性——实际跑过的场景是不是真的值得跑,或者说到底有多少场景值得跑。SV 断言那一章(第 15 章)顺带提了一句 covergroup 这个词就打住了;SV capstone(第 16 章)把生成激励→驱动→检查这个环故意停在检查这一步,没有加覆盖率这一环,承诺这个系列会接着讲。现在就是接上的时候——而且要先把一个区分讲精确,因为这是这个领域最常见的面试题之一。

一个名字下的两件不同的事

"覆盖率"其实指两种完全不同的度量,把它们混为一谈是这个话题在面试回答里最常见的翻车方式:

Functional CoverageCode Coverage
衡量什么声明过自己关心的场景是否发生过RTL本身的行/分支/比特/状态是否被执行过
谁来写验证工程师,用 SystemVerilog 写(covergroup没有人写——工具自动对 RTL 插桩统计
依据什么算出来的你对 spec 的理解,以及你觉得什么值得测DUT 的源代码,机械地分析
100% 意味着什么你想到要声明的每一个场景都至少发生过一次工具找到的每一行/分支/比特/状态都至少被执行过一次
典型陷阱你从没声明过那个真正重要的场景——你衡量到的 100% 对你没衡量到的东西什么都说明不了一行代码可以在值不对的情况下执行,依然算"被覆盖到了"——code coverage 对正确性什么都不保证

两者都不检查正确性——那仍然是断言和 scoreboard 的工作(uvm-advanced 第 2、5 章)。两者都在回答"这测到了吗",但是从两个互相独立的角度:一个问的是你的意图有没有被执行到,另一个问的是代码有没有被执行到——一个设计完全可能在其中一个指标上表现很好,在另一个上很差。一个把同一个寄存器地址打一千遍的测试,在那个地址对应的逻辑上能拿到很高的 code coverage,但在其他每一个寄存器上功能覆盖率都是零;反过来,一套设计得很漂亮的功能覆盖率 bin,如果 DUT 压根没真正跑起来(比如 testbench 本身有问题),两个指标都会是 0%。

先各看一眼——完整语法是第 2 章的事

Functional coverage 直接用 SystemVerilog 声明:

covergroup axi_addr_cg;
  cp_addr: coverpoint txn.addr;
endgroup

这句话说的是"我关心 txn.addr 取了哪些值"——完全没说怎么给这些值分桶(那是 bins,第 2 章的内容),也没说跨多个字段的组合(那是 cross,同样是第 2 章)。这里只是让你认出这个形状:一个 covergroup 里包含一个或多个 coverpoint,像其他跟 class 相关的构造一样例化,自动或按需采样。

Code coverage 完全没有对应的 SystemVerilog 语法可看——没有什么需要写。工具会对编译好的 RTL 插桩,跑完一次仿真之后,报告大致长这样:

Line Coverage:     87.3%  (401/459 lines)
Branch Coverage:   72.1%  (52/72 branches)
Toggle Coverage:   65.0%  (130/200 bits)
FSM Coverage:      50.0%  (2/4 states, 3/6 transitions)

四个不同的指标,各自对同一份源代码问一个不同的问题——第 3 到 5 章会逐一具体展开,用 axil_regfile 自己的 RTL 来讲。

这个模块的 DUT:还是 axil_regfile

这个模块的每一章都沿用 axil_regfileuvm-advanced 第 1 章),而不是 mux2——这是故意的。mux2 只有一行 always_comb:没有什么分支值得讨论,也完全没有状态机。axil_regfile 有真正的 case 语句分支(它的响应码译码逻辑)和一个真正的小状态机(写通道的 aw_have/w_have 锁存行为)——这正是 line/branch/toggle/FSM coverage 需要的、具体而不抽象的素材。如果接下来几章的 RTL 和 driver/monitor 代码看着眼生,去 uvm-advanced 第 1、2 章回顾一下就行;DUT 本身在这里没有任何变化。

一句关于验证可行性的话,只说一次

跟这个项目里之前几次限制一样,是实测确认的,不是猜的:Icarus Verilog 完全没有实现 covergroup——最简单的 covergroup/coverpoint 都编译不过。Code coverage 的统计原则上也没有对应的 Icarus 功能——这本来就是商业工具才有的特性。这意味着这个模块里的每一个例子,都需要和 uvm-advanced 已经定下的同一个换仿真器方案:在 EDA Playground 上选一个商业级仿真器,比如 Aldec Riviera-PRO(免费,不需要你自己的授权),而不是 Icarus。这个模块后面几章会简短地引用这条说明,不会每次都重复一遍。

小结

  • Functional coverage 是人写出来的——验证工程师用 SystemVerilog(covergroup)声明哪些场景重要,工具负责统计它们有没有发生过。
  • Code coverage 是自动的——工具直接对 RTL 本身插桩,报告哪些行/分支/比特/状态被执行过,不需要写任何 SystemVerilog 就能存在。
  • 两者的 100% 都是一个众所周知的陷阱:100% 的 functional coverage 只能证明有人想到要声明的场景发生过;100% 的 line coverage 对"跑过的逻辑是不是真的对"什么都不能保证。
  • 这个模块从头到尾沿用 axil_regfileuvm-advanced 第 1 章),因为它有真正的分支和真正的状态,mux2 从来没有过。
  • Icarus Verilog 跑不了这个模块里的任何一个例子——每一章都需要在 EDA Playground 上换一个商业级仿真器。

Functional coverage 和 code coverage 之间最根本的区别是什么?

一个 DUT 达到了 100% 的 line coverage。这实际上保证了什么?

哪个 SystemVerilog 关键字用来声明一个功能覆盖率模型?(英文小写)