| #!/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 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" |
| |
| |
| class CornerType(OrderedFlag): |
| """ |
| >>> 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): |
| nointpr = 'No internal power' |
| lv = 'Low voltage' |
| ccsnoise = 'Composite Current Source Noise' |
| pwr = 'Power' |
| |
| @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: |
| 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. |
| |
| >>> 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) |
| |
| """ |
| 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 |
| |
| kw = {} |
| kw['flags'] = [] |
| kw['volts'] = [] |
| kw['temps'] = [] |
| |
| bits = extra.split("_") |
| 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', '-'))) |
| else: |
| 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) |
| |
| |
| |
| # 1p60V 5p50V n40C |
| |
| |
| |
| |
| if __name__ == "__main__": |
| import doctest |
| doctest.testmod() |