| #!/usr/bin/env python3 |
| # -*- coding: utf-8 -*- |
| # |
| # Copyright 2020 The SkyWater PDK Authors. |
| # |
| # Use of this source code is governed by the Apache 2.0 |
| # license that can be found in the LICENSE file or at |
| # https://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # 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 |
| |
| |
| def parse_filename(pathname): |
| """Extract library and module name from pathname. |
| |
| >>> 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=None, 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=None, 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(volts=[1.8, 3.3], temps=[100], flags=[], types=[CornerType.t, CornerType.t]) |
| >>> 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] |
| Library(node=LibraryNode.SKY130, source=LibrarySource('fd'), type=LibraryType.io, name=None, version=LibraryVersion(milestone=0, major=2, minor=1, commits=0, hash='')) |
| |
| """ |
| 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, 2): |
| library = Library.parse(bits.pop(0)) |
| extra = "" |
| if bits: |
| extra = bits.pop(0) |
| if version: |
| library.version = version |
| elif len(bits) == 3: |
| library = Cell.parse(bits[0]+SEPERATOR+bits[1]) |
| if version: |
| library.library.version = version |
| extra = bits[2] |
| else: |
| raise NotImplementedError() |
| |
| return (library, extra, extension) |
| |
| |
| SEPERATOR = "__" |
| |
| |
| @dataclass_json |
| @dataclass(order=True, frozen=True) |
| class LibraryVersion: |
| """ |
| |
| >>> 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, 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: Optional[int] = 0 |
| hash: Optional[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): |
| 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 |
| |
| |
| 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)) |
| |
| |
| 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): |
| 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 |
| |
| |
| @dataclass_json |
| @dataclass |
| class Library: |
| """ |
| |
| >>> 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'" |
| |
| |
| """ |
| |
| node: LibraryNode |
| source: LibrarySource |
| type: LibraryType |
| name: Optional[str] = None |
| 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: |
| """ |
| >>> 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() |