本节摘要:把 2.3 的"单个 testbench"升级成一套验证体系。本节讲验证的正确心态(主动找错而非证明没错)、随机激励与约束求解、断言验证、覆盖率指标,以及"验证计划先行"的方法论。读完你能为中等复杂度的模块制定验证计划,写出带随机激励和断言的测试环境。
先看一个教训。某模块交付前,测试工程师问:"验证做到什么程度了?"开发答:"我写了好几个测试,都跑过了。"结果上板两天,出了一个边界 bug:某个计数器溢出时进位没处理,测试里没覆盖到这个边界。为什么漏了?因为"我写了好几个测试,都跑过了"说的是测试,不是验证。
这两个词的心态完全不同:测试是"我证明它没错",激励温和、覆盖顺路;验证是"我假设它有 bug,把它逼出来",激励凶残、专打边界。前端验证大师的一句话值得刻在桌上:"验证不是证明设计正确,是证明设计错误——直到无法再证明。" 抱着这个心态,才会去设计那些"正常人不会输入"的激励:全 0、全 1、翻转到一半断电、非法状态注入。bug 就藏在这些犄角旮旯里。
手写激励列表总有疏漏,且越写越累。更聪明的做法是随机激励:让仿真器生成随机输入,自动覆盖各种组合。SystemVerilog 的约束求解器让"随机"有方向——不是均匀乱撞,而是受约束的随机:指定某个字段在合法范围内随机,同时指定某些组合必须出现。
// 受约束随机激励(SystemVerilog,概念示例) class Packet; rand bit [7:0] length; // 长度随机 rand bit [3:0] priority; // 优先级随机 constraint c_length { length inside {[1:64]}; // 长度 1~64 length dist {1 := 10, [2:63] := 1, 64 := 5}; // 边界值加权 } constraint c_prio { priority inside {0, 3, 7}; // 只取关键值 } endclass
注意 dist 和 inside 的用法:边界值(1 和 64)被加权多抽,中间值少抽——这正是验证要的分布:功能边界最容易藏 bug,所以激励密度往边界倾斜。随机激励 + 约束求解,比手写几千行激励表覆盖得又快又全面。
激励给了,谁来检查结果?人工盯波形不是办法。**断言(Assertion)**把"这里应该满足的条件"写成可执行代码,仿真过程中一旦违反立即报告。它是验证体系里最接近"自动裁判"的东西。
// 断言示例:握手协议要求 VALID 拉高后,直到传输完成不能变 property handshake_stable; @(posedge clk) valid |-> valid[*1:$] ##1 !valid |-> ready; endproperty // 更实用的一个:总线读写期间数据不允许出现 x 态 property no_x_on_bus; @(posedge clk) !$isunknown(data_bus); endproperty assert property (no_x_on_bus) else $error("数据总线出现未知态");
断言的价值在回归:设计改了一版,跑一遍全量仿真,断言自动把关——哪个功能被改坏了,断言第一个叫。断言写得好,测试环境会越用越值钱。
验证最怕"不知道做到哪了"。覆盖率给了量化答案,分两类:
| 覆盖率 | 含义 | 局限 |
|---|---|---|
| 代码覆盖率 | 每行代码、每个分支被执行过没有 | 只证明"跑到过",不证明"验证过" |
| 功能覆盖率 | 关键功能点被覆盖的百分比 | 需要设计者定义"什么算关键" |
代码覆盖率是起点(比如 95% 的行覆盖),但低代码覆盖率必然有漏洞,高代码覆盖率不必然无漏洞。真正的杀手是功能覆盖率:设计者列出"这个模块要验证的 N 个功能点",逐一打勾。功能覆盖率达到多少能交付没有统一标准,取决于模块的重要程度——关键安全模块可能要求 100%,普通逻辑 70% 就放行。
// 功能覆盖率定义示例:验证"复位后、正常、溢出"三种计数场景 covergroup cnt_cover @(posedge clk); option.per_instance = 1; cp_state: coverpoint state { bins reset_after = {2'b00}; // 复位后状态 bins normal = {2'b01, 2'b10}; // 正常计数 bins overflow = {2'b11}; // 溢出状态 } endgroup
把以上工具串起来,核心方法论只有一条:验证计划先行。写设计之前,先写验证计划:
计划先行还有个隐藏收益:写验证计划的过程就是需求澄清的过程。很多"设计 bug"其实是对需求理解不一致,验证计划把这些分歧提前暴露,比写代码时爆雷便宜得多。
💡 关键直觉:验证的投入产出比在"首次流片/首次上板"那一刻兑现。仿真里多花一天,可能省下上板后一周的调试。别省。
inside/dist 把激励往边界加权,比均匀随机高效。下一步进入 7.2:仿真能拦的拦完了,剩下的交给硬件——板级调试与诊断,ILA 与信号完整性登场。