"Call by reference" in Skill/Ocean procedure

Discussion in 'Cadence' started by Stephen Greenwood, Nov 24, 2009.

  1. I am learning to use procedures in Skill, more particularly in Ocean.
    I would like to write a procedure that can either modify variables
    that have been passed ("call by reference"). Is this possible? I
    didn't see a way to do that in the manual description for "procedure
    ()", and by experimenting I know that the default operation is "call
    by value".

    One alternative would be to have the procedure return multiple values,
    which are then used to set multiple variables. But looking at the
    manual it appears that the procedure can only return a single value.
    If that's the case, then my suspicion is that the only way to return
    multiple values is to return a list and to then manipulate the list.

    Thoughts or comments on these questions? Thank you for your time.
    Apologies if this has already been answered before, but I had no
    success searching the archives.
     
    Stephen Greenwood, Nov 24, 2009
    #1
  2. Stephen Greenwood wrote, on 11/24/09 23:50:
    Hi Stephen,

    Actually SKILL does pass by reference. However, you cannot change the value
    pointed to by a variable, so it gives the appearance of being pass by value.

    You can see that it's pass by reference if you pass a list, a defstruct, a table
    and so on - all the these allow you to modify the contents directly, and so that
    may allow you to achieve what you want. For example:

    procedure(MYcollectSomeResults(data)
    let((signal)
    signal=v("/out" ?result 'tran)
    data->max=ymax(signal)
    data->min=ymin(signal)
    data->average=average(signal)
    data
    )
    )

    Then you could do:

    info=ncons(nil)
    MYcollectSomeResults(info)

    and then you'll be able to look at info->max, info->min etc (this is using a
    "Disembodied Property List"). The list is being modified "destructively" within
    the list - and consequently you can pass back multiple values.

    Another way might be to use an association table (aka a "hash"). You could do
    this with the same code as above, but do:

    info=makeTable('myInfoTable nil)
    MYcollectSomeResults(info)

    info is a hash; you can do either info['max] or info->max to get the value out.
    Association Tables can use anything as the key.

    Returning multiple values is straightforward - you can return a list of the
    different things you want to return.

    Finally, another way is to pass in the names of the variables you want to pass
    in (you can sort of think of this as being a pointer):

    procedure(MYcollectSomeResults(maxVar minVar averageVar)
    let((signal)
    signal=v("/out" ?result 'tran)
    set(maxVar ymax(signal))
    set(minVar ymin(signal))
    set(averageVar average(signal))
    t
    )
    )

    MYcollectSomeResults('myMax 'myMin 'myAverage)

    and then you'll have myMax, myMin and myAverage set to the appropriate values
    after it returns.

    I don't really like this approach though, because it relies (sort of) on
    run-time evaluation, and doesn't work (without some changes) in SKILL++ due to
    it needing to know the "environment" for the variables.

    I hope that's given you enough info!

    Regards,

    Andrew.
     
    Andrew Beckett, Nov 25, 2009
    #2
  3. Thanks, Andrew. You're right that I used a variable as the type for my
    test. The code looked something like this:

    --------------------------------------------
    procedure( testThisOut( x )
    printf("First x = %.2f \n" x)
    x = 2.0
    printf("Now x = %.2f \n" x)
    )

    a=1.0
    => 1.0

    testThisOut(a)
    => First x = 1.00
    => Now x = 2.00
    => t

    a
    => 1.0
    --------------------------------------------

    And since variable "a" was unmodified, I concluded (wrongly) that
    procedures were pass-by-value.

    The three methods you mentioned contain constructs with which I'm
    unfamiliar, so I will have to study and experiment with them. Before I
    do so, I have a question about the first example you gave. By
    experimenting, it looks to me that procedures are not scoped locally,
    but have access to outside variables. I may not have the nomenclature
    correct, but I found that I can define a variable in OCEAN, then
    access it inside a procedure without passing it in. And it appears to
    me that the procedure MYcollectSomeResults() in your example "knows"
    that there is a v("/out") in this way. But I want to be able to have
    an argument like v("/out") be one of the parameters of the procedure.
    Is it still applicable? Perhaps I would understand more by asking,
    what is the type of the object called "data"?
    Thank you for your detailed help. Sorry for the bad assumption about
    pass-by-reference... I'm an analog designer with less formal training
    in programming than I need.

    Best,
    Stephen
     
    Stephen Greenwood, Nov 26, 2009
    #3
  4. Stephen Greenwood wrote, on 11/26/09 00:02:
    That's understandable (and a common perception).
    In this case I happened to hardcode the signal within the function, but of
    course a more likely scenario would be to pass it in as an argument. You could
    either pass in the signal name, or the waveform object itself.

    All variables in SKILL are by default global - only if you put them within a
    let() are they local (actually you can do it a few other ways too, but variables
    need to be explicitly declared as local to make them local; this is different
    from, say, Tcl where the default is the other way around). Also, variables are
    dynamically scoped in SKILL (lexical scoping in SKILL++) which means that you
    can imagine each variable as being a stack, where when declared within a let, it
    pushes a new value on that variable's stack, which gets popped off when the let
    returns.

    data in this example is a list, or it could be a structure, or it could be a
    table - there's no type checking, but the syntax within the function would work
    in all three cases.
    If you wanted to pass in the signal, you could do:

    procedure(MYcollectSomeResultsNew(signal data)
    data->max=ymax(signal)
    data->min=ymin(signal)
    data->average=average(signal)
    ; this is the return value - the whole list
    data
    )

    Then you could do:

    info=list(nil)
    out=v("/out" ?result 'tran)
    MYcollectSomeResultsNew(out info)

    and then look at the variable info when it returns - you'll see that the list
    now has some key-value pairs within it.

    I suggest you read the "SKILL Language User Guide"
    <instDir>/doc/sklanguser/sklanguser.pdf as this will give you a good overview of
    the language, step by step. It's written in the style of a book on a programming
    language you might buy in a bookshop, so it's pretty readable.

    Regards,

    Andrew.
     
    Andrew Beckett, Nov 26, 2009
    #4
  5. Thanks, Andrew, for the clarifications. This helps a lot.
    Again, thank you. Until now I've been referring primarily to the
    reference manuals (sklangref.pdf and skartistref.pdf) rather than the
    user guides.

    Best Regards,
    Stephen Greenwood
     
    Stephen Greenwood, Nov 30, 2009
    #5
Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.