3. 验证计划制定:如何编写验证计划、验证范围定义、功能覆盖率规划、验证环境架构设计

好,咱们直接进入正题。验证计划,说白了就是整个验证工作的「作战地图」。没有它,你就像在黑夜里开车——能走,但随时可能翻沟里。我做了十几年芯片验证,见过太多团队上来就写代码,结果到后期发现功能点漏了、覆盖率凑不够、环境跑不通……嗯,那滋味可不好受。

3.1 验证计划到底该怎么写?

我个人习惯,写验证计划之前先问自己三个问题:

  • 测什么?——验证范围
  • 怎么算测完?——功能覆盖率
  • 用什么测?——验证环境架构

这三个问题想清楚了,计划的大纲就出来了。别一上来就堆模板,那是给自己挖坑。

我的小技巧: 验证计划最好用「表格+文字」的形式。表格列功能点,文字写策略。我曾经见过有人写了80页纯文字的计划,结果没人愿意看第二遍。

3.2 验证范围定义——别贪多,也别漏掉

验证范围,就是明确「我们要测哪些功能,不测哪些功能」。这步做不好,后面全是糊涂账。

我建议从三个维度来切分:

维度 说明 举例(服务器芯片)
功能维度 芯片规格书里的每个功能点 Cache一致性、中断处理、DMA传输
接口维度 所有对外接口协议 PCIe、DDR、AXI、I2C、SPI
场景维度 实际使用中的典型场景 多核并发、低功耗模式切换、热插拔

你想想看,这三个维度交叉起来,基本就能覆盖全了。我在项目中遇到过最典型的翻车案例:功能点都测了,接口协议也对,但就是没测「多核同时访问同一个地址」的场景——结果流片回来,Cache一致性直接崩了。

避坑指南: 我曾经因为「这个场景太复杂,先不测了」这句话,多花了三个月做ECO。记住:验证范围里「不测什么」比「测什么」更重要,一定要白纸黑字写清楚,让架构师签字确认。

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 验证环境架构设计——搭积木的艺术

验证环境架构,说白了就是「怎么搭一个能跑、能调、能复用的测试平台」。我个人的经验是:模块级环境要轻量,系统级环境要灵活

一个典型的服务器芯片验证环境,通常包含这几层:

  1. 测试用例层: 各种定向测试和随机测试
  2. 场景生成层: 产生激励的sequence和driver
  3. 协议层: 各种总线协议模型(AXI、PCIe、DDR等)
  4. DUT层: 被测设计,也就是你的芯片RTL
  5. 监测层: monitor、checker、scoreboard
  6. 覆盖率层: 前面说的功能覆盖率模型

我建议用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)              │
└─────────────────────────────────────────┘
我的建议: 环境架构设计时,一定要留出「调试接口」。比如加一个debug_mode信号,可以在仿真时dump关键波形。否则出了问题,你只能对着log猜——那感觉,比看天书还难受。

3.5 验证计划中的时间与资源估算

最后,验证计划里还得写清楚「多久能测完」。这个估算其实挺玄学的,但我有个经验公式:

阶段 时间占比 说明
环境搭建 20% 包括UVM环境、BFM、monitor
定向测试 30% 覆盖所有功能点,跑通基本场景
随机测试 30% 大量随机激励,找边界和bug
覆盖率收敛 15% 补测试、调约束、加assertion
回归与签收 5% 全量回归、报告生成、评审

你可能会问:「为什么随机测试要占30%?」 因为随机测试才是真正能挖出深层次bug的阶段。我记得有一次,定向测试全过了,覆盖率也到了90%,结果随机测试跑了三天,发现了一个只有在特定时序窗口下才会触发的死锁——这种bug,定向测试根本测不到。

最后提醒一句: 验证计划不是写一次就完事的。随着项目推进,你会发现新的功能点、新的边界情况。我建议每两周review一次验证计划,该加的加,该删的删。别死守着最初的版本不放——那是刻舟求剑。

好了,关于验证计划制定,核心就是这些。记住:好的验证计划,能让你的验证工作事半功倍;差的验证计划,只会让你在deadline前夜疯狂补测试。你自己选吧。