From bef52a38023bfce850174a3156d61170f767dd01 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Fri, 5 May 2023 20:34:18 -0700 Subject: [PATCH] add FPSCRState and FPSCRRecord and a FPSCR smoke-test --- src/openpower/fpscr.py | 544 ++++++++++++++++++++++++++++++++++++ src/openpower/test_fpscr.py | 27 ++ 2 files changed, 571 insertions(+) create mode 100644 src/openpower/fpscr.py create mode 100644 src/openpower/test_fpscr.py diff --git a/src/openpower/fpscr.py b/src/openpower/fpscr.py new file mode 100644 index 00000000..98e5ed9e --- /dev/null +++ b/src/openpower/fpscr.py @@ -0,0 +1,544 @@ +# SPDX-License-Identifier: LGPLv3+ +# Funded by NLnet https://nlnet.nl/ +""" Record for FPSCR as defined in +Power ISA v3.1B Book I section 4.2.2 page 136(162) + +FPSCR fields in MSB0: + +| Bits | Mnemonic | Description | +|-------|----------|-------------------------------------------------------------------------| +| 0:28 |   | Reserved | +| 29:31 | DRN | Decimal Rounding Mode | +| 32 | FX | Floating-Point Exception Summary | +| 33 | FEX | Floating-Point Enabled Exception Summary | +| 34 | VX | Floating-Point Invalid Operation Exception Summary | +| 35 | OX | Floating-Point Overflow Exception | +| 36 | UX | Floating-Point Underflow Exception | +| 37 | ZX | Floating-Point Zero Divide Exception | +| 38 | XX | Floating-Point Inexact Exception | +| 39 | VXSNAN | Floating-Point Invalid Operation Exception (SNaN) | +| 40 | VXISI | Floating-Point Invalid Operation Exception (∞ - ∞) | +| 41 | VXIDI | Floating-Point Invalid Operation Exception (∞ ÷ ∞) | +| 42 | VXZDZ | Floating-Point Invalid Operation Exception (0 ÷ 0) | +| 43 | VXIMZ | Floating-Point Invalid Operation Exception (∞ × 0) | +| 44 | VXVC | Floating-Point Invalid Operation Exception (Invalid Compare) | +| 45 | FR | Floating-Point Fraction Rounded | +| 46 | FI | Floating-Point Fraction Inexact | +| 47:51 | FPRF | Floating-Point Result Flags | +| 47 | C | Floating-Point Result Class Descriptor | +| 48:51 | FPCC | Floating-Point Condition Code | +| 48 | FL | Floating-Point Less Than or Negative | +| 49 | FG | Floating-Point Greater Than or Positive | +| 50 | FE | Floating-Point Equal or Zero | +| 51 | FU | Floating-Point Unordered or NaN | +| 52 |   | Reserved | +| 53 | VXSOFT | Floating-Point Invalid Operation Exception (Software-Defined Condition) | +| 54 | VXSQRT | Floating-Point Invalid Operation Exception (Invalid Square Root) | +| 55 | VXCVI | Floating-Point Invalid Operation Exception (Invalid Integer Convert) | +| 56 | VE | Floating-Point Invalid Operation Exception Enable | +| 57 | OE | Floating-Point Overflow Exception Enable | +| 58 | UE | Floating-Point Underflow Exception Enable | +| 59 | ZE | Floating-Point Zero Divide Exception Enable | +| 60 | XE | Floating-Point Inexact Exception Enable | +| 61 | NI | Floating-Point Non-IEEE Mode | +| 62:63 | RN | Floating-Point Rounding Control | +""" + +from nmigen import Record +from typing import NoReturn +from nmutil.plain_data import plain_data +import linecache +from openpower.decoder.selectable_int import ( + FieldSelectableInt, SelectableInt) + + +def _parse_line_fields(line): + # type: (str) -> None | list[str] + sline = line.strip() + if not sline.startswith("|"): + return None + if not sline.endswith("|"): + return None + if sline == "|": + return None + return [v.strip() for v in sline[1:-2].split("|")] + + +_BITS_FIELD = "Bits" +_MNEMONIC_FIELD = "Mnemonic" +FPSCR_WIDTH = 64 + + +@plain_data() +class _MutableField: + __slots__ = "name", "bits_msb0", "lineno", "include_in_record" + + def __init__(self, name, bits_msb0, lineno, include_in_record=True): + # type: (str, int | range, int, bool) -> None + self.name = name + self.bits_msb0 = bits_msb0 + self.lineno = lineno + self.include_in_record = include_in_record + + def bits_msb0_iter(self): + # type: () -> range | tuple[int] + if isinstance(self.bits_msb0, int): + return self.bits_msb0, + return self.bits_msb0 + + def to_field(self): + # type () -> FPSCRField + return FPSCRField(name=self.name, bits_msb0=self.bits_msb0, + include_in_record=self.include_in_record) + + +@plain_data(frozen=True, unsafe_hash=True) +class FPSCRField: + __slots__ = "name", "bits_msb0", "include_in_record" + + def __init__(self, name, bits_msb0, include_in_record): + # type: (str, int | range, bool) -> None + self.name = name + self.bits_msb0 = bits_msb0 + self.include_in_record = include_in_record + """True if this field should be + included in `FPSCRRecord`, since there are some overlapping fields and + `Record` doesn't support that. + """ + + def bits_msb0_iter(self): + # type: () -> range | tuple[int] + if isinstance(self.bits_msb0, int): + return self.bits_msb0, + return self.bits_msb0 + + +def _parse_fields(): + # type: () -> tuple[FPSCRField, ...] + lines = __doc__.splitlines() + in_header_sep = False + in_table_body = False + header_fields = [] # type: list[str] + header_lineno = 0 + fields = {} # type: dict[str, _MutableField] + bit_fields = [None] * FPSCR_WIDTH # type: list[_MutableField | None] + lineno = 0 + + def raise_(msg, col=1, err_lineno=None): + # type: (str, int, int | None) -> NoReturn + nonlocal lineno + if err_lineno is None: + err_lineno = lineno + for i in range(10000): # 10000 is random limit if we can't read + if linecache.getline(__file__, i).strip().startswith('"'): + break + err_lineno += 1 # lines before doc comment start + raise SyntaxError(msg, ( + __file__, err_lineno + 3, col, lines[err_lineno])) + + for lineno, line in enumerate(lines): + line_fields = _parse_line_fields(line) + if in_table_body: + if line_fields is None: + if len(fields) == 0: + raise_("missing table body") + break + if len(line_fields) != len(header_fields): + raise_("wrong number of fields") + fields_dict = {k: v for k, v in zip(header_fields, line_fields)} + name = fields_dict[_MNEMONIC_FIELD] + if name == "" or name == " ": + continue + if not name.isidentifier(): + raise_(f"invalid field name {name!r}") + if name in fields: + raise_(f"duplicate field name {name}") + bits_str = fields_dict[_BITS_FIELD] + bits_fields_str = bits_str.split(":") + if len(bits_fields_str) not in (1, 2) or not all( + v.isascii() and v.isdigit() for v in bits_fields_str): + raise_(f"`{_BITS_FIELD}` field must be " + f"of the form `23` or `23:56`") + bits_fields = [int(v, base=10) for v in bits_fields_str] + if not all(0 <= v < FPSCR_WIDTH for v in bits_fields): + raise_(f"`{_BITS_FIELD}` field value is beyond the " + f"limits of FPSCR: must be `0 <= v < {FPSCR_WIDTH}`") + if len(bits_fields) == 2: + first, last = bits_fields + if first > last: + raise_(f"`{_BITS_FIELD}` field value is an improper " + f"range: {first} > {last}") + bits = range(first, last + 1) + else: + bits = bits_fields[0] + field = _MutableField(name=name, bits_msb0=bits, lineno=lineno) + fields[name] = field + for bit in field.bits_msb0_iter(): + old_field = bit_fields[bit] + if old_field is not None: + # field is overwritten -- don't include in Record + old_field.include_in_record = False + bit_fields[bit] = field + elif in_header_sep: + if line_fields is None: + raise_("missing header separator line") + for v in line_fields: + if v != "-" * len(v): + raise_("header separator field isn't just hyphens") + if len(line_fields) != len(header_fields): + raise_("wrong number of fields") + in_header_sep = False + in_table_body = True + else: + if line_fields is None: + continue + if _BITS_FIELD not in line_fields: + raise_(f"missing `{_BITS_FIELD}` field") + if _MNEMONIC_FIELD not in line_fields: + raise_(f"missing `{_MNEMONIC_FIELD}` field") + if len(set(line_fields)) != len(line_fields): + raise_("duplicate header field") + header_fields = line_fields + in_header_sep = True + header_lineno = lineno + if len(fields) == 0: + raise_("missing table") + # insert reserved fields and check for partially overwritten fields + for bit in range(FPSCR_WIDTH): + field = bit_fields[bit] + if field is None: + start = bit + bit += 1 + while bit < FPSCR_WIDTH and bit_fields[bit] is None: + bit += 1 + field = _MutableField(name=f"RESERVED_{start}_{bit - 1}", + bits_msb0=range(start, bit), + lineno=header_lineno) + if len(field.bits_msb0) == 1: + field.bits_msb0 = start + field.name = f"RESERVED_{start}" + for bit in field.bits_msb0_iter(): + bit_fields[bit] = field + if field.name in fields: + raise_(f"field {field.name}'s name conflicts with a " + f"generated reserved field", + err_lineno=fields[field.name].lineno) + fields[field.name] = field + elif not field.include_in_record: + raise_(f"field {field.name} is partially overwritten -- " + f"this is an error because FPSCRRecord will have " + f"incorrect fields", err_lineno=field.lineno) + else: + bit += 1 + return tuple(f.to_field() for f in fields.values()) + + +FPSCR_FIELDS_MSB0 = _parse_fields() # type: tuple[FPSCRField, ...] +""" All fields in FPSCR. """ + + +def _calc_record_layout_lsb0(): + # type: () -> list[tuple[str, int]] + fields_lsb0 = [] # type: list[tuple[int, int, str]] + for field in FPSCR_FIELDS_MSB0: + if not field.include_in_record: + continue + start_msb0 = field.bits_msb0_iter()[0] + field_len = len(field.bits_msb0_iter()) + start_lsb0 = FPSCR_WIDTH - 1 - start_msb0 + fields_lsb0.append((start_lsb0, field_len, field.name)) + fields_lsb0.sort() + # _parse_fields already checks for partially overlapping fields and + # inserts reserved fields ensuring the returned fields cover every bit + # exactly one, therefore this is correct + return [(name, f_len) for _, f_len, name in fields_lsb0] + + +class FPSCRRecord(Record): + layout = _calc_record_layout_lsb0() + + def __init__(self, name=None): + super().__init__(name=name, layout=FPSCRRecord.layout) + + +class FPSCRState(SelectableInt): + def __init__(self, value=0): + SelectableInt.__init__(self, value, FPSCR_WIDTH) + self.fsi = {} + for field in FPSCR_FIELDS_MSB0: + bits_msb0 = tuple(field.bits_msb0_iter()) + self.fsi[field.name] = FieldSelectableInt(self, bits_msb0) + + @property + def DRN(self): + return self.fsi['DRN'].asint(msb0=True) + + @DRN.setter + def DRN(self, value): + self.fsi['DRN'].eq(value) + + @property + def FX(self): + return self.fsi['FX'].asint(msb0=True) + + @FX.setter + def FX(self, value): + self.fsi['FX'].eq(value) + + @property + def FEX(self): + return self.fsi['FEX'].asint(msb0=True) + + @FEX.setter + def FEX(self, value): + self.fsi['FEX'].eq(value) + + @property + def VX(self): + return self.fsi['VX'].asint(msb0=True) + + @VX.setter + def VX(self, value): + self.fsi['VX'].eq(value) + + @property + def OX(self): + return self.fsi['OX'].asint(msb0=True) + + @OX.setter + def OX(self, value): + self.fsi['OX'].eq(value) + + @property + def UX(self): + return self.fsi['UX'].asint(msb0=True) + + @UX.setter + def UX(self, value): + self.fsi['UX'].eq(value) + + @property + def ZX(self): + return self.fsi['ZX'].asint(msb0=True) + + @ZX.setter + def ZX(self, value): + self.fsi['ZX'].eq(value) + + @property + def XX(self): + return self.fsi['XX'].asint(msb0=True) + + @XX.setter + def XX(self, value): + self.fsi['XX'].eq(value) + + @property + def VXSNAN(self): + return self.fsi['VXSNAN'].asint(msb0=True) + + @VXSNAN.setter + def VXSNAN(self, value): + self.fsi['VXSNAN'].eq(value) + + @property + def VXISI(self): + return self.fsi['VXISI'].asint(msb0=True) + + @VXISI.setter + def VXISI(self, value): + self.fsi['VXISI'].eq(value) + + @property + def VXIDI(self): + return self.fsi['VXIDI'].asint(msb0=True) + + @VXIDI.setter + def VXIDI(self, value): + self.fsi['VXIDI'].eq(value) + + @property + def VXZDZ(self): + return self.fsi['VXZDZ'].asint(msb0=True) + + @VXZDZ.setter + def VXZDZ(self, value): + self.fsi['VXZDZ'].eq(value) + + @property + def VXIMZ(self): + return self.fsi['VXIMZ'].asint(msb0=True) + + @VXIMZ.setter + def VXIMZ(self, value): + self.fsi['VXIMZ'].eq(value) + + @property + def VXVC(self): + return self.fsi['VXVC'].asint(msb0=True) + + @VXVC.setter + def VXVC(self, value): + self.fsi['VXVC'].eq(value) + + @property + def FR(self): + return self.fsi['FR'].asint(msb0=True) + + @FR.setter + def FR(self, value): + self.fsi['FR'].eq(value) + + @property + def FI(self): + return self.fsi['FI'].asint(msb0=True) + + @FI.setter + def FI(self, value): + self.fsi['FI'].eq(value) + + @property + def FPRF(self): + return self.fsi['FPRF'].asint(msb0=True) + + @FPRF.setter + def FPRF(self, value): + self.fsi['FPRF'].eq(value) + + @property + def C(self): + return self.fsi['C'].asint(msb0=True) + + @C.setter + def C(self, value): + self.fsi['C'].eq(value) + + @property + def FPCC(self): + return self.fsi['FPCC'].asint(msb0=True) + + @FPCC.setter + def FPCC(self, value): + self.fsi['FPCC'].eq(value) + + @property + def FL(self): + return self.fsi['FL'].asint(msb0=True) + + @FL.setter + def FL(self, value): + self.fsi['FL'].eq(value) + + @property + def FG(self): + return self.fsi['FG'].asint(msb0=True) + + @FG.setter + def FG(self, value): + self.fsi['FG'].eq(value) + + @property + def FE(self): + return self.fsi['FE'].asint(msb0=True) + + @FE.setter + def FE(self, value): + self.fsi['FE'].eq(value) + + @property + def FU(self): + return self.fsi['FU'].asint(msb0=True) + + @FU.setter + def FU(self, value): + self.fsi['FU'].eq(value) + + @property + def VXSOFT(self): + return self.fsi['VXSOFT'].asint(msb0=True) + + @VXSOFT.setter + def VXSOFT(self, value): + self.fsi['VXSOFT'].eq(value) + + @property + def VXSQRT(self): + return self.fsi['VXSQRT'].asint(msb0=True) + + @VXSQRT.setter + def VXSQRT(self, value): + self.fsi['VXSQRT'].eq(value) + + @property + def VXCVI(self): + return self.fsi['VXCVI'].asint(msb0=True) + + @VXCVI.setter + def VXCVI(self, value): + self.fsi['VXCVI'].eq(value) + + @property + def VE(self): + return self.fsi['VE'].asint(msb0=True) + + @VE.setter + def VE(self, value): + self.fsi['VE'].eq(value) + + @property + def OE(self): + return self.fsi['OE'].asint(msb0=True) + + @OE.setter + def OE(self, value): + self.fsi['OE'].eq(value) + + @property + def UE(self): + return self.fsi['UE'].asint(msb0=True) + + @UE.setter + def UE(self, value): + self.fsi['UE'].eq(value) + + @property + def ZE(self): + return self.fsi['ZE'].asint(msb0=True) + + @ZE.setter + def ZE(self, value): + self.fsi['ZE'].eq(value) + + @property + def XE(self): + return self.fsi['XE'].asint(msb0=True) + + @XE.setter + def XE(self, value): + self.fsi['XE'].eq(value) + + @property + def NI(self): + return self.fsi['NI'].asint(msb0=True) + + @NI.setter + def NI(self, value): + self.fsi['NI'].eq(value) + + @property + def RN(self): + return self.fsi['RN'].asint(msb0=True) + + @RN.setter + def RN(self, value): + self.fsi['RN'].eq(value) + + +if __name__ == "__main__": + from pprint import pprint + print("FPSCR_FIELDS_MSB0:") + pprint(FPSCR_FIELDS_MSB0) + print("FPSCRRecord.layout:") + pprint(FPSCRRecord.layout) + print("FPSCRState.fsi:") + pprint(FPSCRState().fsi) diff --git a/src/openpower/test_fpscr.py b/src/openpower/test_fpscr.py new file mode 100644 index 00000000..7c0c99a4 --- /dev/null +++ b/src/openpower/test_fpscr.py @@ -0,0 +1,27 @@ +from openpower.fpscr import FPSCRState +import unittest + + +class TestFPSCR(unittest.TestCase): + def test_smoke_test(self): + """ test to ensure FPSCR isn't horribly broken -- not at all thorough -- a smoke-test """ + FPSCR = FPSCRState() + self.assertEqual(FPSCR.RN, 0) + FPSCR.RN = 3 + self.assertEqual(FPSCR.RN, 3) + expected = 0x3 + self.assertEqual(FPSCR, expected) + self.assertEqual(FPSCR.VXCVI, 0) + FPSCR.VXCVI = 1 + self.assertEqual(FPSCR.VXCVI, 1) + expected |= 1 << (64 - 55 - 1) + self.assertEqual(FPSCR, expected) + self.assertEqual(FPSCR.FX, 0) + FPSCR.FX = 1 + self.assertEqual(FPSCR.FX, 1) + expected |= 1 << (64 - 32 - 1) + self.assertEqual(FPSCR, expected) + + +if __name__ == "__main__": + unittest.main() -- 2.30.2