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:
- Thread1 takes 30ns to finish.
- Thread2 executes in a begin-end block and finishes after 15ns.
- 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
Thread | Start Time | End Time | Notes |
---|---|---|---|
Thread1 | 0ns | 30ns | Slowest thread |
Thread2 | 0ns | 15ns | Fastest thread |
Thread3 | 0ns | 20ns | Middle 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.