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_Athread_B唤醒,thread_A先执行触发->update_ev结束。 disp_thread现在解封了,模拟器有一个选择:继续disp_threadthread_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_Athread_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