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 process class is, and how process::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 from disable fork in 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:

StateMeaning
RUNNINGCurrently executing, not blocked
WAITINGBlocked on an event, delay, or other timing control
SUSPENDEDExplicitly paused by a call to suspend()
FINISHEDCompleted normally
KILLEDForcibly 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 as process::self(), with no arguments
  • All other methods are called on a process handle you've already obtained, such as my_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 process object; process::self(), called from within a process, returns a handle to it
  • A process can be RUNNING, WAITING, SUSPENDED, FINISHED, or KILLED, checked via status()
  • suspend() and resume() pause and continue a process without losing its place; kill() terminates it permanently
  • kill() 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, a process handle 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.

Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5