Restrictions on fork join_any / join_none

Hi All,

LRM Section 9.3.2 Parallel blocks mentions

A return statement within the context of a fork-join block is illegal and shall result in a compilation error.

For example:
task wait_20;
  fork
   # 20;
   return ; // Illegal: cannot return; task lives in another process
  join_none
endtask

(1) I am trying to understand the logical reason behind this restriction

I understand that there exist 2 child threads due to join_none and the parent thread is where task resides.

The same section further states

Within a fork-join_any or fork-join_none block, it shall be illegal to refer to 
formal arguments passed by reference other than in the initialization value
expressions of variables declared in a block_item_declaration of the fork

(2) I am not clear on the reason behind this restriction. Logically why is it that child threads can’t access arguments pass as ref ?

If I want to detect change in input signal within 3ns ( including change at exactly 3ns ),

I would try to define a task as ::

task automatic (ref int a);
 bit change;
fork
 begin:P

  fork
   begin:C1
    #3ns;
   end

   begin:C2
     @(a);
     change = 1;
   end

  join_any
  uvm_wait_for_nba_region();    
  disable fork;
  if(change) `uvm_info(..)

 end:P
join 
endtask

(3) With the above restricton, how should one model the task ?

(1) This is because each process you create has an independent call stack. Every time you call a function or task, you push a context onto the stack. Returning or exiting from the call pops the stack. Your fork/join_none block creates two processes, each with its own stack. The return statement has nothing to return from. A better example might be:

task wait_20;
  fork
   # 20;
   #100 return ; // Illegal: cannot return; the task has already returned
  join_none
endtask

That return statement would attempt to pop a call context from the stack that doesn’t exist.

(2) This restriction is primarily intended to keep the LRM simple. There are many other restrictions on the usage of automatic variables. One significant issue arises from the requirement to ensure that the lifetime of an automatic variable is synchronized with the lifetimes of all processes that reference it. Tracking this synchronization through a formal reference argument becomes challenging, particularly given the task is unaware of the actual argument’s lifetime. The LRM 1800-2023 partially addresses this issue by introducing a new argument specification called static ref. This specification mandates that the actual argument you pass must have a static lifetime. By doing so, it eliminates all the restrictions associated with automatic variables inside tasks/functions

(2) you can model this with a simple fork/join

task automatic wait_3 (ref int a);
 bit change timeout;
 fork
   begin:C1
    #3ns timeout <=1;
   end
   begin:C2
     @(a,timeout)
     if (!timeout) change = 1;
     disable C1;
   end
  join
 if(change) `uvm_info(..)
endtask

This works as long as there is only one concurrent activation of this task.