Systemverilog - 多个进程触发同一事件
Systemverilog - multiple process triggering same event
为什么我会得到如下所示的结果?我希望 update_ev 事件的多个触发应该导致显示 "Main Loop ,.." 被执行两次。但是只执行一次。
program multiple_trigger();
initial begin
event update_ev;
bit orResult, prev_orResult, A, B;
// Multiple trigg logic
fork
begin : threadDisplay
forever begin
prev_orResult = orResult;
// Update status
@(update_ev);
// Compute A OR B
orResult = A | B;
$display("\n Main Loop , %0t A=%0b, B=%0b orResult=%0b",$time(), A, B, orResult);
if (prev_orResult != orResult) begin
$display("\n In the IF condition, %0t A=%0b, B=%0b orResult=%0b",$time(), A, B, orResult);
end
end // forever
end : threadDisplay
// 10 A=0
begin : threadA
#10;
A = 1'b0;
->update_ev;
end : threadA
// 10 B=1'b1
begin : threadB
#10;
B = 1'b1;
->update_ev;
end : threadB
join_none
#100;
end
endprogram
// Actual Result----------------------------------------
Main Loop , 10 A=0, B=1 orResult=1
In the IF condition , 10 A=0, B=1 orResult=1
//-----------------------------------------------------
// Expected Result----------------------------------------
Main Loop , 10 A=0, B=0 orResult=0
Main Loop , 10 A=0, B=1 orResult=1
In the IF condition , 10 A=0, B=1 orResult=1
// -------------------------------------------------------
实际结果和预期结果都是可能的,因为这是一种竞争条件。没有什么可以阻止第二个 ->update_ev 在 @(update_ev) 响应之前触发。
我通常建议人们避免 SV 事件,在这种情况下,函数调用将达到预期目的。
事件触发器 ->
可能显示为堆栈,具体取决于调度顺序。 LRM 声明调度程序可以 以不确定的顺序评估同一区域中的事件。我使用的每个模拟器似乎都没有随机化事件顺序;他们倾向于优先考虑编译顺序。
让我们为 fork 中的线程指定标签名称。然后浏览一个可能的场景。
- disp_thread 将是
forever
的过程
- thread_A 将是
A = 1'b0;
的过程
- thread_B 将是
B = 1'b1;
的过程
模拟器先执行disp_thread,执行到@(update_ev);
时停止。然后thread_A就开始了,马上睡觉(会在#10
走上去)。 thread_B 统计数据并睡眠 #10
。如果没有安排任何操作,模拟将不正确的时间步长。 thread_A 和 thread_B唤醒,thread_A先执行触发->update_ev
结束。 disp_thread现在解封了,模拟器有一个选择:继续disp_thread或thread_B。通常,它会选择继续 disp_thread 并开始它的第二个循环(因为永远在同一个进程线程中)和在 @(update_ev);
处再次暂停。 thread_B 现在更改为 运行 并触发 ->update_ev
并完成。 disp_thread 再次解锁,因此它可以再次 运行。
如果您将事件触发器移动到事件等待语句之上,您可能看到 $display
消息只发出一次。然而,这是一个脆弱的解决方案,并没有重新开始。
而是使用非阻塞事件触发器 (->>
)。这将移动调度程序的事件触发 NBA 区域。允许触发线程 运行 并在解锁 disp_thread 之前安排更新。进行此更改后,日程安排如下:
模拟器先执行disp_thread,执行到@(update_ev);
时停止。然后thread_A就开始了,马上睡觉(会在#10
走上去)。 thread_B 统计数据并睡眠 #10
。如果没有安排任何操作,模拟将不正确的时间步长。 thread_A 和 thread_B 唤醒,然后 thread_A 先执行,然后 schedules ->>update_ev
for NBA 并结束。此时调度器还在Active区域; disp_thread还是被屏蔽了。 thread_B(目前唯一可以运行的运行)将运行和为 NBA 地区安排 ->>update_ev
并完成。注意到当前区域中可以运行,调度程序继续到下一个非空区域; NBA。 update_ev
的事件触发器都发生了。调度程序重新进入活动区域,disp_thread 现在已解锁。
注意:如果您打算使用它,请保持简单并让自己非常了解调度语义(IEEE std 1800-2012 § 4)。随着复杂性的增加(例如未阻止的阻塞事件触发新事件),可预测性将变得脆弱。当可预测性中断时,它可能随机发生,来自不同的模拟器、版本、机器、OS、系统时间、系统负载等
program multiple_trigger();
initial begin
event update_ev1, update_ev2;
bit orResult, prev_orResult, A, B;
// Multiple trigg logic
fork
begin : threadDisplay
forever begin
prev_orResult = orResult;
// Update status
@(update_ev1, update_ev2);
// Compute A OR B
orResult = A | B;
$display("\n Main Loop , %0t A=%0b, B=%0b orResult=%0b",$time(), A, B, orResult);
if (prev_orResult != orResult) begin
$display("\n In the IF condition, %0t A=%0b, B=%0b orResult=%0b",$time(), A, B, orResult);
end
end // forever
end : threadDisplay
// 10 A=0
begin : threadA
#10;
A = 1'b0;
->>update_ev1;
end : threadA
// 10 B=1'b1
begin : threadB
#10;
wait(update_ev1.triggered);
B = 1'b1;
->>update_ev2;
end : threadB
join_none
#100;
end
endprogram
为什么我会得到如下所示的结果?我希望 update_ev 事件的多个触发应该导致显示 "Main Loop ,.." 被执行两次。但是只执行一次。
program multiple_trigger();
initial begin
event update_ev;
bit orResult, prev_orResult, A, B;
// Multiple trigg logic
fork
begin : threadDisplay
forever begin
prev_orResult = orResult;
// Update status
@(update_ev);
// Compute A OR B
orResult = A | B;
$display("\n Main Loop , %0t A=%0b, B=%0b orResult=%0b",$time(), A, B, orResult);
if (prev_orResult != orResult) begin
$display("\n In the IF condition, %0t A=%0b, B=%0b orResult=%0b",$time(), A, B, orResult);
end
end // forever
end : threadDisplay
// 10 A=0
begin : threadA
#10;
A = 1'b0;
->update_ev;
end : threadA
// 10 B=1'b1
begin : threadB
#10;
B = 1'b1;
->update_ev;
end : threadB
join_none
#100;
end
endprogram
// Actual Result----------------------------------------
Main Loop , 10 A=0, B=1 orResult=1
In the IF condition , 10 A=0, B=1 orResult=1
//-----------------------------------------------------
// Expected Result----------------------------------------
Main Loop , 10 A=0, B=0 orResult=0
Main Loop , 10 A=0, B=1 orResult=1
In the IF condition , 10 A=0, B=1 orResult=1
// -------------------------------------------------------
实际结果和预期结果都是可能的,因为这是一种竞争条件。没有什么可以阻止第二个 ->update_ev 在 @(update_ev) 响应之前触发。
我通常建议人们避免 SV 事件,在这种情况下,函数调用将达到预期目的。
事件触发器 ->
可能显示为堆栈,具体取决于调度顺序。 LRM 声明调度程序可以 以不确定的顺序评估同一区域中的事件。我使用的每个模拟器似乎都没有随机化事件顺序;他们倾向于优先考虑编译顺序。
让我们为 fork 中的线程指定标签名称。然后浏览一个可能的场景。
- disp_thread 将是
forever
的过程
- thread_A 将是
A = 1'b0;
的过程
- thread_B 将是
B = 1'b1;
的过程
模拟器先执行disp_thread,执行到@(update_ev);
时停止。然后thread_A就开始了,马上睡觉(会在#10
走上去)。 thread_B 统计数据并睡眠 #10
。如果没有安排任何操作,模拟将不正确的时间步长。 thread_A 和 thread_B唤醒,thread_A先执行触发->update_ev
结束。 disp_thread现在解封了,模拟器有一个选择:继续disp_thread或thread_B。通常,它会选择继续 disp_thread 并开始它的第二个循环(因为永远在同一个进程线程中)和在 @(update_ev);
处再次暂停。 thread_B 现在更改为 运行 并触发 ->update_ev
并完成。 disp_thread 再次解锁,因此它可以再次 运行。
如果您将事件触发器移动到事件等待语句之上,您可能看到 $display
消息只发出一次。然而,这是一个脆弱的解决方案,并没有重新开始。
而是使用非阻塞事件触发器 (->>
)。这将移动调度程序的事件触发 NBA 区域。允许触发线程 运行 并在解锁 disp_thread 之前安排更新。进行此更改后,日程安排如下:
模拟器先执行disp_thread,执行到@(update_ev);
时停止。然后thread_A就开始了,马上睡觉(会在#10
走上去)。 thread_B 统计数据并睡眠 #10
。如果没有安排任何操作,模拟将不正确的时间步长。 thread_A 和 thread_B 唤醒,然后 thread_A 先执行,然后 schedules ->>update_ev
for NBA 并结束。此时调度器还在Active区域; disp_thread还是被屏蔽了。 thread_B(目前唯一可以运行的运行)将运行和为 NBA 地区安排 ->>update_ev
并完成。注意到当前区域中可以运行,调度程序继续到下一个非空区域; NBA。 update_ev
的事件触发器都发生了。调度程序重新进入活动区域,disp_thread 现在已解锁。
注意:如果您打算使用它,请保持简单并让自己非常了解调度语义(IEEE std 1800-2012 § 4)。随着复杂性的增加(例如未阻止的阻塞事件触发新事件),可预测性将变得脆弱。当可预测性中断时,它可能随机发生,来自不同的模拟器、版本、机器、OS、系统时间、系统负载等
program multiple_trigger();
initial begin
event update_ev1, update_ev2;
bit orResult, prev_orResult, A, B;
// Multiple trigg logic
fork
begin : threadDisplay
forever begin
prev_orResult = orResult;
// Update status
@(update_ev1, update_ev2);
// Compute A OR B
orResult = A | B;
$display("\n Main Loop , %0t A=%0b, B=%0b orResult=%0b",$time(), A, B, orResult);
if (prev_orResult != orResult) begin
$display("\n In the IF condition, %0t A=%0b, B=%0b orResult=%0b",$time(), A, B, orResult);
end
end // forever
end : threadDisplay
// 10 A=0
begin : threadA
#10;
A = 1'b0;
->>update_ev1;
end : threadA
// 10 B=1'b1
begin : threadB
#10;
wait(update_ev1.triggered);
B = 1'b1;
->>update_ev2;
end : threadB
join_none
#100;
end
endprogram