| #!/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() |