Formal arguments passed by reference in fork-join_none

Hi,
system verilog lrm 1800-212 makes following statement on page 176:

“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.”

I am not clear why this restriction should be there. Can someone explain with an example.

also if I have an automatic function foo, which spawns a task tsk1, using fork-join_none and task is passed a local variable of the function by reference. Task modifies a the local variable at 10ns. But since function executes in 0 time, the variable will not exist. Will this result in run-time error?

thanks,
-sunil

In reply to dave_59:

In reply to puranik.sunil@tcs.com:
The problem when passing a variable by reference is that the code inside the function or task knows nothing about the lifetime of the argument being passed, it only know the location where it exists.

Hi Dave,

thanks for the explanation.
Now if the task tsk1 in my question has a parameter prm_out of the type output and this parameter is passed the local variable inside automatic function. Task writes to it at 10 ns. But it will not exist at 10 ns and this will also result in “accessing a variable that no longer exists.” So does this restriction apply to output parameters in addition to reference parameters also? Or the output parameter is simply copied on stack by called function/task and calling function in this case does not read it from stack since calling function no longer exists along with its variables. So output parameter is simply destroyed with stack?
Is this understanding correct?
regards,
-sunil

In reply to puranik.sunil@tcs.com:
I believe what you are asking is this:

module top;
   function automatic foo;
      bit local_prm;
      fork
	 begin : forked
	    tsk1(local_prm);
	    $display("local_prm %b exists till this process ends %t",local_prm, $time);
	 end
      join_none
   endfunction
   task automatic tsk1(output bit prm_out); // ref bit prm_out also allowed
      #10 prm_out = 1;
   endtask

   initial foo();
endmodule

This is allowed because:

The lifetime of a fork-join block (see 9.3.2) shall encompass the execution of all processes spawned by the
block. The lifetime of a scope enclosing any fork-join block includes the lifetime of the fork-join block.

Solocal_prm remains active while the process forked is active even after foo() returns. And it doesn’t matter if prm_out gets passed by reference or not. So it’s a little more complex than just a call stack determining the lifetime of an automatic.

But let’s go back to your original post and modify this example

module top;
   function automatic foo;
      bit prm;
      fork
	 begin : forked
	    tsk1(prm);
	    $display("prm %b exists till this process ends %t",prm, $time);
	 end
      join_none
   endfunction
   task automatic tsk1(ref bit prm_out);
      #10 prm_out = 1;
      fork
	 tsk2(prm_out);
      join_none
   endtask
   task automatic tsk2(ref bit prm_out); // this is illegal
      #10 prm_out = 0;
   endtask
   initial foo;
endmodule

For this to work, the compiler would need to know the lifetime of prm goes beyond the lifetime of the forked process. Generally, it’s not a good idea to have to look inside the task you are calling in determining the behavior of code outside the task call.

[i]In reply to [url=https://verificationacademy.com/forums/
Hi Dave,
thanks for the detailed reply.
The LRM statement below applies only to fork-join. Does it apply to fork-join_none or fork-join_any also? In example 1 which you have given, you have used a fork-join_none block.
regards,
-sunil

In reply to puranik.sunil@tcs.com:

Section 9.3.2 refers to all of the
fork
constructs. When “fork-join” is used without being highlighted as syntax, like
fork join
, it refers to all the parallel
fork
constructs.

In reply to dave_59:


This is allowed because: Solocal_prm remains [I]active
while the process forked is active even after foo() returns. And it doesn’t matter if prm_out gets passed by reference or not. So it’s a little more complex than just a call stack determining the lifetime of an automatic.
But let’s go back to your original post and modify this example

module top;
function automatic foo;
bit prm;
fork
begin : forked
tsk1(prm);
$display("prm %b exists till this process ends %t",prm, $time);
end
join_none
endfunction
task automatic tsk1(ref bit prm_out);
#10 prm_out = 1;
fork
tsk2(prm_out);
join_none
endtask
task automatic tsk2(ref bit prm_out); // this is illegal
#10 prm_out = 0;
endtask
initial foo;
endmodule

For this to work, the compiler would need to know the lifetime of prm goes beyond the lifetime of the forked process. Generally, it’s not a good idea to have to look inside the task you are calling in determining the behavior of code outside the task call.

Hi Dave,
referring to explanation you have given above, i have some more questions.

  1. based on the rule on “scope and lifetime, section 6.21” stated above, lifetime of local_prm in first example will be extended to 10 ns. So the simulator will not de-allocate the memory till 10ns. But how will this be achieved? Will compiler, while compiling the code for tsk1 figure out that local_prm needs to be active till 10ns and put this information in compiled code (using a timestamp?), which simulator will use at runtime?
  2. Also in second example also, considering the scope and lifetime rule, will compiler have to figure out that lifetime of prm needs to be extended till 10ns +10ns so that it can be accessed by tks2. Is this what you are referring to when you say “For this to work, the compiler would need to know the lifetime of prm goes beyond the lifetime of the forked process. Generally, it’s not a good idea to have to look inside the task you are calling in determining the behavior of code outside the task call.”
  3. The difference between first example (which works) and second one (illegal) is that tsk1 is spawning one more task tsk2. So will same problem occur if tsk2 is spawned using just fork-join and not fork-join_none.

regards,
-sunil

In reply to puranik.sunil@tcs.com:

  1. The compiler just needs to associate a particular scope, or set of scopes with an automatic variable that need to exit before deallocating that variable.
  2. The compiler just needs to know that the ‘forked’ scope has to finish before it deallocates prm.
  3. There is no problem when using fork/join to spawn tsk2. That is because the ‘forked’ scope will not end until after the call to tsk1 returns.