Flavours of Fork..Join

Fork..Join:

Fork…Join construct of System Verilog actually enables concurrent execution of each of its statements/threads/processes. This feature is most widely used for forking parallel processes/threads in System Verilog Test Benches. 

System Verilog came up with new and advanced flavors of fork join construct which adds a lot of value for implementers.

  1. Fork..Join_any
  2. Fork..Join_none

Let’s start with basic fork..join concept with examples

Example:

module fork_join;
 
  initial begin
    
    $display("**********BEFORE FORK..JOIN**********");                                     
    
    fork
      #15 $display($time,"\tThread A");
      #5  $display($time,"\tThread B");
      #10 $display($time,"\tThread C");
      #2  $display($time,"\tThread D");
    join

      #7  $display($time,"\tThread E");
 
    $display("**********AFTER FORK..JOIN**********");
    
    $finish;
  end
endmodule

All the threads/processes executed parallelly in the above example so based on timestamp it showed output. As Thread D having least delay so it executed first then Thread B and so on up to Thread A. But you might notice that even after #7 delay to Thread E which is lesser than delay of Thread C and Thread A then why it executed last the reason is just because It will come out of fork..join block only when all the threads/processes finished its execution within fork..join block.

Let’s understand this concept with one more example

module fork_join;
 
  initial begin
    
    $display("**********BEFORE FORK..JOIN**********");
    
    fork
      begin
        #15 $display($time,"\tThread A");
        #5  $display($time,"\tThread B");
      end
      begin
        #10 $display($time,"\tThread C");
        #2  $display($time,"\tThread D");
      end
    join
    
      #7  $display($time,"\tThread E");
 
    $display("**********AFTER FORK..JOIN**********");
    
    $finish;
  end
  
endmodule

Here in this above example there are two begin..end threads/processes and as you know whichever statements inside begin..end executes one by one. So in these two threads least delay is Thread C you might wonder how it is least delay, the reason behind that is in first begin..end Thread A starts with #15 while in second begin..end Thread C starts with #10 so in both the threads Thread C is having least delay so in parallel execution it executes first. 

Let’s understand advanced flavors of fork..join one by one with examples

Fork..Join_Any:

The parent thread blocks until any one of the threads spawned by this fork finish. It means if you have 2 or more process in your fork..join_any block and each thread need different time to finish. In this case, whichever thread finished first, fork..join_any will comes out of the block and will start executing the next thread/statement in simulation. It does not mean that the rest of the threads will be automatically discarded by simulation. Those threads will be running in the background.

Example: 

module fork_join_any;
 
  initial begin
    
    $display("**********BEFORE FORK..JOIN_ANY**********");
    
    fork
        #15 $display($time,"\tThread A");
        #5  $display($time,"\tThread B");
        #10 $display($time,"\tThread C");
    Join_any

        #2  $display($time,"\tThread D");
      #12  $display($time,"\tThread E");
 
    $display("**********AFTER FORK..JOIN_ANY**********");
    
   #30 $finish;
  end
  
endmodule

Here we can see that as per fork..join_any concept Thread A finished first so, it comes out of fork..join_any block and rest of the threads will execute in background. 

Another Example:

module fork_join_any;
 
  initial begin
    
    $display("**********BEFORE FORK..JOIN_ANY**********");
    
    fork
      begin
        #20 $display($time,"\tThread A");
        #25 $display($time,"\tThread B");
      end
      begin
        #10 $display($time,"\tThread C");
        #2  $display($time,"\tThread D");
      end
    join_any
       #4  $display($time,"\tThread E");
       #3  $display($time,"\tThread F");
 
    $display("**********AFTER FORK..JOIN_ANY**********");
    
    #30 $finish;
  end
  
endmodule

We can see clearly see how this above example behaves as per fork..join_any concept.

Fork..Join_none:

The parent thread continues to execute concurrently with the rest of all threads spawned by the fork..join_none. The spawned threads do not start executing until the parent thread executes a blocking statement. This means it does not wait for the completion of any threads, it just starts and then immediately comes out from the fork..join_none block. It does not mean that it will not execute threads at all. Important thing is, it will not block (threads which are executing parallel in the background) the simulation and it will simply come out of fork..join_none block and execute next statements in simulation.

Example:

module fork_join_none;
 
  initial begin
    
    $display("**********BEFORE FORK..JOIN_ANY**********");
    
    fork
        #15 $display($time,"\tThread A");
        #5   $display($time,"\tThread B");
        #10 $display($time,"\tThread C");
    join_none
      
        #2  $display($time,"\tThread D");
      #12  $display($time,"\tThread E");
 
    $display("**********AFTER FORK..JOIN_NONE**********");
    
    #30 $finish;
  end
  
