Dave, See my comments inlined below. Arturo ________________________________ From: Rich, Dave [mailto:Dave_Rich@mentor.com] Sent: Wednesday, February 22, 2006 11:28 AM To: Arturo Salz; sv-ec@eda.org; sv-bc@eda.org Subject: RE: [sv-ec] RE: [sv-bc] Can a function contain a fork/join/any/none? Arturo, The big difference with the side effect is that it is an effect outside the function. (How an implementation might choose to execute the event is not part of the LRM) That's why non-blocking assignments are not allowed in a function either. [Arturo] I would have no problem with having the same non-determinism with respect to fork/join_none. I believe that when fork/join_none was discussed, the committee erred on the side of specifying too much determinism with respect to the execution of the threads under a fork/join_none. This, I recall, was an attempt to get identical behavior from all implementations. In retrospect, I think it would have been best to leave it undetermined, as with all other concurrent constructs. There are no issues with backward compatibility in this discussion because the current LRM does not allow a function to call a task. [Arturo] If we go back and define constructors to be tasks then we will create a backward compatibility issue because constructors are currently defined as functions, thus, callable from other functions. If constructors become tasks then they are not callable from within functions: This limitation is not backward compatible. What you are basically asking for is to ignore all the rules about functions when inside a fork/join_none. If I can call a blocking task inside a fork/join_none, then any other time controlling statement could be wrapped in that task call. [Arturo] Yes, the basic idea is to recognize that the existing limitations on functions apply only to the fork/join forms that suspend (or may suspend) the function, but they do not apply to the statements within the fork/join_none. But now we have to attach scheduling semantics to functions, consider how disable, fine grain process control, and randomization interact with the thread hierarchy. And then there's the DPI which is not expecting an exported function to start a thread. [Arturo] I don't believe this is true. If we treat fork/join_none is just one more of the many side effects already allowed by functions then there is nothing special about a function that creates threads as a side effect. Incidentally, all the issues you bring up must be well defined for tasks; I don't believe they represent anything particular for functions. What problem do you foresee if an exported function creates threads? As I stated earlier, a function can already spawn threads by modifying variables. Whether the function is called via DPI or not seems (to me at least) largely irrelevant. But that does not mean I against any of this, I just want to see a proposal that deals with these issues before the door gets too wide open - like what happen in Vera where functions were allowed (rather never prohibited) to block. [Arturo] I agree with this statement. As a starting point we could define this in terms of the following legal transformation: class C; => class C; function C::new(); static mailbox#(C) mbox = new(); ... fork XXX join_none function C::new(); ... ... endfunction mbox.put( this ); endclass ... endfunction task fork_in_new() fork XXX join_none; endtask endclass always begin C q; C::mbox.get( q ); q.fork_in_new(); end It's important to note is that although the two code versions are semantically equivalent, the right one is a lot more verbose and error prone. Dave ________________________________ From: Arturo Salz [mailto:Arturo.Salz@synopsys.com] Sent: Wednesday, February 22, 2006 9:46 AM To: Rich, Dave; sv-ec@eda.org; sv-bc@eda.org Subject: RE: [sv-ec] RE: [sv-bc] Can a function contain a fork/join/any/none? Dave, I think you are missing my point by focusing on the legality of the trigger operator within the function. The following trivial change makes the code legal: function void F(); function void F(); // some initialization // some initialization fork var = ~var; T1(); endfunction T2(); join_none always @var T1(); endfunction always @var T2(); The issue is that functions are allowed to write to variables, such as var in the example above, as side effects. These side effect assignments can spawn activity and create new threads. Thus, by disallowing fork/join_none within functions, we would not be closing any fundamental semantic hole in the language. Instead, all we would accomplish is add another obstacle for engineers to get around, forcing them to write their code in less than optimal ways. In this case the fork/join_none construct best describes the user intent. Yes, the distinction between tasks and functions in Verilog is fundamental, but the issues are different. If functions were allowed to block (which is definitely not something I am advocating) then evaluation of simple expressions becomes non-deterministic. For example, simple expression such as V = F() + A; becomes hard to predict because it's unclear which value of A to use: the value before or after the function blocks. However, note that the problem of non-determinism already exists since functions are allowed to write to variables as side effects. Thus, if function F() were to modify variable A above, it results in non-determinism similar to the one due to the blocking function. Treating class constructors as special may at first seem like a good approach, but it is ultimately flawed because we would have to disallow calling constructors from within functions. Then users would be unable to write wrapper functions to create objects, such as: function MyClass spawn( bit x ); if( x ) spawn = new( "abc" ); else spawn = new( "def" ); endfunction This is a very common paradigm in OO programming, in particular method functions that create other objects. And, since this is already allowed, treating constructors as special will definitely cause backward compatibility issues. While I agree that packages should not spontaneously create threads, I think the situation we are describing is very different. A class contained within a package must be able to create threads by calling its methods. Such thread creation is not spontaneous and is a fundamental requirement for many models and verification IP. I believe that the LRM needs some work regarding what package declarations are allowed or disallowed. For example, should a class object declaration that includes a call to the constructor be allowed in the package? Similar questions can be asked of any dynamic construct, such as a dynamic array that includes a call to its constructor. However, the problem is not because they spawn threads but because it's unclear under what conditions the object is created. Arturo ________________________________ From: owner-sv-ec@eda.org [mailto:owner-sv-ec@eda.org] On Behalf Of Rich, Dave Sent: Sunday, February 19, 2006 6:51 AM To: sv-ec@eda.org; sv-bc@eda.org Subject: RE: [sv-ec] RE: [sv-bc] Can a function contain a fork/join/any/none? Hi Arturo, There are a few problems with the two versions of F(). The second version of F() has the trigger -> statement, which is not allowed in a function. And the first version of F() has task calls T1 and T2 (Cliff - this is what you missed, the "T" is for "Task") inside the function call. Vera may not have made much of a distinction between tasks and functions, but it's pretty fundamental in Verilog and SystemVerilog. Many other features of the language depend on it. For example, I'm sure the DPI does not expect threads to be created from exported function calls. And as I mentioned before, packages should not have spontaneously generated threads. If the real issue is starting a thread with a constructor, then I could see doing something for that case only. The constructor is already a very special function call; and we need to make sure the constructor is only called from a place where it would be legal to start a thread anyways. Then we're not opening up the door by allowing general functions to call tasks and create threads. Dave ________________________________ From: owner-sv-ec@eda.org on behalf of Arturo Salz Sent: Fri 2/17/2006 4:52 PM To: sv-ec@eda.org; sv-bc@eda.org Subject: RE: [sv-ec] RE: [sv-bc] Can a function contain a fork/join/any/none? As Dave has learned, forking off background threads in class constructors is a very common usage in the world of transaction modeling. A transactor will typically create one or more threads for each instance created during the simulation. The primary motivation of model writers is to encapsulate all the inner workings inside the model and not expose the modeling details to users of the model. When 1364-2001 writes "A function shall execute in one simulation time unit", I understand that users take that quite literally as the meaning of "time consuming". And, after introducing fork/join_none, which is definitely not time consuming, I can understand why many people would conclude that fork/join_none must be allowed in functions. Frankly, I see nothing wrong with allowing fork/join_none in functions. A fork/join_none is semantically equivalent to triggering the execution of one or more threads from within the function, as in the following code: function void F(); function void F(); // some initialization // some initialization fork -> ev; T1(); endfunction T2(); join_none always @ev T1(); endfunction always @ev T2(); Both versions of the above function F() accomplish the same thing, except that the one on the right is more verbose and doesn't encapsulate the functionality as well as the one the left. If the threads triggered by the function are related to the function call --- for example, they may be associated with the particular transactor being created --- then the code on the right becomes a exceedingly more difficult to write: For example, the function may have to add the transactor handle to some dynamic data such as queue, which is then examined by the threads. It's also important to remember that the code on the right cannot be written in a package, thus, making life even more difficult for both model writers and users of the model. Forking threads from within a constructor is very common idiom in both Vera and C++ models. If we were to disallow this in P1800, I imagine a lot of IP writers will be upset. I suspect that these are the same people that have been questioning Dave on the availability of this feature. I'm not advocating that SystemVerilog allow all types of fork/join within functions, only fork/join_none, which is fundamentally not a time consuming construct. Sorry for blasting this to both sv-bc and sv-ec, but I believe it is important to let both committees know about this (sorry for the copies Cliff). Arturo -----Original Message----- From: owner-sv-ec@eda.org [mailto:owner-sv-ec@eda.org] On Behalf Of Rich, Dave Sent: Friday, February 17, 2006 1:31 PM To: sv-ec@eda.org Subject: [sv-ec] RE: [sv-bc] Can a function contain a fork/join/any/none? I'm just going to copy the sv-ec from now on, since from the perspective of the sv-bc, this only deal with issues related to the testbench. The main place that I have seen this is in the new function for a constructor. This snippet of code below was submitted to the SPIRIT working group back in September. Since then I have seen it in a few other places, basically the same. class ahbram; ahb_slv_if.comp sigs; local ahbram_callbacks cbs[$]; local bit [1023:0] ram; extern function new(string instance, ahb_slv_if.comp sigs); extern function bit [1023:0] peek(bit [31:0] addr); extern function void poke(bit [ 31:0] addr, bit [1023:0] data); extern function void register_callbacks(ahbram_callbacks cb); extern local task main(); endclass: ahbram function ahbram::new(string instance, ahb_slv_if.comp sigs); this.sigs = sigs; fork this.main(); join_none endfunction: new I would grant that if a fork/jone_none were legal in a function, then I can see stretching the LRM to say that the task call inside the fork is executing outside the function, or even stretching the LRM to say that the "new" function is not really a function, its a constructor and doesn't have to follow the rules of Verilog function. Now we have the statement ahbram R1 = new(); creating a thread. But that statement could be in a package, or inside another function be called outside of a procedural context Dave > -----Original Message----- > From: owner-sv-bc@eda.org [mailto:owner-sv-bc@eda.org] On Behalf Of Brad > Pierce > Sent: Friday, February 17, 2006 12:23 PM > To: sv-bc@eda.org > Cc: sv-ec@eda.org > Subject: Re: [sv-bc] Can a function contain a fork/join/any/none? > > Cliff, > > That's not at all what I asked. I don't see anything wrong with using > fork-join in functions. > > INSTEAD, I asked Dave, who started this thread, why so many people are > asking him the question in his subject line. My guess is that they are > indeed seeing examples of this style and then asking Dave, "is that > really legal?". > > Yes, it's really legal. > > How might they be seeing examples of this legal usage? Maybe the people > that are asking him are tool developers who are getting bug reports > about this legal usage, in which case the need for backward > compatibility would be even stronger. It would be interesting to know. > > -- Brad > > -----Original Message----- > From: owner-sv-bc@eda.org [mailto:owner-sv-bc@eda.org] On Behalf Of > Clifford E. Cummings > Sent: Friday, February 17, 2006 12:01 PM > To: sv-bc@eda.org > Subject: [sv-bc] Can a function contain a fork/join/any/none? > > Hi, All - > > Just the sv-bc on this message (I'm tired of getting two of everyone > of these messages) > > Brad asked the question and I think Stu hinted around the question: > > What is the use-model for adding fork-join to a function? > > Are there any engineers doing this? (And has anybody slapped them for > doing it? :-) ) > > The engineers that ask this question, do they understand that no > delays are allowed in a function (not even #0 delays or nonblocking > assignments that cause events to be scheduled in later event regions > within the same timestep) and that fork-join are generally not > synthesizable and are really most useful when you add some form of > delay to the forked paths within a testbench? > > I can't say for sure, but I don't believe either Stu or I have ever > shown engineers a fork-join in a function in training and I don't > think we ever intend to teach this coding style. > > I understand that it might be important to add some form of > clarification to the LRM but whatever clarification we add should > come with the strong editorial comment that this is/was a bad idea. > > I will support whatever the group goes for on this and then I will > never teach an engineer to add fork-join to a function (unless > somebody can come up with some ultra-astonishing use-model that I > have not heretofore recognized). > > Regards - Cliff > > ---------------------------------------------------- > Cliff Cummings - Sunburst Design, Inc. > 14314 SW Allen Blvd., PMB 501, Beaverton, OR 97005 > Phone: 503-641-8446 / FAX: 503-641-8486 > cliffc@sunburst-design.com / www.sunburst-design.com > Expert Verilog, SystemVerilog, Synthesis and Verification Training >Received on Wed Feb 22 13:09:23 2006
This archive was generated by hypermail 2.1.8 : Wed Feb 22 2006 - 13:10:20 PST