PAYATHON 2026

SystemVerilog 事件合并后为何只有一个等待线程输出?

数据小陈

结论

应在两个线程开始等待之前执行 event_b = event_a。原代码中的两个线程在时间 0 就进入了 wait,到时间 10 才合并事件。此时 Thread2 已经阻塞,不会因赋值转而等待 event_a 对应的事件。

事件合并只影响之后执行的事件控制或等待操作。这符合 IEEE Std 1800-2005 第 14.7.1 节的事件合并规则,也解释了题目中两个仿真器的输出。

原代码为什么只输出 Thread1?

event 变量引用底层同步对象。执行 event_b = event_a 后,两个变量引用同一个对象,但已经挂起的等待进程不会自动转移。

原代码的执行过程如下:

  1. 时间 0:Thread1 等待原来的 event_a,Thread2 等待原来的 event_b。
  2. 时间 10:event_b = event_a 改变了 event_b 的引用,Thread2 仍在等待原来的事件对象。
  3. 时间 20:->event_a 和 ->event_b 都触发 event_a 对应的对象,因此 Thread1 可以继续执行,Thread2 仍然阻塞。

两个 #20 位于 fork 的独立并行分支中,都在时间 20 执行,而不是分别在时间 20 和时间 40 执行。

如果改成 event_a = event_b,重新赋值影响的就变成 Thread1;Thread2 仍等待原来的 event_b,因此只会看到 Thread2 的输出。

如何修改代码?

把合并操作放在 fork 之前,让两个线程进入等待时,事件变量已经引用同一个对象。下面的代码只触发一次 event_a,用来展示两个等待线程都能被唤醒:

module tb;
  event event_a, event_b;

  initial begin
    event_b = event_a;

    fork
      begin
        wait(event_a.triggered);
        $display("[%0t] Thread1: Wait for event_a is over", $time);
      end

      begin
        wait(event_b.triggered);
        $display("[%0t] Thread2: Wait for event_b is over", $time);
      end

      begin
        #20 ->event_a;
      end
    join
  end
endmodule

按上述语义,两个线程都会在事件触发的时间步输出,两条输出的先后顺序不应作为判断正确性的依据。这里没有指定时间单位,因此不能直接把 #20 称为 20 ns。

还需要注意什么?

wait(event.triggered) 利用事件在当前时间步内保持的触发状态,可以处理同一时间步内先触发、后等待的情况,但不能修复等待开始之后重新绑定事件变量的问题。相关机制见 Accellera 的事件语义说明。

原代码中的 join 仍在等待 Thread2。仿真器提示没有更多可执行事件并结束,并不代表所有分支都已完成:阻塞的等待进程本身不会产生未来的仿真活动。

备注:内容仅供参考。