fork...join family of constructs is how you create, coordinate, and tear down that concurrency deliberately, instead of fighting against it.Every initial and always block is already its own process, running concurrently with every other one the moment simulation starts — no fork required. What fork...join adds is the ability to spawn additional concurrent processes from within a single block, and this chapter covers the different flavors of that construct, along with the statements used to wait for or terminate the processes it spawns.
6 min read | Intermediate Level
What You'll Learn
- What a process is in SystemVerilog, and why every
initial/alwaysblock already runs concurrently - Why testbenches specifically need to spawn additional concurrency from within a single block of code
- The three
fork...joinvariants, and how they differ in when the parent process resumes - How to wait for or terminate processes spawned by a
fork...join
What Is a Process?
Once simulation starts, every initial block and every always block is already an independent process, running concurrently with every other process in the design and testbench — this happens automatically, with no special syntax needed. "Process" and "thread" are used more or less interchangeably in this context.
Why Do Testbenches Need Explicit Concurrency?
A single initial block executes sequentially, one statement after another, in program order. Plenty of verification tasks genuinely need to happen at the same time within that one block's logical flow, though — driving a signal while simultaneously watching for a response, or racing a timeout against an event you're waiting for. fork...join spawns multiple concurrent child processes from within a single block, instead of requiring a separate module-level initial/always block for every concurrent task.
The fork...join Family
All three variants spawn the same kind of child processes — they differ only in when the parent process (the one that executed the fork) is allowed to resume:
- fork...join — the parent waits for every forked child to finish before continuing. See SystemVerilog fork join.
- fork...join_any — the parent continues as soon as any single forked child finishes. See SystemVerilog fork join_any.
- fork...join_none — the parent continues immediately, without waiting for any forked child at all. See SystemVerilog fork join_none.
Quick Check
Q: You want to implement a timeout: wait for a specific event, but give up and move on if a fixed delay elapses first, whichever happens first. Which fork...join variant fits this?
Show Answer
fork...join_any. Spawn the event-wait as one child process and the timeout delay as another; the parent resumes as soon as whichever one finishes first completes, without waiting for the other.
Controlling Spawned Processes
Once a fork...join_any or fork...join_none lets the parent continue while child processes are still running, you sometimes need to explicitly wait for or clean up whatever's left:
- wait fork — blocks until every child process spawned by the current process has finished. See SystemVerilog wait fork.
- disable fork — immediately terminates every descendant process spawned, directly or indirectly, by the current process that's still running — reaching further than just its immediate children. See SystemVerilog disable fork join.
Repeating Work Concurrently: the forever Loop
A forever loop is commonly used inside a forked child process to create a persistent background task — a monitor that continuously watches a signal for the entire duration of a test, for example. SystemVerilog forever loop covers this construct in depth.
Where Does This Show Up in a Real Testbench?
Driving stimulus into a DUT while a separate forked process continuously monitors its outputs is one of the most common patterns in verification — as is a timeout watchdog, implemented with fork...join_any, that races a fixed delay against a wait statement so a hung condition doesn't stall an entire regression indefinitely.
Common Beginner Mistakes
Mistake #1: Assuming a forked block always returns immediately
Only fork...join_none returns immediately. Why? Plain fork...join blocks until every child finishes, and fork...join_any blocks until at least one does — assuming either behaves like join_none leads to code that silently waits far longer than expected.
Mistake #2: Leaving spawned processes running at the end of a test
A process spawned by fork...join_any or fork...join_none keeps running in the background even after the parent moves on. Why? If a test finishes without explicitly waiting for or disabling those leftover processes, they can keep executing (or leave dangling activity) right up to $finish, which is rarely the intended behavior.
Mistake #3: Sharing a variable between concurrent processes with no synchronization
Two processes spawned by the same fork...join run concurrently, so if both read and write the same variable with no coordination, the result depends on simulator scheduling order. Why? This is a classic race condition — the Interprocess Communication chapter covers events, semaphores, and mailboxes, which exist specifically to coordinate concurrent processes safely.
Practice Exercise
Challenge: Match the Scenario to the Right Construct
For each scenario below, name the fork...join variant (or process-control statement) that fits best:
- Drive three independent stimulus sequences in parallel, and don't move on to the next test step until all three have completed.
- Kick off a background monitor that should keep running for the rest of the test, without the parent process waiting for it at all.
- A parent process spawned several background tasks earlier with
fork...join_none; now, right before the test ends, it needs to make sure every one of them has actually finished.
Hint
Match each scenario against how long the parent process needs to wait: forever (until everything finishes), not at all, or a later explicit checkpoint.
Solution
fork...join— the parent needs all three sequences to finish before continuing.fork...join_none— the parent continues immediately, and the monitor keeps running independently in the background.wait fork— this blocks until every child process spawned by the current process (including the earlierjoin_noneones) has completed.
Summary
In this tutorial, you learned:
- Every
initial/alwaysblock is already its own process, running concurrently with every other one, automatically fork...joinspawns additional concurrent child processes from within a single block of code- The three variants —
join,join_any, andjoin_none— differ only in when the parent process is allowed to resume wait forkblocks until all of the current process's immediate children finish;disable forkreaches further, terminating every descendant — children, grandchildren, and so on — immediately- Sharing a variable between concurrently running processes without synchronization is a common source of race conditions
Continuing the Threads & Processes Chapter
Continue with SystemVerilog fork join, SystemVerilog Threads, SystemVerilog forever loop, fork join_none, fork join_any, disable fork join, and wait fork.