SystemVerilog offers powerful support for parallel and concurrent execution using the fork join construct. This feature allows multiple procedural blocks to run simultaneously, enhancing the performance and efficiency of simulations. Let’s explore how to use the fork join construct, its variations, and practical examples in SystemVerilog.


What is SystemVerilog Fork Join?

In SystemVerilog, the fork join construct is used to spawn multiple threads of execution. These threads run concurrently, which means that different parts of the code can execute at the same time. The fork join allows a program to create parallel execution threads, making simulations faster and more efficient.

  • fork starts the concurrent threads.
  • join waits for all threads to complete before moving forward.

Basic Syntax of SystemVerilog Fork Join

The general syntax of fork join is as follows:

fork
   // Thread 1
   // Thread 2
   // ...
   // Thread N
join

When the fork statement is encountered, all threads begin executing in parallel. The join statement causes the main thread to wait until all the spawned threads finish.


Example: Basic fork join in SystemVerilog

Here’s a simple example to demonstrate the usage of fork join.

module tb;
   initial begin
      $display ("[%0t] Main Thread: Fork join going to start", $time);
      
      fork
         // Thread 1
         #30 $display ("[%0t] Thread1 finished", $time);

         // Thread 2
         begin
            #5 $display ("[%0t] Thread2 ...", $time);
            #10 $display ("[%0t] Thread2 finished", $time);
         end

         // Thread 3
         #20 $display ("[%0t] Thread3 finished", $time);
      join
      
      $display ("[%0t] Main Thread: Fork join has finished", $time);
   end
endmodule

Output from the above simulation:

[0] Main Thread: Fork join going to start
[5] Thread2 ...
[15] Thread2 finished
[20] Thread3 finished
[30] Thread1 finished
[30] Main Thread: Fork join has finished

How Fork Join Works

In the example above, three threads are created using fork join:

  1. Thread1 takes 30ns to finish.
  2. Thread2 executes in a begin-end block and finishes after 15ns.
  3. Thread3 finishes earlier at 20ns.

The main thread suspends until all the threads complete execution.


Nested fork join

You can also nest fork join statements within other fork join constructs to create more complex parallel structures.

Example: Nested fork join

module tb;
   initial begin
      $display ("[%0t] Main Thread: Fork join going to start", $time);
      
      fork
         fork
            // Thread 1_0
            #20 $display ("[%0t] Thread1_0", $time);
            // Thread 1_1
            #30 $display ("[%0t] Thread1_1", $time);
         join
         
         // Thread 2
         #10 $display ("[%0t] Thread2", $time);
      join
      
      $display ("[%0t] Main Thread: Fork join has finished", $time);
   end
endmodule

Output:

[0] Main Thread: Fork join going to start
[10] Thread2
[20] Thread1_0
[30] Thread1_1
[30] Main Thread: Fork join has finished

In this example, fork join is used twice, one inside the other. The threads in the inner fork run concurrently, and once they are complete, the outer fork join continues.


Detailed Example: Multiple Nested fork join Threads

Let’s consider another more complex example, where multiple threads inside the fork execute different delays.

module tb;
   initial begin
      $display ("[%0t] Main Thread: Fork join going to start", $time);
      
      fork
         // Nested fork for Thread 1
         fork
            #50 $display ("[%0t] Thread1_0 ...", $time);
            #70 $display ("[%0t] Thread1_1 ...", $time);
            
            begin
               #10 $display ("[%0t] Thread1_2 ...", $time);
               #100 $display ("[%0t] Thread1_2 finished", $time);
            end
         join
         
         // Thread 2
         begin
            #5 $display ("[%0t] Thread2 ...", $time);
            #10 $display ("[%0t] Thread2 finished", $time);
         end

         // Thread 3
         #20 $display ("[%0t] Thread3 finished", $time);
      join
      
      $display ("[%0t] Main Thread: Fork join has finished", $time);
   end
endmodule

Output:

[0] Main Thread: Fork join going to start
[5] Thread2 ...
[10] Thread1_2 ...
[15] Thread2 finished
[20] Thread3 finished
[50] Thread1_0 ...
[70] Thread1_1 ...
[110] Thread1_2 finished
[110] Main Thread: Fork join has finished

Summary of Fork Join Behavior

ThreadStart TimeEnd TimeNotes
Thread10ns30nsSlowest thread
Thread20ns15nsFastest thread
Thread30ns20nsMiddle thread
  • The fork initiates all threads simultaneously.
  • The join ensures that the main thread waits until all parallel threads are complete.
  • You can nest fork join to manage more complex execution sequences.
  • The main thread is suspended until all child threads finish.

Conclusion

The fork join construct in SystemVerilog is a powerful tool for parallel execution in simulations. By using fork join, you can efficiently simulate concurrent behaviors in your design. Whether you are working with simple parallel threads or complex nested fork join constructs, understanding how to use them effectively is crucial for optimizing your SystemVerilog simulations.

By following this guide and examples, you should be able to leverage fork join to create more efficient, concurrent threads in your SystemVerilog code, improving simulation speed and accuracy.

Scroll to Top