disable fork and wait fork both act on every process spawned by the current process at once — there's no way to target just one of them. SystemVerilog's built-in process class fills that gap: it gives you a handle to one specific process, letting you check its status and control it individually, without touching anything else running alongside it.
7 min read | Intermediate Level
What You'll Learn
- What the
processclass is, and howprocess::self()gives you a handle to the currently executing process - The different states a process can be in, and how to check them with
status() - How to suspend, resume, and kill one specific process without affecting any others
- How
process::kill()differs fromdisable forkin scope
What Is the process Class?
Every thread — an initial or always block, or a thread spawned by fork — has a built-in process object associated with it automatically. You can't create a process object yourself with new(); SystemVerilog creates one internally whenever a process is spawned. To get a handle to a process, you call the static method process::self() from within that process — it always returns a handle to whichever process is calling it, never to some other thread.
Process States
A process object's status() method returns one of five states:
| State | Meaning |
|---|---|
| RUNNING | Currently executing, not blocked |
| WAITING | Blocked on an event, delay, or other timing control |
| SUSPENDED | Explicitly paused by a call to suspend() |
| FINISHED | Completed normally |
| KILLED | Forcibly terminated by kill() or disable |
Basic Syntax
The built-in process class provides these members:
class process;
typedef enum { FINISHED, RUNNING, WAITING, SUSPENDED, KILLED } state;
static function process self(); // handle to the calling process
function state status(); // current state of this process
function void suspend(); // pause this process
function void resume(); // resume a suspended process
function void kill(); // terminate this process and its descendants
task await(); // wait for this process to finish
function void srandom (int seed); // reseed this process's own random generator
function string get_randstate(); // save this process's random state
function void set_randstate (string state); // restore a previously saved random state
endclass
Parameters:
self()— a static method, called asprocess::self(), with no arguments- All other methods are called on a
processhandle you've already obtained, such asmy_proc.status()
A Complete Example
A common pattern is to spawn a background task with fork...join_none, capture its own process handle from inside itself, and store that handle somewhere the rest of the code can reach:
module tb;
process bg_proc;
task background_task ();
bg_proc = process::self(); // capture this task's own process handle
forever begin
#10 $display ("[%0t] Background task running", $time);
end
endtask
initial begin
fork
background_task ();
join_none
#25;
$display ("[%0t] Status before kill: %s", $time, bg_proc.status().name());
bg_proc.kill();
$display ("[%0t] Background task killed", $time);
#20 $finish;
end
endmodule
background_task captures its own handle into bg_proc the moment it starts, then loops forever, printing every 10ns. The main thread, meanwhile, continues immediately after join_none, waits 25ns, then checks bg_proc's status. At that point, background_task has already printed at 10ns and 20ns, and is currently blocked inside its third #10 delay — so status() returns WAITING. Calling bg_proc.kill() right after terminates it for good, so it never reaches its next scheduled print at 30ns.
Quick Check
Q: In the example above, what would bg_proc.status() return if it were checked at exactly 10ns, in the same instant background_task executes its $display statement?
Show Answer
RUNNING — at that exact moment it's actively executing a statement, not blocked on anything. It only becomes WAITING again once it re-enters the #10 delay on the next iteration of the forever loop.
Suspending and Resuming a Process
suspend() pauses a process without terminating it, and resume() lets it continue from exactly where it left off:
initial begin
process p;
fork
begin
p = process::self();
forever #5 $display ("[%0t] Tick", $time);
end
join_none
#12 p.suspend(); // pause the ticking process mid-cycle
#20 p.resume(); // let it continue from where it left off
end
Unlike kill(), suspend() doesn't lose the process's place — once resume() is called, it picks back up exactly where it was paused.
process vs disable fork: Which Should You Use?
disable fork terminates every active descendant of the calling process at once — a blunt, all-or-nothing tool. A process handle lets you target one specific process instead, which matters when a single parent has spawned several independent background threads and you only want to stop one of them. That said, kill() isn't perfectly surgical either: killing a process also terminates all of its own descendant processes, so it's scoped to one process's subtree, not just that single process in isolation.
Where Is the process Class Used?
The process class is useful whenever a testbench spawns several independent long-running background components — multiple monitors, multiple scoreboards — and needs to stop or pause just one of them based on some runtime condition, without disturbing the others still running alongside it.
Common Beginner Mistakes
Mistake #1: Calling process::self() from the wrong place
process::self() only ever returns a handle to whichever process is calling it. Why? Calling it from the main thread doesn't give you a handle to a thread you forked elsewhere — to get a handle to a specific forked thread, that thread has to call process::self() on itself, typically as its very first statement.
Mistake #2: Assuming kill() leaves a process's own children running
Killing a process also terminates every one of its own descendant processes. Why? If that process had itself forked off further threads, expecting them to keep running after the parent is killed will be wrong — they're terminated right along with it.
Mistake #3: Trying to construct a process object with new()
process objects can't be created manually. Why? SystemVerilog creates one internally whenever a thread is spawned; attempting process p = new(); yourself is a compile-time error — the only way to obtain a handle is process::self(), called from inside the process you want a handle to.
Practice Exercise
Challenge: Kill One of Two Background Monitors
Fork two independent background tasks, each printing a different message every 5ns via a forever loop, each capturing its own process handle. After 22ns, kill only the first one and let the second keep running.
Hint
Each task needs its own process variable to store its own handle — don't reuse a single variable for both.
Solution
module tb;
process proc_a, proc_b;
task monitor_a ();
proc_a = process::self();
forever #5 $display ("[%0t] Monitor A", $time);
endtask
task monitor_b ();
proc_b = process::self();
forever #5 $display ("[%0t] Monitor B", $time);
endtask
initial begin
fork
monitor_a ();
monitor_b ();
join_none
#22 proc_a.kill();
$display ("[%0t] Monitor A killed, Monitor B still running", $time);
#20 $finish;
end
endmodule
Because each task captures its handle into its own separate variable (proc_a and proc_b), killing proc_a only stops Monitor A — Monitor B keeps printing every 5ns, completely unaffected.
Summary
In this tutorial, you learned:
- Every thread has a built-in
processobject;process::self(), called from within a process, returns a handle to it - A process can be
RUNNING,WAITING,SUSPENDED,FINISHED, orKILLED, checked viastatus() suspend()andresume()pause and continue a process without losing its place;kill()terminates it permanentlykill()also terminates all of that process's own descendant processes, not just the process itself- Unlike
disable fork, which acts on every descendant of the calling process at once, aprocesshandle lets you target one specific process individually
Continuing the Threads & Processes Chapter
See SystemVerilog disable fork join for terminating every spawned process at once, and SystemVerilog wait fork for waiting on all of them instead of just one.