3. 验证计划与策略:VPlan、覆盖率与回归测试

各位同学,今天我们来聊聊验证计划与策略。说实话,这是整个验证流程的「灵魂」。没有计划,你就像无头苍蝇;策略不对,你累死也找不到 bug。

我个人习惯,在项目启动的第一周,花 30% 的时间来制定验证计划。别急着写代码,先想清楚要测什么、怎么测、测到什么程度才算完。这步省了,后面全是坑。

3.1 验证计划(VPlan)的制定

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 |
我的小技巧:VPlan 最好用 Excel 或 Google Sheet 管理,方便追踪状态。每完成一个测试点,就标记为「done」。这样项目进度一目了然。

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
注意:功能覆盖率不是「测完就完」。你要定期查看哪些 bin 没被覆盖到,然后补充测试用例。我曾经有个项目,覆盖率卡在 85% 两周不动,后来发现是一个 reset 场景没写 cover point。

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
避坑指南:我曾经犯过一个错——环境搭得太复杂,结果 debug 时连自己都看不懂。后来我学乖了:先搭一个最小可用环境,跑通一个最简单的 test,再逐步加功能。这叫「渐进式搭建」。

3.4 回归测试策略

回归测试,说白了就是「每次改完代码,把之前的测试全跑一遍」。听起来简单,但做起来学问大了。

我见过很多团队,回归测试跑 3 天都跑不完。为什么?因为 test 太多,仿真太慢。怎么办?

我的策略是:

  1. 分级回归:把 test 分成 smoke、regression、full 三级。smoke 跑 10 分钟,regression 跑 2 小时,full 跑 24 小时。
  2. 并行仿真:用多核或服务器集群,把 test 分散跑。我习惯用 LSF 或 Grid Engine 调度。
  3. 增量回归:只跑受改动影响的 test。怎么判断?靠代码覆盖率分析。
  4. 自动触发:每次代码提交后,自动触发 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
重要提醒:回归测试不是「跑完就完」。你要看结果!我见过有人跑完 regression,发现 10 个 test 失败,但没看 log 就直接 merge 代码了。结果呢?第二天发现 bug 已经进了主干,回退都来不及。

最后,总结一下我的经验:验证计划是「做什么」,覆盖率是「做到没」,环境是「怎么做」,回归是「保证没退步」。四者缺一不可。

好了,这一章就到这里。下一章我们聊聊 UVM 的实战技巧,到时候我会分享一些我在项目中踩过的坑。


公众号:蓝海资料掘金营,微信deep3321