diff --git a/docs/conf.py b/docs/conf.py index ac4865b..12d9e13 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -57,11 +57,13 @@ release = '' # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.todo', - 'sphinx.ext.mathjax', - 'sphinx.ext.ifconfig', - 'sphinx.ext.githubpages', + 'sphinx.ext.autodoc', 'sphinx.ext.autosectionlabel', + 'sphinx.ext.githubpages', + 'sphinx.ext.ifconfig', + 'sphinx.ext.mathjax', + 'sphinx.ext.napoleon', + 'sphinx.ext.todo', 'sphinxcontrib_verilog_diagrams', ] diff --git a/docs/index.rst b/docs/index.rst index fc257a5..954900a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -9,6 +9,8 @@ contributing + Python API + Welcome to SkyWater SKY130 PDK's documentation! =============================================== diff --git a/docs/python-api b/docs/python-api new file mode 120000 index 0000000..66d6680 --- /dev/null +++ b/docs/python-api @@ -0,0 +1 @@ +../scripts/python-skywater-pdk/docs/ \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index e4e2ff4..05f4b41 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,6 @@ # rst_include tool as GitHub doesn't support `.. include::` when rendering # previews. rst_include + +# The Python API for the SkyWater PDK. +-e scripts/python-skywater-pdk diff --git a/scripts/python-skywater-pdk/LICENSE b/scripts/python-skywater-pdk/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/scripts/python-skywater-pdk/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/scripts/python-skywater-pdk/README.rst b/scripts/python-skywater-pdk/README.rst new file mode 100644 index 0000000..970dc60 --- /dev/null +++ b/scripts/python-skywater-pdk/README.rst @@ -0,0 +1,37 @@ +skywater-pdk Python Module +========================== + +This Python module is a small library for working with the files found inside +the SkyWater PDK. + +It includes tools for decoding things like file names into human readable +descriptions. + +It also includes tools for combining files together. + +License +======= + +Like the SkyWater Open Source PDK, the SkyWater Python Module is released under +the +`Apache 2.0 license `_. + +The copyright details (which should also be found at the top of every file) are; + +:: + + Copyright 2020 SkyWater PDK Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + diff --git a/scripts/python-skywater-pdk/docs/index.rst b/scripts/python-skywater-pdk/docs/index.rst new file mode 100644 index 0000000..043d790 --- /dev/null +++ b/scripts/python-skywater-pdk/docs/index.rst @@ -0,0 +1,9 @@ +SkyWater PDK Python API +======================= + +TODO: Add documentation here + +.. toctree:: + :hidden: + + skywater_pdk diff --git a/scripts/python-skywater-pdk/docs/skywater_pdk.rst b/scripts/python-skywater-pdk/docs/skywater_pdk.rst new file mode 100644 index 0000000..8488d1e --- /dev/null +++ b/scripts/python-skywater-pdk/docs/skywater_pdk.rst @@ -0,0 +1,38 @@ +skywater\_pdk package +===================== + +Submodules +---------- + +skywater\_pdk.base module +------------------------- + +.. automodule:: skywater_pdk.base + :members: + :undoc-members: + :show-inheritance: + +skywater\_pdk.sizes module +--------------------------- + +.. automodule:: skywater_pdk.sizes + :members: + :undoc-members: + :show-inheritance: + +skywater\_pdk.utils module +-------------------------- + +.. automodule:: skywater_pdk.utils + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: skywater_pdk + :members: + :undoc-members: + :show-inheritance: diff --git a/scripts/python-skywater-pdk/setup.py b/scripts/python-skywater-pdk/setup.py new file mode 100644 index 0000000..7b7029e --- /dev/null +++ b/scripts/python-skywater-pdk/setup.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright 2020 SkyWater PDK Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + +from setuptools import setup, find_packages +from os import path + +here = path.abspath(path.dirname(__file__)) + +# Get the long description from the README file +with open(path.join(here, 'README.rst'), encoding='utf-8') as f: + long_description = f.read() + +setup( + name='skywater-pdk', + version='0.0.0', + description='Python library for working with files found in the SkyWater PDK', + long_description=long_description, + long_description_content_type='text/x-rst', + url='https://github.com/google/skywater-pdk', + author='SkyWater PDK Authors', + author_email='skywater-pdk-dev@googlegroups.com', + classifiers=[ + 'Development Status :: 3 - Alpha', + 'Intended Audience :: Developers', + 'Topic :: Software Development :: Build Tools', + 'License :: OSI Approved :: Apache Software License', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3 :: Only', + ], + + keywords='asic eda development verilog pdk', + packages=find_packages(where='.'), + python_requires='>=3.6, <4', + + install_requires=[ + 'dataclasses_json', + ], + + extras_require={ + 'dev': ['check-manifest'], + 'test': ['coverage'], + }, + + entry_points={ + 'console_scripts': [ + 'sample=sample:main', + ], + }, + + project_urls={ + 'Bug Reports': 'https://github.com/google/skywater-pdk/issues', + 'Source': 'https://github.com/google/skywater-pdk/', + }, +) diff --git a/scripts/python-skywater-pdk/skywater_pdk/__init__.py b/scripts/python-skywater-pdk/skywater_pdk/__init__.py new file mode 100644 index 0000000..b27a7d1 --- /dev/null +++ b/scripts/python-skywater-pdk/skywater_pdk/__init__.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2020 SkyWater PDK Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 diff --git a/scripts/python-skywater-pdk/skywater_pdk/base.py b/scripts/python-skywater-pdk/skywater_pdk/base.py new file mode 100644 index 0000000..5ace788 --- /dev/null +++ b/scripts/python-skywater-pdk/skywater_pdk/base.py @@ -0,0 +1,561 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright 2020 SkyWater PDK Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + +import os + +from dataclasses import dataclass +from dataclasses_json import dataclass_json +from enum import Enum +from typing import Optional, Union, Tuple + +from .utils import comparable_to_none +from .utils import dataclass_json_passthru_config as dj_pass_cfg + + +LibraryOrCell = Union['Library', 'Cell'] + + +def parse_pathname(pathname): + """Extract library and module name for pathname. + + Returns + ------- + obj : Library or Cell + Library or Cell information parsed from filename + filename : str, optional + String containing any filename extracted. + String containing the file extension + + See Also + -------- + skywater_pdk.base.parse_filename + skywater_pdk.base.Cell + skywater_pdk.base.Library + + Examples + -------- + + >>> parse_pathname('skywater-pdk/libraries/sky130_fd_sc_hd/v0.0.1/cells/a2111o') + (Cell(name='a2111o', library=Library(node=LibraryNode.SKY130, source=LibrarySource('fd'), type=LibraryType.sc, name='hd', version=LibraryVersion(milestone=0, major=0, minor=1, commits=0, hash=''))), None) + + >>> parse_pathname('skywater-pdk/libraries/sky130_fd_sc_hd/v0.0.1/cells/a2111o/README.rst') + (Cell(name='a2111o', library=Library(node=LibraryNode.SKY130, source=LibrarySource('fd'), type=LibraryType.sc, name='hd', version=LibraryVersion(milestone=0, major=0, minor=1, commits=0, hash=''))), 'README.rst') + + >>> parse_pathname('skywater-pdk/libraries/sky130_fd_sc_hd/v0.0.1') + (Library(node=LibraryNode.SKY130, source=LibrarySource('fd'), type=LibraryType.sc, name='hd', version=LibraryVersion(milestone=0, major=0, minor=1, commits=0, hash='')), None) + + >>> parse_pathname('skywater-pdk/libraries/sky130_fd_sc_hd/v0.0.1/README.rst') + (Library(node=LibraryNode.SKY130, source=LibrarySource('fd'), type=LibraryType.sc, name='hd', version=LibraryVersion(milestone=0, major=0, minor=1, commits=0, hash='')), 'README.rst') + + >>> parse_pathname('libraries/sky130_fd_sc_hd/v0.0.1') + (Library(node=LibraryNode.SKY130, source=LibrarySource('fd'), type=LibraryType.sc, name='hd', version=LibraryVersion(milestone=0, major=0, minor=1, commits=0, hash='')), None) + + >>> parse_pathname('libraries/sky130_fd_sc_hd/v0.0.1/README.rst') + (Library(node=LibraryNode.SKY130, source=LibrarySource('fd'), type=LibraryType.sc, name='hd', version=LibraryVersion(milestone=0, major=0, minor=1, commits=0, hash='')), 'README.rst') + + >>> parse_pathname('sky130_fd_sc_hd/v0.0.1') + (Library(node=LibraryNode.SKY130, source=LibrarySource('fd'), type=LibraryType.sc, name='hd', version=LibraryVersion(milestone=0, major=0, minor=1, commits=0, hash='')), None) + + >>> parse_pathname('sky130_fd_sc_hd/v0.0.1/README.rst') + (Library(node=LibraryNode.SKY130, source=LibrarySource('fd'), type=LibraryType.sc, name='hd', version=LibraryVersion(milestone=0, major=0, minor=1, commits=0, hash='')), 'README.rst') + + >>> parse_pathname('sky130_fd_sc_hd/v0.0.1/RANDOM') + (Library(node=LibraryNode.SKY130, source=LibrarySource('fd'), type=LibraryType.sc, name='hd', version=LibraryVersion(milestone=0, major=0, minor=1, commits=0, hash='')), 'RANDOM') + + >>> parse_pathname('RANDOM') #doctest: +ELLIPSIS + Traceback (most recent call last): + ... + ValueError: ... + + >>> parse_pathname('libraries/RANDOM/v0.0.1') #doctest: +ELLIPSIS + Traceback (most recent call last): + ... + ValueError: ... + + >>> parse_pathname('libraries/skywater_fd_sc_hd/vA.B.C') #doctest: +ELLIPSIS + Traceback (most recent call last): + ... + ValueError: ... + """ + if os.path.exists(pathname): + pathname = os.path.abspath(pathname) + + pathbits = pathname.split(os.path.sep) + # Remove any files at the end of the path + filename = None + if '.' in pathbits[-1]: + if not pathbits[-1].startswith('v'): + filename = pathbits.pop(-1) + + obj_type = None + obj_name = None + + lib_name = None + lib_version = None + + while len(pathbits) > 1: + n1 = pathbits[-1] + n2 = pathbits[-2] + if len(pathbits) > 2: + n3 = pathbits[-3] + else: + n3 = '' + + # [..., 'cells', ] + # [..., 'models', ] + if n2 in ('cells', 'models'): + obj_name = pathbits.pop(-1) + obj_type = pathbits.pop(-1) + continue + # [..., 'skywater-pdk', 'libraries', , ] + elif n3 == "libraries": + lib_version = pathbits.pop(-1) + lib_name = pathbits.pop(-1) + assert pathbits.pop(-1) == 'libraries' + # [..., 'skywater-pdk', 'libraries', ] + elif n2 == "libraries": + lib_name = pathbits.pop(-1) + assert pathbits.pop(-1) == 'libraries' + # [, ] + elif n1.startswith('v'): + lib_version = pathbits.pop(-1) + lib_name = pathbits.pop(-1) + elif filename is None: + filename = pathbits.pop(-1) + continue + else: + raise ValueError('Unable to parse: {}'.format(pathname)) + break + + if not lib_name: + raise ValueError('Unable to parse: {}'.format(pathname)) + lib = Library.parse(lib_name) + if lib_version: + lib.version = LibraryVersion.parse(lib_version) + if obj_name: + obj = Cell.parse(obj_name) + obj.library = lib + return obj, filename + else: + return lib, filename + + + +def parse_filename(pathname) -> Tuple[LibraryOrCell, Optional[str], Optional[str]]: + """Extract library and module name from filename. + + Returns + ------- + obj : Library or Cell + Library or Cell information parsed from filename + extra : str, optional + String containing any extra unparsed data (like corner information) + ext : str, optional + String containing the file extension + + See Also + -------- + skywater_pdk.base.parse_pathname + skywater_pdk.base.Cell + skywater_pdk.base.Library + + Examples + -------- + + >>> t = list(parse_filename('sky130_fd_io__top_ground_padonlyv2__tt_1p80V_3p30V_3p30V_25C.wrap.lib')) + >>> t.pop(0) + Cell(name='top_ground_padonlyv2', library=Library(node=LibraryNode.SKY130, source=LibrarySource('fd'), type=LibraryType.io, name='', version=None)) + >>> t.pop(0) + 'tt_1p80V_3p30V_3p30V_25C' + >>> t.pop(0) + 'wrap.lib' + >>> t = list(parse_filename('v0.10.0/sky130_fd_sc_hdll__a211o__tt_1p80V_3p30V_3p30V_25C.wrap.json')) + >>> t.pop(0) + Cell(name='a211o', library=Library(node=LibraryNode.SKY130, source=LibrarySource('fd'), type=LibraryType.sc, name='hdll', version=LibraryVersion(milestone=0, major=10, minor=0, commits=0, hash=''))) + >>> t.pop(0) + 'tt_1p80V_3p30V_3p30V_25C' + >>> t.pop(0) + 'wrap.json' + + >>> t = list(parse_filename('sky130_fd_io/v0.1.0/sky130_fd_io__top_powerhv_hvc_wpad__tt_1p80V_3p30V_100C.wrap.json')) + >>> t.pop(0) + Cell(name='top_powerhv_hvc_wpad', library=Library(node=LibraryNode.SKY130, source=LibrarySource('fd'), type=LibraryType.io, name='', version=LibraryVersion(milestone=0, major=1, minor=0, commits=0, hash=''))) + >>> from skywater_pdk.corners import parse_filename as pf_corners + >>> pf_corners(t.pop(0)) + (Corner(corner=(CornerType.t, CornerType.t), volts=(1.8, 3.3), temps=(100,), flags=None), []) + >>> t.pop(0) + 'wrap.json' + + >>> parse_filename('libraries/sky130_fd_io/v0.2.1/cells/analog_pad/sky130_fd_io-analog_pad.blackbox.v')[0] + Cell(name='analog_pad', library=Library(node=LibraryNode.SKY130, source=LibrarySource('fd'), type=LibraryType.io, name='', version=LibraryVersion(milestone=0, major=2, minor=1, commits=0, hash=''))) + + >>> t = list(parse_filename('skywater-pdk/libraries/sky130_fd_sc_hd/v0.0.1/cells/a2111o/sky130_fd_sc_hd__a2111o.blackbox.v')) + >>> t.pop(0) + Cell(name='a2111o', library=Library(node=LibraryNode.SKY130, source=LibrarySource('fd'), type=LibraryType.sc, name='hd', version=LibraryVersion(milestone=0, major=0, minor=1, commits=0, hash=''))) + >>> assert t.pop(0) is None + >>> t.pop(0) + 'blackbox.v' + + """ + dirname, filename = os.path.split(pathname) + + # Extract a version if it exists. + dirbase, dirversion = os.path.split(dirname) + if dirbase.endswith('cells'): + dirbase, dirversion = os.path.split(dirbase) + assert dirversion == 'cells', (dirbase, dirversion) + dirbase, dirversion = os.path.split(dirbase) + try: + version = LibraryVersion.parse(dirversion) + except TypeError: + version = None + + # Extract the file extension + if '.' in filename: + basename, extension = filename.split('.', 1) + else: + basename = filename + extension = '' + + basename = basename.replace('-', SEPERATOR) # FIXME: !!! + + # Parse the actual filename + bits = basename.split(SEPERATOR, 3) + if len(bits) in (1,): + library = Library.parse(bits.pop(0)) + extra = "" + if bits: + extra = bits.pop(0) + if version: + library.version = version + elif len(bits) in (2, 3): + library = Cell.parse(bits[0]+SEPERATOR+bits[1]) + if version: + library.library.version = version + extra = None + if len(bits) > 2: + extra = bits[2] + else: + raise NotImplementedError() + + return (library, extra, extension) + + +SEPERATOR = "__" + +@comparable_to_none +@dataclass_json +@dataclass(order=True, frozen=True) +class LibraryVersion: + """Version number for a library. + + See Also + -------- + skywater_pdk.base.LibraryNode + skywater_pdk.base.LibrarySource + skywater_pdk.base.LibraryType + skywater_pdk.base.LibraryVersion + + Examples + -------- + + >>> v0 = LibraryVersion.parse("v0.0.0") + >>> v0 + LibraryVersion(milestone=0, major=0, minor=0, commits=0, hash='') + >>> v1a = LibraryVersion.parse("v0.0.0-10-g123abc") + >>> v1a + LibraryVersion(milestone=0, major=0, minor=0, commits=10, hash='123abc') + >>> v1b = LibraryVersion.parse("v0.0.0-4-g123abc") + >>> v1b + LibraryVersion(milestone=0, major=0, minor=0, commits=4, hash='123abc') + >>> v2 = LibraryVersion.parse("v0.0.2") + >>> v2 + LibraryVersion(milestone=0, major=0, minor=2, commits=0, hash='') + >>> v3 = LibraryVersion.parse("v0.2.0") + >>> v3 + LibraryVersion(milestone=0, major=2, minor=0, commits=0, hash='') + >>> v4 = LibraryVersion.parse("v0.0.10") + >>> v4 + LibraryVersion(milestone=0, major=0, minor=10, commits=0, hash='') + >>> v0 < v1a + True + >>> v1a < v2 + True + >>> v0 < v2 + True + >>> l = [v1a, v2, v3, None, v1b, v0, v2] + >>> l.sort() + >>> [i.fullname for i in l] + ['0.0.0', '0.0.0-4-g123abc', '0.0.0-10-g123abc', '0.0.2', '0.0.2', '0.2.0'] + """ + milestone: int = 0 + major: int = 0 + minor: int = 0 + + commits: int = 0 + hash: str = '' + + @classmethod + def parse(cls, s): + if not s.startswith('v'): + raise TypeError("Unknown version: {}".format(s)) + kw = {} + if '-' in s: + git_bits = s.split('-') + if len(git_bits) != 3: + raise TypeError("Unparsable git version: {}".format(s)) + s = git_bits[0] + kw['commits'] = int(git_bits[1]) + assert git_bits[2].startswith('g'), git_bits[2] + kw['hash'] = git_bits[2][1:] + kw['milestone'], kw['major'], kw['minor'] = ( + int(i) for i in s[1:].split('.')) + return cls(**kw) + + def as_tuple(self): + return (self.milestone, self.major, self.minor, self.commits, minor) + + @property + def fullname(self): + o = [] + s = "{}.{}.{}".format( + self.milestone, self.major, self.minor) + if self.commits: + s += "-{}-g{}".format(self.commits, self.hash) + return s + + +class LibraryNode(Enum): + """Process node for a library.""" + + SKY130 = "SkyWater 130nm" + + @classmethod + def parse(cls, s): + s = s.upper() + if not hasattr(cls, s): + raise ValueError("Unknown node: {}".format(s)) + return getattr(cls, s) + + def __repr__(self): + return "LibraryNode."+self.name + + def to_json(self): + return self.name + + +class LibrarySource(str): + """Where a library was created.""" + Known = [] + + @classmethod + def parse(cls, s): + try: + return cls.Known[cls.Known.index(s)] + except ValueError: + return cls(s) + + @property + def fullname(self): + if self in self.Known: + return self.__doc__ + else: + return 'Unknown source: '+str.__repr__(self) + + def __repr__(self): + return 'LibrarySource({})'.format(str.__repr__(self)) + + def to_json(self): + if self in self.Known: + return self.__doc__ + return str.__repr__(self) + + +Foundary = LibrarySource("fd") +Foundary.__doc__ = "The SkyWater Foundary" +LibrarySource.Known.append(Foundary) + +Efabless = LibrarySource("ef") +Efabless.__doc__ = "Efabless" +LibrarySource.Known.append(Efabless) + +OSU = LibrarySource("osu") +OSU.__doc__ = "Oklahoma State University" +LibrarySource.Known.append(OSU) + + +class LibraryType(Enum): + """Type of library contents.""" + + pr = "Primitives" + sc = "Standard Cells" + sp = "Build Space (Flash, SRAM, etc)" + io = "IO and Periphery" + xx = "Miscellaneous" + + @classmethod + def parse(cls, s): + if not hasattr(cls, s): + raise ValueError("Unknown library type: {}".format(s)) + return getattr(cls, s) + + def __repr__(self): + return "LibraryType."+self.name + + def __str__(self): + return self.value + + def to_json(self): + return self.value + + +@comparable_to_none +@dataclass_json +@dataclass +class Library: + """Library of cells. + + See Also + -------- + skywater_pdk.base.parse_pathname + skywater_pdk.base.parse_filename + skywater_pdk.base.Cell + skywater_pdk.base.LibraryNode + skywater_pdk.base.LibrarySource + skywater_pdk.base.LibraryType + skywater_pdk.base.LibraryVersion + + Examples + -------- + + >>> l = Library.parse("sky130_fd_sc_hd") + >>> l + Library(node=LibraryNode.SKY130, source=LibrarySource('fd'), type=LibraryType.sc, name='hd', version=None) + >>> l.fullname + 'sky130_fd_sc_hd' + >>> l.source.fullname + 'The SkyWater Foundary' + >>> print(l.type) + Standard Cells + + >>> l = Library.parse("sky130_rrr_sc_hd") + >>> l + Library(node=LibraryNode.SKY130, source=LibrarySource('rrr'), type=LibraryType.sc, name='hd', version=None) + >>> l.fullname + 'sky130_rrr_sc_hd' + >>> l.source.fullname + "Unknown source: 'rrr'" + + >>> l1 = Library.parse("sky130_fd_sc_hd") + >>> l2 = Library.parse("sky130_fd_sc_hdll") + >>> l = [l2, None, l1] + >>> l.sort() + + """ + + node: LibraryNode = dj_pass_cfg() + source: LibrarySource = dj_pass_cfg() + type: LibraryType = dj_pass_cfg() + name: str = '' + version: Optional[LibraryVersion] = None + + @property + def fullname(self): + output = [] + output.append(self.node.name.lower()) + output.append(self.source.lower()) + output.append(self.type.name) + if self.name: + output.append(self.name) + return "_".join(output) + + @classmethod + def parse(cls, s): + if SEPERATOR in s: + raise ValueError( + "Found separator '__' in library name: {!r}".format(s)) + + bits = s.split("_") + if len(bits) < 3: + raise ValueError( + "Did not find enough parts in library name: {}".format(bits)) + + kw = {} + kw['node'] = LibraryNode.parse(bits.pop(0)) + kw['source'] = LibrarySource.parse(bits.pop(0)) + kw['type'] = LibraryType.parse(bits.pop(0)) + if bits: + kw['name'] = bits.pop(0) + return cls(**kw) + + +@dataclass_json +@dataclass +class Cell: + """Cell in a library. + + See Also + -------- + skywater_pdk.base.parse_pathname + skywater_pdk.base.parse_filename + skywater_pdk.base.Library + + Examples + -------- + + >>> c = Cell.parse("sky130_fd_sc_hd__abc") + >>> c + Cell(name='abc', library=Library(node=LibraryNode.SKY130, source=LibrarySource('fd'), type=LibraryType.sc, name='hd', version=None)) + >>> c.fullname + 'sky130_fd_sc_hd__abc' + + >>> c = Cell.parse("abc") + >>> c + Cell(name='abc', library=None) + >>> c.fullname + Traceback (most recent call last): + ... + ValueError: Can't get fullname for cell without a library! Cell(name='abc', library=None) + """ + + name: str + library: Optional[Library] = None + + @property + def fullname(self): + if not self.library: + raise ValueError( + "Can't get fullname for cell without a library! {}".format( + self)) + return "{}__{}".format(self.library.fullname, self.name) + + @classmethod + def parse(cls, s): + kw = {} + if SEPERATOR in s: + library, s = s.split(SEPERATOR, 1) + kw['library'] = Library.parse(library) + kw['name'] = s + return cls(**kw) + + + +if __name__ == "__main__": + import doctest + doctest.testmod() diff --git a/scripts/python-skywater-pdk/skywater_pdk/sizes.py b/scripts/python-skywater-pdk/skywater_pdk/sizes.py new file mode 100644 index 0000000..aa3b02b --- /dev/null +++ b/scripts/python-skywater-pdk/skywater_pdk/sizes.py @@ -0,0 +1,365 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright 2020 SkyWater PDK Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + +import abc +import os +import operator + +from dataclasses import dataclass +from dataclasses_json import dataclass_json + + +def parse_size(s): + """ + + >>> parse_size('_1') + CellSizeNumeric(units=1) + + >>> parse_size('sky130_fd_sc_ms__sdfrtp_1.v') + CellSizeNumeric(units=1) + + >>> parse_size('libraries/sky130_fd_sc_ms/v0.0.1/cells/sdfrtp/sky130_fd_sc_ms__sdfrtp_1.v') + CellSizeNumeric(units=1) + + >>> parse_size('libraries/sky130_fd_sc_ms/v0.0.1/cells/sdfrtp/sky130_fd_sc_ms__sdfrtp_1.bb.blackbox.v') + CellSizeNumeric(units=1) + + >>> parse_size('libraries/sky130_fd_sc_ms/v0.0.1/cells/sdfrtp/sky130_fd_sc_ms__sdfrtp.v') + >>> parse_size('sky130_fd_sc_ms__sdfrtp.v') + >>> parse_size('_blah') + """ + dirname, s = os.path.split(s) + if '.' in s: + s = s.split('.', 1)[0] + if s.count('_') > 1: + s = '_' + (s.rsplit('_', 1)[-1]) + if not s or s == '_': + return None + try: + return CellSize.from_suffix(s) + except InvalidSuffixError as e: + return None + + +class InvalidSuffixError(ValueError): + def __init__(self, s): + ValueError.__init__(self, "Invalid suffix: {}".format(s.strip())) + + +class CellSize(abc.ABC): + """Drive strength variants of a given cell. + + See Also + -------- + skywater_pdk.base.Cell + skywater_pdk.sizes.CellSizeNumeric + skywater_pdk.sizes.CellSizeLowPower + skywater_pdk.sizes.CellSizeMinimum + + Examples + -------- + >>> d1 = CellSize.from_suffix("_1") + >>> d2 = CellSize.from_suffix("_lp") + >>> d3 = CellSize.from_suffix("_m") + >>> d4 = CellSize.from_suffix("_2") + >>> CellSize.from_suffix("_abc") + Traceback (most recent call last): + ... + InvalidSuffixError: Invalid suffix: _abc + >>> l = [d1, d2, d3, d4] + >>> l + [CellSizeNumeric(units=1), CellSizeLowPower(lp_variant=0), CellSizeMinimum(), CellSizeNumeric(units=2)] + >>> l.sort() + >>> l + [CellSizeNumeric(units=1), CellSizeNumeric(units=2), CellSizeLowPower(lp_variant=0), CellSizeMinimum()] + """ + + @abc.abstractmethod + def describe(self): + raise NotImplementedError + + @property + @abc.abstractmethod + def suffix(self): + raise NotImplementedError + + @classmethod + def from_suffix(cls, s): + errors = [] + for subcls in cls.__subclasses__(): + try: + return subcls.from_suffix(s) + except (ValueError, AssertionError) as e: + errors.append((subcls.__name__, e)) + assert errors, ("Unknown error!?", s) + msg = [s, ''] + for cls_name, e in errors: + if isinstance(e, ValueError): + continue + msg.append("{} failed with: {}".format(cls_name, e)) + raise InvalidSuffixError("\n".join(msg)) + + def __str__(self): + return "with size {}".format(self.describe()) + + def _cmp(self, op, o): + if not isinstance(o, CellSize): + return False + return op(self.suffix, o.suffix) + + # Comparison operators + def __lt__(self, o): + return self._cmp(operator.lt, o) + + def __le__(self, o): + return self._cmp(operator.le, o) + + def __eq__(self, o): + return self._cmp(operator.eq, o) + + def __ne__(self, o): + return self._cmp(operator.ne, o) + + def __ge__(self, o): + return self._cmp(operator.ge, o) + + def __gt__(self, o): + return self._cmp(operator.gt, o) + + +@dataclass_json +@dataclass(frozen=True) +class CellSizeNumeric(CellSize): + """ + + See Also + -------- + skywater_pdk.base.Cell + skywater_pdk.sizes.CellSize + skywater_pdk.sizes.CellSizeLowPower + skywater_pdk.sizes.CellSizeMinimum + + Examples + -------- + >>> s1 = CellSizeNumeric.from_suffix("_1") + >>> s2 = CellSizeNumeric.from_suffix("_2") + >>> s3 = CellSizeNumeric.from_suffix("_3") + >>> CellSizeNumeric.from_suffix("_-1") + Traceback (most recent call last): + ... + InvalidSuffixError: Invalid suffix: _-1 + >>> s1 + CellSizeNumeric(units=1) + >>> s2 + CellSizeNumeric(units=2) + >>> s3 + CellSizeNumeric(units=3) + >>> str(s1) + 'with size of 1 units' + >>> str(s2) + 'with size of 2 units' + >>> str(s3) + 'with size of 3 units (invalid?)' + >>> s1.describe() + 'of 1 units' + >>> s2.describe() + 'of 2 units' + >>> s3.describe() + 'of 3 units (invalid?)' + >>> s1.suffix + '_1' + >>> s2.suffix + '_2' + >>> s3.suffix + '_3' + """ + units: int + + VALID_UNIT_VALUES = (0, 1, 2, 4, 8, 6, 12, 14, 16, 20, 32) + + def describe(self): + suffix = "" + if self.units not in self.VALID_UNIT_VALUES: + suffix = " (invalid?)" + + return "of {} units{}".format(self.units, suffix) + + @property + def suffix(self): + return "_{}".format(self.units) + + @classmethod + def from_suffix(cls, s): + if not s.startswith("_"): + raise InvalidSuffixError(s) + i = int(s[1:]) + if i < 0: + raise InvalidSuffixError(s) + return cls(i) + + +@dataclass_json +@dataclass(frozen=True) +class CellSizeLowPower(CellSize): + """ + + See Also + -------- + skywater_pdk.base.Cell + skywater_pdk.sizes.CellSize + skywater_pdk.sizes.CellSizeNumeric + skywater_pdk.sizes.CellSizeMinimum + + Examples + -------- + >>> lp = CellSizeLowPower.from_suffix("_lp") + >>> lp2 = CellSizeLowPower.from_suffix("_lp2") + >>> lp3 = CellSizeLowPower.from_suffix("_lp3") + >>> CellSizeLowPower.from_suffix("_ld") + Traceback (most recent call last): + ... + InvalidSuffixError: Invalid suffix: _ld + >>> lp + CellSizeLowPower(lp_variant=0) + >>> lp2 + CellSizeLowPower(lp_variant=1) + >>> lp3 + CellSizeLowPower(lp_variant=2) + >>> str(lp) + 'with size for low power' + >>> str(lp2) + 'with size for low power (alternative)' + >>> str(lp3) + 'with size for low power (extra alternative 0)' + >>> lp.describe() + 'for low power' + >>> lp2.describe() + 'for low power (alternative)' + >>> lp3.describe() + 'for low power (extra alternative 0)' + >>> lp.suffix + '_lp' + >>> lp2.suffix + '_lp2' + >>> lp3.suffix + '_lp3' + """ + lp_variant: int = 0 + + def describe(self): + if self.lp_variant == 0: + suffix = "" + elif self.lp_variant == 1: + suffix = " (alternative)" + else: + assert self.lp_variant >= 2, self.lp_variant + suffix = " (extra alternative {})".format(self.lp_variant-2) + return "for low power"+suffix + + @property + def suffix(self): + if self.lp_variant == 0: + return "_lp" + else: + assert self.lp_variant > 0, self.lp_variant + return "_lp{}".format(self.lp_variant+1) + + @classmethod + def from_suffix(cls, s): + if not s.startswith("_lp"): + raise InvalidSuffixError(s) + if s == "_lp": + return cls() + elif s == "_lp2": + return cls(1) + else: + try: + i = int(s[3:]) + except ValueError as e: + raise InvalidSuffixError(s) + assert i > 2, (s, i) + return cls(i-1) + + +class CellSizeMinimum(CellSize): + """ + + See Also + -------- + skywater_pdk.base.Cell + skywater_pdk.sizes.CellSize + skywater_pdk.sizes.CellSizeNumeric + skywater_pdk.sizes.CellSizeLowPower + + + Examples + -------- + >>> m = CellSizeMinimum.from_suffix("_m") + >>> CellSizeMinimum.from_suffix("_m2") + Traceback (most recent call last): + ... + InvalidSuffixError: Invalid suffix: _m2 + >>> m + CellSizeMinimum() + >>> str(m) + 'with size minimum' + >>> m.describe() + 'minimum' + >>> m.suffix + '_m' + + >>> m1 = CellSizeMinimum() + >>> m2 = CellSizeMinimum() + >>> assert m1 is m2 + """ + _object = None + def __new__(cls): + if cls._object is None: + cls._object = object.__new__(cls) + return cls._object + + def __repr__(self): + return "CellSizeMinimum()" + + def describe(self): + return "minimum" + + @property + def suffix(self): + return "_m" + + @classmethod + def from_suffix(cls, s): + if s != "_m": + raise InvalidSuffixError(s) + return cls() + + def __hash__(self): + return id(self) + + def to_dict(self): + return {'minimum': None} + + +CellSizeMinimum._object = CellSizeMinimum() + + +if __name__ == "__main__": + import doctest + doctest.testmod() diff --git a/scripts/python-skywater-pdk/skywater_pdk/utils.py b/scripts/python-skywater-pdk/skywater_pdk/utils.py new file mode 100644 index 0000000..9f7a641 --- /dev/null +++ b/scripts/python-skywater-pdk/skywater_pdk/utils.py @@ -0,0 +1,234 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright 2020 SkyWater PDK Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + +import dataclasses +import dataclasses_json +import functools +import random +import sys + +from dataclasses import dataclass +from dataclasses_json import dataclass_json +from enum import Flag +from typing import Optional, Tuple, Any + + +def dataclass_json_passthru_config(*args, **kw): + return dataclasses.field( + *args, + metadata=dataclasses_json.config( + encoder=lambda x: x.to_json(), + #decoder=lambda x: x.from_json(), + ), + **kw, + ) + +def dataclass_json_passthru_sequence_config(*args, **kw): + def to_json_sequence(s): + if s is None: + return None + o = [] + for i in s: + if hasattr(i, 'to_json'): + o.append(i.to_json()) + else: + o.append(i) + return o + + return dataclasses.field( + *args, + metadata=dataclasses_json.config( + encoder=to_json_sequence, + #decoder=lambda x: x.from_json(), + ), + **kw, + ) + + + +def comparable_to_none(cls): + """ + + Examples + -------- + + >>> @comparable_to_none + ... @dataclass(order=True) + ... class A: + ... a: int = 0 + >>> @comparable_to_none + ... @dataclass(order=True) + ... class B: + ... b: Optional[A] = None + >>> b0 = B() + >>> repr(b0) + 'B(b=None)' + >>> str(b0) + 'B(b=None)' + >>> b1 = B(A()) + >>> repr(b1) + 'B(b=A(a=0))' + >>> str(b1) + 'B(b=A(a=0))' + >>> b2 = B(A(2)) + >>> repr(b2) + 'B(b=A(a=2))' + >>> str(b2) + 'B(b=A(a=2))' + >>> l = [b0, b1, b2, None] + >>> for i in range(0, 3): + ... random.shuffle(l) + ... l.sort() + ... print(l) + [None, B(b=None), B(b=A(a=0)), B(b=A(a=2))] + [None, B(b=None), B(b=A(a=0)), B(b=A(a=2))] + [None, B(b=None), B(b=A(a=0)), B(b=A(a=2))] + + """ + class ComparableToNoneVersion(cls): + def __ge__(self, other): + if other is None: + return True + return super().__ge__(other) + def __gt__(self, other): + if other is None: + return True + return super().__gt__(other) + def __le__(self, other): + if other is None: + return False + return super().__le__(other) + def __lt__(self, other): + if other is None: + return False + return super().__lt__(other) + def __eq__(self, other): + if other is None: + return False + return super().__eq__(other) + def __hash__(self): + return super().__hash__() + def __repr__(self): + s = super().__repr__() + return s.replace('comparable_to_none..ComparableToNoneVersion', cls.__name__) + + for a in functools.WRAPPER_ASSIGNMENTS: + if not hasattr(cls, a): + continue + setattr(ComparableToNoneVersion, a, getattr(cls, a)) + + return ComparableToNoneVersion + + +def _is_optional_type(t): + """ + + Examples + -------- + + >>> _is_optional_type(Optional[int]) + True + >>> _is_optional_type(Optional[Tuple]) + True + >>> _is_optional_type(Any) + False + """ + return hasattr(t, "__args__") and len(t.__args__) == 2 and t.__args__[-1] is type(None) + + +def _get_the_optional_type(t): + """ + + Examples + -------- + + >>> _get_the_optional_type(Optional[int]) + + >>> _get_the_optional_type(Optional[Tuple]) + typing.Tuple + >>> class A: + ... pass + >>> _get_the_optional_type(Optional[A]) + + >>> _get_type_name(_get_the_optional_type(Optional[A])) + 'A' + """ + assert _is_optional_type(t), t + return t.__args__[0] + + +def _get_type_name(ot): + """ + + Examples + -------- + + >>> _get_type_name(int) + 'int' + >>> _get_type_name(Tuple) + 'Tuple' + >>> _get_type_name(Optional[Tuple]) + 'typing.Union[typing.Tuple, NoneType]' + """ + if hasattr(ot, "_name") and ot._name: + return ot._name + elif hasattr(ot, "__name__") and ot.__name__: + return ot.__name__ + else: + return str(ot) + + +class OrderedFlag(Flag): + def __ge__(self, other): + if other is None: + return True + if self.__class__ is other.__class__: + return self.value >= other.value + return NotImplemented + def __gt__(self, other): + if other is None: + return True + if self.__class__ is other.__class__: + return self.value > other.value + return NotImplemented + def __le__(self, other): + if other is None: + return False + if self.__class__ is other.__class__: + return self.value <= other.value + return NotImplemented + def __lt__(self, other): + if other is None: + return False + if self.__class__ is other.__class__: + return self.value < other.value + return NotImplemented + def __eq__(self, other): + if other is None: + return False + if self.__class__ is other.__class__: + return self.value == other.value + return NotImplemented + def __hash__(self): + return hash(self._name_) + + +if __name__ == "__main__": + import doctest + doctest.testmod()