[sv-cc] Re-proposed user data API


Subject: [sv-cc] Re-proposed user data API
From: Warmke, Doug (doug_warmke@mentorg.com)
Date: Wed Mar 26 2003 - 11:55:54 PST


John, Team,

After reading John's comments here, a little more follow-up,
and a night of sleeping on this, I believe that the current
ID-based scheme is not usable or intuitive enough for the
purpose of accessing user data.

Andrzej's latest proposal seems like it will work, but the
essence of it is "for some memory savings, put the complexity
of managing instance-to-instance inter-language connections
squarely on the user's shoulders". I think this is in
opposition to the "simple but powerful" spirit of DPI.
Please read on, for a very simple but powerful modification
of John/Francoise's earlier work. Memory usage is *slightly*
higher than Andrzej's in some cases, but the power and
simplicity elements are much higher for the user.

Let's back up and review the problem we are trying to solve.
Basically, specific instances of C models want to be bound
to specific instances of SV models.

User data is used to manage this binding, since context isn't
as explicit as is necessary, and implicit context isn't possible
(as in pure C++) due to the two different language systems.

Now to cut to the chase:
Consider an SV module m, which declares three context import
functions f1, f2, and f3. Consider a design which contains
three instances of m: i1, i2, and i3.

There are different ways to organize user data. The most flexible
and useful is that each function declaration instance will store
unique user data. This is just one pointer, not expensive. In our
example design, there will be nine unique user data elements:

              i1.f1 i2.f1 i3.f1
              i1.f2 i2.f2 i3.f2
              i1.f3 i2.f3 i3.f3

An important question is, "How do we work with these in the C API?"
We need to put data into the storage areas, usually at init time.
We need to get data out of the storage areas, at runtime, high speed.

In this kind of 2-D storage scheme we need two keys to address the
specific data that is needed. The first is svScope. That is already
present in the API, and it is used to fetch up i1, i2, i3. The second
is the name of the function. This second key has been the focus of
our debates over the past days.

Here is a summary of the approaches that have been considered
for the 2nd key:

1. String-based
     void svPutUserData(svScope instance, const char* funcName, void* data);
     void* svGetUserData(svScope instance, const char* funcName);

   This is too slow at runtime due to string comparison.

2. Function scope handle based
     svFunctionScope svGetFunctionScope(svScope instance, const char*
funcName);
     void svPutUserData(svScope instance, svFunctionScope function, void*
data);
     void* svGetUserData(svScope instance, svFunctionScope function);

   Personally I don't see anything fundamentally wrong with this,
   and I think we should adopt this or some variant of it. (See #4 below)

   At one point, there was a problem regarding overlapping usage
   of function scope and instance scope. I agree that was confusing
   and it should be avoided.

   From what I understand, others have objected to the fact that all
   nine user data (in our example) need to have static storage allocated,
   even if they are not used. I don't think that is a big deal.
   Several points here:
     a) What needs to be stored is very small, just 32-bits
     b) This is only for context import functions, not all import functions
     c) There won't be a large number of instances of declarative scopes
        containing context import functions. Even 100 would be an
        aggressively large number. 400 bytes storage across the simulation
        is not a lot.
     d) There won't be a large number of context import function
declarations
        in any given declarative scope. Once again, the multiplier effect
        that really hurts memory and performance is absent.

3. ID-based scheme.
   I believe the motivation here is to only have "pay-as-you-go" storage
   requirements for user data. User data ID's are dynamically allocated
   on a request basis. Then they can be used to set and retrieve data
   with good performance.

   As we've seen there are several difficulties with this approach:
     a) John's example and my comments show that it is highly unintuitive
     b) John's subsequent mail about requiring "module name" in order
        to fetch a user data ID really has me lost. I think users will
        be more than lost when confronted with this kind of system.