endmodule

Here in above example output it is clearly showcase of fork..join_none concept.

Wait Fork:

In fork..join concept every verification engineer/testbench implementer comes in a situation that  “What if we need to wait for threads to finish after some simulation time? It means we do not want to move forward until each thread will finished in fork..join block. So to solve such problem System Verilog comes with one more construct called ‘wait fork‘. 

Let’s understand more thoroughly with example:

Example_1:
module wait_fork;
 
  initial begin
    $display("**********BEFORE_FORK**********");
 
 
    fork
      begin
        $display($time,"\tThread A");
        #15;
        $display($time,"\tThread B");
      end
 
      begin
        $display($time,"\tThread C");
        #30;
        $display($time,"\tThread D");
      end
    join_any
 
    $display("**********AFTER_FORK**********");

    $finish;
  
  end
endmodule

In the above example, After the completion of Thread B i.e, after 15ns fork-join_any will get unblocked, $finish will get called and it ends the simulation. 

Simulation will get ended in the middle of execution, this can be avoided with the use of wait-fork. 

Example_2:

module wait_fork;
 
  initial begin
    $display("**********BEFORE_WAIT_FORK**********");
 
 
    fork
      begin
        $display($time,"\tThread A");
        #15;
        $display($time,"\tThread B");
      end
 
      begin
        $display($time,"\tThread C");
        #30;
        $display($time,"\tThread D");
      end
    join_any
    
    wait fork; //waiting for all active fork threads to be finished
 
      $display("**********AFTER_WAIT_FORK**********");

    $finish;
  
  end
endmodule

In the above example, wait fork will wait for the rest of the active threads to be finish in the fork-join_any.

Disable Fork:

Now suppose you have exited the fork block by join_none or join_any and after some statements or after some simulation time you want to kill all the active threads by the previous fork block. Confused? So don’t worry, System Verilog comes with a feature that is called “disable fork” for the same.

Let’s understand with an example:

module disable_fork;
 
  initial begin
    $display("**********BEFORE_DISABLE_FORK**********");
 
 
    fork
      begin
        $display($time,"\tThread A");
        #15;
        $display($time,"\tThread B");
      end
 
      begin
        $display($time,"\tThread C");
        #30;
        $display($time,"\tThread D");
      end
    join_any
    
    fork
      begin
        $display($time,"\tThread A1");
        #15;
        $display($time,"\tThread B1");
      end
 
      begin
        $display($time,"\tThread C1");
        #30;
        $display($time,"\tThread D1");
      end
    join_none
    
    disable fork;
 
    $display("**********AFTER_DISABLE_FORK**********");

    $finish;
  
  end
endmodule

In the below example, On execution of disable fork, all the active process will get terminated. 

Thread D, Thread A1, Thread B1, Thread C1 and Thread D1 will get terminated.

Disable Specific Thread:

This feature of System Verilog fork..join block is a real beauty and value addition. There are some scenarios where testbench implementer need some kind of control through which we can disable particular threads out of multiple threads running concurrently in fork..join block.

Let’s understand through basic example:

module disable_specific_thread;
 
  initial begin
    $display("**********BEFORE_DISABLE_SPECIFIC_THREAD**********");
 
 
    fork
      begin : A1
        $display($time,"\tThread A");
        #30;
        $display($time,"\tThread B");
      end
 
      begin : B1
        $display($time,"\tThread C");
        #15;
        $display($time,"\tThread D");
      end
    join_any
         
    disable A1;
 
    $display("**********AFTER_DISABLE_SPECIFIC_THREAD**********");

    #50 $finish;
  
  end
endmodule

Here in the above example we have exited fork loop by fork..join_any or fork..join_none and after some steps I want to kill just one thread out of two. To solve this problem system verilog has construct/block called “disable“. To disable particular thread we need to have named begin..end thread and call ‘disable’

Here in example I want to disable only Thread A1 after exiting the loop via fork..join_any or fork..join_none, then add “disable A1″ at the point where you want to disable the thread. 

I hope this write up on fork..join and it’s advanced flavors should be helpful to give you a fairly good picture of the different versions of fork..join_any, fork..join_none, wait fork and disable fork.

I wish you all the best for your learning! See you soon with next post..till then bye !!

8 thoughts on “Flavours of Fork..Join”

Comments are closed.