api: Adding corners processing. Signed-off-by: Tim 'mithro' Ansell <me@mith.ro>
diff --git a/scripts/python-skywater-pdk/docs/skywater_pdk.rst b/scripts/python-skywater-pdk/docs/skywater_pdk.rst index 8488d1e..0e832dd 100644 --- a/scripts/python-skywater-pdk/docs/skywater_pdk.rst +++ b/scripts/python-skywater-pdk/docs/skywater_pdk.rst
@@ -12,6 +12,14 @@ :undoc-members: :show-inheritance: +skywater\_pdk.corners module +---------------------------- + +.. automodule:: skywater_pdk.corners + :members: + :undoc-members: + :show-inheritance: + skywater\_pdk.sizes module ---------------------------
diff --git a/scripts/python-skywater-pdk/skywater_pdk/corners.py b/scripts/python-skywater-pdk/skywater_pdk/corners.py new file mode 100644 index 0000000..6e212a1 --- /dev/null +++ b/scripts/python-skywater-pdk/skywater_pdk/corners.py
@@ -0,0 +1,292 @@ +#!/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 re +import os + +from enum import Flag +from dataclasses import dataclass +from dataclasses_json import dataclass_json +from typing import Tuple, Optional + +from . import base +from .utils import OrderedFlag +from .utils import comparable_to_none +from .utils import dataclass_json_passthru_sequence_config as dj_pass_cfg + + +CornerTypeMappings = {} +# "wo" is "worst-case one" and corresponds to "fs" +CornerTypeMappings["wo"] = "fs" +# "wz" is "worst-case zero" and corresponds to "sf" +CornerTypeMappings["wz"] = "sf" +# "wp" is "worst-case power" and corresponds to "ff" +CornerTypeMappings["wp"] = "ff" +# "ws" is "worst-case speed" and corresponds to "ss" +CornerTypeMappings["ws"] = "ss" + +CornerTypeValues = [ + 'ff', + 'ss', + 'tt', + 'fs', + 'sf', +] +CORNER_TYPE_REGEX = re.compile('[tfs][tfs]') + + +class CornerType(OrderedFlag): + """ + + See Also + -------- + skywater_pdk.corners.Corner + skywater_pdk.corners.CornerFlag + + Examples + -------- + + >>> CornerType.parse('t') + CornerType.t + >>> CornerType.parse('tt') + [CornerType.t, CornerType.t] + >>> CornerType.parse('wp') + [CornerType.f, CornerType.f] + """ + t = 'Typical' # all nominal (typical) values + f = 'Fast' # fast, that is, values that make transistors run faster + s = 'Slow' # slow + + @classmethod + def parse(cls, s): + if s in CornerTypeMappings: + return cls.parse(CornerTypeMappings[s]) + if len(s) > 1: + try: + o = [] + for c in s: + o.append(cls.parse(c)) + return o + except TypeError: + raise TypeError("Unknown corner type: {}".format(s)) + if not hasattr(cls, s): + raise TypeError("Unknown corner type: {}".format(s)) + return getattr(cls, s) + + def __repr__(self): + return 'CornerType.'+self.name + + def __str__(self): + return self.value + + def to_json(self): + return self.name + + +class CornerFlag(OrderedFlag): + """ + + See Also + -------- + skywater_pdk.corners.Corner + skywater_pdk.corners.CornerType + """ + + nointpr = 'No internal power' + lv = 'Low voltage' + hv = 'High voltage' + lowhv = 'Low High Voltage' + ccsnoise = 'Composite Current Source Noise' + pwr = 'Power' + xx = 'xx' + w = 'w' + + @classmethod + def parse(cls, s): + if hasattr(cls, s): + return getattr(cls, s) + else: + raise TypeError("Unknown CornerFlags: {}".format(s)) + + def __repr__(self): + return 'CornerFlag.'+self.name + + def __str__(self): + return self.value + + def to_json(self): + return self.name + + +@comparable_to_none +class OptionalTuple(tuple): + pass + + +@comparable_to_none +@dataclass_json +@dataclass(frozen=True, order=True) +class Corner: + """ + + See Also + -------- + skywater_pdk.corners.parse_filename + skywater_pdk.base.Cell + skywater_pdk.corners.CornerType + skywater_pdk.corners.CornerFlag + + """ + corner: Tuple[CornerType, CornerType] = dj_pass_cfg() + volts: Tuple[float, ...] = dj_pass_cfg() + temps: Tuple[int, ...] = dj_pass_cfg() + flags: Optional[Tuple[CornerFlag, ...]] = dj_pass_cfg(default=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') +TEMP_REGEX = re.compile('(n?)([0-9][0-9]+)C') +def parse_filename(pathname): + """Extract corner information from a filename. + + See Also + -------- + skywater_pdk.base.parse_pathname + skywater_pdk.base.parse_filehname + + Examples + -------- + + >>> parse_filename('tt_1p80V_3p30V_3p30V_25C') + (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(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(corner=(CornerType.t, CornerType.t), volts=(1.8,), temps=(100,), flags=None), []) + + >>> parse_filename('sky130_fd_sc_ms__tt_1p80V_100C.wrap.lib') + (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(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(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(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(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(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), []) + + >>> parse_filename('sky130_fd_sc_ls__lpflow_lsbuf_lh_1__lpflow_wc_lh_level_shifters_ss_1p95V_n40C.cell.json') + (Corner(corner=(CornerType.s, CornerType.s), volts=(1.95,), temps=(-40,), flags=None), ['wc', 'lh', 'level', 'shifters']) + + >>> parse_filename('sky130_fd_sc_hvl__lsbufhv2hv_hl_1__ff_5p50V_lowhv_1p65V_lv_ss_1p60V_100C.cell.json') + (Corner(corner=(CornerType.f, CornerType.s), volts=(5.5, 1.65, 1.6), temps=(100,), flags=(CornerFlag.lowhv, CornerFlag.lv)), []) + + """ + if base.SEPERATOR in pathname: + cell, extra, extension = base.parse_filename(pathname) + else: + cell = None + extra = pathname + extension = '' + + 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: + extra = cell.name + cell = None + + # FIXME: Hack? + extra = extra.replace("lpflow_","") + extra = extra.replace("udb_","") + + kw = {} + kw['flags'] = [] + kw['volts'] = [] + kw['temps'] = [] + + bits = extra.split("_") + random = [] + while len(bits) > 0: + b = bits.pop(0) + try: + kw['corner'] = CornerType.parse(b) + break + except TypeError as e: + random.append(b) + + while len(bits) > 0: + b = bits.pop(0) + + if VOLTS_REGEX.match(b): + assert b.endswith('V'), b + kw['volts'].append(float(b[:-1].replace('p', '.'))) + elif TEMP_REGEX.match(b): + assert b.endswith('C'), b + kw['temps'].append(int(b[:-1].replace('n', '-'))) + elif CORNER_TYPE_REGEX.match(b): + # FIXME: These are horrible hacks that should be removed. + assert len(b) == 2, b + assert b[0] == b[1], b + assert 'corner' in kw, kw['corners'] + assert len(kw['corner']) == 2, kw['corners'] + assert kw['corner'][0] == kw['corner'][1], kw['corners'] + other_corner = CornerType.parse(b) + assert len(other_corner) == 2, other_corner + assert other_corner[0] == other_corner[1], other_corner + kw['corner'][1] = other_corner[0] + else: + kw['flags'].append(CornerFlag.parse(b)) + + for k, v in kw.items(): + kw[k] = tuple(v) + + if not kw['flags']: + del kw['flags'] + + if 'corner' not in kw: + raise TypeError('Invalid corner value: '+extra) + + return Corner(**kw), random + + + +# 1p60V 5p50V n40C + + + + +if __name__ == "__main__": + import doctest + doctest.testmod()