4. Hybrid ID - Function scope approach
   In a way, approach #2 (function scopes) is almost like an ID scheme.
   If we changed that approach to:
     int svGetFunctionID(svScope instance, const char* funcName);
     void svPutUserData(svScope instance, int functionID, void* data);
     void* svGetUserData(svScope instance, int functionID);

   Then we have an ID scheme, and there is no confusion with overlapping
   scope concepts. The only drawback is that it's not "pay as you go".
   But consider the mitigating points discussed in 2. above. This is
   already far better than VPI, in which user data must be accessible
   at every call site. There can be many more of those than context
   import function declaration scope instances.

   Now, an additional improvement became clear as I was recoding John's
   example below to make use of this approach. In most cases, the
   desired functionID is directly associated with the name of the
   context import function in which C code is running. Rather than
   requiring storage and retrieval of a functionID for such cases,
   we should reserve functionID 0 to denote the functionID of the
   currently executing context import function. This makes John's
   example below much simpler, as I hope you will agree.

*** In summary, I urge us to adopt approach 4. ***

Here is John's example, recoded using approach 4.

SV Side:

[...]

C Side:

// Define the function and model class on the C++ side:
class MyCModel {
     private:
         int locallyMapped(int portID); // Does something interesting...

     public:
         // Constructor
         MyCModel(const char* instancePath) {
             svScope scope = svGetScopeFromName(instancePath);

             // Allocate a unique ID to be used as key to associate
             // user data with "MyCFunc" in its module scope.
             int functionId = svGetFunctionID(scope, "MyCFunc");

             // Associate "this" with SV scope (avoids a hash in C++ code)
             svPutUserData(scope, functionID, this);
         }

     friend int MyCFunc(int portID);
};

// Implementation of context import function callable in SV
int MyCFunc(int portID) {
    // Retrieve SV module instance scope (i.e. this function's context).
    svScope scope = svGetScope();

    // Retrieve and make use of user data stored in SV scope
    MyCModel* me = (MyCModel*)svGetUserData(scope, 0);

    return me->locallyMapped(portID);
}

Let's do a little analysis on what the example is actually doing
and how it relates to the previous imaginary design I started with.
Imagine that there are three instances of this C++ class. Each one
is associated with a unique SV instance of the module which declares
import function "MyCFunc".

In this case, instead of a 3x3 matrix of user data, there is only
a 1x3 matrix of data. The three user data storage areas will contain
the three unique "this" pointers of the three C++ objects. Each cell
of the matrix is addressed by (svScope, functionID). Probably all 3
functionID's will be identical low integers, but that is up to the
implementation and not important. As each C++ object is constructed,
it fetches the appropriate svScope and functionID, then stores its
"this" pointer into userData.

It's easy to imagine extending this example to contain 3 separate
C++ classes, each of which has a unique friend context import function
declared in the same SV module. We would get to the original 3x3 matrix
of user data that way. If it's not easy to imagine that, I would be
happy to code up such an example, just let me know.

Now imagine another scenario: One C++ class and one instance of that
class, but 3 separate friend functions associated with 3 context import
functions declared in the same SV module. In this case we would have a
3x1 matrix. All three user data storage areas would have the same
"this" pointer.

Now a more complex scenario: Three C++ classes, three friend functions
associated with each class (context import functions), and 1 instance
of each class.
Call the C++ "this" pointers ci1, ci2, and ci3.
Call the SV instances i1, i2, i3, as before.
Call the three context import (friend) functions f1, f2, f3, as before.
Recall the "formal" 3x3 matrix entries are:
               i1.f1 i2.f1 i3.f1
               i1.f2 i2.f2 i3.f2
               i1.f3 i2.f3 i3.f3

In this case the actual values would be:
                ci1 ci2 ci3
                ci1 ci2 ci3
                ci1 ci2 ci3

Now another complex scenario: Three C++ classes, one context
import (friend) function associated with each class, and 3 instances
of each class. Each class instance is associated with one SV instance.
Call the three classes c1, c2, c3.
Call the "this" pointers of the classes c1i1, c1i2, c1i3, c2i1, etc.
Call the context import (friend) functions f1, f2, f3.
Call the SV instances i1, i2, i3.

Then the matrix values fill out to:
                c1i1 c1i2 c1i3
                c2i1 c2i2 c2i3
                c3i1 c3i2 c3i3

******

