验证方法论:从定向测试到随机测试
做验证这些年,我最大的感触就是——验证方法论的演进,其实就是一个「从确定到不确定」的过程。你想想看,早期我们做验证,脑子里想的是「这个功能我要测,那个场景我要跑」,一条一条列出来,像写检查清单一样。后来发现,这玩意儿根本测不完。
今天我们就来聊聊,定向测试和随机测试到底有什么区别?UVM 那套方法论到底在解决什么问题?还有覆盖率驱动验证(CDV)这个概念,为什么成了现代验证的基石。
定向测试:最朴素的验证方式
定向测试,说白了就是「我知道你要干什么,我就测什么」。比如我要验证一个加法器,我就写个测试用例:输入 1+1,检查输出是不是 2。再写一个:输入 2+3,检查输出是不是 5。一条一条写下去。
我在刚入行那会儿,就是这么干的。那时候项目小,功能点也就几十个,写个两三百条定向测试用例,感觉还挺有成就感。但后来项目越来越大,功能点动辄上千,定向测试就扛不住了。
定向测试有几个明显的痛点:
- 人力成本高:每个测试用例都要手动编写,一个功能点可能要写好几条
- 覆盖率低:你只能测到你想到的场景,想不到的就漏了
- 维护困难:设计一改,测试用例得跟着改,改到崩溃
我曾经踩过的坑:有一次做总线仲裁器的验证,我写了 200 多条定向测试,自认为覆盖得很全面了。结果仿真一跑,发现有个边界情况——两个 master 同时发起请求,且优先级相同——我压根没写测试用例。后来花了三天补用例,还差点延误项目节点。从那以后,我就再也不敢只靠定向测试了。
随机测试:让工具帮你找 bug
随机测试的思路很简单:我不指定具体的输入值,而是给一个范围或约束,让仿真器随机生成输入。比如加法器,我约束输入范围是 0~255,然后跑 10000 次随机,看看有没有问题。
你可能会问:「随机出来的结果,我怎么知道对不对?」嗯,这就是关键——你得有参考模型或者断言来检查结果。没有检查机制,随机测试就是瞎跑。
随机测试的好处很明显:
- 自动生成大量测试:跑一次仿真,相当于几千条定向测试
- 发现意外场景:你没想到的边界情况,随机可能帮你撞到
- 减少人力投入:写约束比写具体用例快得多
但随机测试也不是万能的。我记得有个项目,我们跑了一周的随机测试,覆盖率纹丝不动。后来一查,发现约束写得太松了,随机出来的值全在同一个区间打转。所以,随机测试的关键在于——约束要精准,覆盖率要盯着。
UVM 方法论简介
UVM(Universal Verification Methodology)说白了就是一套验证的「标准打法」。它把验证平台拆成几个固定的组件,每个组件干自己的活,互相之间通过 TLM(Transaction Level Modeling)通信。
UVM 的核心组件包括:
| 组件 | 作用 | 我常用的写法 |
|---|---|---|
| test | 测试用例的入口,控制整个验证流程 | 在 build_phase 里配置环境 |
| env | 验证环境的容器,把各个组件组装起来 | 一般只做连线,不做逻辑 |
| agent | 封装 driver、monitor、sequencer | 一个接口对应一个 agent |
| driver | 把 sequence 转成具体的信号时序 | 用 get_next_item 拿数据 |
| monitor | 监听接口信号,转成 transaction | 用 analysis_port 发送出去 |
| scoreboard | 比对实际结果和期望结果 | 用 compare 函数做检查 |
UVM 最大的好处是什么?我觉得是「可复用」。你写一个 agent,换个项目换个接口,改几行代码就能用。我做过一个项目,三个不同的 IP 共用同一套 UVM 环境,只是改了改约束和参考模型,省了至少两个月的工作量。
我个人习惯:写 UVM 环境时,先把 interface 和 transaction 定义好,这两个是骨架。骨架搭好了,后面的组件往里填就行,不容易乱。
覆盖率驱动验证(CDV)概念
覆盖率驱动验证,英文叫 Coverage-Driven Verification,简称 CDV。这个概念其实很简单:用覆盖率来指导验证的进度和方向。
覆盖率分两种:
- 代码覆盖率:看你的 RTL 代码跑了多少行、多少分支、多少状态机状态。这个工具自动收集,不需要你额外写东西。
- 功能覆盖率:看你的功能点有没有被覆盖到。这个需要你手动定义 covergroup 和 coverpoint。
CDV 的流程大概是这样的:
- 先跑一轮随机测试
- 收集覆盖率数据
- 分析哪些功能点没覆盖到
- 针对性地加约束或写定向测试
- 再跑,再看覆盖率
- 直到所有覆盖率目标达成
我画了一张图,帮你理解 CDV 的核心逻辑:
从图上你能看到,CDV 是一个闭环流程。随机测试跑完,看覆盖率,没达标就加定向测试,再跑,再看。直到所有覆盖率目标都达到,验证才算完成。
我的经验:功能覆盖率不要一开始就追求 100%。先定个 70% 的目标,跑一轮,看看哪些点容易覆盖,哪些点死活覆盖不到。那些死活覆盖不到的点,往往就是 bug 藏身的地方。我遇到过好几次,某个 coverpoint 一直为 0,一查发现是设计逻辑有漏洞。
三种方法论的对比
我把定向测试、随机测试和 CDV 放在一起对比一下,这样更直观:
| 维度 | 定向测试 | 随机测试 | CDV |
|---|---|---|---|
| 测试生成方式 | 手动编写 | 自动生成 | 自动+手动补充 |
| 覆盖率控制 | 靠经验 | 靠约束 | 靠覆盖率反馈 |
| 人力投入 | 高 | 中 | 中高 |
| 发现意外 bug 的能力 | 低 | 高 | 高 |
| 适用场景 | 小项目、关键功能 | 中大型项目 | 复杂项目、高质量要求 |
你可能会问:「那我到底该用哪种?」我的建议是——别死磕一种。实际项目中,三种方法往往是混合使用的。关键功能用定向测试保底,常规功能用随机测试铺量,再用 CDV 的覆盖率数据来指导补充。
注意:覆盖率 100% 不代表验证完了。代码覆盖率 100% 只能说明每行代码都跑到了,但跑到的结果对不对,那是另一回事。功能覆盖率 100% 也只能说明你定义的功能点都覆盖了,但你没定义到的功能点呢?所以,覆盖率只是手段,不是目的。
好了,关于验证方法论的内容就聊到这里。定向测试、随机测试、UVM 方法论、CDV 概念,这几个东西是验证工程师的必修课。理解它们各自的优缺点和适用场景,你就能在实际项目中做出更合理的选择。
可可精品资料库