• Skip to main content
  • Skip to search
  • Skip to footer
Cadence Home
  • This search text may be transcribed, used, stored, or accessed by our third-party service providers per our Cookie Policy and Privacy Policy.

  1. Community Forums
  2. Custom IC SKILL
  3. symbol properties versus defstruct versus ?

Stats

  • Locked Locked
  • Replies 2
  • Subscribers 143
  • Views 14008
  • Members are here 0
This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

symbol properties versus defstruct versus ?

drdanmc
drdanmc over 8 years ago

This is really a general skill programming question.

I have a collection of skill procedures that all work together as somewhat of a package.  There is a need to have some common data shared among all of them.  This is analogous to the schRegPostCheckTrigger/schGetPostCheckTriggers/schUnregPostCheckTrigger/[schematic check code] procedures that are part of virtuoso.

Currently I have  a handful (15-20) of moderately short lists (typically < 10 elements) to be stored as well as maybe a few dozen boolean variables.  That is a lot of global name space pollution although I do have them consistently prefixed.  I've read the section in the skill users guide about using symbol properties as a way to control/limit the number of global variables.  However, I'm missing a few basic but critical steps.

1)  if I use a symbol with properties to hold all of this configuration data, it needs to be a consistent symbol so my procedures know how to find it.  gensym() creates a unique symbol when you call it so that seems questionable.  Or maybe I should assign a global variable to the result like:

when(!boundp('MYconfig)

  MYconfig = gensym("MYconfig")

 MYconfg->setting1 = "default1" 

 MYconfig->setting2 = "default2

)

2)  If I do this, it seems like I have to be more careful with loading the file with that gensym() in it first than the old globals way.  In the old way I could just do

when(! boundp('MYsetting1) MYsetting1 = "default value")

and it didn't really matter if you loaded this code before or after a user loaded settings.

3)  should I instead create the property list as a disembodied property list like this:

when(!boundp('MYconfig)

  MYconfig = ncons(nil)

 MYconfg->setting1 = "default1" 

 MYconfig->setting2 = "default2

)

4)  Should I reserve using the property list of a symbol that has a function binding for data which is used exclusively by that function but needs to persist from one call to another call?

5) Should I think about defstructs instead? The manual says the should be faster to access for the slots which were statically declared.  

defstruct(MYconfig_struct setting1 setting2)

