| #!/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 typing import List, Optional |
| |
| from .base import parse_filename as base_parse_filename |
| |
| |
| CornerTypeMappings = {} |
| # "fs" is sometimes called "wp" meaning worst-case power, |
| CornerTypeMappings["wp"] = "fs" |
| # "sf" is sometimes called "ws" meaning worst-case speed |
| CornerTypeMappings["ws"] = "sf" |
| |
| |
| class CornerType(Flag): |
| """ |
| >>> CornerType.parse('t') |
| CornerType.t |
| >>> CornerType.parse('tt') |
| [CornerType.t, CornerType.t] |
| >>> CornerType.parse('wp') |
| [CornerType.f, CornerType.s] |
| """ |
| 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: |
| o = [] |
| for c in s: |
| o.append(cls.parse(c)) |
| return o |
| return getattr(cls, s) |
| |
| def __repr__(self): |
| return 'CornerType.'+self.name |
| |
| def __str__(self): |
| return self.value |
| |
| |
| |
| class CornerFlag(Flag): |
| 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): |
| return getattr(cls, s) |
| else: |
| raise TypeError("Unknown CornerFlags: {}".format(s)) |
| |
| def __repr__(self): |
| return 'CornerType.'+self.name |
| |
| def __str__(self): |
| return self.value |
| |
| @dataclass |
| class Corner: |
| volts: List[float] |
| temps: List[int] |
| flags: List[CornerFlag] |
| types: Optional[List[CornerType]] = None |
| |
| |
| 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('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]) |
| |
| >>> parse_filename('sky130_fd_sc_ms-tt_1p80V_100C.wrap.json') |
| Corner(volts=[1.8], temps=[100], flags=[], types=[CornerType.t, CornerType.t]) |
| |
| >>> parse_filename('sky130_fd_sc_ms-tt_1p80V_100C.wrap.lib') |
| Corner(volts=[1.8], temps=[100], flags=[], types=[CornerType.t, CornerType.t]) |
| |
| >>> 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]) |
| |
| >>> parse_filename('sky130_fd_sc_ms-wp_1p65V_n40C.wrap.json') |
| Corner(volts=[1.65], temps=[-40], flags=[], types=[CornerType.f, CornerType.s]) |
| |
| >>> 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]) |
| |
| >>> 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]) |
| |
| >>> 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]) |
| |
| """ |
| cell, extra, extension = base_parse_filename(pathname) |
| |
| if extension not in ('', 'lib', '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)) |
| |
| 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: |
| try: |
| kw['flags'].append(CornerFlag.parse(b)) |
| except TypeError as e: |
| if 'types' not in kw: |
| kw['types'] = CornerType.parse(b) |
| else: |
| raise |
| |
| return Corner(**kw) |
| |
| |
| |
| # 1p60V 5p50V n40C |
| |
| |
| |
| |
| if __name__ == "__main__": |
| import doctest |
| doctest.testmod() |