3. 验证计划制定:如何编写验证计划、验证范围定义、功能覆盖率规划、验证环境架构设计
好,咱们直接进入正题。验证计划,说白了就是整个验证工作的「作战地图」。没有它,你就像在黑夜里开车——能走,但随时可能翻沟里。我做了十几年芯片验证,见过太多团队上来就写代码,结果到后期发现功能点漏了、覆盖率凑不够、环境跑不通……嗯,那滋味可不好受。
3.1 验证计划到底该怎么写?
我个人习惯,写验证计划之前先问自己三个问题:
- 测什么?——验证范围
- 怎么算测完?——功能覆盖率
- 用什么测?——验证环境架构
这三个问题想清楚了,计划的大纲就出来了。别一上来就堆模板,那是给自己挖坑。
3.2 验证范围定义——别贪多,也别漏掉
验证范围,就是明确「我们要测哪些功能,不测哪些功能」。这步做不好,后面全是糊涂账。
我建议从三个维度来切分:
| 维度 | 说明 | 举例(服务器芯片) |
|---|---|---|
| 功能维度 | 芯片规格书里的每个功能点 | Cache一致性、中断处理、DMA传输 |
| 接口维度 | 所有对外接口协议 | PCIe、DDR、AXI、I2C、SPI |
| 场景维度 | 实际使用中的典型场景 | 多核并发、低功耗模式切换、热插拔 |
你想想看,这三个维度交叉起来,基本就能覆盖全了。我在项目中遇到过最典型的翻车案例:功能点都测了,接口协议也对,但就是没测「多核同时访问同一个地址」的场景——结果流片回来,Cache一致性直接崩了。
3.3 功能覆盖率规划——别等最后才想起来
功能覆盖率,是验证质量的「硬指标」。很多团队把覆盖率当成事后统计,这是大错特错。正确的做法是:写验证计划时,就把覆盖率模型设计好。
我个人习惯把功能覆盖率分成三类:
- 状态覆盖率:FSM走到了哪些状态?有没有死循环?
- 数据覆盖率:数据通路上的值域、边界、特殊模式是否都覆盖了?
- 交互覆盖率:多个模块之间的握手、冲突、超时是否都测到了?
举个例子,一个简单的AXI总线仲裁器,你的覆盖率规划可以这样写:
// 功能覆盖率模型示例(SystemVerilog)
covergroup axi_arbiter_cg @(posedge clk);
// 状态覆盖率:仲裁状态机
arb_state_cp: coverpoint arb_fsm_state {
bins idle = {IDLE};
bins grant_a = {GRANT_A};
bins grant_b = {GRANT_B};
bins conflict = {CONFLICT};
}
// 数据覆盖率:请求优先级组合
priority_cp: coverpoint {req_a, req_b} {
bins single_a = {2'b10};
bins single_b = {2'b01};
bins both = {2'b11};
bins none = {2'b00};
}
// 交互覆盖率:连续仲裁次数
grant_count_cp: coverpoint grant_count {
bins single = {1};
bins multi = {[2:5]};
bins many = {[6:$]};
}
endgroup
嗯,这里要注意:覆盖率bin的数量不是越多越好。我见过有人一个coverpoint写了200个bin,结果仿真跑三天都覆盖不到——那是浪费资源。合理的做法是:先覆盖典型值和边界值,特殊值用assertion来补。
3.4 验证环境架构设计——搭积木的艺术
验证环境架构,说白了就是「怎么搭一个能跑、能调、能复用的测试平台」。我个人的经验是:模块级环境要轻量,系统级环境要灵活。
一个典型的服务器芯片验证环境,通常包含这几层:
- 测试用例层: 各种定向测试和随机测试
- 场景生成层: 产生激励的sequence和driver
- 协议层: 各种总线协议模型(AXI、PCIe、DDR等)
- DUT层: 被测设计,也就是你的芯片RTL
- 监测层: monitor、checker、scoreboard
- 覆盖率层: 前面说的功能覆盖率模型
我建议用UVM来搭环境,原因很简单:UVM的factory机制和config机制,能让你在后期加测试点时少改很多代码。我曾经在一个项目里,因为没用UVM,每次加新测试都要改driver——改到后面自己都分不清哪个版本是哪个了。
这里给一个简化的环境架构图(用文字描述):
┌─────────────────────────────────────────┐
│ Test Layer │
│ (test_base, test_random, test_directed) │
├─────────────────────────────────────────┤
│ Scenario Generator │
│ (sequence_lib, virtual_sequencer) │
├──────────┬──────────┬────────────────────┤
│ AXI Agent│ PCIe Agt │ DDR Agent │
│ (drv/mon)│(drv/mon) │ (drv/mon) │
├──────────┴──────────┴────────────────────┤
│ DUT (RTL) │
├─────────────────────────────────────────┤
│ Monitor & Checker │
│ (protocol_checker, scoreboard) │
├─────────────────────────────────────────┤
│ Coverage Collector │
│ (functional_cg, code_cov) │
└─────────────────────────────────────────┘
3.5 验证计划中的时间与资源估算
最后,验证计划里还得写清楚「多久能测完」。这个估算其实挺玄学的,但我有个经验公式:
| 阶段 | 时间占比 | 说明 |
|---|---|---|
| 环境搭建 | 20% | 包括UVM环境、BFM、monitor |
| 定向测试 | 30% | 覆盖所有功能点,跑通基本场景 |
| 随机测试 | 30% | 大量随机激励,找边界和bug |
| 覆盖率收敛 | 15% | 补测试、调约束、加assertion |
| 回归与签收 | 5% | 全量回归、报告生成、评审 |
你可能会问:「为什么随机测试要占30%?」 因为随机测试才是真正能挖出深层次bug的阶段。我记得有一次,定向测试全过了,覆盖率也到了90%,结果随机测试跑了三天,发现了一个只有在特定时序窗口下才会触发的死锁——这种bug,定向测试根本测不到。
好了,关于验证计划制定,核心就是这些。记住:好的验证计划,能让你的验证工作事半功倍;差的验证计划,只会让你在deadline前夜疯狂补测试。你自己选吧。