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