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