RE: [sv-ec] disabling fork join threads under task

From: Steven Sharp <sharp_at_.....>
Date: Wed Jul 08 2009 - 13:24:17 PDT
>From: "Daniel Mlynek" <daniel.mlynek@aldec.com>

>Back to my example I've found a LRM quotation: "Thus, disable shall end all
>processes executing a particular block, whether the processes were forked by
>the calling thread or not, while disable fork shall end only the processes
>that were spawned by the calling thread." 
>
>From above I assume that disable in time foo should also kill dynamic
>processess spawned by fork - Am I right?

No, I don't think so.  The quote refers to multiple processes "executing"
the block.  To me that means processes that entered the block from outside.
A process started inside the block is not executing the block.  Would you
say that a process started inside a task is executing the task?  I wouldn't.
The 1364 LRM was more specific for tasks: "If a task is enabled more than
once, then disabling such a task shall disable all activations of the task."

The quote is referring to the situation where multiple processes have
entered the block and are executing it.  For example:

module top;
 task automatic  foo;
   #20 $display($time);
 endtask
 
 initial foo;  // process 1
 initial foo;  // process 2
 initial foo;  // process 3

        initial #5  disable foo;
endmodule


It is saying that the disable will affect all three processes executing
in the block (the task in this case, though we could add a named block
inside the task and disable that instead).  It is mistaken in saying that
it will "end" them, since it will only cause them to exit the task.  Some
other activities inside the block do get ended.  It is unclear whether
fork..join_none processes are included in those.

The quote is saying that all processes in the block are affected equally,
regardless of their relationship with the process executing the disable.
This is contrasted with "disable fork", which only affects processes that
were created by the process executing the disable (and does end them).

The quote that would be most applicable here is the one  that says "All 
activities enabled within the named block or task shall be terminated as
well."  It is not clear what this means.  The text makes clear that if
the process has called another task, then everything further down in
the call chain gets disabled.

It explicitly leaves undefined the effect on nonblocking assignments and
procedural continuous assignments (assign and force statements).  It doesn't
mention some other cases, such as $monitor, that I don't think got disabled
in Verilog-XL.  Since these are all free-running processes, similar to a
fork..join_none subprocess, it could be argued that the same rule should
apply.

You can try to compare it to fork..join instead.  Here I will use the
behavior of Verilog-XL for comparison, since I don't know for sure that
all implementations agree on this.

If a process executing in a task is waiting at a join when it is disabled,
all of the forked subprocesses get terminated when the task is disabled.
So you could argue that subprocesses forked in the block should be
terminated.  But it turns out that if a task is waiting at a join when it
is disabled, all of its forked subprocesses get terminated *even if they
are not inside the block being disabled*.  For example:

module top;

  task bar;
    fork
      #20 $display("hello");
      #30 $display("world");
    join
  endtask

  task foo;
    bar;  
  endtask
  
  initial foo;
//initial bar;
  
  initial #5 disable foo;
endmodule

When foo gets disabled, so do the subprocesses created in bar, even though
they were not created inside the scope of foo.  So they aren't really an
activity inside foo.

You could try to argue that the LRM says that disabling a task within a
chain of calls disables all tasks downward on the chain, so bar is being
disabled, and that disables the subprocesses.  But if you add a separate
process that calls bar directly (like the one commented-out above), that
process is not affected when foo is disabled.  If bar were disabled, that
would affect all processes executing in bar.

So why do the fork..join subprocesses get disabled?  I would argue that it
is because their parent process must exit foo, but it is not allowed to
continue executing until all the fork subprocesses terminate.  Therefore
the child processes must be terminated.  The parent has temporarily
divided its execution up between the subprocesses, whose execution is a
replacement for its own execution.  Stopping its own execution is the
same as stopping its divided-up execution: i.e. the subprocesses.

The child processes in a fork..join_none are not a replacement for the
parent process.  They run independently of the execution of the parent.
The parent can exit a task whether they continue running or not.  So there
is no need to terminate them when the parent is disabled.

It is harder to see how a fork..join_any should behave.  If the parent is
no longer waiting at the join_any, then the child subprocesses are as
free-running as join_none subprocesses.  But what if it is waiting at the
join_any?  Like the fork..join, it is not supposed to leave the join_any
until the first child ends (as opposed to the last child ending for a join
to continue).  Should the disable try to satisfy that condition, as it
does with a fork..join?  How should it do it?  

Terminating one child at random would satisfy the condition, but would be
arbitrary and rather silly.  Also, most uses of join_any are immediately
followed by a disable fork to terminate the other subprocesses.  The user
is using join_any and disable fork to build their own construct where the
parent doesn't leave until all of the children have terminated.  Leaving
some children running would violate that intent.

Terminating all the child processes would satisfy the condition and be
fair and deterministic.  It would satisfy the intent of the usual compound
construct.  But if the user intended to leave the children running, then
it doesn't satisfy that intent.  And it is prone to race conditions, where
behavior depends on whether the parent woke up at the join_any before or
after the disable.  Leaving the child processes alone would avoid that.

This is another place where it would have been better to have added a
fork..join_disable construct that has the desired behavior of the compound
construct.  In that case it would be clear that the parent was not supposed
to leave until the children were terminated.

SV gives the user flexible mechanisms for building their own protocols
for synchronization and control between processes.  This means that the
language can't assume it knows the rules the user wants for old-style
disables, the way it does with fork..join.  In general, the user will
need to use the newer constructs to get the behavior they want.

We could still define rules for old-style disables.  But if users will
need to use the new constructs most of the time to get the behavior they
want, it may not be that important to do so.

Steven Sharp
sharp@cadence.com


-- 
This message has been scanned for viruses and
dangerous content by MailScanner, and is
believed to be clean.
Received on Wed Jul 8 13:31:08 2009

This archive was generated by hypermail 2.1.8 : Wed Jul 08 2009 - 13:32:01 PDT