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 知识体系总览

下面这张图把本章的核心知识点串起来了。你可以把它当作一个快速索引。

基础激励编写知识体系 激励编写核心 简单赋值 Force / Release 系统任务 阻塞 = 非阻塞 <= force release $display $monitor $time 💡 核心原则 驱动DUT输入用非阻塞赋值,调试用force但要记得release,打log用$monitor监控关键信号

4.5 实战小练习

光看不练假把式。给你一个小任务:

  1. 写一个initial块,用非阻塞赋值驱动一个计数器
  2. 用 $monitor 打印计数器的值
  3. 在某个时刻用 force 把计数器强行置为0
  4. 观察波形,看看 force 生效后,$monitor 是否立即打印