1800'2017 9.3.2 Parallel blocks: Why Within a fork-join_any it shall be illegal to refer to formal arguments passed by reference

Why does 1800’2017 9.3.2 impose the restriction that within a fork-join_any it shall be illegal to refer to formal arguments passed by reference?

In the example below, in the initial block, I have fork t_top(vtop); where vtop is a module variable. t_top is a task with the formal argument declared as a ref.


   initial fork t_top(vtop);  
   task automatic t_top(ref int vtop);[/b] 
     fork
       t_2nd(vtop);   // ** ERROR **
..

Tools demonstrate the points of 1800’2017 9.3.2 Parallel blocks.
My question is WHY DID THE COMMITTEE WRITE THIS RESTRICTION?
I have an application where I wanted to pass my arguments by reference in forked tasks.


module top; 
    bit clk;
    int vtop;      
    initial forever #10 clk=!clk;   
    
    task automatic t_top(ref int vtop);  
        int a; 
        a= vtop +1; 
        fork
            t_2nd(vtop);   // ** ERROR **
                    //  Reference argument cannot be accessed inside fork-join_none or fork-join_any                 
        join_none    
    endtask :  t_top
    
    task automatic t_2nd(ref int vc);
        vc=vc+2; 
    endtask
    
    
    initial   begin 
        @(posedge clk)  begin 
            fork 
                t_top(vtop);  // **ERROR** 
                    // Actual argument 'vtop' passed as reference cannot be used within 
                    // fork-join_any or fork_join_none blocks
                // 1800'2017 9.3.2 Parallel blocks: 
                // 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.  
            join_none
        end 
    end	
    
    initial repeat(2) @(posedge clk); 
endmodule    

Ben SystemVerilog.us

In reply to ben@SystemVerilog.us:

Hi Ben,

The problem here is when you have a fork/join_none/join_any, you need to know the lifetime of the variables declared outside referenced from inside. SystemVerilog has to extend the lifetime of the variable to include the lifetime of the forked processes to make sure they are still valid at any point while process is still active. When you pass a variable by reference to a task or function, you lose that information because all you know is that you have a handle to an int type. The implementation of the task/function is written to be intenpendent of what gets passed to it.

In mot cases you can easily workaround this limitation as pass by references is rarely needed. In your example, you can replace ref with inout and still get the same functionality.

In reply to dave_59:
Thanks Dave. Below is actually how I intend to use this type of structyre.
After some experiments, it became obvious that if

  • task1 forks task2, and task2 forks task3 then
  • for task3 to write to task1’s variables, task2 must be alive when task3 does the out (the write)
  • in t_2nd, I used the wait (done);, where done is updated in t_3rd.

module top; 
    bit clk;
    
    initial forever #10 clk=!clk;   
    
    task automatic t_top();  
        int vtop=100; 
        bit done;          
        fork
            t_2nd(vtop, done);                  
        join_none   
        $display ("@ %t before the wait, vtop=%d", $realtime, vtop);  

        wait (done); 
        $display ("@ %t after the wait in t_top, vtop=%d", $realtime,  vtop); 
    endtask :  t_top
    
    task automatic t_2nd(inout int vtop, inout bit done);
        // MUST NOT COMPLETE BEFORE t_3rd ENDS
        done=1'b0;  // works with done=1'b1; 
        vtop = vtop +2; 
        $display ("%t vtop in t_2nd=%d", $realtime, vtop);        
        //@(posedge clk);
        fork
            t_3rd(vtop, done);
        join_none
        wait (done); 
        // repeat(5) @(posedge clk) ;
        $display ("@ %t after the wait in t_2nd, vtop=%d", $realtime,  vtop); 

    endtask
    
    task automatic t_3rd(inout int vtop, inout bit done);
        vtop = vtop +1; 
        $display ("%t vtop in t_3rd=%d", $realtime, vtop);        
        @(posedge clk);  
        done=1'b1; 
    endtask
    
    initial   begin 
        @(posedge clk)  begin 
            fork 
                t_top();  
            join_none            
            
        end 
    end	
    
    initial repeat(2) @(posedge clk); 
endmodule   
// simulation 
# @                   10 before the wait, vtop=        100
#                   10 vtop in t_2nd=        102
#                   10 vtop in t_3rd=        103
# @                   30 after the wait in t_2nd, vtop=        103
# @                   30 after the wait in t_top, vtop=        103