3. 验证计划与策略:VPlan、覆盖率与回归测试
各位同学,今天我们来聊聊验证计划与策略。说实话,这是整个验证流程的「灵魂」。没有计划,你就像无头苍蝇;策略不对,你累死也找不到 bug。
我个人习惯,在项目启动的第一周,花 30% 的时间来制定验证计划。别急着写代码,先想清楚要测什么、怎么测、测到什么程度才算完。这步省了,后面全是坑。
3.1 验证计划(VPlan)的制定
VPlan 是什么?说白了,它就是一张「验证地图」。告诉你每个功能点对应哪些测试用例,覆盖率目标是多少,谁负责,什么时候完成。
我在项目中遇到过最惨的一次,就是 VPlan 写得太粗,结果验证到一半发现某个关键场景根本没覆盖到。嗯,从那以后,我定了个规矩:VPlan 必须经过设计、架构、验证三方评审。
- 功能分解:把 spec 里的每一条功能拆成可验证的原子点
- 测试场景:每个功能点对应哪些正常/异常/边界场景
- 覆盖率目标:功能覆盖率、代码覆盖率分别定多少
- 优先级:哪些是 P0(必须测),哪些是 P1(尽量测)
- 责任人:谁写 test,谁 review,谁 sign-off
举个例子,一个简单的 AXI 总线验证,VPlan 可能长这样:
| 功能点 | 测试场景 | 覆盖率目标 | 优先级 |
|--------|----------|------------|--------|
| 写传输 | 单次写、突发写、outstanding写 | 100% | P0 |
| 读传输 | 单次读、突发读、乱序读 | 100% | P0 |
| 地址对齐 | 对齐/非对齐地址 | 100% | P1 |
| 响应错误 | DECERR、SLVERR | 90% | P1 |
| 超时处理 | 等待超时、重试 | 80% | P2 |
3.2 功能覆盖率的定义
功能覆盖率,说白了就是「你测了没有」。它不像代码覆盖率那样自动生成,需要你手动定义 cover point 和 cross cover。
你想想看,代码覆盖率 100% 不代表功能正确。我曾经见过一个模块,代码覆盖率 95%,但功能覆盖率只有 60%。结果呢?流片回来发现一个 corner case 没覆盖,芯片直接废了。
定义功能覆盖率时,我建议遵循以下原则:
- 从 spec 出发:每个功能点至少对应一个 cover point
- 关注边界:比如 FIFO 满/空、计数器溢出、状态机跳转
- 交叉覆盖:两个独立功能同时发生时,会不会出问题?
- 不要过度:覆盖率不是越高越好,100% 有时不现实,80-90% 就够
看一段 SystemVerilog 的覆盖率定义示例:
covergroup fifo_cg @(posedge clk);
// 覆盖 FIFO 深度
wr_ptr: coverpoint wr_addr {
bins low = {[0:15]};
bins mid = {[16:31]};
bins high = {[32:47]};
bins full = {63};
}
// 覆盖读写同时发生
rd_wr: coverpoint {rd_en, wr_en} {
bins idle = {2'b00};
bins read = {2'b10};
bins write = {2'b01};
bins both = {2'b11};
}
// 交叉覆盖:深度 x 读写
cross wr_ptr, rd_wr;
endgroup
3.3 验证环境的搭建策略
验证环境怎么搭?我个人的经验是:分层、可复用、可配置。
分层是什么意思?就是把环境拆成:
- Test layer:测试用例层,只关心「测什么」
- Sequence layer:激励生成层,负责产生各种 transaction
- Driver/Monitor layer:驱动和监测层,负责和 DUT 交互
- Scoreboard layer:比对层,负责检查结果是否正确
可复用呢?举个例子,你写了一个 AXI VIP,下次做另一个项目,直接拿来用就行。别每次都从头写,那是浪费生命。
可配置就更重要了。同一个环境,通过参数配置,可以测不同规格的 DUT。比如:
class my_env_cfg extends uvm_object;
rand int data_width; // 数据位宽:8/16/32/64
rand int fifo_depth; // FIFO深度:16/32/64
rand bit has_parity; // 是否带奇偶校验
rand bit enable_timeout; // 是否使能超时检测
constraint valid_cfg {
data_width inside {8, 16, 32, 64};
fifo_depth inside {16, 32, 64};
}
endclass
3.4 回归测试策略
回归测试,说白了就是「每次改完代码,把之前的测试全跑一遍」。听起来简单,但做起来学问大了。
我见过很多团队,回归测试跑 3 天都跑不完。为什么?因为 test 太多,仿真太慢。怎么办?
我的策略是:
- 分级回归:把 test 分成 smoke、regression、full 三级。smoke 跑 10 分钟,regression 跑 2 小时,full 跑 24 小时。
- 并行仿真:用多核或服务器集群,把 test 分散跑。我习惯用 LSF 或 Grid Engine 调度。
- 增量回归:只跑受改动影响的 test。怎么判断?靠代码覆盖率分析。
- 自动触发:每次代码提交后,自动触发 smoke 和 regression。失败了发邮件通知。
举个例子,回归测试的 Makefile 可能长这样:
# 回归测试 Makefile
smoke:
vcs -f filelist.f -l smoke.log
./simv +UVM_TESTNAME=smoke_test
regression:
for test in $(TESTS); do \
vcs -f filelist.f -l $$test.log; \
./simv +UVM_TESTNAME=$$test; \
done
full_regression:
for test in $(ALL_TESTS); do \
vcs -f filelist.f -l $$test.log; \
./simv +UVM_TESTNAME=$$test; \
done
最后,总结一下我的经验:验证计划是「做什么」,覆盖率是「做到没」,环境是「怎么做」,回归是「保证没退步」。四者缺一不可。
好了,这一章就到这里。下一章我们聊聊 UVM 的实战技巧,到时候我会分享一些我在项目中踩过的坑。
公众号:蓝海资料掘金营,微信deep3321