HomeSystemVerilog VerificationDay 11
DAY 11 · OOP PHASE

SystemVerilog Threads — fork/join, disable, Join_any, Join_none

By EcrioniX · Updated Jun 13, 2026

Now for the final piece of core OOP verification: parallel threads. Days 7–10 showed you how to design reusable components and synchronize them. Threads let you run multiple tasks concurrently — driver and monitor running in parallel, a timeout watcher, a scoreboard checker — all driven from the same testbench context.

fork/join: wait for all threads

fork...join creates parallel threads. All statements in the fork block start at the same time. The join waits for all threads to finish before continuing.

fork
  begin: thread_A
    $display("Thread A starts");
    #100;
    $display("Thread A done");
  end

  begin: thread_B
    $display("Thread B starts");
    #50;
    $display("Thread B done");
  end
join  // Waits for BOTH to finish (at t=100)

$display("Both threads done");

fork/join_any: wait for first to finish

fork...join_any waits for any one thread to finish, then continues. The other threads keep running in the background. This is useful for timeout patterns.

fork
  begin: main_logic
    do_something();      // Main test
    $display("Test done");
  end

  begin: timeout
    #10000;              // Timeout after 10000 units
    $display("TIMEOUT!");
  end
join_any  // Continues when first finishes

// If main_logic finishes first, we're good
// If timeout fires first, test hung

fork/join_none: start and don't wait

fork...join_none starts the threads but doesn't wait. Execution continues immediately. Useful for fire-and-forget background tasks.

fork
  begin
    // Background monitor — keep checking
    forever begin
      if (error_flag) $display("Error detected!");
      #5;
    end
  end
join_none  // Return immediately, monitor runs in background

$display("Testbench continues while monitor runs");

disable fork: kill threads

disable fork kills all threads in the current scope. disable label kills a specific block. Useful for cleanup or timeout handling.

Timeout with disable

fork
  begin: test_thread
    for (int i = 0; i < 1000; i++) begin
      do_transaction(i);
      #100;
    end
    $display("Test completed successfully");
  end

  begin: timeout_thread
    #50000;  // 50000 time unit timeout
    $display("TIMEOUT! Killing test.");
    disable test_thread;  // Kill just the test
  end
join

Complete example: parallel driver/monitor/checker

parallel_tb.sv
class transaction;
  rand logic [7:0] data;
endclass

class driver;
  mailbox tx_mb;
  function new(mailbox m);
    tx_mb = m;
  endfunction
  task run();
    for (int i = 0; i < 10; i++) begin
      transaction tx = new();
      tx.data = 8'h10 + i;
      tx_mb.put(tx);
      $display("[%0t] DRV: Sent %h", $time, tx.data);
      #20;
    end
  endtask
endclass

class monitor;
  mailbox tx_mb;
  mailbox result_mb;
  function new(mailbox t, mailbox r);
    tx_mb = t;
    result_mb = r;
  endfunction
  task run();
    transaction tx;
    for (int i = 0; i < 10; i++) begin
      tx_mb.get(tx);
      $display("[%0t] MON: Got %h", $time, tx.data);
      result_mb.put(tx);
    end
  endtask
endclass

class checker;
  mailbox result_mb;
  event checker_done;
  function new(mailbox r, event e);
    result_mb = r;
    checker_done = e;
  endfunction
  task run();
    transaction tx;
    int count = 0;
    forever begin
      if (result_mb.try_get(tx)) begin
        $display("[%0t] CHK: Verified %h", $time, tx.data);
        count++;
      end
      #10;
      if (count >= 10) break;
    end
    -> checker_done;
  endtask
endclass

module parallel_test;
  mailbox tx_mb;
  mailbox result_mb;
  event checker_done;

  driver   drv;
  monitor  mon;
  checker  chk;

  initial begin
    tx_mb = new();
    result_mb = new();

    drv = new(tx_mb);
    mon = new(tx_mb, result_mb);
    chk = new(result_mb, checker_done);

    // Run all in parallel
    fork
      begin: driver_thread
        drv.run();
      end
      begin: monitor_thread
        mon.run();
      end
      begin: checker_thread
        chk.run();
      end
      begin: timeout
        #500;  // Timeout if not done by 500
        $display("TIMEOUT!");
        disable fork;
      end
    join

    @(checker_done);
    $display("Test complete!");
    $finish;
  end
endmodule

Practical patterns

Timeout pattern: use join_any or disable to catch hung tests.

Fire-and-forget: use join_none for background monitors that don't need to finish before test continues.

Producer-consumer: driver and monitor in parallel threads communicating via mailboxes (as above).

Day 11 takeaways

  • fork/join: parallel execution; join waits for ALL threads to finish.
  • fork/join_any: waits for ANY thread; useful for timeout detection.
  • fork/join_none: start threads without waiting; good for background tasks.
  • disable fork: kills all threads in scope. disable label: kills specific block.
  • Combine threads with mailboxes and events for coordinated multi-process testbenches.
  • Timeout pattern: race between main logic and timeout timer with join_any or disable.

Frequently Asked Questions

What is fork/join?

fork starts parallel threads; join waits for ALL to finish. Useful for running driver and monitor concurrently, then waiting for both before verifying results.

What is join_any?

fork...join_any waits for the FIRST thread to finish, then continues. The other threads keep running. Used for timeout detection: race between main test logic and a timer thread.

How do I implement a timeout?

Use fork/join_any with a timer thread: if your test finishes first, great. If the timer fires first, you have a hung test. Alternatively, use disable fork in a timeout handler.

What is disable fork?

disable fork kills all threads in the current scope. disable label kills a specific named block. Useful for cleanup or emergency shutdown of runaway threads.

← Full course roadmap