Fork-join_none with delay

Hi,

Can someone please explain why j = 0 is not printed in this case?

module fork_test1;
initial begin
for (int j=0; j<3; j++)
begin
  $display($time, "#####");
  #1;

fork
$display(j);
join_none
end
end
endmodule:fork_test1

Thanks!!

In reply to ssakshi:
Please use code tags making your code easier to read. I have added them for you.

Because the first iteration of the fork does not begin execution until the section iteration hits the #1. j will have already advanced to 1.

module fork_test1;
initial 
for (int j=0; j<3; j++)
begin
  $display($time, "#####");
  #1;
 
  fork
    automatic int k = j;
    $display(j,,k);
  join_none
end
endmodule:fork_test1

Hi Dave and ssakshi
I was also was a little bit confused after reading Dave answer so I changed the code a little bit to better understand the issue.
In the first code listing I did what did just a little bit different.


module fork_test1;
  initial begin
    for (int j=0; j<2; j++) begin
      #1;
      $display("j before fork %0d", j);
      fork
        $display("j inside fork-join_none block %0d", j);
      join_none
      $display("j after join_none %0d", j);
   end
  end
endmodule:fork_test1

The results were as you saw in the original question


j before fork 0
j after join_none 0
j inside fork-join_none block 1
j before fork 1
j after join_none 1
j inside fork-join_none block 2


In the second code listing, I added delay after the join_none so j will not advance
before fork-join_none starts


module fork_test1;
  initial begin
    for (int j=0; j<2; j++) begin
      #1;
      $display("j before fork %0d", j);
      fork
        $display("j inside fork-join_none block %0d", j);
      join_none
      $display("j after join_none %0d", j);
      // Add delay
      #1;
   end
  end
endmodule:fork_test1


j before fork 0
j after join_none 0
j inside fork-join_none block 0
j before fork 1
j after join_none 1
j inside fork-join_none block 1


Thanks Dave and shimonc!!
I have a follow-up question.

As per LRM,
Fork-join_none: The parent process continues to execute concurrently with all the processes spawned by the fork.The spawned processes do not start executing until the parent thread executes a blocking statement or terminates.

In the above example,
#1 delay before fork-join_none can be treated as a blocking statement to start execution of spawned processes (ie. $display(“j inside fork-join_none”)) or not?

In reply to ssakshi:

That is correct.

In reply to dave_59:

Dave. Again, Does “exit from task” consider as “terminates” ?
Thanks

In reply to VE:

Calling a task does not create a process, therefore exiting a task does not terminate one. However, every statement in a fork/join block creates a process. When that statement completes, the process terminates. If that statement happens to be a task call, then of course exiting the task call completes the statement, which terminates the process.

In reply to dave_59:
This sounds like a silly question but I am still not able to understand if #1 delay before fork-join_none can be treated as a blocking statement to start the execution of spawned processes, then why does display statement after fork-join_none is executed(printed) before the display statement in fork-join_none.
(Why does the spawned process wait until the parent thread terminates?)

