Question on Fork join with case statement

I have question on fork join working in the below code where the case 3 is always selected, is it guaranteed that case 3 is always selected if the signal “complete” changes from 0 to F or can it pick other cases as well randomly?

What is the better way to make sure all the cases are selected even if the signal “complete” changes from 0 to F.?

module fork_test1;
logic [3:0] complete = 4'h0;
logic in_progress;
  
initial  begin
  #10;
  capture_complete();
end

initial begin
  in_progress = '1;
  #15;
  complete = '0;
  #30;
  complete = 4'hF;
  #100; $finish();
end 
    
task capture_complete();
  for(int i = 0; i < 4; i++) begin
    fork
      automatic int j = i;
      $display("%0t,J = %0d",$time,j);
      forever @(complete[j]) begin
        $display("%0t,entering forever j=%0d",$time,j);
        if(in_progress == 1'b1) begin
          $display("%0t,Captured initcomplete[%0d] = %0b",$time ,j, complete[j]);
          case(j)
            0: begin
               if(complete[j] === 1'b1) begin
                 $display("%0t,case0 selected",$time);
               end
            end
            1: begin
               if(complete[j] === 1'b1) begin
                 $display("%0t,case1 selected",$time);
               end
            end
            2: begin
               if(complete[j] === 1'b1) begin
                 $display("%0t,case2 selected", $time);
               end
            end
            3: begin
               if(complete[j] === 1'b1) begin
                 $display("%0t,case3 selected",$time);
                 in_progress = 1'b0;
                 $display("in_progress = 0");
               end
            end
          endcase
        end
      end
    join_none
  end
endtask : capture_complete
  
endmodule:fork_test1

Output observed:
10,J = 0
10,J = 1
10,J = 2
10,J = 3
45,entering forever j=3
45,Captured initcomplete[3] = 1
45,case3 selected
in_progress = 0
45,entering forever j=2
45,entering forever j=1
45,entering forever j=0

You have a race condition in which case gets selected first. The same version of the same tool might consistently select the same ordering, but different tools, and different versions of the same tool may select different orderings (the tools on EDAPlayground.com have different orderings).

The golden rule in Verilog is

Use nonblocking assignments whenever one process writes to a variable and another process read the same variable and both processed are synchronized to the same event.

In your example, all the forked processes are synchronised to a value change on complete. Using a nonblocking assignment to in_progess guarantees all other processes read the old value of in_progress.

Thanks Dave for your response. I have understood your solution to this problem.

There is a follow up question on the same where actually i want to execute case 0/1 before case 2 and case 3 always (0/1 in any order followed by 2 and then 3)

I haven’t shown the complete code in the previous question and just kept the display statements for convenience but in actual problem there is lot more happening in each case branch ( all in 0 time).

I want the code to work in same way even if the signal “complete” changes in a staged manner like 0->1->7->F or directly from 0->F

The signal “complete” is guaranteed to change like 0->1->3->7->F or 0->F or 0->7->F etc.(each bit representing some event)

I have tried below solution for the same and seems like working( added Delay in case branches) , please let me know your inputs on the same.


module fork_test1;

logic [3:0] complete = 4'h0;
logic in_progress;
  
  
initial  begin
  #10;
  capture_init_complete();
end

initial begin
  in_progress = '1;
  #15;
  complete = '0;
  #5;
  complete = 'h1;
  #10;
  complete = 'h7;
  #30;
  complete = 4'hF;
  #100; $finish();
end 
  
  
task capture_init_complete();
  for(int i = 0; i < 4; i++) begin
    fork
      automatic int j = i;
      $display("%0t,J = %0d",$time,j);
      forever @(complete[j]) begin
        $display("%0t,entering forever j=%0d",$time,j);
        if(in_progress == 1'b1) begin
          $display("%0t,Captured initcomplete[%0d] = %0b",$time ,j, complete[j]);
          case(j)
            0: begin
               if(complete[j] === 1'b1) begin
                 $display("%0t,case0 selected",$time);
               end
            end
            1: begin
               if(complete[j] === 1'b1) begin
                 $display("%0t,case1 selected",$time);
               end
            end
            2: begin
               #1;
               if(complete[j] === 1'b1) begin
                 $display("%0t,case2 selected", $time);
               end
            end
            3: begin
               #2;
               if(complete[j] === 1'b1) begin
                 $display("%0t,case3 selected",$time);
                 in_progress = 1'b0;
                 $display("in_progress = %0d",in_progress);
               end
            end
          endcase
        end
      end
    join_none
  end
endtask : capture_init_complete

  
endmodule:fork_test1

Output Observed:
10,J = 0
10,J = 1
10,J = 2
10,J = 3
20,entering forever j=0
20,Captured initcomplete[0] = 1
20,case0 selected
30,entering forever j=2
30,Captured initcomplete[2] = 1
30,entering forever j=1
30,Captured initcomplete[1] = 1
30,case1 selected
31,case2 selected
60,entering forever j=3
60,Captured initcomplete[3] = 1
62,case3 selected
in_progress = 0

Adding #0 or #1 to fix race conditions is a bad idea. If you have other races, they become more difficult to deal with. You are just postponing more race conditions. What happens if complete changes faster than all the things you need to order with a delay? If you need a specific ordering, your code should be structured to guarantee that.

Without seeing everything that’s going on, it’s going to be hard to suggest a better way. You don’t explain why you need forked processes to begin with.