System Verilog fork confusion, statements executed between fork and begin

Rich picture Rich · Jan 2, 2013 · Viewed 7.7k times · Source

See the simplified example code here:

    process job[num_objs]; 
    // assume also, arr_obj1s (array of type obj1) and 
    // arr_obj2s (array of type obj2) are arrays of size 
    // num_objs, and the objects define a run() function
    foreach (arr_obj1s[i]) begin
        fork
            automatic int j = i;
            arr_obj1s[j].run(); // these run forever loops
            begin
                job[j] = process::self();
                arr_obj2s[j].run(); // these run finite logic
            end
        join_none
    end

    foreach (job[i]) begin
        wait (job[i] != null);
        job[i].await();
    end

    // How do we ever reach here?

My confusion is that the calls to arr_obj1s[j].run() will never return (they run forever loops) and I don't quite follow the meaning of that call's placement outside the begin/end block. Which process is that forever run() executed on, and how can it be that each call to await() will return if some process is running a run() which won't return?

EDIT: Here is some more information. Posting the full code would be pages and pages, but I hope this extra bit helps.

obj1's run() function looks like this:

virtual task run;
  fork
    run_a(); // different logically separated tasks
    run_b();
    run_c();
  join
endtask: run

And as an example, run_a looks basically like this (they are all similar):

virtual task run_a;
  // declare some local variables
  forever begin
    @(posedge clk)
    // ...
  end
endtask: run_a

But obj2's run() function looks basically like this:

virtual task run;
  fork
    run_d(); // different logically separated tasks
    run_e();
  join
endtask: run

And as an example run_d() looks like this:

virtual task run_d;
  while ((data_que.size() > 0)) begin
    // process a pre-loaded queue,
    // data will not be pushed on during the simulation
  end
endtask:run_d

Answer

user597225 picture user597225 · Jan 2, 2013

This code fragment looks like it is demonstrating process control so here's my guess as to what's going on. There is a group of processes in arr_obj1s and arr_obj2s:

  • Those in arr_obj1s run forever so they only need to be spawned once and forgotten about.
  • Those in arr_obj2s accomplish some task and return, so the parent process needs to know when this happens.
  • All processes have the same parent

My confusion is that the calls to arr_obj1s[j].run() will never return (they run forever loops) and I don't quite follow the meaning of that call's placement outside the begin/end block

So all that's needed to spawn all processes are three lines of code in the fork..join_none block.

foreach (arr_obj1s[i]) begin
  fork
    automatic int j = i; // Spawns process
    arr_obj1s[j].run();  // Spawns process
    arr_obj2s[j].run();  // Spawns process
  join_none
end

The join_none keyword indicates that execution will continue after the parallel block completes, thus the entire foreach loop will execute and then the parent process will continue on to the next foreach loop. Further, the join_none also means that the child processes will not start until the parent process reaches a blocking statement.

However this won't allow us to detect when the child processes complete, unless they have some sort of shared variable they modify. To get around having to code that, SystemVerilog allows a handle to a process so it can schedule an event when the process completes. It doesn't, however, provide the ability to get the handle of a single statement. You must use process::self() inside a procedural context to get the process handle. Thus this won't work right if added directly to the fork-join block.

foreach (arr_obj1s[i]) begin
  fork
    automatic int j = i;
    arr_obj1s[j].run();
    job[j] = process::self(); // Will return parent process
    arr_obj2s[j].run();
  join_none
end

To fix this we need to create a new sequential procedural context that we can get the process handle of, then run the function from there:

foreach (arr_obj1s[i]) begin
  fork
    automatic int j = i;
    arr_obj1s[j].run(); // Spawns a new process for those that don't complete
    begin               // Spawns a new process for those that complete
      job[j] = process::self(); // Saves handle to this begin..end process
      arr_obj2s[j].run();       // Process continues though here
    end
  join_none
end

The final foreach loop only waits on processes for which we have a handle of. The processes that run forever are ignored.