第 1 章 · 共 15 章
一页看懂覆盖率:Functional 与 Code Coverage 的区别
分清 Functional Coverage(你声明自己关心什么)和 Code Coverage(工具从 RTL 自动统计出什么)——这是一道常见的面试题,也是整个模块的立足点——在讲任何 covergroup 语法之前先把这件事说清楚。
uvm 和 uvm-advanced 两条赛道检查的都是正确性:观察到的 transaction 是否符合预期。两者都从没衡量过彻底性——实际跑过的场景是不是真的值得跑,或者说到底有多少场景值得跑。SV 断言那一章(第 15 章)顺带提了一句 covergroup 这个词就打住了;SV capstone(第 16 章)把生成激励→驱动→检查这个环故意停在检查这一步,没有加覆盖率这一环,承诺这个系列会接着讲。现在就是接上的时候——而且要先把一个区分讲精确,因为这是这个领域最常见的面试题之一。
一个名字下的两件不同的事
"覆盖率"其实指两种完全不同的度量,把它们混为一谈是这个话题在面试回答里最常见的翻车方式:
| Functional Coverage | Code 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_regfile(uvm-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_regfile(uvm-advanced第 1 章),因为它有真正的分支和真正的状态,mux2从来没有过。 - Icarus Verilog 跑不了这个模块里的任何一个例子——每一章都需要在 EDA Playground 上换一个商业级仿真器。
Functional coverage 和 code coverage 之间最根本的区别是什么?
一个 DUT 达到了 100% 的 line coverage。这实际上保证了什么?
哪个 SystemVerilog 关键字用来声明一个功能覆盖率模型?(英文小写)