Fork-Join in SystemVerilog

Fork-join allows you to run multiple processes in parallel - essential for verification where you need to drive stimulus and monitor responses simultaneously.

Why Parallel Execution?

In a real testbench, you need to do multiple things at the same time:

  • Driver - Send stimulus to DUT
  • Monitor - Watch DUT outputs
  • Checker - Compare expected vs actual
  • Timeout watchdog - Ensure test doesn't hang

Without fork-join, you'd have to do these one after another, which doesn't match how real hardware works!

Three Flavors of Fork-Join

1. fork...join (Wait for ALL)

Waits for ALL spawned processes to complete before continuing:

fork...join Example
initial begin
    $display("[%0t] Starting parallel tasks", $time);
    
    fork
        // Process 1
        begin
            #10 $display("[%0t] Process 1 done", $time);
        end
        
        // Process 2
        begin
            #20 $display("[%0t] Process 2 done", $time);
        end
        
        // Process 3
        begin
            #15 $display("[%0t] Process 3 done", $time);
        end
    join  // Waits for ALL three processes
    
    $display("[%0t] All processes complete!", $time);
end

// Output:
// [0] Starting parallel tasks
// [10] Process 1 done
// [15] Process 3 done
// [20] Process 2 done
// [20] All processes complete!

2. fork...join_any (Wait for FIRST)

Continues as soon as ANY ONE process completes:

fork...join_any Example
initial begin
    fork
        begin
            #10 $display("[%0t] Fast process done", $time);
        end
        begin
            #100 $display("[%0t] Slow process done", $time);
        end
    join_any  // Continues after FIRST one finishes
    
    $display("[%0t] At least one process done!", $time);
    disable fork;  // Kill remaining processes
end

// Output:
// [10] Fast process done
// [10] At least one process done!

3. fork...join_none (Don't Wait)

Spawns processes and immediately continues - doesn't wait at all:

fork...join_none Example
initial begin
    $display("[%0t] Before fork", $time);
    
    fork
        begin
            #10 $display("[%0t] Background process done", $time);
        end
    join_none  // Doesn't wait at all!
    
    $display("[%0t] Immediately after fork", $time);
    #20;
    $display("[%0t] End of test", $time);
end

// Output:
// [0] Before fork
// [0] Immediately after fork
// [10] Background process done
// [20] End of test

Practical Testbench Example

Real Testbench Pattern
class my_test;
    virtual bus_if vif;
    
    task run();
        fork
            drive_stimulus();
            monitor_response();
            timeout_watchdog();
        join_any
        
        disable fork;  // Clean up remaining processes
        $display("Test completed!");
    endtask
    
    task drive_stimulus();
        repeat(10) begin
            @(posedge vif.clk);
            vif.data <= $urandom;
            vif.valid <= 1;
            @(posedge vif.clk);
            vif.valid <= 0;
        end
        $display("All stimulus sent!");
    endtask
    
    task monitor_response();
        forever begin
            @(posedge vif.clk);
            if (vif.valid && vif.ready) begin
                $display("Response received: %h", vif.data);
            end
        end
    endtask
    
    task timeout_watchdog();
        #10000;
        $fatal("TIMEOUT: Test took too long!");
    endtask
endclass

disable fork - Cleaning Up

When using join_any or join_none, background processes may still be running. Use disable fork to kill all child processes:

Proper Cleanup
task run_with_timeout(int timeout_ns);
    fork
        begin
            // Actual work
            do_something();
            $display("Work completed successfully");
        end
        begin
            // Timeout
            #timeout_ns;
            $display("TIMEOUT!");
        end
    join_any
    
    disable fork;  // Kill whichever one is still running
endtask

Quick Summary

Type Behavior Use When
join Wait for ALL All tasks must complete
join_any Wait for FIRST Race conditions, timeouts
join_none Don't wait Background tasks, monitors