Confusion in fork join ... disable fork

Hello ,
I am unable to get the exact functionality of disable fork.
Example ;

task t1();
  fork
    begin : a1
    #1;
    $display("a1"); 
    end
    begin: a2
    #2;
    $display("a2"); 
    end
  join_none
  fork
    begin : a3
    #5;
    $display("a3"); 
    end  
    begin : a4
    #6;
    $display("a4"); 
    end 
  join_any
  disable fork

  fork
    begin : a5
    #5;
    $display("a5"); 
    end  
    begin : a6
    #6;
    $display("a6"); 
    end 
  join_any
  endtask

Now will this disable fork will disable a1 and a2 also??? Will there be any output in this case??? And, What will happen to a5 and a6???

Hi Anamika,
disable fork statement terminates all active child processes, grandchild processes etc. where it is called.

No, disable fork shall terminate only a3 process because when it is called only a3 process is active.
Both a5 and a6 process shall be continued because disable fork has been executed just before.

The outputs would be:-
a1,
a2,
a3,
a5,
a6

It is best to start from the root thread, which is started by an initial or always block.
module top;

initial begin : i1
           fork : f1
              #10 $display("f1")
           join_none
           t1();
        end
endmodule

The disable fork statements kills the children of the currently running thread. (i.e the thread executing the disable fork statement).

In this example, the parent thread is enclosed by the scope i1. It starts a child thread f1, then calls task t1. Inside task t1, the first fork starts threads a1 and a2, then the second fork starts a3 and a3. Since the second fork has join_any, it waits for one of the threads to complete before executing the disable fork statement.

The disable fork will not execute until thread a3 completes at time 5, and only threads f1 and a4 are still active and will be killed. Had you used join_none instead of join_any, or used longer delays then the disable fork statement would have also killed threads a1, a2 and a3.

The threads a4 and a5 are forked after the disable fork, so it has no effect on those threads.

In reply to dave_59:

Dave,

What if threads a1 and a2 had longer delays then a3 and join_any instead of join_none, does a1 and a2 also comes under list of active threads(along with f1 and a4) and will get killed?

In reply to RameshRP:

Yes, I said that.

In reply to dave_59:

Hi Dave,
In the example u mentioned above, thread f1 won’t get killed.
Here is what IEEE 1800-2009 std, clause “9.6.3 Disable fork statement”, pg 174 says:

“The disable fork statement terminates all descendants of the calling process as well as
================
the descendants of the process’s descendants.”

Here is my understanding:
Since calling process i.e. process which called “disable fork” is “t1”, all threads inside t1 and their descendants will get killed. Since thread f1 is not a descendant thread of t1 it should not get killed. I confirmed the same by simulating it w/ VCS too.

Thanks,
Ashish

In reply to AshishP:

Calling a task or function does not create a new process; only a fork can do that. In this case, t1 is a task activation, not new process. So process f1 and a1-a6 are all descendents of the same parent process.

In reply to dave_59:

Hi Dave,
Then is it a bug in VCS since it behaves the way it’s mentioned in comment #6 which doesn’t match w/ what you mentioned in comment #7?

Thanks,
Ashish

In reply to AshishP:

Since I work for Mentor, I can comment on Questa’s behavior, and as a member of the IEEE SystemVerilog 1800 committee, I can comment on my interpretation of the LRM, and whether it is correctly worded. In this case, Questa behavior matches my interpretation of the standard, and VCS does not.

In reply to dave_59:

Thanks, Dave.

In reply to AshishP:

Hi Dave,

On similar lines, can you explain what should be the ideal behavior for the below code (I was thinking disable fork should disable ff2, please clarify):

class a_bus_monitor;
   bit my_event1;
   int iter;
 
   task my_monitor();
      fork : ft1
         forever begin:ff1
            #5ns;
            $display("Passed 5 ns now");
            iter++;

            if (iter == 20) begin
               $display("Breaking because iter is 20");
               break;
            end
         end

         forever begin:ff2
            @(my_event1);
            $display("At time %0t found an event on my_event1 (= %0d)",$time,my_event1);

            if (my_event1 == 0) begin
               $display("Disabling fork now");
               disable fork; // ???Should not this disable fork disable ff2???
            end
         end
      join
      
   endtask:my_monitor;
 
endclass 
 
module top;
   a_bus_monitor abm1;
 
   initial begin: i1
      abm1 = new();
      fork:fm1
         begin:fmf1
            abm1.my_monitor();
         end
         begin:fmf2
            #6ns;
            abm1.my_event1 = 1;
            #10ns;
            abm1.my_event1 = 0;
            #20ns;
            abm1.my_event1 = 1;
            #6ns;
            abm1.my_event1 = 0;
         end
      join

      $display("main fork has been joined");
   end 
