SystemVerilog 事件合并后为何只有一个等待线程输出?
结论
应在两个线程开始等待之前执行 event_b = event_a。原代码中的两个线程在时间 0 就进入了 wait,到时间 10 才合并事件。此时 Thread2 已经阻塞,不会因赋值转而等待 event_a 对应的事件。
事件合并只影响之后执行的事件控制或等待操作。这符合 IEEE Std 1800-2005 第 14.7.1 节的事件合并规则,也解释了题目中两个仿真器的输出。
原代码为什么只输出 Thread1?
event 变量引用底层同步对象。执行 event_b = event_a 后,两个变量引用同一个对象,但已经挂起的等待进程不会自动转移。
原代码的执行过程如下:
- 时间 0:Thread1 等待原来的
event_a,Thread2 等待原来的event_b。 - 时间 10:
event_b = event_a改变了event_b的引用,Thread2 仍在等待原来的事件对象。 - 时间 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。仿真器提示没有更多可执行事件并结束,并不代表所有分支都已完成:阻塞的等待进程本身不会产生未来的仿真活动。
备注:内容仅供参考。
