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 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 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.