4. 基础激励编写:简单赋值、force/release、系统任务
好了,到了真正动手写激励的时候了。前面我们搭好了环境,搞清楚了波形怎么看,现在该让DUT动起来了。
激励编写,说白了就是给DUT的输入端口“喂数据”。你喂什么,它就跑什么。我见过不少新手,一上来就想着写复杂的sequence,结果连最基本的赋值都翻车。咱们先把地基打牢。
4.1 简单赋值:阻塞 vs 非阻塞
这是最基础,也是最容易踩坑的地方。SystemVerilog里赋值分两种:阻塞赋值(=)和非阻塞赋值(<=)。
核心区别一句话:阻塞赋值是“立即生效”,非阻塞赋值是“事件结束后统一更新”。
在激励块(initial/always)里,我个人的习惯是:
- 组合逻辑建模用阻塞赋值
- 时序逻辑建模用非阻塞赋值
- Testbench顶层驱动——嗯,这里要注意,驱动时钟沿附近的信号,我建议用非阻塞,避免仿真竞争。
举个例子:
// 阻塞赋值 - 立即生效
initial begin
a = 1;
b = a; // b 立即变成 1
#10;
a = 0;
b = a; // b 立即变成 0
end
// 非阻塞赋值 - 事件调度
initial begin
a <= 1;
b <= a; // b 还是原来的 a 值(旧值)
#10;
a <= 0;
b <= a; // b 还是 #10 之前的 a 值(1)
end
我曾经在项目中遇到过一个问题:用阻塞赋值驱动时钟沿采样的数据总线,结果仿真波形看起来完全正确,但后仿就是时序违例。查了半天,发现是阻塞赋值导致仿真器“提前”看到了数据,掩盖了真实的时序关系。从那以后,我驱动DUT输入时,一律用非阻塞。
| 赋值类型 | 执行时机 | 推荐场景 | 避坑点 |
|---|---|---|---|
= (阻塞) |
立即执行,阻塞后续语句 | 组合逻辑、变量计算 | 不要在时钟沿附近驱动DUT输入 |
<= (非阻塞) |
当前时间步结束时统一更新 | 时序逻辑、TB顶层驱动 | 不要在同一always块内混合使用 |
4.2 Force/Release:调试利器,慎用
force 和 release 是仿真里的“暴力手段”。它能强行覆盖DUT内部任何信号的值,不管你是reg还是wire。
你想想看,调试的时候发现某个内部状态机卡住了,你想看看如果它跳到另一个状态会怎样?用 force 就对了。
initial begin
// 正常驱动
clk = 0;
rst_n = 0;
#100 rst_n = 1;
// 强行把DUT内部状态机状态改成 IDLE
force dut.u_state_machine.current_state = 2'b00;
#20;
// 释放,让DUT自己跑
release dut.u_state_machine.current_state;
end
警告:Force 是双刃剑。我曾经在项目中用 force 临时修了一个bug,结果忘了 release,后仿的时候那个信号一直卡在固定值,整个模块功能全乱套了。记住:force 之后一定要 release,尤其是在回归测试里。
另外,force 有优先级:
force可以覆盖assign连续赋值force可以覆盖always块内的赋值- 多个
force同时作用时,最后执行的生效
4.3 系统任务:$display, $monitor, $time
这三个是调试的“三件套”。我刚开始做验证时,全靠它们打log找bug。
$display:打印一次
最常用的打印任务。遇到就打印,打完就完事。
initial begin
$display("Time = %0t, rst_n = %b, data = %h", $time, rst_n, data);
end
格式化符号要记牢:%d 十进制,%h 十六进制,%b 二进制,%0t 时间(不带前导零)。
$monitor:持续监控
这个任务很聪明。你只要声明一次,只要监控的变量发生变化,它就自动打印。不用你写循环。
initial begin
$monitor("Time = %0t, a = %d, b = %d, sum = %d", $time, a, b, sum);
end
我个人习惯在测试开始的时候开一个 $monitor,盯着关键信号。但要注意,如果监控的信号太多,仿真速度会变慢。我曾经在调试一个DMA控制器时,开了十几个 $monitor,结果仿真跑了一整天……后来改成只在关键阶段开,速度提升明显。
$time:当前仿真时间
返回当前仿真时间戳。配合 $display 和 $monitor 使用,定位bug的时间点。
小技巧:用 $realtime 可以返回带小数的时间精度,适合后仿时检查时序裕量。
4.4 知识体系总览
下面这张图把本章的核心知识点串起来了。你可以把它当作一个快速索引。
4.5 实战小练习
光看不练假把式。给你一个小任务:
- 写一个initial块,用非阻塞赋值驱动一个计数器
- 用
$monitor打印计数器的值 - 在某个时刻用
force把计数器强行置为0 - 观察波形,看看
force生效后,$monitor是否立即打印