Working on extracting metadata from found files.
diff --git a/scripts/python-skywater-pdk/collect_metadata.py b/scripts/python-skywater-pdk/collect_metadata.py
new file mode 100755
index 0000000..99a77da
--- /dev/null
+++ b/scripts/python-skywater-pdk/collect_metadata.py
@@ -0,0 +1,94 @@
+#!/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
+import pprint
+import sys
+import traceback
+
+from skywater_pdk import base, corners, drives
+
+
+def process(cellpath):
+ assert os.path.exists(cellpath), cellpath
+ assert os.path.isdir(cellpath), cellpath
+
+ files = [
+ (f, os.path.abspath(os.path.join(cellpath, f)))
+ for f in os.listdir(cellpath)]
+ files.sort()
+
+ dcell, fname = base.parse_pathname(cellpath)
+ assert isinstance(dcell, base.Cell), (cellpath, dcell, fname)
+ assert fname is None, (cellpath, dcell, fname)
+ extensions = set()
+ dcorners = set()
+ ddrives = set()
+ errors = []
+ for fname, fpath in files:
+ print("Processing:", fname)
+ if fname in ('README.rst',):
+ continue
+ try:
+ fcell, fextra, fext = base.parse_filename(fpath)
+ except Exception as e:
+ traceback.print_exc()
+ errors.append(e)
+ assert isinstance(fcell, base.Cell), (fpath, fcell, fextra, ext)
+
+ extensions.add(fext)
+
+ assert fcell.library == dcell.library, (fcell, dcell)
+ if not fextra:
+ continue
+
+ try:
+ fcorner = corners.parse_filename(fextra)
+ except Exception as e:
+ traceback.print_exc()
+ errors.append(e)
+
+ try:
+ assert fcell.name.startswith(dcell.name), (fcell, dcell)
+ fdrive = fcell.name[len(dcell.name):]
+
+ ddrives.add(drives.parse_drive(fdrive))
+ except Exception as e:
+ traceback.print_exc()
+ errors.append(e)
+
+ dcorners.add(fcorner)
+
+ dcorners = list(sorted(dcorners))
+ ddrives = list(sorted(ddrives))
+
+ print()
+ print(cellpath)
+ print('-'*75)
+ print('Cell:', dcell)
+ print('Cell drives:', ddrives)
+ print('Cell corners:')
+ pprint.pprint(dcorners)
+ print('File types:', extensions)
+ if errors:
+ raise ValueError("\n".join(errors))
+
+
+def main(args):
+ for a in args:
+ print()
+ print()
+ process(os.path.abspath(a))
+
+
+
+if __name__ == "__main__":
+ sys.exit(main(sys.argv[1:]))
diff --git a/scripts/python-skywater-pdk/skywater_pdk/base.py b/scripts/python-skywater-pdk/skywater_pdk/base.py
index 08096a6..0b2e205 100644
--- a/scripts/python-skywater-pdk/skywater_pdk/base.py
+++ b/scripts/python-skywater-pdk/skywater_pdk/base.py
@@ -16,6 +16,8 @@
from enum import Enum
from typing import Optional, Union, Tuple
+from .utils import comparable_to_none
+
LibraryOrCell = Union['Library', 'Cell']
@@ -151,7 +153,7 @@
>>> 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))
+ 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)
@@ -166,15 +168,22 @@
>>> 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='')))
+ 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(volts=[1.8, 3.3], temps=[100], flags=[], types=[CornerType.t, CornerType.t])
+ 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]
- Library(node=LibraryNode.SKY130, source=LibrarySource('fd'), type=LibraryType.io, name=None, version=LibraryVersion(milestone=0, major=2, minor=1, commits=0, hash=''))
+ 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)
@@ -201,18 +210,20 @@
# Parse the actual filename
bits = basename.split(SEPERATOR, 3)
- if len(bits) in (1, 2):
+ 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) == 3:
+ elif len(bits) in (2, 3):
library = Cell.parse(bits[0]+SEPERATOR+bits[1])
if version:
library.library.version = version
- extra = bits[2]
+ extra = None
+ if len(bits) > 2:
+ extra = bits[2]
else:
raise NotImplementedError()
@@ -221,7 +232,7 @@
SEPERATOR = "__"
-
+@comparable_to_none
@dataclass_json
@dataclass(order=True, frozen=True)
class LibraryVersion:
@@ -251,7 +262,7 @@
True
>>> v0 < v2
True
- >>> l = [v1a, v2, v3, v1b, v0, v2]
+ >>> 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']
@@ -260,8 +271,8 @@
major: int = 0
minor: int = 0
- commits: Optional[int] = 0
- hash: Optional[str] = ''
+ commits: int = 0
+ hash: str = ''
@classmethod
def parse(cls, s):
@@ -362,6 +373,7 @@
return self.value
+@comparable_to_none
@dataclass_json
@dataclass
class Library:
@@ -385,13 +397,17 @@
>>> 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
source: LibrarySource
type: LibraryType
- name: Optional[str] = None
+ name: str = ''
version: Optional[LibraryVersion] = None
@property
diff --git a/scripts/python-skywater-pdk/skywater_pdk/corners.py b/scripts/python-skywater-pdk/skywater_pdk/corners.py
index f1ad75a..c46dba1 100644
--- a/scripts/python-skywater-pdk/skywater_pdk/corners.py
+++ b/scripts/python-skywater-pdk/skywater_pdk/corners.py
@@ -15,9 +15,11 @@
from enum import Flag
from dataclasses import dataclass
from dataclasses_json import dataclass_json
-from typing import List, Optional
+from typing import Tuple, Optional
from . import base
+from .utils import OrderedFlag
+from .utils import comparable_to_none
CornerTypeMappings = {}
@@ -31,14 +33,14 @@
CornerTypeMappings["ws"] = "ss"
-class CornerType(Flag):
+class CornerType(OrderedFlag):
"""
>>> CornerType.parse('t')
CornerType.t
>>> CornerType.parse('tt')
[CornerType.t, CornerType.t]
>>> CornerType.parse('wp')
- [CornerType.f, CornerType.s]
+ [CornerType.f, CornerType.f]
"""
t = 'Typical' # all nominal (typical) values
f = 'Fast' # fast, that is, values that make transistors run faster
@@ -67,36 +69,43 @@
return self.value
-class CornerFlag(Flag):
+class CornerFlag(OrderedFlag):
nointpr = 'No internal power'
lv = 'Low voltage'
ccsnoise = 'Composite Current Source Noise'
- ns5 = '5 nanoseconds'
pwr = 'Power'
@classmethod
def parse(cls, s):
- if s == "5ns":
- return cls.ns5
- elif hasattr(cls, s):
+ if hasattr(cls, s):
return getattr(cls, s)
else:
raise TypeError("Unknown CornerFlags: {}".format(s))
def __repr__(self):
- return 'CornerType.'+self.name
+ return 'CornerFlag.'+self.name
def __str__(self):
return self.value
+@comparable_to_none
+class OptionalTuple(tuple):
+ pass
+
+
+@comparable_to_none
@dataclass_json
-@dataclass
+@dataclass(frozen=True, order=True)
class Corner:
- volts: List[float]
- temps: List[int]
- flags: List[CornerFlag]
- types: Optional[List[CornerType]] = None
+ corner: Tuple[CornerType, CornerType]
+ volts: Tuple[float, ...]
+ temps: Tuple[int, ...]
+ flags: Optional[Tuple[CornerFlag, ...]] = None
+
+ def __post_init__(self):
+ if self.flags:
+ object.__setattr__(self, 'flags', OptionalTuple(self.flags))
VOLTS_REGEX = re.compile('([0-9]p[0-9]+)V')
@@ -105,49 +114,49 @@
"""Extract corner information from a filename.
>>> parse_filename('tt_1p80V_3p30V_3p30V_25C')
- Corner(volts=[1.8, 3.3, 3.3], temps=[25], flags=[], types=[CornerType.t, CornerType.t])
+ Corner(corner=(CornerType.t, CornerType.t), volts=(1.8, 3.3, 3.3), temps=(25,), flags=None)
>>> parse_filename('sky130_fd_io__top_ground_padonlyv2__tt_1p80V_3p30V_3p30V_25C.wrap.lib')
- Corner(volts=[1.8, 3.3, 3.3], temps=[25], flags=[], types=[CornerType.t, CornerType.t])
+ Corner(corner=(CornerType.t, CornerType.t), volts=(1.8, 3.3, 3.3), temps=(25,), flags=None)
>>> parse_filename('sky130_fd_sc_ms__tt_1p80V_100C.wrap.json')
- Corner(volts=[1.8], temps=[100], flags=[], types=[CornerType.t, CornerType.t])
+ Corner(corner=(CornerType.t, CornerType.t), volts=(1.8,), temps=(100,), flags=None)
>>> parse_filename('sky130_fd_sc_ms__tt_1p80V_100C.wrap.lib')
- Corner(volts=[1.8], temps=[100], flags=[], types=[CornerType.t, CornerType.t])
+ Corner(corner=(CornerType.t, CornerType.t), volts=(1.8,), temps=(100,), flags=None)
>>> parse_filename('sky130_fd_sc_ms__tt_1p80V_25C_ccsnoise.wrap.json')
- Corner(volts=[1.8], temps=[25], flags=[CornerType.ccsnoise], types=[CornerType.t, CornerType.t])
-
- >>> parse_filename('sky130_fd_sc_ms__wp_1p56V_n40C_5ns.wrap.json')
- Corner(volts=[1.56], temps=[-40], flags=[CornerType.ns5], types=[CornerType.f, CornerType.s])
+ Corner(corner=(CornerType.t, CornerType.t), volts=(1.8,), temps=(25,), flags=(CornerFlag.ccsnoise,))
>>> parse_filename('sky130_fd_sc_ms__wp_1p65V_n40C.wrap.json')
- Corner(volts=[1.65], temps=[-40], flags=[], types=[CornerType.f, CornerType.s])
+ Corner(corner=(CornerType.f, CornerType.f), volts=(1.65,), temps=(-40,), flags=None)
>>> parse_filename('sky130_fd_sc_ms__wp_1p95V_85C_pwr.wrap.lib')
- Corner(volts=[1.95], temps=[85], flags=[CornerType.pwr], types=[CornerType.f, CornerType.s])
+ Corner(corner=(CornerType.f, CornerType.f), volts=(1.95,), temps=(85,), flags=(CornerFlag.pwr,))
>>> parse_filename('sky130_fd_sc_ms__wp_1p95V_n40C_ccsnoise.wrap.json')
- Corner(volts=[1.95], temps=[-40], flags=[CornerType.ccsnoise], types=[CornerType.f, CornerType.s])
+ Corner(corner=(CornerType.f, CornerType.f), volts=(1.95,), temps=(-40,), flags=(CornerFlag.ccsnoise,))
>>> parse_filename('sky130_fd_sc_ms__wp_1p95V_n40C_pwr.wrap.lib')
- Corner(volts=[1.95], temps=[-40], flags=[CornerType.pwr], types=[CornerType.f, CornerType.s])
+ Corner(corner=(CornerType.f, CornerType.f), volts=(1.95,), temps=(-40,), flags=(CornerFlag.pwr,))
+
+ >>> parse_filename('sky130_fd_sc_hd__a2111o_4__ss_1p76V_n40C.cell.json')
+ Corner(corner=(CornerType.s, CornerType.s), volts=(1.76,), temps=(-40,), flags=None)
"""
- pathname = pathname.replace('-', base.SEPERATOR) # FIXME: !!!
-
if base.SEPERATOR in pathname:
- _, extra, extension = base.parse_filename(pathname)
+ cell, extra, extension = base.parse_filename(pathname)
else:
+ cell = None
extra = pathname
extension = ''
- if extension not in ('', 'lib', 'wrap.lib', 'wrap.json'):
+ if extension not in ('', 'lib', 'cell.lib', 'cell.json', 'wrap.lib', 'wrap.json'):
raise ValueError('Not possible to extract corners from: {!r}'.format(extension))
if not extra:
- raise ValueError('No corners found in: {!r}'.format(pathname))
+ extra = cell.name
+ cell = None
kw = {}
kw['flags'] = []
@@ -165,11 +174,17 @@
assert b.endswith('C'), b
kw['temps'].append(int(b[:-1].replace('n', '-')))
else:
- if 'types' not in kw:
- kw['types'] = CornerType.parse(b)
+ if 'corner' not in kw:
+ kw['corner'] = CornerType.parse(b)
else:
kw['flags'].append(CornerFlag.parse(b))
+ for k, v in kw.items():
+ kw[k] = tuple(v)
+
+ if not kw['flags']:
+ del kw['flags']
+
return Corner(**kw)
diff --git a/scripts/python-skywater-pdk/skywater_pdk/drives.py b/scripts/python-skywater-pdk/skywater_pdk/drives.py
index 165ee45..1b6b5ac 100644
--- a/scripts/python-skywater-pdk/skywater_pdk/drives.py
+++ b/scripts/python-skywater-pdk/skywater_pdk/drives.py
@@ -17,6 +17,10 @@
from dataclasses_json import dataclass_json
+def parse_drive(s):
+ return DriveStrength.from_suffix(s)
+
+
class InvalidSuffixError(ValueError):
def __init__(self, s):
ValueError.__init__(self, "Invalid suffix: {}".format(s.strip()))
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..d3752b2
--- /dev/null
+++ b/scripts/python-skywater-pdk/skywater_pdk/utils.py
@@ -0,0 +1,170 @@
+#!/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 dataclasses
+import random
+import sys
+
+from dataclasses import dataclass
+from enum import Flag
+from typing import Optional, Tuple, Any
+
+
+def comparable_to_none(cls):
+ """
+
+ >>> @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.<locals>.ComparableToNoneVersion', cls.__name__)
+
+ return ComparableToNoneVersion
+
+
+def _is_optional_type(t):
+ """
+ >>> _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):
+ """
+ >>> _get_the_optional_type(Optional[int])
+ <class 'int'>
+ >>> _get_the_optional_type(Optional[Tuple])
+ typing.Tuple
+ >>> class A:
+ ... pass
+ >>> _get_the_optional_type(Optional[A])
+ <class '__main__.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):
+ """
+ >>> _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()