As per my understanding, fork-join_none should start execution as soon as the parent thread executes a blocking statement (#1 before fork-join_none).

In reply to ssakshi:

The rule says “until the parent thread executes a blocking statement or terminates”. This rule eliminates one race condition which allows you to queue up all your forked processes first before allowing them to execute.

It might to explain what’s going on by unrolling the for-loop from shimonc’s first example.
module fork_test1;

  initial begin
    int j;
    j = 0;
    begin
      #1;
      $display("j before fork %0d", j);
      fork
        $display("j inside fork-join_none block %0d", j);
      join_none
      $display("j after join_none %0d", j);
    end
    j++;
    begin
      #1; // this triggers the previous fork/join j is now 1
      $display("j before fork %0d", j);
      fork
        $display("j inside fork-join_none block %0d", j);
      join_none
      $display("j after join_none %0d", j);
    end    
    j++;
    begin
      #1; // this triggers the previous fork/join j is now 2
      $display("j before fork %0d", j);
      fork
        $display("j inside fork-join_none block %0d", j);
      join_none
      $display("j after join_none %0d", j);
    end
    j++;
end // initial process terminates, triggers the previous fork/join j is now 3
endmodule:fork_test1

In reply to dave_59:

Thank you Dave for the detailed explanation.

In reply to dave_59:

In reply to ssakshi:
Please use code tags making your code easier to read. I have added them for you.
Because the first iteration of the fork does not begin execution until the section iteration hits the #1. j will have already advanced to 1.

module fork_test1;
initial 
for (int j=0; j<3; j++)
begin
$display($time, "#####");
#1;
fork
automatic int k = j;
$display(j,,k);
join_none
end
endmodule:fork_test1

Thanks for the explanation. But I made a small change in the above code :

module fork_test1;
initial 
for (int j=0; j<3; j++)
begin
  $display($time, "#####");
  #1;
 
  fork
    $display(j,,k);
  join_none
end
endmodule:fork_test1

Now too will it display
1
2
3
???

In reply to SanjanaDhiran:

It should display:
0#####
1#####
1
2#####
2
3

Explanation:
Below Sequence will be followed-

  1. “0#####” will be executed first
  2. It will execute blocking statement #1
  3. Fork will be spawned but will not start
  4. “1#####” - will Execute the Display statement
  5. “1” - The Blocking statement has been executed it will execute the first spawn “1”
  6. “2#####” Will execute the display statement
  7. “2” - Blocking statement has been executed and will execute the next thread
  8. “3” All statements have been executed and will come out of the loop and display remaining

Thanks
Irshad

In reply to dave_59:

Hi Dave,
I came across a similar question

module t;
  initial
    begin
      $display("%0t main thread started",$time);
      fork
        print(20,"thread_0");
        print(10,"thread_2");
        print(15,"thread_1");
      join_none
      $display("%0t main thread finished",$time);
    end
  task  print(int time_t,string t_name);
   #(time_t)$display("[%0t] %s",$time,t_name);
  endtask  
endmodule

the output is
0 main thread started
0 main thread finished
[10] thread_1
[15] thread_1
[20] thread_1

but when code is modified to

module t;
  initial
    begin
      $display("%0t main thread started",$time);
      fork
        print(20,"thread_0");
        print(10,"thread_2");
        print(15,"thread_1");
      join_none
      $display("%0t main thread finished",$time);
    end
  task  print(int time_t,string t_name);
	$display("[%0d] %s",time_t,t_name);
  endtask  
endmodule

i got output as
0 main thread started
0 main thread finished
[20] thread_0
[10] thread_2
[15] thread_1
In both examples, task is static. can you please help

In reply to roopatoms:

In the both examples, there is only one t_name static variable.

In the first example, by the time the $display executes, t_name has be assigned three times, so it display whatever the last value was. That is a race condition, any you could have seen any of the three thread names three times.

In the second example, there is no delay inside the print task. So there is a race condition between execution of calling all print tasks and the $display statement. It just happens that each task gets called and executes $display before calling the next task in another thread. Technically it’s possible to get the same output as the first example if after calling the print task, it suspends the thread and jumps to the next thread before calling $display.

Thank you Dave. The topic was confusing for me.

In reply to dave_59:
Hi Dave, In the first example by @roopatoms, I understand why t_name gets overwritten since t_name is a static variable. Why isn’t time_t a static variable as well? time_t is also just an argument to the task similar to t_name in this case.

[10] thread_1
[15] thread_1
[20] thread_1
Since thread1 seems to have been the last executed in this case, shouldn’t all tasks be delayed by the last time_t (15) instead of variable delay?

Thanks

In reply to svq:

t_name and time_t are both static variables, and this is a race condition. When executing
#(delay)
, the delay expression gets evaluated first, then the process gets suspended for the delay value amount of time. If the operands of the delay expression later change while the process is suspended, the expression does not get re-evaluated.

Technically it’s possible for one of the task to get called in one process and then immediately switch to another process that calls the same task again and overwrites the arguments. But usually a process runs uninterrupted until hitting a blocking statement.

In reply to dave_59:

Thanks Dave.

In reply to dave_59:

Hi Dave,

In the above code, below statement gets executed immediately i.e. does not wait for #1 scenario of the next iteration.

automatic int k = j;

Is this because of the below reason mentioned in LRM?

LRM:
Variables declared in the block_item_declaration of a fork-join block shall be initialized to their
initialization value expression whenever execution enters their scope and before any processes are spawned.

Thanks

In reply to kuki2002:

That is correct. Actually for this example, it would have made no difference if the automatic variable declaration was moved out of the fork block. Each iteration of the for-loop creates a new scope activation and another instance of the automatic variable k.

module fork_test1;
initial 
for (int j=0; j<3; j++)
begin
  automatic int k = j;
  $display($time, "#####");
  #1;
  fork
    $display(j,,k);
  join_none
end
endmodule:fork_test1