endmodule 

Questasim 10.3b gives below output:

Passed 5 ns now

At time 6 found an event on my_event1 (= 1)

Passed 5 ns now

Passed 5 ns now

At time 16 found an event on my_event1 (= 0)

Disabling fork now

Passed 5 ns now

Passed 5 ns now

Passed 5 ns now

Passed 5 ns now

At time 36 found an event on my_event1 (= 1)

Passed 5 ns now

At time 42 found an event on my_event1 (= 0)

Disabling fork now

Passed 5 ns now

Passed 5 ns now

Passed 5 ns now

Passed 5 ns now

Passed 5 ns now

Passed 5 ns now

Passed 5 ns now

Passed 5 ns now

Passed 5 ns now

Passed 5 ns now

Passed 5 ns now

Passed 5 ns now

Breaking because iter is 20

In reply to Sailaja:

No.

The disable fork statements kills the children of the currently running thread. (i.e the thread executing the disable fork statement).

Your ff2 thread is a single forever statement. You can use the break statement instead to get out of the forever loop.

In reply to dave_59:

Got it…
Thanks

In reply to Sailaja:

Hi Dave,

Here in the below code if i get timer_flag as ‘0’ even before the completion of thread named label, I am trying to disable that process. As per my analysis it should disable that particular iteration and again it has to wait for timer_flag to be 1 to continue, and it has to wait for time run_duration before making expired bit 1 from the last valid time_flag that it receives .But this logic is calculating run_duration from the first valid timer_flag.(Consider if re-setting timer_flag to 0 for two times at 0ns, 5ns and 10ns and run_duration value is 50ns,then the expired bit should get assert at 60ns but its happening at 50ns itself,which is not my requirement.) How does it happens even after disabling label thread?

Could you please provide some clarity on this.

Code::

fork 
  forever
  begin :label //{
      wait(timer_flag==1);
      #(run_duration);
      expired=1;
      timer_flag=0;
  end //}

  forever
  begin //{
  @(timer_flag);
     if(timer_flag==0)
     begin
     ovm_report_info(get_type_name,$psprintf("Disabling label thread at time %.6t", $realtime));
     disable label;
     end
   end //}
 join //}

Thanks

In reply to sandy1664:

You need to show how you reset the timer flag. If the timer_flag was set to 0 and back to 1 in zero delay, there is no guarantee that the second forever loop would see timer_flag==0 and disable the label

...
timer_flag = 0;
timer_flag <= 1; // this make sure it is 0 for one delta cycle.
...

In reply to dave_59:

Everytime i reset the timer , the timer_flag becomes 0 and after we start the timer again by making timer_falg to 1 after 1ps delay.

In reply to Sumit Jain:

Hi Sumit ,
please correct me if i am wrong but I believe this code will give output as :-
a1
a2
a3
a5
I tried with Vcs simulator , above is the output i got , I am not seeing any a6 there.

Thanks
Arpit

In reply to dave_59:

Hi Dave,

For the below example , as main task is calling the dev_state task which contains disable_fork .
Then as per LRM Std 1800-2012 , disable fork will kill all the remaining threads of main task also right .

So the output should be
5 THREAD 1
10 THREAD 3
10 After task call

Could you please let me know whether my understanding is correct

module test; 
initial begin 
           main(); 
        end 
task main();
  fork 
    #5  $display($time,,"THREAD 1"); 
    #25 $display($time,,"THREAD 2"); 
  join_any 
  dev_state(); 
  $display($time,,"After task call"); 
  #100 $finish; 
endtask 

task dev_state();
  fork 
     #5  $display($time,,"THREAD 3");
     #10 $display($time,,"THREAD 4"); 
  join_any 
  disable fork;
endtask 
endmodule

In reply to kvssrohit:
Correct, sort of. It will kill all remaining child threads of the initial block. A task call is not a thread.

Please use code tags to make your code easier to read. I have added them for you.

Hi Dave,

I wanted to know if a disable fork inside a task can kill threads launched by another task using fork…join_none. For example: In the following code, if task a2() is called 10 units after calling task a1(), will the threads inside a1() be killed?

class alpha;
  
  task a1();
    fork
      #(20) $display("run1");
      #(30) $display("run2");
    join_none
  endtask: a1

  task a2();
    disable fork;
  endtask: a2

endclass: alpha

module top;
  alpha abc;
  initial begin
    abc = new();
    abc.a1();
    #(10) abc.a2();
  end
endmodule