blob: 84ce1826f0bb5d1a6804a4af7acd0e3d8f2e67d7 [file] [log] [blame]
#!/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()