[wpbread]
Concurrency enhances the efficiency of digital design and verification. In SystemVerilog, a language that plays a pivotal role in hardware design and verification, the fork-join construct is the key to unlocking the potential of parallel execution. It’s a fundamental mechanism that allows for concurrent execution of statements, threads, and processes.
The Verilog fork-join block initiates parallel execution and waits for all the processes to complete. However, SystemVerilog takes this concept a step further, offering three distinct options for specifying when the parent process should resume execution: fork-join, fork-join_any, and fork-join_none. These fork-join flavours provide implementers with a versatile way of coding to manage parallelism effectively.
In this blog, we will dive deep into the world of SystemVerilog’s fork-join constructs and explore how they can revolutionize your approach to test benches, verification, and hardware design.
fork-Join: This classic construct begins all processes within it in parallel and patiently waits for their completion.
fork-join_any: Unlike Fork-Join, this variant unblocks as soon as any of the processes completes, making it particularly useful in scenarios where responsiveness is critical.
fork-join_none: Here, the fork block operates in a non-blocking manner. It initiates parallel processes and immediately moves on without waiting for their completion. This can be a game-changer for certain use cases, where you need to maximize efficiency.
wait fork: Lets you fine-tune your parallelism management by allowing a process to block until the completion of all processes started from fork blocks.
Table of Contents
fork-join
Fork-Join will start all the processes inside it parallel and wait for the completion of all the processes.
Example: simple example, this code demonstrates how to use the fork-join construct to initiate parallel execution of threads and synchronize their completion before continuing with the rest of the code.
module top();
initial begin
$display($time,"==== Befor fork-Join ====");
fork
//Thread/Process-1
#15 $display($time," \tThread-1 Finished");
//Thread/Process-2
#20 $display($time," \tThread-2 Finished");
join
$display($time,"==== Outside fork-Join ====");
#50 $display($time,"\tCalling $finish");
$finish;
end
endmodule
Run on EdaPlayground
Simulator Output:
0==== Befor fork-Join ====
15 Thread-1 Finished
20 Thread-2 Finished
20==== Outside fork-Join ====
Explanation: A fork block is initiated with the fork keyword. Inside the fork block, two threads are started concurrently.
The first thread, “Thread-1,” uses $display to print the current simulation time ($time) along with the message “Thread-1 Finished” after a delay of 15 time units (#15).
The second thread, “Thread-2,” uses $display to print the current simulation time along with the message “Thread-2 Finished” after a delay of 20 time units (#20).
After forking the two threads, a join statement is used to specify that the parent process (in this case, the initial block) should wait for the completion of both threads before proceeding.
fork-join_any
The parent thread waits until at least one of the threads spawned within the fork-join_any block completes. When multiple threads have different execution times, the fork-join_any exits as soon as any one of the thread finishes. This doesn’t mean the remaining threads are discarded, they continue running concurrently in the background.
module top();
initial begin
$display($time,"==== Befor fork-Join ====");
fork
//Thread/Process-1
#15 $display ($time," \tThread-1 Finished");
//Thread/Process-2
#20 $display($time," \tThread-2 Finished");
join_any
$display($time,"==== Outside fork-Join ====");
#50 $display($time,"\tCalling $finish");
$finish;
end
endmodule
Run on EdaPlayground
Simulator Output:
0==== Befor fork-Join ====
15 Thread-1 Finished
15==== Outside fork-Join ====
20 Thread-2 Finished
65 Calling $finish
Explanation:
- A fork block is initiated with the fork keyword. Inside the fork block, two threads are started concurrently.
- The first thread, “Thread-1,” uses $display to print the current simulation time ($time) along with the message “Thread-1 Finished” after a delay of 5 time units (#15).
- The second thread, “Thread-2,” uses $display to print the current simulation time along with the message “Thread-2 Finished” after a delay of 20 time units (#20).
- After forking the two threads, a join_any statement is used. This means that the parent process will unblock as soon as any one of the threads finishes, regardless of whether the other thread(s) have completed or not.
- Once the join_any condition is met, the code proceeds to the $display statement outside of the fork-join_any block, printing the current simulation time along with the message “==== Outside fork-Join ====”.
fork-join_none
In a fork-join_none block, it doesn’t wait for any threads to finish. It simply starts them and exits the block immediately. This means the threads run concurrently and don’t block the parent process, and execute next statements in simulation. The threads only begin when the parent thread hits a blocking statement in the code.
module top();
initial begin
$display($time,"==== Befor fork-Join ====");
fork
//Thread/Process-1
#15 $display ($time," \tThread-1 Finished");
//Thread/Process-2
#20 $display($time," \tThread-2 Finished");
join_none
$display($time,"==== Outside fork-Join ====");
#50 $display($time,"\tCalling $finish");
$finish;
end
endmodule
Run on EdaPlayground
Simulator Output:
0==== Before fork-Join ====
0==== Outside fork-Join ====
15 Thread-1 Finished
20 Thread-2 Finished
50 Calling $finish
this code illustrates how the join_none construct within a fork block makes the spawned threads non-blocking, allowing them to run concurrently. The parent process doesn’t wait for the threads to complete and proceeds with its execution immediately.
wait_fork
In the fork-join concept of SystemVerilog, there’s a common situation that verification engineers and testbench implementers face. They often need to ensure that they don’t proceed with the simulation until all threads within a fork-join block have finished. To address this, SystemVerilog introduces a solution called ‘wait fork’.
‘wait fork’ causes the process to block until the completion of all processes started from fork blocks.
module top();
initial begin
$display($time,"==== Before fork-Join ====");
fork
#15 $display ($time," \tThread-1 Finished"); //Thread/Process-1
#20 $display($time," \tThread-2 Finished"); //Thread/Process-2
Join_any
$display($time,"==== Outside fork-Join ====");
wait fork;
$display($time,"==== Calling $finish ====");
#50 $finish;
end
endmodule
Explanation: In the above example, wait fork will wait for the rest of the active threads to be finish in the fork-join_any.