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