Disabling a named fork in a task inside a class object disables the forks in all other objects of the same class

The following code illustrates the problem:

class A;
   bit b = 1'b0;
   bit c = 1'b0;
   task t1;
      $display("At time %0d - Start with task t1 (b=%b)",$time,b);
      fork: fk0
	 begin
	    c = 1'b0;
	    #2;
	    c = 1'b1;
	 end
	 begin
	    #1;
	    if (b) #5;
	    disable fk0;
	 end
      join
      $display("At time %0d - Done with task t1 (b=%b,c=%b)",$time,b,c);
   endtask // t1
endclass // A

module test_disable_fork;
   initial begin
      A c0,c1;
      c0 = new;
      c1 = new;
      c1.b = 1'b1;
      fork
	 c0.t1;
	 c1.t1;
      join
      $display("At time %0d - Done with both c0 and c1 t1 tasks",$time);
      $finish;
   end
   
endmodule // test_disable_fork

This outputs the following when run using Cadence irun:
At time 0 - Start with task t1 (b=0)
At time 0 - Start with task t1 (b=1)
At time 1 - Done with task t1 (b=1,c=0)
At time 1 - Done with task t1 (b=0,c=0)
At time 1 - Done with both c0 and c1 t1 tasks
Simulation complete via $finish(1) at time 1 NS + 0
.

Is this the expected behavior, or is this a bug in the simulator?
What is the suggested workaround to keep the fork names unique across objects?

I just discovered a workaround using the special built-in process class. Process objects declared within the t1 task are unique across objects of the A class, so killing a process in one object does not go and kill processes in other objects of class A.

I still would like to know if the original code is expected to behave this way or if it is a bug in the simulator.

I ran into the same problem, and this is the correct behavior according to the SV spec. See this question I posted here:

In reply to dwikle:

Thanks, dwikle, I agree with you that this behavior is not expected and not very useful in my opinion, even if it is according to the SV LRM spec. My conclusion is NEVER DISABLE A BLOCK INSIDE A CLASS, instead use the process class for fine-grained process control inside classes. I’ve been getting away with violating this rule simply because, up until recently, the same tasks in different objects of the same class were not running concurrently.

In reply to edcarstens:

Ed, that is a good rule to follow. Unfortunately there is a lot of stuff in the SystemVerilog LRM left in for backward compatibility with Verilog and it’s difficult to know what is only there for legacy code.

  • Disable Named block
  • Non-Ansi style port and argument lists
  • always @(*)
  • procedural continuous assignments assign/deassign