4、功能仿真策略:定向测试与随机测试、覆盖率驱动验证、断言(SVA)基础
功能仿真,说白了就是我们在流片前,用软件模拟芯片的行为。你想想看,一颗芯片动辄几百万、几千万门,要是直接拿去流片,回来发现功能不对,那损失可就大了。所以,仿真策略的好坏,直接决定了你能不能在流片前把 bug 抓干净。
我个人习惯把功能仿真分成三个层次:定向测试打基础,随机测试扫盲区,覆盖率驱动做收尾。再加上断言(SVA)这个“电子警察”,基本就能把验证做扎实了。
4.1 定向测试:先保证“能跑通”
定向测试,就是针对特定的功能点,写特定的测试用例。比如你要验证一个加法器,那就写个 1+1=2,再写个 255+1=0(溢出)。
我在项目中遇到过,有些新人一上来就搞随机测试,结果跑了一整天,连最基本的复位功能都没验证。这其实是个误区。定向测试虽然“土”,但它是最可靠的。
举个例子,验证一个 FIFO(先进先出存储器):
- 正常路径:写一个数,读一个数,看看对不对。
- 边界条件:写满后还能写吗?读空后还能读吗?
- 异常情况:同时读写同一个地址,会不会冲突?
嗯,这里要注意,定向测试的代码要写得“干净”。我习惯把每个测试用例封装成一个独立的 task,方便后期维护和复用。
4.2 随机测试:让“意外”帮你找 bug
定向测试能覆盖你想到的,但芯片的 bug 往往出现在你没想到的地方。这时候,随机测试就派上用场了。
说白了,随机测试就是让仿真工具随机生成输入激励。比如给一个总线接口,随机发地址、随机发数据、随机发控制信号。你想想看,人工写 1000 个定向用例可能累死,但随机测试一晚上就能跑几万个。
我曾经在一个项目中,用定向测试跑了两个星期,覆盖率卡在 85% 上不去。后来加了随机测试,三天就冲到了 95%。
随机测试的典型流程:
- 定义随机变量(地址、数据、控制信号)。
- 添加约束(比如地址不能超过 0xFF)。
- 启动仿真,收集结果。
- 分析失败用例,定位 bug。
4.3 覆盖率驱动验证:用数据说话
你可能会问:“我怎么知道验证做够了没有?” 答案就是——看覆盖率。
覆盖率驱动验证(CDV),说白了就是“用数据指导验证”。我们不再凭感觉说“我觉得测够了”,而是看覆盖率报告:代码覆盖率、功能覆盖率、翻转覆盖率……
| 覆盖率类型 | 含义 | 我的建议 |
|---|---|---|
| 代码覆盖率 | RTL 代码中每行、每个分支是否被执行 | 目标 100%,但有些死代码可以排除 |
| 功能覆盖率 | 设计规格中定义的功能点是否被覆盖 | 必须 100%,这是验证的“合同” |
| 翻转覆盖率 | 信号是否从 0 翻到 1、从 1 翻到 0 | 重点关注控制信号和状态机 |
我在项目中遇到过,代码覆盖率到了 98%,但功能覆盖率只有 60%。这说明什么?说明很多代码虽然被执行了,但关键功能没测到。所以,功能覆盖率才是硬指标。
4.4 断言(SVA)基础:给芯片装上“电子警察”
断言,SystemVerilog Assertion(SVA),是验证中最强大的工具之一。它就像交通摄像头,时刻盯着芯片内部的关键信号,一旦出现违规,立刻报警。
举个例子,你要验证一个握手协议:请求信号 req 拉高后,应答信号 ack 必须在 3 个时钟周期内拉高。用 SVA 写出来就是:
property p_req_ack;
@(posedge clk)
req |-> ##[1:3] ack;
endproperty
assert property (p_req_ack);
你看,就三行代码。但如果没有断言,你可能要在仿真波形里手动去数时钟周期,累死个人。
我个人习惯把断言分成三类:
- 立即断言:检查当前时刻的信号值。比如“复位期间,所有输出必须为 0”。
- 并发断言:检查跨时钟周期的时序关系。比如上面的 req-ack 例子。
- 序列断言:检查多个事件的先后顺序。比如“先写后读,不能先读后写”。
嗯,这里还要提一句:断言不仅能帮你抓 bug,还能帮你理解设计。我在调试一个复杂的 DMA 控制器时,就是靠断言一步步理清了状态机的跳转逻辑。说白了,断言就是设计的“说明书”。
4.5 本章小结:策略组合拳
好了,我们来捋一捋:
- 定向测试:打基础,保底线。
- 随机测试:扫盲区,找意外。
- 覆盖率驱动:定标准,看结果。
- 断言(SVA):装监控,抓违规。
这四个东西不是孤立的。我通常的做法是:先用定向测试把基本功能跑通,然后上随机测试配合断言,最后用覆盖率报告来评估验证质量。如果覆盖率不达标,就补充定向用例或者调整随机约束。
你想想看,这套组合拳打下来,芯片流片回来的成功率,是不是高多了?