when(!boundp('MYconfig)

MYconfig = make_MYconfig_struct(?setting1 "default1" ?setting2 "default2")

)

Then last, I'm trying to decide on a user or project configuring the code by direct setting of properties versus an API which gives a procedure for each.  The latter hides some implementation details (a good thing) at the expense of a proliferation of procedures which are just short wrappers around assigning values.

I know this should be basic stuff especially for how long I've used/written/hacked skill code... but it is never too late to improve.

Thanks

-Dan

  • Cancel
Parents
  • Andrew Beckett
    Andrew Beckett over 8 years ago

    Hi Dan,

    OK, some responses to each of your 5 (or more) points:

    1. I probably wouldn't do it this way - there's no particular need for the symbol to be unique each time. Rather than using the arrow directive, you could just use:
      MYconfig.setting1="default1"
      etc. The benefit here is that you can then just use MYconfig.setting3 and if not set it will return nil, and there's no need to do any boundp() check. Still, I probably wouldn't use a property list like this, but it does have the benefit of grouping everything together.
    2. I agree - I really see no advantage in using gensym unless you really need it unique. If you want to use the symbol approach, then you could combine it with the approach I show above, and use:
      MYconfig='MYconfig
      Then you can use:
      MYconfig->setting1 and you'll get "default1". In essence the -> is an indirect way of updating the symbol property list, whereas the "." is direct. It's a bit like a pointer in C (sort of). Having a variable whose value is the symbol allows you to do this (in essence the -> is using getq/putpropq whereas the "." is using getqq/putpropqq - where both the symbol and property names are implicitly quoted)
    3. You could do it with a DPL. The downside of a DPL (and a symbol property list) is that if there are lots of keys, the search is sequential. More on that later. Not bad for simplicity, and when there aren't lots of slots.
    4. Not sure what you mean by this. 
    5. Yes, this is another reasonable approach - it's good when you know all the slot names in advance as it can be more efficient and is self-documenting. If there are some slots that can be added dynamically, that works too - but these are less efficiently accessed (I've not done a benchmark recently; historically they were stored in a DPL but I've not checked recently for dynamic slots).
    6. Another idea is to use a hash table. If you use:
      MYconfig=makeTable('MYconfig nil)
      MYconfig->setting1="default1"
      In essence, if you use the -> operator with a hash table, it's equivalent to using MYconfig['setting1] but with more familiar syntax. I've seen applications switch from using DPLs everywhere to using hash tables and see a big performance improvement with virtually no change to the code. I quite often use this approach when I have a lot of related data I need to keep together. That said, I'd probably use a SKILL++ closure to avoid needing to have any global variables at all - that way you can retain state amongst a collection of functions without any global variables being harmed in the process ;->
    7. Your last suggestion is about defining access functions for your data. This is simply using "procedural abstraction" which is generally seen as good practice, as it allows you to hide the implementation details of your data, and allow you to optimise the data if needed without disturbing your code. You can also then keep private data on the objects too with the recommendation that all interfacing to the data should be thorough your procedural interface. Calling functions is cheap in SKILL, so this is quite a good approach. When combined with using SKILL++ where you can have local (private) functions, you can do a good job of designing a package that hides a lot of the details internally, and only exposes the interface functions you choose to expose.

    In case you're interested in the use of SKILL++ to create a "package" - here's an example which has some private data (all the functions are public in this case, but there could be some local functions too): How to schedule a repeating function with a timer in SKILL?

    Regards,

    Andrew

    • Cancel
    • Vote Up 0 Vote Down
    • Cancel
Reply
  • Andrew Beckett
    Andrew Beckett over 8 years ago

    Hi Dan,

    OK, some responses to each of your 5 (or more) points:

    1. I probably wouldn't do it this way - there's no particular need for the symbol to be unique each time. Rather than using the arrow directive, you could just use:
      MYconfig.setting1="default1"
      etc. The benefit here is that you can then just use MYconfig.setting3 and if not set it will return nil, and there's no need to do any boundp() check. Still, I probably wouldn't use a property list like this, but it does have the benefit of grouping everything together.
    2. I agree - I really see no advantage in using gensym unless you really need it unique. If you want to use the symbol approach, then you could combine it with the approach I show above, and use:
      MYconfig='MYconfig
      Then you can use:
      MYconfig->setting1 and you'll get "default1". In essence the -> is an indirect way of updating the symbol property list, whereas the "." is direct. It's a bit like a pointer in C (sort of). Having a variable whose value is the symbol allows you to do this (in essence the -> is using getq/putpropq whereas the "." is using getqq/putpropqq - where both the symbol and property names are implicitly quoted)
    3. You could do it with a DPL. The downside of a DPL (and a symbol property list) is that if there are lots of keys, the search is sequential. More on that later. Not bad for simplicity, and when there aren't lots of slots.
    4. Not sure what you mean by this. 
    5. Yes, this is another reasonable approach - it's good when you know all the slot names in advance as it can be more efficient and is self-documenting. If there are some slots that can be added dynamically, that works too - but these are less efficiently accessed (I've not done a benchmark recently; historically they were stored in a DPL but I've not checked recently for dynamic slots).
    6. Another idea is to use a hash table. If you use:
      MYconfig=makeTable('MYconfig nil)
      MYconfig->setting1="default1"
      In essence, if you use the -> operator with a hash table, it's equivalent to using MYconfig['setting1] but with more familiar syntax. I've seen applications switch from using DPLs everywhere to using hash tables and see a big performance improvement with virtually no change to the code. I quite often use this approach when I have a lot of related data I need to keep together. That said, I'd probably use a SKILL++ closure to avoid needing to have any global variables at all - that way you can retain state amongst a collection of functions without any global variables being harmed in the process ;->
    7. Your last suggestion is about defining access functions for your data. This is simply using "procedural abstraction" which is generally seen as good practice, as it allows you to hide the implementation details of your data, and allow you to optimise the data if needed without disturbing your code. You can also then keep private data on the objects too with the recommendation that all interfacing to the data should be thorough your procedural interface. Calling functions is cheap in SKILL, so this is quite a good approach. When combined with using SKILL++ where you can have local (private) functions, you can do a good job of designing a package that hides a lot of the details internally, and only exposes the interface functions you choose to expose.

    In case you're interested in the use of SKILL++ to create a "package" - here's an example which has some private data (all the functions are public in this case, but there could be some local functions too): How to schedule a repeating function with a timer in SKILL?

    Regards,

    Andrew

    • Cancel
    • Vote Up 0 Vote Down
    • Cancel
Children
No Data

Community Guidelines

The Cadence Design Communities support Cadence users and technologists interacting to exchange ideas, news, technical information, and best practices to solve problems and get the most from Cadence technology. The community is open to everyone, and to provide the most value, we require participants to follow our Community Guidelines that facilitate a quality exchange of ideas and information. By accessing, contributing, using or downloading any materials from the site, you agree to be bound by the full Community Guidelines.

© 2025 Cadence Design Systems, Inc. All Rights Reserved.

  • Terms of Use
  • Privacy
  • Cookie Policy
  • US Trademarks
  • Do Not Sell or Share My Personal Information