Why loop variable should not be used in FORK JOIN_NONE

Hi Forum.,
This question is very lengthy as i have described my question in great detail.Please go through it patiently.
In the below code attached(FIRST SNIPPET), I am using loop variable directly in the FORK JOIN_NONE block.If i use like that i understood that fork join_none will take last updated loop variable value(in this example last updated loop variable value will be i=3). And result of that display i have pasted below.

But in same code(SECOND SNIPPET), If i consider automatic variable in the fork join_none block ,it will keep each iteration value in separate memorey and it will update accordingly. I have pasted of SECOND Snippet result as well.
My question is ,1)why when we are using loop variable in fork join_none it will take last updated value(Please give solid reason).
2)As i am using this task as a class member ‘i’(loop variable should also have been automatic variable)

(FIRST SNIPPET):-


module test;
  class member;   
    task main_task(bit[7:0] a,b,c,d);
      fork 
        sub_task(a,b,"FIRST THREAD");
        sub_task(c,d,"SECOND THREAD");
      join
    endtask
    task automatic sub_task(bit[7:0] p,q,string label);
      bit[7:0] m,n;
      for(int i=0;i<3;i++)
        begin //for begin
          fork 
            begin //fork begin
              m=p*i;
              n=q*i;
              $display("Tag=%s,m=%0d,n=%0d,i=%0d",label,m,n,i);
            end //fork end
          join_none
        end //for end
    endtask
  endclass
  
  member m1;
  initial begin
    m1=new();
    for(int i=1;i<5;i++) begin
      m1.main_task(i,i+1,i+2,i+3); end
  end
endmodule

Result of FIRST SNIPPET :=
Tag=FIRST THREAD,m=3,n=6,i=3
Tag=FIRST THREAD,m=3,n=6,i=3
Tag=FIRST THREAD,m=3,n=6,i=3
Tag=SECOND THREAD,m=9,n=12,i=3
Tag=SECOND THREAD,m=9,n=12,i=3
Tag=SECOND THREAD,m=9,n=12,i=3
Tag=FIRST THREAD,m=6,n=9,i=3
Tag=FIRST THREAD,m=6,n=9,i=3
Tag=FIRST THREAD,m=6,n=9,i=3
Tag=SECOND THREAD,m=12,n=15,i=3
Tag=SECOND THREAD,m=12,n=15,i=3
Tag=SECOND THREAD,m=12,n=15,i=3
Tag=FIRST THREAD,m=9,n=12,i=3
Tag=FIRST THREAD,m=9,n=12,i=3
Tag=FIRST THREAD,m=9,n=12,i=3
Tag=SECOND THREAD,m=15,n=18,i=3
Tag=SECOND THREAD,m=15,n=18,i=3
Tag=SECOND THREAD,m=15,n=18,i=3
Tag=FIRST THREAD,m=12,n=15,i=3
Tag=FIRST THREAD,m=12,n=15,i=3
Tag=FIRST THREAD,m=12,n=15,i=3
Tag=SECOND THREAD,m=18,n=21,i=3
Tag=SECOND THREAD,m=18,n=21,i=3
Tag=SECOND THREAD,m=18,n=21,i=3

(SECOND SNIPPET)


module test;
  class member;   
    task main_task(bit[7:0] a,b,c,d);
      fork 
        sub_task(a,b,"FIRST THREAD");
        sub_task(c,d,"SECOND THREAD");
      join
    endtask
    task automatic sub_task(bit[7:0] p,q,string label);
      bit[7:0] m,n;
      for(int i=0;i<3;i++)
       automatic int ch=i;
        begin //for begin
          fork 
            //automatic int ch=i;
            begin //fork begin
              m=p*ch;
              n=q*ch;
              $display("Tag=%s,m=%0d,n=%0d,i=%0d",label,m,n,i);
            end //fork end
          join_none
        end //for end
    endtask
  endclass
  
  member m1;
  initial begin
    m1=new();
    for(int i=1;i<5;i++) begin
      m1.main_task(i,i+1,i+2,i+3); end
  end
endmodule

Result of SECOND SNIPPET :=
Tag=FIRST THREAD,m=0,n=0,ch=0
Tag=FIRST THREAD,m=1,n=2,ch=1
Tag=FIRST THREAD,m=2,n=4,ch=2
Tag=SECOND THREAD,m=0,n=0,ch=0
Tag=SECOND THREAD,m=3,n=4,ch=1
Tag=SECOND THREAD,m=6,n=8,ch=2
Tag=FIRST THREAD,m=0,n=0,ch=0
Tag=FIRST THREAD,m=2,n=3,ch=1
Tag=FIRST THREAD,m=4,n=6,ch=2
Tag=SECOND THREAD,m=0,n=0,ch=0
Tag=SECOND THREAD,m=4,n=5,ch=1
Tag=SECOND THREAD,m=8,n=10,ch=2
Tag=FIRST THREAD,m=0,n=0,ch=0
Tag=FIRST THREAD,m=3,n=4,ch=1
Tag=FIRST THREAD,m=6,n=8,ch=2
Tag=SECOND THREAD,m=0,n=0,ch=0
Tag=SECOND THREAD,m=5,n=6,ch=1
Tag=SECOND THREAD,m=10,n=12,ch=2
Tag=FIRST THREAD,m=0,n=0,ch=0
Tag=FIRST THREAD,m=4,n=5,ch=1
Tag=FIRST THREAD,m=8,n=10,ch=2
Tag=SECOND THREAD,m=0,n=0,ch=0
Tag=SECOND THREAD,m=6,n=7,ch=1
Tag=SECOND THREAD,m=12,n=14,ch=2

