| #!/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', <cellname>] | |
| # [..., 'models', <modname>] | |
| if n2 in ('cells', 'models'): | |
| obj_name = pathbits.pop(-1) | |
| obj_type = pathbits.pop(-1) | |
| continue | |
| # [..., 'skywater-pdk', 'libraries', <library name>, <library version>] | |
| elif n3 == "libraries": | |
| lib_version = pathbits.pop(-1) | |
| lib_name = pathbits.pop(-1) | |
| assert pathbits.pop(-1) == 'libraries' | |
| # [..., 'skywater-pdk', 'libraries', <library name>] | |
| elif n2 == "libraries": | |
| lib_name = pathbits.pop(-1) | |
| assert pathbits.pop(-1) == 'libraries' | |
| # [<library name>, <library version>] | |
| 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, self.hash) | |
| @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() |