4、基础仿真验证:手动激励编写、波形观察、状态跳转检查、覆盖率概念引入
各位同学,咱们今天聊点实在的。
仿真验证,说白了就是给设计“喂”信号,看它怎么动。很多新手一上来就想着自动化、UVM,其实没必要。我做了十几年验证,最深的体会是:手动激励是基本功,波形观察是眼睛,状态跳转是灵魂。这三样玩不转,后面再高级的工具也是白搭。
4.1 手动激励编写——别小看这“笨办法”
手动激励,就是直接在testbench里用initial块或者task,一条条地给信号赋值。听起来很原始对吧?但我在项目中遇到过好几次,自动生成的激励死活复现不了某个bug,最后用手动写了几行就抓到了。
举个例子,一个简单的状态机,有IDLE、READ、WRITE三个状态。手动激励可以这样写:
initial begin
// 复位
rst_n = 0;
#100 rst_n = 1;
#20;
// 发起一次读操作
req = 1;
rw = 0; // 读
#10;
req = 0;
// 等待状态跳转
#50;
// 发起一次写操作
req = 1;
rw = 1; // 写
#10;
req = 0;
#100;
$finish;
end
嗯,这里要注意:时序一定要对齐。我见过太多人写激励时,时钟沿和赋值时机没配合好,结果仿真波形看起来对,实际逻辑根本没跑到。
4.2 波形观察——你的第二双眼睛
波形是验证工程师的“X光片”。你不会看波形,就等于医生不会看片子。
我个人习惯,打开波形后先做三件事:
- 看时钟和复位——这是基础,乱了就全乱
- 看状态机当前状态——用radix设为enum或symbolic,别盯着二进制数猜
- 看关键控制信号——比如req、ack、done这些握手信号
为什么会这样?因为状态机出问题,十有八九是状态跳转条件没满足,或者跳到了不该去的状态。波形上一眼就能看出来。
4.3 状态跳转检查——验证的核心
状态机验证,说白了就是检查它能不能按设计意图跳转。我一般分三步走:
- 第一步:列出所有合法跳转路径。比如IDLE可以到READ,READ可以到DONE,DONE回到IDLE。
- 第二步:手动激励覆盖每条路径。别偷懒,每条路径至少跑一次。
- 第三步:检查非法跳转。比如从IDLE能不能直接跳到DONE?如果能,那就是bug。
你想想看,如果状态机有5个状态,合法跳转可能有10条。手动写10个激励场景,花不了多少时间,但能发现80%的bug。
// 检查状态跳转的简单方法
always @(posedge clk) begin
if (!rst_n) begin
// 复位后必须在IDLE
assert (state == IDLE);
end else begin
case (state)
IDLE: begin
if (req && !rw) assert (next_state == READ);
if (req && rw) assert (next_state == WRITE);
end
READ: begin
if (done) assert (next_state == IDLE);
end
// ... 其他状态
endcase
end
end
4.4 覆盖率概念引入——从“跑没跑过”到“跑没跑全”
手动激励写完了,波形也看了,状态跳转也检查了。然后呢?你怎么知道你的激励够不够?
这就是覆盖率要解决的问题。
覆盖率说白了就两件事:
- 代码覆盖率:你的激励让RTL代码里的每一行、每一个分支都执行过吗?
- 功能覆盖率:你的激励覆盖了所有你想测的场景吗?
我记得刚入行时,领导问我:“你测了哪些场景?”我答:“都测了。”领导又问:“你怎么证明?”我哑口无言。后来才知道,覆盖率就是用来“证明”的。
对于状态机,最常用的覆盖率是:
| 覆盖率类型 | 检查什么 | 手动激励能覆盖吗 |
|---|---|---|
| 状态覆盖率 | 每个状态是否都进入过 | 可以,手动跑一遍就行 |
| 状态跳转覆盖率 | 每条跳转路径是否都走过 | 可以,但路径多了就累 |
| 条件覆盖率 | 每个跳转条件是否都测过 | 手动写比较麻烦 |
等你把状态机的所有跳转都手动跑了一遍,再去看工具自动收集的覆盖率报告,你会发现:原来我漏了这么多边角情况。这时候,你就真正理解覆盖率的意义了。
嗯,今天就先聊到这儿。手动激励、波形观察、状态跳转检查、覆盖率概念——这四个东西是基础中的基础。别急着学UVM,先把这些基本功练扎实了,后面学什么都快。