RTL 与验证

功能覆盖率(functional coverage)

设想你为一台自动售货机写了一份详尽的测试计划:它应当能收准确的零钱、找零、拒收外币硬币、处理某一格已售罄的情况、在购买途中断了一下电后还能恢复。现在你把机器交给一名测试员,问他:「这些情况里,你到底实际试过哪几种?」功能覆盖率就是回答这个问题的记分牌——一份滚动累计的清单,记录你的测试究竟真正演练过哪些设计意图中的行为和场景,每碰到一种,就在仿真过程中逐一打钩。

说得更确切些,功能覆盖率衡量的是你对照一份亲手写出的覆盖模型所取得的进展:这份清单列出了一个正确的设计必须应对的那些值得关注的情形。你会声明覆盖点(cover point,记录这个操作码字段取过的每一个值)、分桶(bins,把这些值归并到真正要紧的几个桶里——最小值、最大值、溢出)以及交叉覆盖(cross coverage,一次读和一次写有没有在同一个周期里撞上?)。当你的测试跑起来——在 UVM 验证平台里,往往是一大批约束随机激励——仿真器就记录下哪些桶被命中了。被覆盖到的桶所占的百分比,就是你对「我们测够了没有?」这一问的诚实回答。

至关重要的一点是,它不是那个空有相似名字的「代码覆盖率」的近亲。代码覆盖率(行、分支、翻转)只告诉你哪些 RTL 被执行过——你可以把每一行都跑到 100%,却从未测过那个「连续背靠背请求」的场景,而臭虫恰恰就藏在那里。功能覆盖率追踪的是意图:那些你一开始就认定值得检查的场景。代码覆盖率高而功能覆盖率低,意味着你只是闭着眼睛跑了一大堆代码;功能覆盖率高、且各项检查都通过,才让一个团队能板着脸、问心无愧地签字放行。

covergroup cg @(posedge clk);
  cp_op    : coverpoint opcode { bins arith[] = {ADD, SUB}; bins mem[] = {LD, ST}; }
  cp_burst : coverpoint burst  { bins single = {0}; bins burst = {1}; }
  cross cp_op, cp_burst;   // did each op happen during a burst?
endgroup

一个 covergroup 在每个时钟沿采样操作码和突发标志;其中的 cross(交叉)则追问:每一种操作是否都在一次突发传输期间被演练过。

「覆盖收敛」(coverage closure)——把功能覆盖率推到(接近)100%——是一个项目里程碑,并不等于正确性的保证。覆盖率只告诉你某个场景被走到过;至于到了那里之后设计行为对不对,则要由你的检查器和断言来裁定。一个被覆盖到却没有任何检查的桶,不过是在一页没人读过的纸上打了个勾。

又称
functional coveragefeature coveragecoverage closure (metric)功能覆盖率功能覆蓋率