Waiting for events inside fork-join

I have the following code.
According to my understanding the fork-join should wat for all the child processes to be completed.
Inside process B, I am waiting for an event which never happened. Shouldn’t this code end up in timeout ?

module top();
  event e1;
  bit clk;
  task process_A();
    $display("@%0t: Before triggering event e1", $time);
    ->e1;
    $display("@%0t: After triggering event e1", $time);
  endtask 
  task process_B();
    #10;
    $display("@%0t: waiting for the event e1", $time);
    //@e1;
    @(posedge clk);
    $display("@%0t: event e1 is triggered", $time);
  endtask
  initial begin
    fork
      process_A();
      process_B();
    join
    #10;
  end
endmodule

Simulation result says

@0: Before triggering event e1
@0: After triggering event e1
@10: waiting for the event e1

Time: 10 ns

I thought the end time will be minimum 20ns as I have a 10ns delay at the end

Your code can never reach the #10 because the join never happens. The simulation ends at 10 because it has deadlocked - there is nothing left to simulate.

Hi @dave_59, why don’t we see the event e1 in process_b?

module top();
  event e1;
  bit clk;
  task process_A();
    
    $display("@%0t: Before triggering event e1", $time);
    ->e1;
    $display("@%0t: After triggering event e1", $time);
  endtask 
  task process_B();
    #10;
    $display("@%0t: waiting for the event e1", $time);
    @e1;
    @(posedge clk);
    $display("@%0t: event e1 is triggered", $time);
  endtask
  initial begin
    fork
      process_A();
      process_B();
    join
    #10;
  end
endmodule

In process_B, @e1 is set to wait for a trigger after time 10. However, since e1 was triggered at time 0, process_B never detects it.

I thought once e1 was triggered, we can see it any time in the future? Isn’t that the case?

No, that’s not how SystemVerilog events work. When an event (like e1) is triggered, it does not remain “active” indefinitely. Events in SystemVerilog are transient, meaning they are active only at the exact simulation time when they are triggered. If no process is waiting for the event (@e1) at that specific moment, the event is essentially lost.

For this scenario, you can use a semaphore instead of an event.

module top();
  semaphore e1 = new();
  task process_A();
    #5
    $display("@%0t: Before put e1", $time);
    e1.put();
    $display("@%0t: After put e1", $time);
  endtask 
  task process_B();
    #10;
    $display("@%0t: waiting for the get e1", $time);
    e1.get();
    $display("@%0t: put e1 is got", $time);
  endtask
  initial begin
    fork
      process_A();
      process_B();
    join
    #10;
    $display("@%0t: joined", $time);
  end
endmodule