Fork statement design, wait for one thread only

What is best way to build a fork/join_xx strategy which blocks on one particular task, then un-blocks when that task finishes, ignoring what the others have done.

In this example three threads are launched, how can we block until run_control task completes, then move on to the test finished task.
The example finish time should be 2 (maybe 3 ??).

Its almost like a wait_fork, but only wait for one thread.

The use case is for a testbench which launches gen,driver,monitor and scoreboard.
I want to launch them all in parallel, but only wait for the scoreboard to finish.

Looking for a SystemVerilog answer; realize the advanced three letter methodologies have more powerful ways to do this in a testbench.

This code is kind of the base for the question:
How can the :main block be modified to give the specified behavior?
Would like to avoid the use of semaphores, seems like this would be an every day thing to do.

module test;
  
  task run_long;
    for(int i=0;i < 7;i++)
       begin
         $display("*******long time = %0d",$time);
       #1;
       end    
  endtask
  
  task run_short;
    for(int i=0;i < 1;i++)
       begin
         $display("*short time = %0d",$time);
       #1;
       end    
  endtask  
  
  task run_control;
    for(int i=0;i < 2;i++)
       begin
         $display("***control time = %0d",$time);
       #1;
       end    
  endtask
  
  task test_finished;
    $display("Test finished %0d",$time);
  endtask

  initial
  begin :main
    fork
      run_long;
      run_short;
      run_control;
    join;
    test_finished;            
  end :main

endmodule

Here is one thing I tried

  initial
  begin :main
    fork
      fork
        run_long;
        run_short;
      join_none;
      //
      run_control;
      //
    join_any;
    test_finished;            
  end :main 

This produces the output
Test finished 0
***control time = 0
*******long time = 0
*short time = 0
***control time = 1
*******long time = 1
*******long time = 2
*******long time = 3
*******long time = 4
*******long time = 5
*******long time = 6

Which is not desired because the test is still waiting for the long time thread to run.

In reply to mikefitzgerald:

Keep test_contol in the main process

initial
  begin :main
    fork
      run_long;
      run_short;
    join_none;
    run_control;
    disable fork; // if you want to kill run_long
    test_finished;            
  end :main

In reply to dave_59:

Thanks for the reply.

Is this a decent general solution for launching threads and ending a basic OO SV testbench?
That is launch generator,driver,monitor, and checker: then let the checker decide when the test is done.

A downside is that it requires the checker know when the test is done (probably time or a number of transactions). So we need a way to get than configuration from the test to the checker.

Mike

In reply to mikefitzgerald:
The problem is the Test is the least reusable component of your testbench, and there is no generic way of knowing when the test is done. A decent general solution for launching threads and ending a basic OO SystemVerilog testbench is using the UVM. (Sorry, you set yourself up for that!)

The UVM objection mechanism simplifies this by giving any component the ability to raise an objection to ending the test. The test ends when there are no more objections. It act like a semaphore in reverse.

In reply to dave_59:

Hi Dave,

Even if the test raises objection and drops the objection, the transactions might still be in the DUT (may not have come out), assuming the objections are raised and dropped in the test only. I guess without a proper end of test criteria, for example have all the packets have come out sort of thing, the objections cannot guarantee when the test can be ended. Is that right?

Thanks,
Madhu