| #!/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 abc |
| import os |
| import operator |
| |
| from dataclasses import dataclass |
| from dataclasses_json import dataclass_json |
| |
| |
| def parse_drive(s): |
| return DriveStrength.from_suffix(s) |
| |
| |
| class InvalidSuffixError(ValueError): |
| def __init__(self, s): |
| ValueError.__init__(self, "Invalid suffix: {}".format(s.strip())) |
| |
| |
| class DriveStrength(abc.ABC): |
| """Drive strength variants of a given cell. |
| |
| >>> d1 = DriveStrength.from_suffix("_1") |
| >>> d2 = DriveStrength.from_suffix("_lp") |
| >>> d3 = DriveStrength.from_suffix("_m") |
| >>> d4 = DriveStrength.from_suffix("_2") |
| >>> DriveStrength.from_suffix("_abc") |
| Traceback (most recent call last): |
| ... |
| InvalidSuffixError: Invalid suffix: _abc |
| >>> l = [d1, d2, d3, d4] |
| >>> l |
| [DriveStrengthNumeric(units=1), DriveStrengthLowPower(variant=0), DriveStrengthMinimum(), DriveStrengthNumeric(units=2)] |
| >>> l.sort() |
| >>> l |
| [DriveStrengthNumeric(units=1), DriveStrengthNumeric(units=2), DriveStrengthLowPower(variant=0), DriveStrengthMinimum()] |
| """ |
| |
| @abc.abstractmethod |
| def describe(self): |
| raise NotImplementedError |
| |
| @property |
| @abc.abstractmethod |
| def suffix(self): |
| raise NotImplementedError |
| |
| @classmethod |
| def from_suffix(cls, s): |
| errors = [] |
| for subcls in cls.__subclasses__(): |
| try: |
| return subcls.from_suffix(s) |
| except (ValueError, AssertionError) as e: |
| errors.append((subcls.__name__, e)) |
| assert errors, ("Unknown error!?", s) |
| msg = [s, ''] |
| for cls_name, e in errors: |
| if isinstance(e, ValueError): |
| continue |
| msg.append("{} failed with: {}".format(cls_name, e)) |
| raise InvalidSuffixError("\n".join(msg)) |
| |
| def __str__(self): |
| return "drive strength {}".format(self.describe()) |
| |
| def _cmp(self, op, o): |
| if not isinstance(o, DriveStrength): |
| return False |
| return op(self.suffix, o.suffix) |
| |
| # Comparison operators |
| def __lt__(self, o): |
| return self._cmp(operator.lt, o) |
| |
| def __le__(self, o): |
| return self._cmp(operator.le, o) |
| |
| def __eq__(self, o): |
| return self._cmp(operator.eq, o) |
| |
| def __ne__(self, o): |
| return self._cmp(operator.ne, o) |
| |
| def __ge__(self, o): |
| return self._cmp(operator.ge, o) |
| |
| def __gt__(self, o): |
| return self._cmp(operator.gt, o) |
| |
| |
| @dataclass_json |
| @dataclass(frozen=True) |
| class DriveStrengthNumeric(DriveStrength): |
| """ |
| >>> s1 = DriveStrengthNumeric.from_suffix("_1") |
| >>> s2 = DriveStrengthNumeric.from_suffix("_2") |
| >>> s3 = DriveStrengthNumeric.from_suffix("_3") |
| >>> DriveStrengthNumeric.from_suffix("_0") |
| Traceback (most recent call last): |
| ... |
| InvalidSuffixError: Invalid suffix: _0 |
| >>> s1 |
| DriveStrengthNumeric(units=1) |
| >>> s2 |
| DriveStrengthNumeric(units=2) |
| >>> s3 |
| DriveStrengthNumeric(units=3) |
| >>> str(s1) |
| 'drive strength 1 units' |
| >>> str(s2) |
| 'drive strength 2 units' |
| >>> str(s3) |
| 'drive strength 3 units (invalid?)' |
| >>> s1.describe() |
| '1 units' |
| >>> s2.describe() |
| '2 units' |
| >>> s3.describe() |
| '3 units (invalid?)' |
| >>> s1.suffix |
| '_1' |
| >>> s2.suffix |
| '_2' |
| >>> s3.suffix |
| '_3' |
| """ |
| units: int |
| |
| VALID_UNIT_VALUES = (0, 1, 2, 4) |
| |
| def describe(self): |
| suffix = "" |
| if self.units not in self.VALID_UNIT_VALUES: |
| suffix = " (invalid?)" |
| |
| return "{} units{}".format(self.units, suffix) |
| |
| @property |
| def suffix(self): |
| return "_{}".format(self.units) |
| |
| @classmethod |
| def from_suffix(cls, s): |
| if not s.startswith("_"): |
| raise InvalidSuffixError(s) |
| i = int(s[1:]) |
| if i < 0: |
| raise InvalidSuffixError(s) |
| return cls(i) |
| |
| |
| @dataclass_json |
| @dataclass(frozen=True) |
| class DriveStrengthLowPower(DriveStrength): |
| """ |
| >>> lp = DriveStrengthLowPower.from_suffix("_lp") |
| >>> lp2 = DriveStrengthLowPower.from_suffix("_lp2") |
| >>> lp3 = DriveStrengthLowPower.from_suffix("_lp3") |
| >>> DriveStrengthLowPower.from_suffix("_ld") |
| Traceback (most recent call last): |
| ... |
| InvalidSuffixError: Invalid suffix: _ld |
| >>> lp |
| DriveStrengthLowPower(variant=0) |
| >>> lp2 |
| DriveStrengthLowPower(variant=1) |
| >>> lp3 |
| DriveStrengthLowPower(variant=2) |
| >>> str(lp) |
| 'drive strength Low Power' |
| >>> str(lp2) |
| 'drive strength Low Power (alternative)' |
| >>> str(lp3) |
| 'drive strength Low Power (extra alternative 0)' |
| >>> lp.describe() |
| 'Low Power' |
| >>> lp2.describe() |
| 'Low Power (alternative)' |
| >>> lp3.describe() |
| 'Low Power (extra alternative 0)' |
| >>> lp.suffix |
| '_lp' |
| >>> lp2.suffix |
| '_lp2' |
| >>> lp3.suffix |
| '_lp3' |
| """ |
| variant: int = 0 |
| |
| def describe(self): |
| if self.variant == 0: |
| suffix = "" |
| elif self.variant == 1: |
| suffix = " (alternative)" |
| else: |
| assert self.variant >= 2, self.variant |
| suffix = " (extra alternative {})".format(self.variant-2) |
| return "Low Power"+suffix |
| |
| @property |
| def suffix(self): |
| if self.variant == 0: |
| return "_lp" |
| else: |
| assert self.variant > 0, self.variant |
| return "_lp{}".format(self.variant+1) |
| |
| @classmethod |
| def from_suffix(cls, s): |
| if not s.startswith("_lp"): |
| raise InvalidSuffixError(s) |
| if s == "_lp": |
| return cls() |
| elif s == "_lp2": |
| return cls(1) |
| else: |
| try: |
| i = int(s[3:]) |
| except ValueError as e: |
| raise InvalidSuffixError(s) |
| assert i > 2, (s, i) |
| return cls(i-1) |
| |
| |
| class DriveStrengthMinimum(DriveStrength): |
| """ |
| >>> m = DriveStrengthMinimum.from_suffix("_m") |
| >>> DriveStrengthMinimum.from_suffix("_m2") |
| Traceback (most recent call last): |
| ... |
| InvalidSuffixError: Invalid suffix: _m2 |
| >>> m |
| DriveStrengthMinimum() |
| >>> str(m) |
| 'drive strength minimum' |
| >>> m.describe() |
| 'minimum' |
| >>> m.suffix |
| '_m' |
| """ |
| |
| def __repr__(self): |
| return "DriveStrengthMinimum()" |
| |
| def describe(self): |
| return "minimum" |
| |
| @property |
| def suffix(self): |
| return "_m" |
| |
| @classmethod |
| def from_suffix(cls, s): |
| if s != "_m": |
| raise InvalidSuffixError(s) |
| return cls() |
| |
| |
| if __name__ == "__main__": |
| import doctest |
| doctest.testmod() |