Lots of such permutations are possible with approach #4.
It is the most general, simple approach to user data storage.
No "user layer" complexity is required, no verbose explanations
in the LRM (or ancillary money-making follow-on docs ;) ), etc.
And for a very acceptable price, in my opinion.
Simplicity does have a price, and in this case I think
we need to pay it for context import functions.

Thanks and regards,
Doug

> -----Original Message-----
> From: Stickley, John
> Sent: Tuesday, March 25, 2003 10:22 PM
> To: Warmke, Doug
> Cc: sv-cc@server.eda.org
> Subject: Re: [sv-cc] LRM modifications for svGet/PutUserData proposal
>
>
> Doug,
>
> Warmke, Doug wrote:
> > Hi John,
> >
> > Real nice work hammering out a compromise here.
> > I have a couple of corrections to make in the example. Please see
> > below.
> >
> > Regards,
> > Doug
> >
> > > -----Original Message-----
> > > From: Stickley, John
> > > Sent: Tuesday, March 25, 2003 2:07 PM
> > > To: sv-cc@server.eda.org
> > > Subject: [sv-cc] LRM modifications for svGet/PutUserData
> proposal
> > > >
> > > Team,
> > >
> > > In the interest of expediency and ease of insertion, I've >
> > tried to frame Francoise's proposal in the form that can > be
> > inserted directly into Joao's draft of the LRM. >
> > > Note to Joao: There are a number of errors in the example
> > > in section A.8.4. Please supersede with my version where those
> > > errors have been corrected (and where the new get/putUserData()
> > > usage is demonstrated). Also, what is missing is an
> > > example of invoking an exported function after setting
> > > svPutScope(). I can provide an extension to the existing
> > > example that shows this if you wish.
> > >
> > > Here are the required updates to the LRM.
> > >
> > > ---------------------- cut here ----------------------
> > >
> > > A.8.3 Working with DPI context functions in C code
> > >
> > > [...]
> > >
> > > /* Return a unique ID that can be used as a key to store user
> > > data in a given module scope. */
> > > int svGetUserDataIdForScope(const svScope scope);
> > >
> > > /* Set arbitrary user data pointer into specified
> instance scope */
> > > void svPutUserData(const svScope scope, int userDataId, void* >
> > userData); >
> > > /* Retrieve arbitrary user data from specified instance scope */
> > > void* svGetUserData(const svScope scope, int userDataId );
> > >
> > > [...]
> > >
> > > New section
> > > | | | | |
> > > V V V V V
> > >
> > > A.8.3.1 Associating User Data with Module Contexts
> > >
> > > The DPI allows association of multiple user defined data
> > > pointers with module instances by allocating unique ID's
> > > that can serve has keys to identify each distinct user
> > > data pointer to be associated with a given module instance.
> > >
> > > The user data pointer is of type void * and is never
> > > interpreted by the SV infrastructure - only by the user
> > > application.
> > >
> > > The svGetUserDataIdForScope() function can be called at
> > > initialization time to request unique IDs that can be
> > > used to identify user data pointers to be associated
> > > with a given module scope.
> > >
> > > The returned ID can be stored in a place known to the
> > > C code (most likely a static or global variable) and
> > > later be frequently referenced during run-time when
> > > svGetUserData() is called to retrieve user data
> > > pointers inside imported C functions that are called
> > > from SystemVerilog.
> > >
> > > The functions svPutUserData() and svGetuserData() each
> > > require a module scope handle and a user data ID argument.
> > >
> > > ID=0 is reserved to denote the NULL or invalid ID. This
> > > allows a variable used to store an ID to be conveniently
> > > tested for validity.
> > >
> > > A.8.4 Example 1 - Using DPI context functions
> > >
> > > SV Side:
> > >
> > > [...]
> > >
> > > C Side:
> >
> > DOUG: [Admittedly minor nitpick]
> > We should use consistent whitespace here.
> > The examples all use K&R whitespace, such as
> > if (condition) {
> > doSomething();
> > }
> >
> > In this code, there are a few tidbits like:
> > if( condition ){
> > doSomething();
> > }
> >
> > Let's clean those up in the LRM so all is consistent.
>
> johnS:
> No problem - I can fix this. Hard to break old habits !
>
> >
> > >
> > > // Define the function and model class on the C++ side: > class
> > MyCModel {
> > > private:
> > > static int dUserDataId;
> > > int locallyMapped(int portID); // Does something
> > > interesting...
> > >
> > > public:
> > > // Constructor
> > > MyCModel(const char* instancePath) {
> > > svScope scope = svGetScopeByName(instancePath);
> > >
> > > // Allocate a unique ID to be used as key
> to associate
> > > // user data with module scope. Skip this step if ID
> > > // has already been assigned.
> > > if( dUserDataId != 0 )
> > > dUserDataId = svGetUserDataIdForScope( scope );
> > >
> > > // Associate "this" with SV scope (avoids a
> hash in C++
> > code)
> > > svPutUserData(scope, this);
> > DOUG: You forgot to use the new second argument to this function.
> > Should be:
> > svPutUserData(scope, dUserDataId, this);
>
> johnS:
> You are right. Thank-you.
>
> >
> > Further, I'm not exactly clear on why you are using the
> static class
> > member rather than a normal class member. It doesn't seem to be
> > reasonable, considering that this is not a static class.
> If there is
> > more than one instance of this class, the different
> constructors will
> > overwrite each other's user data with different "this"
> pointers. The
> > final constructed class instance will dominate the others.
> >
>
> johnS:
> Your confusion is certainly justified and is the key reason
> I still am not completely comfortable with this interface
> - mainly because I think users will be equally confused about
> the subtlety of the requirement for static storage of the user ID.
>
> So let me try to explain. The ID itself cannot be stored in the user's
> instance of the C model. This is because the ID must be used inside an
> imported function to fetch the user's C model instance in the first
> place (using svGetUserData()).
>
> The thing probably causing most of your confusion however
> is that there's a flaw in passing the svScope into
> svGetUserDataIdForScope().
>
> I just sent out an e-mail explaining that this function needs
> to take moduleName rather than scope handle. This allows you
> to obtain a single unique ID that is usable with *all*
> instances of that module.
>
> Which leads to the issue of why a static is used. By storing
> the ID as a static in the C model, the imported function can
> fetch the access ID before actually having the C model
> (a.k.a. user data) pointer. This is why static storage is
> required here.
>
> It works but I fear it is non-intuitive. However, it does
> addess Andrzej's and Joao's concern about the overhead of
> having to set up a current function scope object each time an
> imported function is called which my proposal of yesterday
> would have required. With the ID scheme you avoid this. The
> only thing that has to be set up is the current module scope.
>
> > Please see if you think I have misunderstood something, and if not,
> > please double-check your intentions. If you do intend to
> demonstrate
> > using static class data to represent a "shared user data", then I
> > think the comments or text surrounding the example should
> express the
> > intention in English language.
> >
> > Other than this confusion (quite possibly my own!), great job here.
> >
> > Regards,
> > Doug
> >
> > > }
> > >
> > > friend int MyCFunc(int portID);
> > > };
> > >
> > > int MyCModel::dUserDataId = 0;
> > >
> > > // Implementation of external context function callable in SV >
> > int MyCFunc(int portID) {
> > > // Retrieve SV module instance scope (i.e. this function's
> > context).
> > > svScope scope = svGetScope();
> > >
> > > // Retrieve and make use of user data stored in SV scope
> > > MyCModel* me = (MyCModel*)svGetUserData( scope,
> dUserDataId );
> > >
> > > return me->locallyMapped(portID);
> > > }
> > >
> > >
> __
> ______ | \
> ______________________/ \__ / \
> \ H Dome ___/ |
> John Stickley E | a __ ___/ / \____
> Principal Engineer l | l | \ /
> Verification Solutions Group | f | \/ ____
> Mentor Graphics Corp. - MED C \ -- / /
> 17 E. Cedar Place a \ __/ / /
> Ramsey, NJ 07446 p | / ___/
> | / /
> mailto:John_Stickley@mentor.com \ /
> Phone: (201)818-2585 \ /
> ---------
>



This archive was generated by hypermail 2b28 : Wed Mar 26 2003 - 11:57:33 PST