add FPSCRState and FPSCRRecord and a FPSCR smoke-test
authorJacob Lifshay <programmerjake@gmail.com>
Sat, 6 May 2023 03:34:18 +0000 (20:34 -0700)
committerLuke Kenneth Casson Leighton <lkcl@lkcl.net>
Fri, 2 Jun 2023 18:51:17 +0000 (19:51 +0100)
src/openpower/fpscr.py [new file with mode: 0644]
src/openpower/test_fpscr.py [new file with mode: 0644]

diff --git a/src/openpower/fpscr.py b/src/openpower/fpscr.py
new file mode 100644 (file)
index 0000000..98e5ed9
--- /dev/null
@@ -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  | &nbsp;   | 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    | &nbsp;   | 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 == "&nbsp;":
+                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 (file)
index 0000000..7c0c99a
--- /dev/null
@@ -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()