Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Initial commit

  • Loading branch information...
commit 4bb1eb3e8f84a81e8590ba64ead361b4faf0e8a6 0 parents
@russdill authored
6 .gitignore
@@ -0,0 +1,6 @@
+build/
+dist/
+deb_dist/
+MANIFEST
+*~
+*.pyc
502 LICENSE
@@ -0,0 +1,502 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard. To achieve this, non-free programs must be
+allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library. It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
3  MANIFEST.in
@@ -0,0 +1,3 @@
+include LICENSE
+recursive-include examples *.py
+recursive-include samples *.ibs *.vhd README
324 README
@@ -0,0 +1,324 @@
+=================================
+PyBIS -- Python based IBIS parser
+=================================
+
+Introduction
+============
+
+This module contains a python based IBIS parser. The latest IBIS specification
+can be found here:
+
+ http://eda.org/pub/ibis/ver5.0/ver5_0.txt
+
+Parsing an IBIS file can be accomplished as follows:
+
+ import pybis
+ results = pybis.IBSParser().parse('test.ibs')
+ print results.header.file_rev
+
+The additional parsers 'PKGParser' and 'EBDParser' are available for parsing
+'.pkg' and '.ebd' files respectively.
+
+Handling Parse Results
+======================
+
+Parse results are return as a tree of 'IBISNode' objects. An 'IBISNode' object
+is a python dict. Each key/value pair represents a section, keyword, or param.
+Each value can be a:
+
+ - IBISNode
+ - Range
+ - list
+ - dict
+ - numpy matrix
+ - string
+ - float
+ - int
+ - None
+
+In the case of a dict or list, the children can be any of the above except dict
+and list.
+
+Children of an IBISNode can be accessed through the standard dict accessors as
+well as with the python attribute accessors. Additionally, any accesses are
+case insensitive and allow the use of a '_' in place of ' ' or '/', 'p' in
+place of '+', and 'n' in place of '-':
+
+ results.model["SSTL18"].model_spec.vinlp
+ results.MODEL["SSTL18"].Model_Spec["Vinl+"]
+ results["Model"]["SSTL18"]["Model Spec"].Vinlp
+
+Objects represented by dict values, such as the set of components, do not have
+this property.
+
+If the specification indicates that a certain keyword is required in certain
+circumstances or defaults to a certain value, the parser will ensure that
+condition is met. For optional keywords, and if block or try/except block can
+be used:
+
+ if 'Copyright' in results.header:
+ print results.header.copyright
+ else:
+ print 'Not specified'
+
+ try:
+ print results.header["Copyright"]
+ except:
+ print 'Not specified'
+
+ print result.header.get('Copyright', 'Not specified')
+
+A 'Range' type represents a typ/min/max set. It can be accessed through the
+following methods:
+
+ [0-2] - Return raw values (0 = typ, 1 = min, 2 = max).
+ (0-2) - Return typ for min or max if they are 'None'.
+ (0-2, invert=False) - Ensure max > min.
+ (0-2, invert=True) - Ensure mix > max.
+ .typ, .min, .max - Easy accessors for (0-2).
+ .norm - Return version of object where max > min.
+ .inv - return version of object where min > max.
+
+A keyword such as 'Pin', 'Pin Mapping', or 'Pin EMI' that names columns, is
+stored as a dict of IBISNode's indexed by the key in the first column. The
+column names are the keys of the IBISNode's. This is also true of sections
+that implicitly name columns such as 'Driver Schedule'.
+
+Keywords that represent waveforms or I-V curves such as 'Pulldown', 'GND
+Clamp', and 'Rising Waveform' are stored as a 'Range' object. Each item in the
+Range object is an x, y tuple who's members are a list of floats.
+
+'NA' and 'NC' entries are stored as None.
+
+'[End...]' keywords are not stored.
+
+Parse Results - Section 4 - File Header Information
+===================================================
+
+Keywords within the file header are stored in a "fake" section called "header".
+Multiple occurrences of multi-line sections, such as 'Source' and 'Copyright'
+are merged together. The 'Comment Char' keyword does not appear in parse
+output.
+
+ print results.header["File Name"]
+
+Parse Results - Section 5 - Component Description
+=================================================
+
+Components are stored as a dict within the 'Component' keyword with each key
+representing a component name, and each value representing a component.
+
+Items in the 'Series Pin Mapping' keyword are indexed by the tuple (pin_1,
+pin_2).
+
+'Series Switch Groups' are just stored as a list of lists, which each list
+starting with the keyword 'on' or 'off'.
+
+Parse Results - Section 6 - Model Statement
+===========================================
+
+Models are stored as a dict within the 'Model' keyword with each key
+representing a model name, and each value representing a model.
+
+Drain model types are deprecated and replaced by the parser with the associated
+sink type.
+
+The 'Rising Waveform' and 'Falling Waveform' sections are stored as a list
+of sections. Because the section contains multiple keywords along with waveform
+data, the waveform data is stored under the keyword 'waveform'. Additionally,
+the keywords 'V_fixture_min' and 'V_fixture_max' are combined with the
+'V_fixture' keyword to create a Range.
+
+'Series MOSFET' I-V tables are indexed by their 'Vds' value.
+
+'Test Data' and 'Test Load' are handled the same way as 'Model' and 'Component'
+keywords.
+
+Parse Results - Section 6a - Add Submodel Description
+=====================================================
+
+'Submodel' keywords are handling in a similar way to 'Model' keywords.
+
+Parse Results - Section 6b - Multi-lingual Model Extensions
+===========================================================
+
+Multi-lingual extensions are supported as additional keywords under the
+'Component' and 'Model' keywords as well as the additional top level 'External
+Circuit' keyword.
+
+The data under the 'Corner' keyword is reorganized so that the 'file_name'
+keyword and 'circuit_name' keyword under 'Corner' each contain a Range object.
+
+The 'D_to_A' and 'A_to_D' keywords are stored as dict's indexed by 'd_port'.
+Additionally, each parameter is stored as a Range object.
+
+'Portmap' keywords are stored as dict's indexed by 'port'.
+
+Parse Results - Section 6c - Algorithmic Modeling Interface (AMI)
+=================================================================
+
+The 'Algorithmic Model' keyword is supported under the 'Model' keyword.
+Although parsing of the keyword supported, pybis does not at this time parse
+AMI files.
+
+Parse Results - Section 7 - Package Modeling
+============================================
+
+The matrices in 'Model Data' are stored as as a numpy matrix. A keyword, 'Pin
+Mapping', is added to 'Model Data' that provides a mapping from pin name to
+numpy matrix index.
+
+The parser insures that a 'Package Model' contains either 'Model Data' or
+section data associated with pins, but not both. In either case, 'Pin Numbers'
+is stored as a dict indexed by pin name. In the 'Model Data' case, the value
+for each pin is 'None'. In the sections case, the value for each pin is a list
+of stubs. A stub can either be a 'Len', 'R', 'L', 'C' IBISNode, or it can be a
+list of stubs in the case of a fork.
+
+Parse Results - Section 8 - Electrical Board Description
+========================================================
+
+The 'Path Description' section under a 'Begin Board Description' keyword is
+stored as a list. Each element in the list can be an IBISNode containing 'Pin',
+'Node', or 'Len'. If the IBISNode contains 'Len' it is a stub and may also
+contain 'L', 'R', and/or 'C'. An element in the list can also be a list, which
+indicates a fork.
+
+Parse Results - Section 11 - EMI Parameters
+===========================================
+
+Extra EMI keywords are supported under the 'Component' and 'Model' keywords.
+
+Parse Errors
+============
+
+Parse errors throw an Exception along with a parse tree backtrace, which due to
+the immaturity of the parser, often does not return helpful data:
+
+ In 'body ' , 'Model BPS2P10F_PU50K' :
+ Parsing failed on line 2144: 'Ref = 1Mohms'
+ Traceback (most recent call last):
+ File "pybis.py", line 2148, in <module>
+ root = parser.parse(open(sys.argv[1], 'rb'))
+ File "pybis.py", line 2019, in parse
+ self.parseLine(line)
+ File "pybis.py", line 2090, in parseLine
+ self.current.parser.fin(self.current)
+ File "pybis.py", line 1366, in fin
+ .format(model_type, keyword))
+ Exception: Type 'i/o' missing required keyword 'Ramp'
+
+In the above backtrace, the error listed is:
+
+ Type 'i/o' missing required keyword 'Ramp'
+
+However, the cause of the error is a misspelling of the 'Rref' keyword. This is
+because upon encountering and unknown keyword in a section, the parser assumes
+the keyword is contained in a higher level section and so closes the section.
+The close function of the section notices that the 'Ramp' keyword is missing
+and throws an exception.
+
+Most parse errors will occur when sections are closed. The line number given in
+this case will be the line number that cause the section to be closed:
+
+ In 'body ' , 'Model BPS2P10F_PU50K' :
+ Parsing failed on line 2785: '[Model] BPS2P4F_PD50K'
+ Traceback (most recent call last):
+ File "pybis.py", line 2148, in <module>
+ root = parser.parse(open(sys.argv[1], 'rb'))
+ File "pybis.py", line 2019, in parse
+ self.parseLine(line)
+ File "pybis.py", line 2090, in parseLine
+ self.current.parser.fin(self.current)
+ File "pybis.py", line 1366, in fin
+ .format(model_type, keyword))
+ Exception: Type 'i/o' missing required keyword 'C_comp'
+
+In this case the Exception is correct, but the line number is misleading. In
+both cases, the first line of the backtrace (starting with 'In') can be used to
+locate the error.
+
+Performance
+===========
+
+Performance of PyBIS is slower than the golden parser (ibischk5) by a factor of
+10. Although this is unfortunate, the performance should still be acceptable
+for many workloads.
+
+# of lines | ibischk5 | pybis | factor
+79178 0m0.394s 0m3.438s 8.7
+9624 0m0.056s 0m0.539s 9.6
+
+Compatibility
+=============
+
+Because the IBIS specification is somewhat vague, the definition of what is or
+is not a valid IBIS file is typically defined by what can pass though the
+golden parser (ibischk5).
+
+Because of this, the highest priority of PyBIS is to accept any files accepted
+without errors by ibischk5. A secondary goal is to emit any valid errors or
+warnings that the golden parser emits.
+
+Example Code
+============
+
+Example code is included in examples/, which currently consists of models.py.
+Given an IBIS file as an argument, models.py will list the models contained in
+the IBIS file:
+
+ ./models.py sample1.ibs
+ BIP00F input
+ BIPIN15F input
+ BPIN15F_PU50K input
+ BPIST02F input
+ BPIST02F_PU50K input
+ BPOZ2F 3-state
+ BPOZ4F 3-state
+ BPS2P10F_PU50K i/o
+ BPS2P4F_PD50K i/o
+ BPS2P4F_PU50K i/o
+ BT2Z50CX i/o
+ BT2Z50CX_PU50K i/o
+ BUSB6AU_HIGH_SPEED i/o
+ BUSB6AU_LOW_SPEED i/o
+
+Selecting a model then displays the Time based and I-V curves of that model:
+
+ ./models.py sample1.ibs BPOZ2F
+ (scipy plots are displayed)
+
+Sample Data
+===========
+
+Some sample IBIS files have been collected and are present in samples/.
+
+Installation
+============
+
+Do the usual:
+
+ python setup.py install
+
+Future Changes
+==============
+
+In the long term, it is desired that PyBIS will expand into a suite of python
+based IBIS tools. In the short term, the parser requires incremental changes.
+Some of the areas that need improvement are:
+
+ - Improved error messages.
+ - Warning messages.
+ - Improved compatibility.
+ - Performance improvements.
+ - Line numbers in parse results.
+ - Cleaned up code base.
+ - Additional IBIS samples including code test cases.
+
+Some possible future feature ideas include:
+
+ - AMI parsing.
+ - IBIS version handling.
+ - Folding together of newer and older IBIS fields and features in parse
+ output. For example, Vinl appears in 3 different keywords.
+ - A GUI based IBIS file viewer/browser.
60 examples/models.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+
+import sys
+import pybis
+import matplotlib.pyplot as plt
+
+output = pybis.IBSParser().parse(sys.argv[1])
+
+try:
+ model = output.model[sys.argv[2]]
+
+except:
+ for name, model in output.model.iteritems():
+ print name, model.model_type
+ exit()
+
+plt.suptitle(sys.argv[2])
+plt.subplots_adjust(hspace=0.5)
+for n, plot in enumerate([ "Pullup", "Power Clamp", "Pulldown", "GND Clamp" ]):
+ plt.subplot(3, 2, n + 1)
+ if plot in model:
+ plt.plot(*model[plot].typ, color="green", label="typ")
+ plt.plot(*model[plot].min, color="blue", label="min")
+ plt.plot(*model[plot].max, color="red", label="max")
+ ymin, ymax = plt.ylim()
+ extra = (ymax - ymin) * 0.01
+ plt.ylim(ymin-extra, ymax+extra)
+ plt.title(plot)
+ if n > 1:
+ plt.xlabel('Volts')
+ plt.legend(loc=4)
+ else:
+ plt.legend()
+ if not n & 1:
+ plt.ylabel('Amps')
+ plt.grid()
+
+for n, plot in enumerate([ "Rising Waveform", "Falling Waveform" ]):
+ plt.subplot(3, 2, n + 5)
+ if plot in model:
+ labels = [ "typ", "min", "max" ]
+ for w in model[plot]:
+ plt.plot(*w.waveform.typ, color="green", label=labels[0])
+ plt.plot(*w.waveform.min, color="blue", label=labels[1])
+ plt.plot(*w.waveform.max, color="red", label=labels[2])
+ labels = [ None, None, None ]
+ ymin, ymax = plt.ylim()
+ extra = (ymax - ymin) * 0.01
+ plt.ylim(ymin-extra, ymax+extra)
+ plt.title(plot)
+ plt.xlabel('Time (s)')
+ if not n & 1:
+ plt.ylabel('Volts')
+ plt.legend(loc=4)
+ else:
+ plt.legend()
+ plt.grid()
+
+plt.show()
+
2,163 pybis.py
@@ -0,0 +1,2163 @@
+# module pybis.py
+#
+# Copyright (C) 2012 Russ Dill <Russ.Dill@asu.edu>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+
+import sys
+import math
+import copy
+import re
+from string import maketrans
+from pyparsing import alphanums, alphas, CaselessKeyword, Dict, Each, Forward, Group, LineStart, LineEnd, Literal, NotAny, oneOf, Optional, ParseException, ParserElement, ParseResults, printables, restOfLine, Token, Suppress, Word
+from collections import OrderedDict
+from numpy import zeros
+
+__all__ = ["Range", "IBSParser", "PKGParser", "EBDParser", "dump"]
+
+def ParseReal(val):
+ """Parse an IBIS formatted number."""
+ try:
+ return float(val)
+ except:
+ ext = val.lstrip('+-0123456789.eE')
+ e = 1
+ if len(ext):
+ val = val[0:-len(ext)]
+ e = 'fpnum.kMGT'.find(ext[0])
+ if e == -1:
+ e = 5
+ e = 10**((e - 5) * 3)
+ # 'e' or 'E' can be a unit
+ if val[-1] == 'e' or val[-1] == 'E':
+ val = val[:-1]
+ return float(val) * e
+
+def in_range(lower, upper):
+ """Throw an exception if a number is not in a range."""
+ def func(n):
+ if not lower <= n <= upper:
+ raise Exception("'{}' is not in range '{}'".format(n, (lower, upper)))
+ return n
+ return func
+
+positive = in_range(0, float("inf"))
+
+class Real(Token):
+ """pyparse a Real."""
+ def __init__(self, check=lambda f: f):
+ super(Real, self).__init__()
+ self.check = check
+
+ def parseImpl(self, instring, loc, doActions=True):
+ tokens = re.split("[^a-zA-Z0-9\.\-\+]+", instring[loc:], 1)
+ try:
+ return loc + len(tokens[0]), self.check(ParseReal(tokens[0]))
+ except:
+ raise ParseException(tokens[0], loc, "Could not parse float, '{}'".format(tokens[0]))
+
+class Integer(Token):
+ """pyparse an Integer."""
+ def __init__(self, check=lambda f: f):
+ super(Integer, self).__init__()
+ self.check = check
+
+ def parseImpl(self, instring, loc, doActions=True):
+ tokens = re.split("[^0-9\-\+]+", instring[loc:], 1)
+ val = tokens[0]
+ try:
+ return loc + len(val), self.check(int(val))
+ except:
+ raise ParseException(tokens[0], loc, "Could not parse integer")
+
+class NAReal(Real):
+ """pyparse an "optional" number, gives 'None' for 'NA'."""
+ def parseImpl(self, instring, loc, doActions=True):
+ if instring[loc:2+loc] == "NA":
+ return loc + 2, None
+ return super(NAReal, self).parseImpl(instring, loc, doActions)
+
+class Range(list):
+ """A typ, min, max range object.
+ [0-2] - Return raw values (0 = typ, 1 = min, 2 = max).
+ (0-2) - Return typ for min or max if they are 'None'.
+ (0-2, invert=False) - Ensure max > min.
+ (0-2, invert=True) - Ensure mix > max.
+ .typ, .min, .max - Easy accessors for (0-2).
+ .norm - Return version of object where max > min.
+ .inv - return version of object where min > max.
+ """
+ def __getattr__(self, name):
+ if name == "typ":
+ return self[0]
+ elif name == "min":
+ return self(1)
+ elif name == "max":
+ return self(2)
+ elif name == "norm":
+ if self.min > self.max:
+ return Range([self[0], self[2], self[1]])
+ else:
+ return self
+ elif name == "inv":
+ if self.min < self.max:
+ return Range([self[0], self[2], self[1]])
+ else:
+ return self
+ else:
+ raise AttributeError(name)
+
+ def __call__(self, n, invert=None):
+ if n == 0:
+ return self[0]
+ elif n < 3:
+ if invert is not None and invert == self.min < self.max:
+ n = 3 - n
+ return self[n] if self[n] is not None else self[0]
+ else:
+ raise Exception
+
+class RealRange(Token):
+ """pyparser for a line of 'count' Real tokens.
+ 'check' should raise Exception on invalid value.
+ """
+ def __init__(self, count=3, check=lambda f: f):
+ self.count = count * 2 - 1
+ self.check = check
+ super(RealRange, self).__init__()
+
+ def parseImpl(self, instring, loc, doActions=True):
+ tokens = re.split("([^a-zA-Z0-9\.\-\+]+)", instring[loc:])
+ ret = Range()
+ intok = True
+ for tok in tokens[:self.count]:
+ if intok:
+ if len(ret) and tok == "NA":
+ ret.append(None)
+ else:
+ try:
+ ret.append(self.check(ParseReal(tok)))
+ except:
+ raise ParseException(tok, loc, "Count not parse float")
+ intok = not intok
+ loc += len(tok)
+ return loc, [ret]
+
+def RampRange():
+ """pyparser for range of IBIS ramp values (See Section 6 - [Ramp])."""
+ ramp_data = Group(Real(check=positive) + Suppress(Literal("/")) + Real(check=positive))
+ ret = (ramp_data -
+ (ramp_data | CaselessKeyword("NA")) -
+ (ramp_data | CaselessKeyword("NA"))
+ )
+ def fin(tokens):
+ for i, t in enumerate(tokens[1:]):
+ if t == "NA": tokens[i + 1] = None
+ return Range(tokens.asList())
+ ret.setParseAction(fin)
+ return ret
+
+def oneof(val):
+ """pyparser for set of caseless keywords.
+ All parsed values will be lowercase.
+ """
+ return oneOf(val.lower(), caseless=True)
+
+def orderedDict(tokenlist):
+ """pyparse action to create and OrderedDict from a set of tokens."""
+ ret = OrderedDict()
+ for i, tok in enumerate(tokenlist):
+ ret[tok[0]] = tok[1]
+ return ret
+
+class IBISNode(OrderedDict):
+ """IBIS parse results object.
+ This object typically represents a section with named children.
+ Because most IBIS keywords are case insensitive and many even don't
+ distinguish between and '_' and ' ', this class allows loose access to
+ children. All children are stored by their 'pretty_name', such as
+ "Number of Sections". When an access is made, it is translated to a
+ 'simple_name' by lower() and translating ' ' and '/' to '_', '+' to 'p',
+ and '-' to 'n'. The pretty_name is then looked up in an internal dict.
+ The pretty_name is then used to find the child.
+ In addition to normal dict() accessors, '__getattribute__' can also be
+ used. For example, 'obj["Vinl+"] and obj.vinlp both work.
+ """
+ ibis_trans = maketrans('+- /', 'pn__')
+
+ def __init__(self, *args, **kwds):
+ object.__setattr__(self, 'pretty_names', dict())
+ super(IBISNode, self).__init__(*args, **kwds)
+
+ @staticmethod
+ def simple_name(name):
+ return name.translate(IBISNode.ibis_trans).lower()
+
+ def pretty_name(self, name):
+ return self.pretty_names[IBISNode.simple_name(name)]
+
+ def __getattribute__(self, name):
+ try:
+ return OrderedDict.__getattribute__(self, name)
+ except AttributeError:
+ try:
+ return self[name]
+ except KeyError:
+ raise AttributeError(name)
+
+ def __setattr__(self, name, value):
+ try:
+ pretty = self.pretty_name(name)
+ OrderedDict.__setitem__(self, pretty, value)
+ except KeyError:
+ OrderedDict.__setattr__(self, name, value)
+
+ def __getitem__(self, name):
+ return OrderedDict.__getitem__(self, self.pretty_name(name))
+
+ def __setitem__(self, name, value):
+ simple = IBISNode.simple_name(name)
+ doset = False
+ if simple not in self.pretty_names:
+ pretty = name
+ doset = True
+ else:
+ pretty = self.pretty_names[simple]
+ OrderedDict.__setitem__(self, pretty, value)
+ if doset:
+ self.pretty_names[simple] = pretty
+
+ def __delitem__(self, name):
+ simple = IBISNode.simple_name(name)
+ pretty = self.pretty_names.pop(simple)
+ OrderedDict.__delitem__(self, pretty)
+
+ def __contains__(self, name):
+ return IBISNode.simple_name(name) in self.pretty_names
+
+class container(object):
+ def __init__(self):
+ self.data = None
+
+class Node(container):
+ """Intermediate parse results holder."""
+ def __init__(self):
+ super(Node, self).__init__()
+ self.children = IBISNode()
+ self.parser = None
+ self.parent = None
+ self.data = None
+
+ def __str__(self):
+ return str(self.data)
+ __repr__ = __str__
+
+ def add(self, child):
+ orig = container()
+ if child.parser.key in self.children:
+ orig.data = self.children[child.parser.key]
+ else:
+ # FIXME: Double assign of initial?
+ orig.data = copy.deepcopy(child.parser.initvalue)
+ child.parser.merge(orig, child)
+ self.children[child.parser.key] = orig.data
+
+ def __iadd__(self, child):
+ self.add(child)
+ return self
+
+class Parse(object):
+ """Base pybis Parse object."""
+ def __init__(self, key, pyparser=None, default=None, initvalue=None, data_name=None, list_merge=False, asList=False, asDict=False, required=False):
+ """key: Name of element.
+ pyparser: Parser to call with pyparse
+ default: Default value of object if not found
+ initvalue: Default value of object on first merge
+ data_name: Make the data of this node a child with name 'data_name'
+ list_merge: Merge multiple copies together as list
+ asList: Interpret pyparse results as a list
+ asDict: Interpret pyparse results as a dict
+ required: raise Exception if not found
+ """
+ self.key = key
+ self.flat_key = key.replace(' ', '_').lower()
+ self.data_name = data_name
+ self.default = default
+ self.initvalue = initvalue
+ self.pyparser = pyparser
+ self.list_merge = list_merge
+ if list_merge and initvalue is None:
+ self.initvalue = list()
+ self.asList = asList
+ self.asDict = asDict
+ self.children = OrderedDict()
+ self.parent = None
+ self.globals = None
+ self.required = required
+
+ def add(self, obj):
+ obj.parent = self
+ self.children[obj.key] = obj
+
+ def __iadd__(self, obj):
+ self.add(obj)
+ return self
+
+ def get_globals(self):
+ """Get the global parse object, for things that are parseable in all contexts."""
+ if self.globals is None and self.parent is not None:
+ self.globals = self.parent.get_globals()
+ return self.globals
+
+ def find_parser(self, text):
+ """Find a child parser that can parse 'text'."""
+ for name, child in self.children.iteritems():
+ if child.can_parse(text):
+ return child
+ if self.get_globals() is not None:
+ return self.globals.find_parser(text)
+ return None
+
+ def can_parse(self, text):
+ """True if we can parse 'text'."""
+ return False
+
+ def initial(self, text, comment):
+ """Parse the first line of text and return a Node object."""
+ node = Node()
+ node.parser = self
+ # Fill in defaults
+ if self.initvalue is not None and self.default is None:
+ # FIXME: Maybe we can shortcut merge here...
+ node.data = copy.deepcopy(self.initvalue)
+ for key, parser in self.children.iteritems():
+ if parser.default is not None:
+ node.children[key] = copy.deepcopy(parser.default)
+ return node
+
+ def parse(self, node, text, comment):
+ """Parse a subsequent line of text, False means we can't."""
+ return not text
+
+ def pyparse(self, text):
+ """Use self.pyparser to parse 'text', returns parse tokens.
+ Returns 'text' if there is no pyparser object.
+ """
+ if self.pyparser is not None:
+ try:
+ return self.pyparser.parseString(text, parseAll=True)
+ except ParseException as e:
+ raise Exception("Failed to parse '{}': {}".format(text, e.msg))
+ else:
+ return text.strip()
+
+ def fin(self, node):
+ """Add a node to the parent."""
+ if node.data is not None:
+ self.flatten(node)
+ if self.data_name:
+ node.children[self.data_name] = node.data
+
+ for name, p in self.children.iteritems():
+ if p.required and not name in node.children:
+ raise Exception("'{}' is missing required '{}'".format(self.key, name))
+
+ if node.children:
+ node.data = node.children
+
+ # Oi, so some vendors think it'd be funny to add sections, such as
+ # [Series Pin Mapping] without any data
+ if node.data is not None and node.parent:
+ node.parent += node
+
+ def pop(self, new, name):
+ """Remove 'name' from ParseResults 'new'."""
+ ret = new.data[name]
+ del new.data[name]
+ if len(new.data.keys()) == 0:
+ for i, item in enumerate(new.data.asList()):
+ if item == ret:
+ del new.data[i]
+ break
+ return ret
+
+ def flatten(self, new):
+ """Reformat pyparse results as what we'd expect."""
+ if isinstance(new.data, ParseResults):
+ if self.asList:
+ new.data = new.data.asList()
+ elif self.asDict:
+ new.data = IBISNode(new.data.asDict())
+ else:
+ new.data = new.data[0]
+
+ def merge(self, orig, new):
+ """Merge two instances of the same parser under one parent."""
+ if self.list_merge:
+ # FIXME: Maybe specify this behavior?
+ if isinstance(new.data, list):
+ orig.data.extend(new.data)
+ else:
+ orig.data.append(new.data)
+ elif orig.data != self.initvalue and orig.data != self.default:
+ raise Exception("'{}' already assigned".format(self.key))
+ else:
+ orig.data = new.data
+
+class Bracket(Parse):
+ """An item that starts with '[<name>] ...'"""
+ def can_parse(self, text):
+ # FIXME: can_parse seems like a wasteful linear serach
+ if text and text[0] == '[':
+ sectionName, _, sectionText = text[1:].partition(']')
+ sectionName = sectionName.replace(' ', '_').lower()
+ if sectionName == self.flat_key:
+ return True
+ return False
+
+ def initial(self, text, comment):
+ node = super(Bracket, self).initial(text, comment)
+ sectionName, _, sectionText = text[1:].partition(']')
+ node.sectionText = sectionText.lstrip()
+ return node
+
+class Comment(Bracket):
+ """IBIS '[Comment Char]' keyword."""
+ def __init__(self, comment_holder):
+ super(Comment, self).__init__("Comment Char")
+ self.holder = comment_holder
+
+ def initial(self, text, comment):
+ node = super(Comment, self).initial(text, comment)
+
+ if len(node.sectionText) == 0:
+ # sectionText probably got commented out, no comment char change.
+ pass
+ elif not node.sectionText[1:].startsWith("_char"):
+ raise Exception("Invalid format, expected '<char>_char'")
+ elif not node.sectionText[0] in "!\"#$%&'()*,:;<>?@\\^`{|}~":
+ raise Exception("Invalid comment char, '{}'".format(node.sectionText[0]))
+ else:
+ self.holder[0] = node.sectionText[0]
+ return node
+
+ def fin(self, node):
+ """Ignore this node."""
+ pass
+
+class End(Bracket):
+ """IBIS '[End]' keyword."""
+ def initial(self, text, comment):
+ """Return None as node, special meaning to close out parent."""
+ return None
+
+class Keyword(Bracket):
+ """[<keyword>] <data>."""
+ def initial(self, text, comment):
+ node = super(Keyword, self).initial(text, comment)
+ if not node.sectionText:
+ raise Exception("Expected text after '{}'".format(text))
+ node.data = self.pyparse(node.sectionText)
+ return node
+
+class Text(Bracket):
+ """IBIS text section, such as '[Copyright]'
+ Since some IBIS files include important copyright data in comments
+ below the keyword, we capture all text, including comments.
+ """
+ def __init__(self, key, comments=True, **kwds):
+ self.comments = comments
+ super(Text, self).__init__(key, **kwds)
+
+ def initial(self, text, comment):
+ node = super(Text, self).initial(text, comment)
+ self.parse(node, node.sectionText, comment)
+ return node
+
+ def parse(self, node, text, comment):
+ if self.comments:
+ if len(text) and len(comment):
+ text += ' '
+ text += comment
+ if node.data:
+ if len(node.data) and len(text):
+ node.data += '\n'
+ node.data += text
+ else:
+ node.data = text
+ return True
+
+ def merge(self, orig, new):
+ """Just merge together subsequent entries."""
+ if orig.data is None:
+ orig.data = ""
+ if len(orig.data):
+ orig.data += '\n'
+ orig.data += new.data
+
+ def flatten(self, node):
+ node.data = node.data.strip('\n')
+
+class Section(Bracket):
+ """Multi-line token, such as '[Model]'"""
+ def __init__(self, key, pyparser=None, labeled=False, **kwds):
+ """If a Section is labeled, the data is an OrderedDict of objects
+ indexed by sectionText.
+ """
+ self.needs_text = labeled
+ if labeled:
+ kwds["initvalue"] = OrderedDict()
+ self.merge = self.labeled_merge
+ super(Section, self).__init__(key, pyparser, **kwds)
+
+ def initial(self, text, comment):
+ node = super(Section, self).initial(text, comment)
+ if self.needs_text and not node.sectionText:
+ raise Exception("Expected text after '{}'".format(text))
+ elif not self.needs_text and node.sectionText:
+ raise Exception("Unexpected text after keyword, '{}'".format(node.sectionText))
+ node.key = node.sectionText
+ return node
+
+ def labeled_merge(self, orig, new):
+ if new.key in orig.data:
+ raise Exception("'{}' already contains '{}'".format(self.key, new.key))
+ orig.data[new.key] = new.data
+
+class TokenizeSection(Section):
+ """Text from entire section is collected and parsed with pyparser."""
+ def __init__(self, key, pyparser=None, **kwds):
+ super(TokenizeSection, self).__init__(key, pyparser, **kwds)
+
+ def initial(self, text, comment):
+ node = super(TokenizeSection, self).initial(text, comment)
+ node.text = ""
+ return node
+
+ def parse(self, node, text, comment):
+ node.text += text
+ node.text += '\n'
+ return True
+
+ def fin(self, node):
+ node.data = self.pyparse(node.text)
+ super(TokenizeSection, self).fin(node)
+
+class TableSection(Section):
+ """[<section name>] <col1 name> <col2 name> <...>
+ <row name> <col1 data> <col2 data> <...>
+ """
+ def __init__(self, key, pyparser=None, headers=[], optional=[], **kwds):
+ kwds["initvalue"] = OrderedDict()
+ super(TableSection, self).__init__(key, pyparser, **kwds)
+ self.needs_text = True
+ self.parsers = OrderedDict()
+ self.headers = [ key.lower() for key in headers ]
+ self.optional = [ key.lower() for key in optional ]
+
+ def initial(self, text, comment):
+ node = super(TableSection, self).initial(text, comment)
+ node.keys = node.sectionText.lower().split()
+ if len(node.keys) != len(set(node.keys)):
+ raise Exception("'{}' contains duplicates".format(node.sectionText))
+ for key in self.headers:
+ if key not in node.keys:
+ raise Exception("Expected header names to contain '{}'".format(key))
+ for key in node.keys:
+ if key not in self.headers and key not in self.optional:
+ raise Exception("Unexpected header name, '{}'".format(key))
+ for key in self.optional:
+ if key not in node.keys:
+ node.keys.append(key)
+ return node
+
+ def assign_row(self, node, key, row):
+ """For Seires_Pin_Mapping to override."""
+ if key in node.data:
+ raise Exception("'{}' already contains '{}'".format(self.key, key))
+ node.data[key] = row
+
+ def parse(self, node, text, comment):
+ if super(TableSection, self).parse(node, text, comment):
+ return True
+
+ tokens = text.split()
+ if not tokens:
+ return True
+
+ row = IBISNode(zip(node.keys, tokens[1:]))
+ for name, parser in self.parsers.iteritems():
+ name = name.lower()
+ if name in row:
+ tmp = Node()
+ try:
+ tmp.data = parser.parseString(row[name], parseAll=True)
+ except ParseException as e:
+ raise Exception("Failed to parse '{}', '{}': {}".format(name, row[name], e.msg))
+ self.flatten(tmp)
+ row[name] = tmp.data
+ for key in self.headers:
+ if key not in row:
+ raise Exception("Required column '{}' missing".format(key))
+ for key in self.optional:
+ if key not in row:
+ row[key] = None
+
+ self.assign_row(node, tokens[0], row)
+ return True
+
+ def add(self, obj):
+ if isinstance(obj, ParserElement):
+ self.parsers[obj.resultsName] = obj
+ else:
+ super(Section, self).add(obj)
+
+class ListSection(Section):
+ """Each line is an element of a list."""
+ def __init__(self, key, pyparser=None, **kwds):
+ kwds["initvalue"] = list()
+ super(ListSection, self).__init__(key, pyparser, **kwds)
+
+ def parse(self, node, text, comment):
+ if not super(ListSection, self).parse(node, text, comment):
+ tmp = Node()
+ tmp.data = self.pyparse(text)
+ self.flatten(tmp)
+ node.data.append(tmp.data)
+ return True
+
+class DictSection(Section):
+ """Each line is indexed by the first token."""
+ def __init__(self, key, pyparser=None, **kwds):
+ kwds["initvalue"] = OrderedDict()
+ super(DictSection, self).__init__(key, pyparser, **kwds)
+
+ def parse(self, node, text, comment):
+ if not super(DictSection, self).parse(node, text, comment):
+ tokens = text.split(None, 1)
+ tmp = Node()
+ tmp.data = self.pyparse(tokens[1])
+ self.flatten(tmp)
+ if tokens[0] in node.data:
+ raise Exception("'{}' already contains '{}'".format(self.key, tokens[0]))
+ node.data[tokens[0]] = tmp.data
+ return True
+
+class RangeSection(Section):
+ """Indexed tabular typ/min/max data, such as '[Pullup]'"""
+ def __init__(self, key, increasing_idx=False, decreasing_idx=False, **kwds):
+ self.increasing_idx = increasing_idx
+ self.decreasing_idx = decreasing_idx
+ super(RangeSection, self).__init__(key, RealRange(4), **kwds)
+
+ def initial(self, text, comment):
+ node = super(RangeSection, self).initial(text, comment)
+ node.data = Range([ ( [], [] ), ( [], [] ), ( [], [] ) ])
+ node.last_idx = None
+ return node
+
+ def parse(self, node, text, comment):
+ if not super(RangeSection, self).parse(node, text, comment):
+ tokens = self.pyparse(text)[0]
+ if not node.last_idx:
+ pass
+ elif self.increasing_idx:
+ if tokens[0] <= node.last_idx:
+ raise Exception("Index values not monotonic")
+ elif self.decreasing_idx:
+ if tokens[0] >= node.last_idx:
+ raise Exception("Index values not monotonic")
+ elif tokens[0] > node.last_idx:
+ self.increasing_idx = True
+ elif tokens[0] < node.last_idx:
+ self.decreasing_idx = True
+ else:
+ raise Exception("Index values not monotonic")
+ node.last_idx = tokens[0]
+ for i in range(3):
+ if not tokens[i + 1] is None:
+ node.data[i][0].append(tokens[0])
+ node.data[i][1].append(tokens[i + 1])
+ return True
+
+ def flatten(self, node):
+ pass
+
+ def fin(self, node):
+ for i in range(3):
+ if i > 0 and not len(node.data[i][0]):
+ node.data[i] = None
+ elif len(node.data[i][0]) < 2:
+ raise Exception("Requires at least 2 points")
+ super(RangeSection, self).fin(node)
+
+
+# FIXME: Make special object to encapsulate matrix with pin mapping.
+class MatrixSection(Section):
+ """Such as '[Resistance Matrix]'"""
+ def __init__(self, key, pyparser=Real(), check=lambda f: f, **kwds):
+ super(MatrixSection, self).__init__(key, pyparser, **kwds)
+ self += Keyword("Bandwidth", Integer(check=positive))
+ self += TokenizeSection("Row", labeled=True, pyparser=Word(printables) * (1,), asList=True)
+ self.needs_text = True
+ self.check = check
+
+ def fin(self, node):
+ try:
+ pn = node.parent.parent.children.pin_numbers.keys()
+ except:
+ raise Exception("Could not find associated [Pin Numbers] section")
+
+ try:
+ count = node.parent.parent.children.number_of_pins
+ except:
+ raise Exception("Could not find associated [Number Of Pins] section")
+
+ if count != len(pn):
+ assert Exception("[Number Of Pins] does not match length of [Pin Numbers]")
+
+ try:
+ pm = node.parent.parent.children.pin_mapping
+ except:
+ # Create a 'Pin Mapping' child that maps pin names to matrix indexes.
+ pm = dict((pin, i) for i, pin in enumerate(pn))
+ node.parent.parent.children["Pin Mapping"] = pm
+ node.data = zeros([len(pm), len(pm)])
+ node.sectionText = node.sectionText.lower()
+
+ for row, data in node.children.row.iteritems():
+ try:
+ r = pm[row]
+ except KeyError:
+ raise Exception("Unknown pin name '{}' in matrix".format(row))
+
+ if node.sectionText == "sparse_matrix":
+ for col, val in zip(data[::2], data[1::2]):
+ try:
+ c = pm[col]
+ except KeyError:
+ raise Exception("Unknown pin name '{}' in matrix".format(col))
+ node.data[r][c] = self.check(ParseReal(val))
+
+ elif node.sectionText == "banded_matrix" or node.sectionText == "full_matrix":
+ c = r
+ for val in data:
+ node.data[r][c] = self.check(ParseReal(val))
+ c = (c + 1) % len(pm)
+
+ else:
+ raise Exception("incomplete/unknown matrix".format(node.sectionText))
+
+ node.children = None
+ super(MatrixSection, self).fin(node)
+
+class Param(Parse):
+ """A single line param, such as 'Vinl 5.6', or 'Vinl_dc = 100mV'.
+ 'delim' is required if specified.
+ """
+ def __init__(self, key, pyparser=None, delim=None, **kwds):
+ super(Param, self).__init__(key, pyparser, **kwds)
+ self.delim = delim
+
+ def can_parse(self, text):
+ if text and text[0] != '[':
+ if self.delim != None:
+ name, d, _ = text.partition(self.delim)
+ if d and name.strip().lower() == self.flat_key:
+ return True
+ else:
+ tokens = text.split(None, 1)
+ if tokens[0].lower() == self.flat_key:
+ return True
+ return False
+
+ def initial(self, text, comment):
+ node = super(Param, self).initial(text, comment)
+
+ if self.delim != None:
+ _, d, text = text.partition(self.delim)
+ else:
+ tokens = text.split(None, 1)
+ text = tokens[1]
+ node.data = self.pyparse(text)
+ return node
+
+class DictParam(Param):
+ """Label indexing object as _key, such as 'Port_map'."""
+ def __init__(self, key, pyparser, **kwds):
+ kwds["initvalue"] = OrderedDict()
+ super(DictParam, self).__init__(key, pyparser, **kwds)
+
+ def flatten(self, new):
+ new.key = self.pop(new, "_key")
+ super(DictParam, self).flatten(new)
+
+ def merge(self, orig, new):
+ if new.key in orig.data:
+ raise Exception("'{}' already contains '{}'".format(self.key, new.key))
+ orig.data[new.key] = new.data
+
+class RangeParam(Param):
+ """Results are tagged with a _range, output is a typ, min, max,
+ such as 'Corner'
+ """
+ def __init__(self, key, pyparser, **kwds):
+ kwds["initvalue"] = Range([None, None, None])
+ super(RangeParam, self).__init__(key, pyparser, **kwds)
+
+ def flatten(self, new):
+ if "_range" in new.data:
+ new.range = self.pop(new, "_range").lower()
+ else:
+ new.range = "typ"
+ super(RangeParam, self).flatten(new)
+
+ def merge(self, orig, new):
+ if new.range == "typ":
+ r = 0
+ elif new.range == "min":
+ r = 1
+ elif new.range == "max":
+ r = 2
+ else:
+ raise Exception("Unknown range key, '{}'".format(new.range))
+
+ if orig.data[r] is None:
+ orig.data[r] = new.data
+ else:
+ raise Exception("{} value for '{}' already specified".format(new.range, self.key))
+
+class RangeDictParam(Param):
+ """Oi...we need a combination of the above two, as in 'D_to_A'."""
+ def __init__(self, key, pyparser, **kwds):
+ kwds["initvalue"] = OrderedDict()
+ super(RangeDictParam, self).__init__(key, pyparser, **kwds)
+
+ def flatten(self, new):
+ new.key = self.pop(new, "_key")
+ if "_range" in new.data:
+ new.range = self.pop(new, "_range").lower()
+ else:
+ new.range = "typ"
+ super(RangeDictParam, self).flatten(new)
+
+ def merge(self, orig, new):
+ if new.key not in orig.data:
+ orig.data[new.key] = Range([None, None, None])
+
+ if new.range == "typ":
+ r = 0
+ elif new.range == "min":
+ r = 1
+ elif new.range == "max":
+ r = 2
+ else:
+ raise Exception("Unknown range key, '{}'".format(new.range))
+
+ if orig.data[new.key][r] is None:
+ orig.data[new.key][r] = new.data
+ else:
+ raise Exception("{} value for '{}' already specified".format(new.range, new.key))
+
+class Header(Parse):
+ """Section 4."""
+ def __init__(self, **kwds):
+ kwds["required"] = True
+ super(Header, self).__init__("header", **kwds)
+ self += Keyword("IBIS Ver", required=True)
+ self += Keyword("File Name", required=True)
+ self += Keyword("File Rev", required=True)
+ self += Keyword("Date")
+ self += Text("Source")
+ self += Text("Notes")
+ self += Text("Disclaimer")
+ self += Text("Copyright")
+
+class Series_Pin_Mapping(TableSection):
+ def __init__(self):
+ super(Series_Pin_Mapping, self).__init__("Series Pin Mapping",
+ headers=["pin_2", "model_name"], optional=["function_table_group"])
+
+ def assign_row(self, node, key, row):
+ key = (key, row["pin_2"] )
+ del row["pin_2"]
+ if key[0] == key[1]:
+ raise Exception("series pin '{}' maps to itself".format(key[0]))
+ super(Series_Pin_Mapping, self).assign_row(node, key, row)
+
+class Component(Section):
+ """Section 5."""
+ def __init__(self, **kwds):
+ kwds["required"] = True
+ kwds["labeled"] = True
+ super(Component, self).__init__("Component", **kwds)
+
+ package = Section("Package", required=True)
+ package += Param("R_pkg", RealRange(), required=True)
+ package += Param("L_pkg", RealRange(), required=True)
+ package += Param("C_pkg", RealRange(), required=True)
+
+ pin = TableSection("Pin", required=True,
+ headers=["signal_name", "model_name"],
+ optional=["R_pin", "L_pin", "C_pin"])
+ pin += NAReal(check=positive)("R_pin")
+ pin += NAReal(check=positive)("L_pin")
+ pin += NAReal(check=positive)("C_pin")
+
+ alternate_package_models = ListSection("Alternate Package Models")
+ alternate_package_models += End("End Alternate Package Models")
+
+ diff_pin = TableSection("Diff Pin",
+ headers=["inv_pin", "vdiff", "tdelay_typ"],
+ optional=["tdelay_min", "tdelay_max"])
+ diff_pin += NAReal(check=positive)("vdiff")
+ diff_pin += NAReal()("tdelay_typ")
+ diff_pin += NAReal()("tdelay_min")
+ diff_pin += NAReal()("tdelay_max")
+
+ switch_group = oneof("On Off") + Word(alphanums) * (0,) + Literal("/").suppress()
+
+ series_switch_groups = TokenizeSection("Series Switch Groups",
+ Group(switch_group) * (0,), asList=True)
+
+ self += Param("Si_location", oneof("Pin Die"))
+ self += Param("Timing_location", oneof("Pin Die"))
+ self += Keyword("Manufacturer", required=True)
+ self += package
+ self += pin
+ self += Keyword("Package Model")
+ self += alternate_package_models
+ self += TableSection("Pin Mapping",
+ headers=["pulldown_ref", "pullup_ref"],
+ optional=["gnd_clamp_ref", "power_clamp_ref", "ext_ref"])
+ self += diff_pin
+ self += Series_Pin_Mapping()
+ self += series_switch_groups
+ self += Node_Declarations()
+ self += Circuit_Call()
+ self += EMI_Component()
+
+ def fin(self, node):
+ super(Component, self).fin(node)
+
+ # NB: Even though 'POWER', 'GND', and 'NC' are 'reserverd words' and
+ # therefore case sensitive, the golden parser doesn't care.
+ for name, pin in node.children.pin.iteritems():
+ if pin.model_name.upper() in [ "POWER", "GND", "NC", "CIRCUITCALL" ]:
+ pin.model_name = pin.model_name.upper()
+ if pin.model_name == "NC":
+ pin.model_name = None
+
+ if "Pin Mapping" in node.children:
+ # Make sure all pins under both pin and Pin Mapping
+ if len(node.children.pin) != len(node.children.pin_mapping):
+ raise Exception("Pin mapping table is incomplete")
+
+ # Make sure the bus mappings line up properly
+ available_bus = set()
+ used_bus = set()
+ for pin_name, mapping in node.children.pin_mapping.iteritems():
+ for ref in [ "pullup_ref", "pulldown_ref", "gnd_clamp_ref", "power_clamp_ref", "ext_ref" ]:
+ if mapping[ref] and mapping[ref].upper() == "NC":
+ mapping[ref] = None
+
+ if pin_name not in node.children.pin:
+ raise Exception("Invalid pin, '{}', listed in Pin Mapping".format(pin_name))
+ pin_info = node.children.pin[pin_name]
+
+ if pin_info.model_name == "GND":
+ if mapping.pullup_ref is not None:
+ raise Exception("Expected 'NC' for pin mapping of '{}'".format(pin_name))
+ if mapping.pulldown_ref is None:
+ raise Exception("Unexpected 'NC' for pin mapping of '{}'".format(pin_name))
+ available_bus.add(mapping.pulldown_ref)
+
+ elif pin_info.model_name == "POWER":
+ if mapping.pulldown_ref is not None:
+ raise Exception("Expected 'NC' for pin mapping of '{}'".format(pin_name))
+ if mapping.pullup_ref is None:
+ raise Exception("Unexpected 'NC' for pin mapping of '{}'".format(pin_name))
+ available_bus.add(mapping.pullup_ref)
+
+ else:
+ for bus in mapping.values():
+ if bus is not None:
+ used_bus.add(bus)
+
+ missing = used_bus.difference(available_bus)
+ if len(missing):
+ raise Exception("'{}' listed in pin mapping, but not available".format(missing))
+
+ if "Diff Pin" in node.children:
+ for pin_name, mapping in node.children.diff_pin.iteritems():
+ if pin_name not in node.children.pin:
+ raise Exception("Invalid pin, '{}', listed in Diff Pin".format(pin_name))
+ if mapping.inv_pin not in node.children.pin:
+ raise Exception("Invalid pin, '{}', listed in Diff Pin".format(mapping.inv_pin))
+ if pin_name == mapping.inv_pin:
+ raise Exception("Diff Pin '{}' maps to itself".format(pin_name))
+
+ # Fill in vdiff default
+ if mapping.vdiff is None:
+ mapping.vdiff = 200e-3
+
+ # Create tdelay Range and fill in typ/min defaults
+ tdelay = Range([])
+ for t in [ "tdelay_typ", "tdelay_min", "tdelay_max" ]:
+ tdelay.append(mapping[t])
+ del mapping[t]
+ if tdelay[0] is None: tdelay[0] = 0
+ if tdelay[1] is None: tdelay[1] = 0
+ mapping["tdelay"] = tdelay
+
+ function_table_groups = set()
+ if "Series Pin Mapping" in node.children:
+ for pin_tuple, mapping in node.children.series_pin_mapping.iteritems():
+ for pin_name in pin_tuple:
+ if pin_name not in node.children.pin:
+ raise Exception("Invalid pin, '{}', listed in series pin mapping"
+ .format(pin_name))
+ group = mapping.function_table_group
+ if group is not None:
+ function_table_groups.add(group)
+
+ if function_table_groups:
+ if not "Series Switch Groups" in node.children:
+ raise Exception("Series Pin Mapping has function_table_group elements, "
+ "but no Series Switch Groups exist")
+ for group in node.children.series_switch_groups:
+ groups = set(group[1:])
+ missing = groups.difference(function_table_groups)
+ if len(missing):
+ raise Exception("Series Switch Groups list '{}' which are not "
+ "available in the series pin mapping table"
+ .format(missing))
+ elif "Series Switch Groups" in node.children:
+ raise Exception("Series Switch Groups exists but no function_table_group "
+ "elements exist under series pin mapping")
+
+ if "Circuit Call" in node.children:
+ for name, call in node.children.circuit_call.iteritems():
+ for n in [ "signal_pin", "diff_signal_pins", "series_pins" ]:
+ if n in call:
+ obj = call[n]
+ if not isinstance(obj, list):
+ obj = [ obj ]
+ for pin in obj:
+ if pin not in node.children["pin"]:
+ raise Exception("Circuit Call '{}' contains unknown pin '{}'"
+ .format(name, pin))
+
+ if "Begin EMI Component" in node.children:
+ if "Pin EMI" in node.children.begin_emi_component:
+ for pin, item in emi_component.pin_emi.iteritems():
+ if pin not in node.children.pin:
+ raise Exception("EMI Component lists unknown pin '{}'"
+ .format(pin))
+
+class BaseModel(Section):
+ """Section 6/6a."""
+ def __init__(self, key, **kwds):
+ kwds["labeled"] = True
+ super(BaseModel, self).__init__(key, **kwds)
+
+ # FIXME: check required for submodel types
+ ramp = Section("Ramp")
+ ramp += Param("dV/dt_r", RampRange())
+ ramp += Param("dV/dt_f", RampRange())
+ ramp += Param("R_load", Real(check=positive), delim='=', default=50)
+
+ # FIXME: Check for monotonic
+ for s in [ "Pullup", "Pulldown", "GND Clamp", "POWER Clamp" ]:
+ self += RangeSection(s)
+
+ for s in [ "Rising Waveform", "Falling Waveform" ]:
+ table = RangeSection(s, data_name="waveform", increasing_idx=True, list_merge=True)
+ table += Param("R_fixture", Real(check=positive), delim='=', required=True)
+ table += Param("V_fixture", Real(), delim='=', required=True)
+ for p in [ "V_fixture_min", "V_fixture_max" ]:
+ table += Param(p, Real(), delim='=')
+ for p in [ "C_fixture", "L_fixture", "R_dut", "L_dut", "C_dut" ]:
+ table += Param(p, Real(check=positive), delim='=', default=0)
+ table += RangeSection("Composite Current")
+ self += table
+
+ self += ramp
+
+ def fin(self, node):
+ super(BaseModel, self).fin(node)
+
+ # Make v_fixture a range, axe _min, _max
+ for s in [ "Rising Waveform", "Falling Waveform" ]:
+ if s in node.children:
+ for waveform in node.children[s]:
+ r = Range([waveform.V_fixture])
+ for m in [ "V_fixture_min", "V_fixture_max" ]:
+ if m in waveform:
+ r.append(waveform[m])
+ del waveform[m]
+ else:
+ r.append(None)
+ waveform.V_fixture = r
+
+class Series_MOSFET(RangeSection):
+ def __init__(self):
+ super(Series_MOSFET, self).__init__("Series MOSFET", initvalue=OrderedDict())
+ self += Param("Vds", Real(), delim='=')
+
+ def flatten(self, node):
+ try:
+ node.key = node.children.vds
+ except:
+ raise Exception("Series MOSFET missing Vds")
+ node.children = None
+ super(Series_MOSFET, self).flatten(node)
+
+ def merge(self, orig, new):
+ if new.key in orig.data:
+ raise Exception("'{}' already contains '{}'".format(self.key, new.key))
+ orig.data[new.key] = new.data
+
+class Model(BaseModel):
+ """Section 6."""
+ def __init__(self, **kwds):
+ # NB: The spec claims that Model is required, but ibischk5 does not care.
+ # Really it should only be required if there is a referenced model
+ # kwds["required"] = True
+ super(Model, self).__init__("Model", **kwds)
+
+ model_spec = Section("Model Spec")
+ for p in ["Vinh", "Vinl", "Vinh+", "Vinh-", "Vinl+", "Vinl-",
+ "S_overshoot_high", "S_overshoot_low",
+ "D_overshoot_high", "D_overshoot_low",
+ "D_overshoot_ampl_h", "D_overshoot_ampl_l",
+ "Pulse_high", "Pulse_low", "Vmeas", "Vref",
+ "Vref_rising", "Vref_falling", "Vmeas_rising", "Vmeas_falling" ]:
+ model_spec += Param(p, RealRange())
+ for p in [ "D_overshoot_time", "D_overshoot_area_h", "D_overshoot_area_l",
+ "Pulse_time", "Vref", "Cref", "Rref", "Cref_rising", "Cref_falling",
+ "Rref_rising", "Rref_falling", "Rref_diff", "Cref_diff"]:
+ model_spec += Param(p, RealRange(check=positive))
+
+ receiver_thresholds = Section("Receiver Thresholds")
+ for p in [ "Vth", "Vth_min", "Vth_max",
+ "Vinh_ac", "Vinh_dc", "Vinl_ac", "Vinl_dc", "Threshold_sensitivity",
+ "Vcross_low", "Vcross_high",
+ "Vdiff_ac", "Vdiff_dc", "Tslew_ac", "Tdiffslew_ac" ]:
+ receiver_thresholds += Param(p, Real(), delim='=')
+ receiver_thresholds += Param("Reference_supply",
+ oneof("Power_clamp_ref Gnd_clamp_ref Pullup_ref Pulldown_ref Ext_ref"))
+
+ driver_schedule = DictSection("Driver Schedule",
+ NAReal()("Rise_on_dly") - NAReal()("Rise_off_dly") -
+ NAReal()("Fall_on_dly") - NAReal()("Fall_off_dly"), asDict=True)
+
+ receiver_thresholds = Section("Receiver Thresholds")
+ for p in [ "Vth", "Vth_min", "Vth_max",
+ "Vinh_ac", "Vinh_dc", "Vinl_ac", "Vinl_dc", "Threshold_sensitivity",
+ "Vcross_low", "Vcross_high",
+ "Vdiff_ac", "Vdiff_dc", "Tslew_ac", "Tdiffslew_ac" ]:
+ receiver_thresholds += Param(p, Real(), delim='=')
+ receiver_thresholds += Param("Reference_supply",
+ oneof("Power_clamp_ref Gnd_clamp_ref Pullup_ref Pulldown_ref Ext_ref"))
+
+ self += Param("Model_type",
+ oneof("Input Output I/O 3-state Open_drain I/O_open_drain " +
+ "Open_sink I/O_open_sink Open_source I/O_open_source " +
+ "Input_ECL Output_ECL I/O_ECL 3-state_ECL " +
+ "Input_diff Output_diff I/O_diff 3-state_diff " +
+ "Series Series_switch Terminator"), required=True)
+ self += Param("Polarity", oneof("Non-Inverting Inverting"))
+ self += Param("Enable", oneof("Active-High Active-Low"))
+ for p in [ "Vinl", "Vinh", "Vmeas", "Vref" ]:
+ self += Param(p, Real(), delim='=')
+ for p in [ "Cref", "Rref", "Rref_diff", "Cref_diff" ]:
+ self += Param(p, Real(check=positive), delim='=')
+ for p in [ "C_comp", "C_comp_pullup", "C_comp_pulldown",
+ "C_comp_power_clamp", "C_comp_gnd_clamp" ]:
+ self += Param(p, RealRange(check=positive))
+ self += Keyword("Temperature Range", RealRange(), default=Range([50, 0, 100]))
+ for p in [ "Voltage Range",
+ "POWER Clamp Reference", "GND Clamp Reference",
+ "External Reference", "TTgnd", "TTpower",
+ "Pullup Reference", "Pulldown Reference" ]:
+ self += Keyword(p, RealRange())
+ for p in [ "R Series", "L Series", "Rl Series",
+ "C Series", "Lc Series", "Rc Series",
+ "Rgnd", "Rpower", "Rac", "Cac" ]:
+ self += Keyword(p, RealRange(check=positive))
+ for p in [ "ISSO PD", "ISSO PU" ]:
+ self += RangeSection(p)
+ self += EMI_Model()
+
+ for s in [ "On", "Off" ]:
+ series = Section(s)
+ series += RangeSection("Series Current")
+ series += Series_MOSFET()
+ for p in [ "R Series", "L Series", "Rl Series",
+ "C Series", "Lc Series", "Rc Series" ]:
+ series += Keyword(p, RealRange(check=positive))
+ self += series
+
+ self += model_spec
+ self += receiver_thresholds
+ self += DictSection("Add Submodel", oneof("Driving Non-Driving All"))
+ self += driver_schedule
+ self += External_Model()
+ self += Algorithmic_Model()
+ self += RangeSection("Series Current")
+ self += Series_MOSFET()
+ self += receiver_thresholds
+
+ def fin(self, node):
+ super(Model, self).fin(node)
+
+ # Drop old drain naming
+ model_type = node.children.model_type
+ if model_type == "open_drain":
+ model_type = "open_sink"
+ node.children.model_type = model_type
+ elif model_type == "i/o_open_drain":
+ model_type = "i/o_open_sink"
+ node.children.model_type = model_type
+
+ type_diff = model_type.endswith("diff")
+ type_ecl = model_type.endswith("ecl")
+ type_source = model_type.endswith("source")
+ type_sink = model_type.endswith("sink")
+ type_output = model_type.startswith(("i/o", "output", "3-state", "open"))
+ type_input = model_type.startswith(("i/o", "input"))
+ type_series = model_type.startswith("series")
+ type_enable = model_type.startswith(("i/o", "3-state"))
+ type_switch = model_type == "series_switch"
+ type_terminator = model_type == "terminator"
+
+ required = []
+ disallowed = []
+ if type_diff:
+ required += ["External Model"]
+ elif type_input:
+ # Fill in Vinl/Vinh defaults
+ if type_ecl:
+ defaults = [ [ "Vinl", -1.475 ], [ "Vinh", -1.165 ] ]
+ else:
+ defaults = [ [ "Vinl", 0.8 ], [ "Vinh", 2.0 ] ]
+ for n, v in defaults:
+ if n not in node.children or node.children[n] is None:
+ print "Warning: '{}' for model '{}' not defined, using '{}'".format(n, node.key, v)
+ node.children[n] = v
+
+ if not type_input:
+ disallowed += ["Receiver Thresholds"]
+
+ if type_series and not type_switch:
+ disallowed += ["Add Submodel"]
+ if "L Series" not in node.children:
+ disallowed += ["Rl Series"]
+ elif "Rl Series" not in node.children:
+ node.children["Rl Series"] = 0.0
+ if "C Series" not in node.children:
+ disallowed += [ "Rc Series", "Lc Series" ]
+ else:
+ if "Rc Series" not in node.children:
+ node.children["Rc series"] = 0.0
+ if "Lc Series" not in node.children:
+ node.children["Lc Series"] = 0.0
+ else:
+ disallowed += [ "R Series", "L Series", "Rl Series", "C Series",
+ "Lc Series", "Rc Series", "Series Current", "Series Mosfet" ]
+
+ if type_switch:
+ required += [ "On", "Off" ]
+ disallowed += ["Add Submodel"]
+ else:
+ disallowed += [ "On", "Off" ]
+
+ if not type_terminator:
+ disallowed += [ "Rgnd", "Rpower", "Rac", "Cac" ]
+
+ if "Rac" in node.children != "Cac" in node.children:
+ raise Exception("'Rac' and 'Cac' must be specified together")
+
+ if type_output and not "External Model" in node.children:
+ required += ["Ramp"]
+
+ if "Add Submodel" in node.children:
+ for submodel_name, mode in node.children.add_submodel.iteritems():
+ if mode == "driving" and type_output:
+ raise Exception("Type '{}' cannot have submodel '{}' of mode '{}'"
+ .format(model_type, submodel_name, mode))
+ if mode == "non-driving" and not type_input and not type_enable:
+ raise Exception("'Type '{}' cannot have submodel '{}' of mode '{}'"
+ .format(model_type, submodel_name, mode))
+
+ if "Receiver Thresholds" in node.children:
+ thresh = node.children.receiver_thresholds
+ if "Threshold_sensitivity" in thresh:
+ if "Reference_supply" not in thresh:
+ raise Exception("'Reference_supply' required when 'Threshold_sensitivity' is present")
+ required += ["{}erence".format(thresh.reference_supply)]
+
+ if "Driver Schedule" in node.children:
+ for driver, data in node.children.driver_schedule.iteritems():
+ count = 0
+ for name, val in data.iteritems():
+ if val and not val >= 0:
+ raise Exception("'{}' driver schedule item '{}' has invalid value '{}'"
+ .format(driver, name, val))
+ if val is not None:
+ count += 1
+ invalid = False
+ if count == 2:
+ print data
+ for combination in [ [ "rise_on_dly", "fall_off_dly" ],
+ [ "rise_off_dly", "fall_on_dly" ] ]:
+ if combination[0] in data and combination[1] in data:
+ invalid = True
+ if (count != 2 and count != 4) or invalid:
+ raise Exception("Invalid dly combination for driver schedule '{}'"
+ .format(driver))
+
+ if "Voltage Range" not in node.children:
+ required += [ "Pullup Reference", "Pulldown Reference",
+ "POWER Clamp Reference", "GND Clamp Reference" ]
+
+ c_comp_required = True
+ for keyword in [ "pullup", "pulldown", "power_clamp", "gnd_clamp" ]:
+ if "C_comp_{}".format(keyword) in node.children:
+ c_comp_required = False
+ break
+ if "External Model" in node.children:
+ c_comp_required = False
+ ports = set()
+ if type_input:
+ ports.add("D_receive")
+ if type_output:
+ ports.add("D_drive")
+ if type_enable:
+ ports.add("D_enable")
+ if type_switch:
+ ports.add("D_switch")
+
+ if type_diff:
+ ports |= set([ "A_signal_pos", "A_signal_neg" ])
+ elif type_series:
+ ports |= set(["A_pos", "A_neg"])
+ else:
+ ports.add("A_signal")
+
+ ext = node.children.external_model
+ used = set(ext.ports)
+ for t in ["D_to_A", "A_to_D"]:
+ if t in ext:
+ for name, obj in ext[t].iteritems():
+ used.add(name)
+ for port in ["port1", "port2"]:
+ used.add(obj[port])
+ for port in ports:
+ if port not in used:
+ raise Exception("External Model missing port '{}'"
+ .format(port))
+
+ if c_comp_required:
+ required += ["C_comp"]
+
+ for keyword in disallowed:
+ if keyword in node.children:
+ raise Exception("Keyword '{}' not permitted in type '{}'"
+ .format(keyword, model_type))
+ for keyword in required:
+ if keyword not in node.children:
+ raise Exception("Type '{}' missing required keyword '{}'"
+ .format(model_type, keyword))
+
+ if type_switch:
+ for d in [ "On", "Off"]:
+ disallowed = []
+ sect = node.children[d]
+ if "L Series" not in sect:
+ disallowed += ["Rl Series"]
+ elif "Rl Series" not in sect:
+ sect["Rl Series"] = 0.0
+ if "C Series" not in sect:
+ disallowed += [ "Rc Series", "Lc Series" ]
+ else:
+ if "Rc Series" not in sect:
+ sect["Rc Series"] = 0.0
+ if "Lc Series" not in sect:
+ sect["Lc Series"] = 0.0
+ for keyword in disallowed:
+ if keyword in sect:
+ raise Exception("Keyword '{}' not permitted in '{}'"
+ .format(keyword, d))
+
+class Test_Data(Section):
+ """See http://www.vhdl.org/pub/ibis/birds/bird142.txt"""
+ def __init__(self, **kwds):
+ kwds["labeled"] = True
+ super(Test_Data, self).__init__("Test Data", **kwds)
+
+ self += Param("Test_data_type", oneof("Single_ended Differential"), required=True)
+ self += Param("Driver_model", required=True)
+ self += Param("Driver_model_inv")
+ self += Param("Test_load", required=True)
+ for s in [ "Rising Waveform Near", "Falling Waveform Near",
+ "Rising Waveform Far", "Falling Waveform Far",
+ "Diff Rising Waveform Near", "Diff Falling Waveform Near",
+ "Diff Rising Waveform Far", "Diff Falling Waveform Far" ]:
+ self += RangeSection(s, increasing_idx=True)
+
+ def fin(self, node):
+ super(Test_Data, self).fin(node)
+
+ if "Driver_model_inv" in node.children:
+ if node.children.test_data_type != "differential":
+ raise Exception("Contains Driver_model_inv but is not differential")
+ for s in [ "Rising Waveform Near", "Falling Waveform Near",
+ "Rising Waveform Far", "Falling Waveform Far",
+ "Diff Rising Waveform Near", "Diff Falling Waveform Near",
+ "Diff Rising Waveform Far", "Diff Falling Waveform Far" ]:
+ if s in node.children:
+ has = True