In reply to Yash_wanth12345:

First of all, there is no need for the automatic keyword in any of this code as class methods can only have automatic lifetimes, and all the variables declared within a class method have automatic lifetimes by default.

The key to answering your question is understanding when an automatic variable gets created and initialized, and when statements inside fork/join_none get executed.

In the first snippet, the initial block calls main_task, which forks two calls to sub_task. Inside the sub_task, there is a for loop that creates a loop iterator variable i. Since main_task gets called 4 times, that means 8 calls to sub_task and that creates 8 instances of the loop iterator variable i There is a fork_join/none inside the for loop having a begin/end that references i, so the lifetime of i gets extended until that begin/end block finishes, and by then all 8 loop iterators have the value 3.

A fork/join_none does not start executing any of its nested blocks or statements until the parent thread suspends or terminates. Since the inner for loop iterates 3 times, there are a total 24 begin/end blocks queued to start executing when the parent initial thread terminates. At that point, all 8 instances of the iterator variable i have reached their final value 3.

In the second snippet you have declared ch inside the inner for loop, so that means there will be 24 instances of that variable, each with their lifetime beginning at iteration of the for loop initialized with the current value of i.

Note that you have a typo with the declaration of ch. you have it outside the for-begin block; it should be inside. It also makes no difference if you put the declaration of ch inside the fork-begin. In both cases there we be 24 instances of that variable, each with their lifetime beginning at iteration of the for loop initialized with the current value of i.

In reply to dave_59:

Thanks Dave for your suggestion.
I have one more doubt. In the below attached snippet what is the difference if i declare automatic int ch=i inside for_begin block or inside fork_join_none block(as i have commented in the code)
If i use automatic int ch=i inside fork_join_none block ,that statement will also be treated as separate thread and there will be two different threads one is automatic int ch=i and one more is begin_end block inside fork_join_none. And what if begin_end block inside fork_join_none tries to get ch value before it is updated.
NOTE :- I have corrected my typo as you suggested in above reply.


module test;
  class member;   
    task main_task(bit[7:0] a,b,c,d);
      fork 
        sub_task(a,b,"FIRST THREAD");
        sub_task(c,d,"SECOND THREAD");
      join
    endtask
    task automatic sub_task(bit[7:0] p,q,string label);
      bit[7:0] m,n;
      for(int i=0;i<3;i++)
        begin //for begin
         automatic int ch=i;
          fork 
            //automatic int ch=i;
            begin //fork begin
              m=p*ch;
              n=q*ch;
              $display("Tag=%s,m=%0d,n=%0d,i=%0d",label,m,n,i);
            end //fork end
          join_none
        end //for end
    endtask
  endclass
 
  member m1;
  initial begin
    m1=new();
    for(int i=1;i<5;i++) begin
      m1.main_task(i,i+1,i+2,i+3); end
  end
endmodule

In reply to Yash_wanth12345:
It makes no difference if the declaration of ch is in the begin or fork blocks—initializations of automatic variables work the same. A fork block does not consider them as executable statements.

In reply to dave_59:


module test;
  task automatic write_int (input int i, k) ;
    $display ("in task =%d and k= %d", i, k);  
  endtask  
  initial begin 
    for ( int i=0;i<3;i++) begin        
        automatic int k =i;
      fork
        $display ("iterator =%d and %d",i,k);
        write_int(i, k);
      join_none;  
    end
  end 
endmodule

iterator = 3 and 0
in task = 3 and k= 0
iterator = 3 and 1
in task = 3 and k= 1
iterator = 3 and 2
in task = 3 and k= 2

Hi Dave, In the above code snippet (slight variation of original code), for-loop is calling an automatic task 3 times. Since automatic tasks are reentrant and has its own stack frame and own copy of variables, shouldn’t i (loop variable)in the task print 0, 1, and 2 instead of 3. I understand statements inside fork join none only executes after a blocking call but for-loop is calling the task and shouldn’t ‘i’ be initialized at the time of call with 0, 1 and 2 and not be overwritten since automatic task has its own copy of loop variable.

Also will the behavior change if the task write_int is a static task?

In reply to svq:

This has to do with the actual values passed into write_int(). Even though each call to the task has its own copy of the formal arguments, they all will have the same value of i by the time the call occurs. It has nothing to do with the task being static or automatic in this case.

In reply to dave_59:

Thanks for the explanation. It makes sense.So you are saying though the for loop schedules the task to execute 3 times, it gets actually called after i changes to 3. So in case of static task we get value of 3 as well.