Search code examples
pythonverilogfpgahdl

Evaluation Event Scheduling - Verilog Stratified Event Queue


I am trying to implement a simple event based Verilog simulator in Python, but I actually struggle to find some details in the specification (section 11 of IEEE 1364-2005).

Let's say I just perfomed an update event on clk which now obtained the new value 1 (0 before). According to the specification this requires me to schedule 'evaluation events' for sensitive processes.

Do I have to schedule the evaluation of an always @(posedge clk) block as active or inactive event? I'm guessing that the latter is correct?

Or actually, to speak more general. Are basically all events scheduled as inactive events with the following exceptions:

  • Non blocking assignment updates as non-blocking assignment events
  • continuous assignments as active events

Thanks a lot!


Solution

  • By default everything runs in the Active region. The exceptions being:

    • #0 blocking assignments are in the Inactive region
    • Non-blocking assignments are in the NBA region
    • $monitor, $strobe, and PLI/VPI are in the Monitor region

    We can prove that @ happens in the Active region, by running the following in any existing simulator:

    int x; 
    initial begin 
       $monitor("From    $monitor: x is %0d",x); 
       #0 x = 5; // inactive event 
       x = 3; // active event (after inactive event)
       #1; // go to next time stamp
       fork
         #0 x = 7; // inactive event 
         x = 11; // active event 
       join
       #0 fork // inactive region
         #0 x = 2; // inactive event 
         x = 5; // active event 
         x <= 4; // NBA event 
       join
    end 
    // active event region
    always @* $display("From @* $display: x is %0d",x); 
    

    Outputs:

     From @* $display: x is 3
     From $monitor: x is 3
     From @* $display: x is 11
     From @* $display: x is 7
     From @* $display: x is 5
     From @* $display: x is 2
     From @* $display: x is 4
     From $monitor: x is 4
    

    Display reports more times then monitor. This rules out @ accruing in the Monitor or Future region. Display is is reporting on every event region. Each event region can only loop back to the Active region. Therefor, the @ must be handled in the Active region and each region that updates the variable x is triggering a new Active region event.

    This can also be proved by knowing looking at the history of Verilog. Unfortunately, this is not well documented. I leaned learned about it through people that were using/developing verilog in the late '80s early '90s. The over all explanation is: The Inactive and NBA regions were added to Verilog before IEEE Std 1364-1995, @ predates these two region. The regions were added to add determinism to a non-deterministic simulator.

    always @(posedge clk) pipe0 = in;
    always @(posedge clk) pipe1 = pipe0; // unpredictable, non-deterministic order
    
    always @(posedge clk) #0 pipe0 = in; // Inactive region added some determinism
    always @(posedge clk) pipe1 = pipe0; 
    
    always @(posedge clk) #0 pipe0 = in; // But was fragile
    always @(posedge clk) #0 pipe1 = pipe0; // unpredictable order again
    always @(posedge clk) pipe2 = pipe1; 
    
    always @(posedge clk) pipe0 <= in; 
    always @(posedge clk) pipe1 <= pipe0; 
    always @(posedge clk) pipe2 <= pipe1; // NBA region fixed it 
     ...
    always @(posedge clk) pipeN <= pipeM; //             and made it scalable
    



    clarification based on feedback from comments

    Events are activated, not moved. Activated NBA events enter the true condition of if (E is an update event), modified object and schedule new evaluation events (handled the next time Active region entered). Once all the activated events are completed, the schedule goes back to the top of the while-loop. The NBA region only assigns values, the evaluation was actually done in an earlier Active region stage.
    From your example:

    module TEST;
       reg test = 0;
       reg test2 = 0;
       reg clk = 0;
    
       initial begin
          clk <= 1;
          test <= 1;
       end
    
       always @(posedge clk) begin
          test2 <= test;
       end
    endmodule

    Each iteration of the while loop would look something like this:

    Iteration:0
      Active: <----- This is region is executing
          clk$tmp = eval(1)
          test$tmp = eval(1)
      Inactive:
      NBA:
          clk = clk$tmp
          test = test$tmp
    Iteration:1
      Active:
      Inactive:
      NBA: <----- This is region is executing
          clk = clk$tmp
          test = test$tmp
          Active.schedule( eval( "@(posedge clk)" )
    Iteration:2
      Active: <----- This is region is executing
          eval( "@(posedge clk)" )
          Active.schedule( "test2$tmp = eval(test)" )
          NBA.schedule( "test2 = test2$tmp" )
      Inactive:
      NBA:
    Iteration:3
      Active: <----- This is region is executing
          test2$tmp = eval(test)
      Inactive:
      NBA:
          test2 = test2$tmp
    Iteration:4
      Active:
      Inactive:
      NBA: <----- This is region is executing
          test2 = test2$tmp
    Iteration:5 --> next simulation cycle