use log function for warnings about .mdwn files in pagereader.py
[openpower-isa.git] / src / openpower / decoder / power_insn.py
index 5346913648c34dd72546cd2150b06a956a3036f6..6ea7f58d37e5ed5091f4d8cd032f4d8936ff8d9e 100644 (file)
@@ -200,7 +200,7 @@ class PPCRecord:
     cry_in: _CryIn = _CryIn.ZERO
     ldst_len: _LDSTLen = _LDSTLen.NONE
     upd: _LDSTMode = _LDSTMode.NONE
-    rc: _RCOE = _RCOE.NONE
+    Rc: _RCOE = _RCOE.NONE
     form: _Form = _Form.NONE
     conditions: str = ""
     unofficial: bool = False
@@ -212,6 +212,7 @@ class PPCRecord:
         "CR out": "cr_out",
         "cry in": "cry_in",
         "ldst len": "ldst_len",
+        "rc": "Rc",
         "CONDITIONS": "conditions",
     }
 
@@ -226,13 +227,49 @@ class PPCRecord:
                 flags.add(flag)
         record["flags"] = PPCRecord.Flags(flags)
 
-        return dataclass(cls, record, keymap=PPCRecord.__KEYMAP, typemap=typemap)
+        return dataclass(cls, record,
+            keymap=PPCRecord.__KEYMAP,
+            typemap=typemap)
 
     @cached_property
     def names(self):
         return frozenset(self.comment.split("=")[-1].split("/"))
 
 
+class PPCMultiRecord(frozenset):
+    @cached_property
+    def unified(self):
+        def merge(lhs, rhs):
+            value = 0
+            mask = 0
+            lvalue = lhs.opcode.value
+            rvalue = rhs.opcode.value
+            lmask = lhs.opcode.mask
+            rmask = rhs.opcode.mask
+            bits = max(lmask.bit_length(), rmask.bit_length())
+            for bit in range(bits):
+                lvstate = ((lvalue & (1 << bit)) != 0)
+                rvstate = ((rvalue & (1 << bit)) != 0)
+                lmstate = ((lmask & (1 << bit)) != 0)
+                rmstate = ((rmask & (1 << bit)) != 0)
+                vstate = lvstate
+                mstate = True
+                if (not lmstate or not rmstate) or (lvstate != rvstate):
+                    vstate = 0
+                    mstate = 0
+                value |= (vstate << bit)
+                mask |= (mstate << bit)
+
+            opcode = opcode=Opcode(value=value, mask=mask)
+
+            return _dataclasses.replace(lhs, opcode=opcode)
+
+        return _functools.reduce(merge, self)
+
+    def __getattr__(self, attr):
+        return getattr(self.unified, attr)
+
+
 @_dataclasses.dataclass(eq=True, frozen=True)
 class SVP64Record:
     class ExtraMap(tuple):
@@ -283,7 +320,6 @@ class SVP64Record:
     cr_in: _CRInSel = _CRInSel.NONE
     cr_out: _CROutSel = _CROutSel.NONE
     extra: ExtraMap = ExtraMap()
-    pu: bool = False
     conditions: str = ""
     mode: _SVMode = _SVMode.NORMAL
 
@@ -294,7 +330,6 @@ class SVP64Record:
         "Etype": "etype",
         "CR in": "cr_in",
         "CR out": "cr_out",
-        "PU": "pu",
     }
 
     @classmethod
@@ -304,7 +339,11 @@ class SVP64Record:
             if value == "0":
                 record[key] = "NONE"
 
-        record["extra"] = cls.ExtraMap(record.pop(f"{index}") for index in range(0, 4))
+        extra = []
+        for idx in range(0, 4):
+            extra.append(record.pop(f"{idx}"))
+
+        record["extra"] = cls.ExtraMap(extra)
 
         return dataclass(cls, record, keymap=cls.__KEYMAP)
 
@@ -386,7 +425,7 @@ class Fields:
 
         def transform(item):
             (name, bitrange) = item
-            return (name, tuple(bitrange.values()))
+            return (name, list(bitrange.values()))
 
         self.__mapping = dict(map(transform, items))
 
@@ -405,93 +444,164 @@ class Fields:
         return self.__mapping.get(key, None)
 
 
-class Operands:
-    @_dataclasses.dataclass(eq=True, frozen=True)
-    class Operand:
-        name: str
+@_dataclasses.dataclass(eq=True, frozen=True)
+class Operand:
+    name: str
+
+    def disassemble(self, value, record, verbose=False):
+        raise NotImplementedError
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class DynamicOperand(Operand):
+    def disassemble(self, value, record, verbose=False):
+        span = record.fields[self.name]
+        value = value[span]
+        if verbose:
+            yield f"{int(value):0{value.bits}b}"
+            yield repr(span)
+        else:
+            yield str(int(value))
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class ImmediateOperand(DynamicOperand):
+    pass
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class StaticOperand(Operand):
+    value: int
+
+    def disassemble(self, value, record, verbose=False):
+        span = record.fields[self.name]
+        value = value[span]
+        if verbose:
+            yield f"{int(value):0{value.bits}b}"
+            yield repr(span)
+        else:
+            yield str(int(value))
 
-        def disassemble(self, value, record):
-            raise NotImplementedError
 
-    @_dataclasses.dataclass(eq=True, frozen=True)
-    class DynamicOperand(Operand):
-        def disassemble(self, value, record):
-            return str(int(value[record.fields[self.name]]))
+@_dataclasses.dataclass(eq=True, frozen=True)
+class DynamicOperandTargetAddrLI(DynamicOperand):
+    @property
+    def name(self):
+        return "LI"
 
-    @_dataclasses.dataclass(eq=True, frozen=True)
-    class StaticOperand(Operand):
-        value: int = None
+    @name.setter
+    def name(self, _):
+        pass
 
-    @_dataclasses.dataclass(eq=True, frozen=True)
-    class DynamicOperandIFormLI(DynamicOperand):
-        def disassemble(self, value, record):
-            return hex(int(_selectconcat(
-                value[record.fields["LI"]],
+    def disassemble(self, value, record, verbose=False):
+        span = record.fields["LI"]
+        value = value[span]
+        if verbose:
+            yield f"{int(value):0{value.bits}b}"
+            yield repr(span)
+            yield "target_addr = EXTS(LI || 0b00))"
+        else:
+            yield hex(int(_selectconcat(value,
                 _SelectableInt(value=0b00, bits=2))))
 
-    class DynamicOperandBFormBD(DynamicOperand):
-        def disassemble(self, value, record):
-            return hex(int(_selectconcat(
-                value[record.fields["BD"]],
+
+class DynamicOperandTargetAddrBD(DynamicOperand):
+    @property
+    def name(self):
+        return "BD"
+
+    @name.setter
+    def name(self, _):
+        pass
+
+    def disassemble(self, value, record, verbose=False):
+        span = record.fields["BD"]
+        value = value[span]
+        if verbose:
+            yield f"{int(value):0{value.bits}b}"
+            yield repr(span)
+            yield "target_addr = EXTS(BD || 0b00))"
+        else:
+            yield hex(int(_selectconcat(value,
                 _SelectableInt(value=0b00, bits=2))))
 
-    @_dataclasses.dataclass(eq=True, frozen=True)
-    class DynamicOperandGPR(DynamicOperand):
-        def disassemble(self, value, record):
-            return f"r{super().disassemble(value=value, record=record)}"
 
-    @_dataclasses.dataclass(eq=True, frozen=True)
-    class DynamicOperandFPR(DynamicOperand):
-        def disassemble(self, value, record):
-            return f"f{super().disassemble(value=value, record=record)}"
+@_dataclasses.dataclass(eq=True, frozen=True)
+class DynamicOperandGPR(DynamicOperand):
+    def disassemble(self, value, record, verbose=False):
+        span = record.fields[self.name]
+        value = value[span]
+        if verbose:
+            yield f"{int(value):0{value.bits}b}"
+            yield repr(span)
+        else:
+            yield f"r{str(int(value))}"
+
 
-    def __init__(self, insn, iterable):
+@_dataclasses.dataclass(eq=True, frozen=True)
+class DynamicOperandFPR(DynamicOperand):
+    def disassemble(self, value, record, verbose=False):
+        span = record.fields[self.name]
+        value = value[span]
+        if verbose:
+            yield f"{int(value):0{value.bits}b}"
+            yield repr(span)
+        else:
+            yield f"f{str(int(value))}"
+
+
+class Operands(tuple):
+    def __new__(cls, insn, iterable):
         branches = {
-            "b": {"LI": self.__class__.DynamicOperandIFormLI},
-            "ba": {"LI": self.__class__.DynamicOperandIFormLI},
-            "bl": {"LI": self.__class__.DynamicOperandIFormLI},
-            "bla": {"LI": self.__class__.DynamicOperandIFormLI},
-            "bc": {"BD": self.__class__.DynamicOperandBFormBD},
-            "bca": {"BD": self.__class__.DynamicOperandBFormBD},
-            "bcl": {"BD": self.__class__.DynamicOperandBFormBD},
-            "bcla": {"BD": self.__class__.DynamicOperandBFormBD},
+            "b": {"target_addr": DynamicOperandTargetAddrLI},
+            "ba": {"target_addr": DynamicOperandTargetAddrLI},
+            "bl": {"target_addr": DynamicOperandTargetAddrLI},
+            "bla": {"target_addr": DynamicOperandTargetAddrLI},
+            "bc": {"target_addr": DynamicOperandTargetAddrBD},
+            "bca": {"target_addr": DynamicOperandTargetAddrBD},
+            "bcl": {"target_addr": DynamicOperandTargetAddrBD},
+            "bcla": {"target_addr": DynamicOperandTargetAddrBD},
         }
 
         operands = []
         for operand in iterable:
-            dynamic_cls = self.__class__.DynamicOperand
-            static_cls = self.__class__.StaticOperand
+            dynamic_cls = DynamicOperand
+            static_cls = StaticOperand
 
             if "=" in operand:
                 (name, value) = operand.split("=")
                 operand = static_cls(name=name, value=int(value))
+                operands.append(operand)
             else:
+                if operand.endswith(")"):
+                    operand = operand.replace("(", " ").replace(")", "")
+                    (immediate, _, operand) = operand.partition(" ")
+                else:
+                    immediate = None
+
+                if immediate is not None:
+                    operands.append(ImmediateOperand(name=immediate))
+
                 if insn in branches and operand in branches[insn]:
                     dynamic_cls = branches[insn][operand]
 
                 if operand in _RegType.__members__:
                     regtype = _RegType[operand]
                     if regtype is _RegType.GPR:
-                        dynamic_cls = self.__class__.DynamicOperandGPR
+                        dynamic_cls = DynamicOperandGPR
                     elif regtype is _RegType.FPR:
-                        dynamic_cls = self.__class__.DynamicOperandFPR
+                        dynamic_cls = DynamicOperandFPR
 
                 operand = dynamic_cls(name=operand)
+                operands.append(operand)
 
-            operands.append(operand)
-
-        self.__operands = operands
-
-        return super().__init__()
-
-    def __repr__(self):
-        return self.__operands.__repr__()
+        return super().__new__(cls, operands)
 
-    def __iter__(self):
-        yield from self.__operands
+    def __contains__(self, key):
+        return self.__getitem__(key) is not None
 
     def __getitem__(self, key):
-        for operand in self.__operands:
+        for operand in self:
             if operand.name == key:
                 return operand
 
@@ -500,13 +610,13 @@ class Operands:
     @property
     def dynamic(self):
         for operand in self:
-            if isinstance(operand, self.__class__.DynamicOperand):
+            if isinstance(operand, DynamicOperand):
                 yield operand
 
     @property
     def static(self):
         for operand in self:
-            if isinstance(operand, self.__class__.StaticOperand):
+            if isinstance(operand, StaticOperand):
                 yield operand
 
 
@@ -660,7 +770,13 @@ class Instruction(_Mapping):
     def __hash__(self):
         return hash(int(self))
 
-    def disassemble(self, db, byteorder="little"):
+    def record(self, db):
+        record = db[self]
+        if record is None:
+            raise KeyError(self)
+        return record
+
+    def disassemble(self, db, byteorder="little", verbose=False):
         raise NotImplementedError
 
 
@@ -672,25 +788,98 @@ class WordInstruction(Instruction):
     def integer(cls, value, byteorder="little"):
         return super().integer(bits=32, value=value, byteorder=byteorder)
 
-    def disassemble(self, db, byteorder="little"):
+    @property
+    def binary(self):
+        bits = []
+        for idx in range(32):
+            bit = int(self[idx])
+            bits.append(bit)
+        return "".join(map(str, bits))
+
+    def spec(self, db):
+        record = self.record(db=db)
+
+        immediate = ""
+        dynamic_operands = []
+        for operand in record.operands.dynamic:
+            name = operand.name
+            if immediate:
+                name = f"{immediate}({name})"
+                immediate = ""
+            if isinstance(operand, ImmediateOperand):
+                immediate = operand.name
+            if not immediate:
+                dynamic_operands.append(name)
+
+        static_operands = []
+        for operand in record.operands.static:
+            static_operands.append(f"{operand.name}={operand.value}")
+
+        operands = ""
+        if dynamic_operands:
+            operands += f" {','.join(dynamic_operands)}"
+        if static_operands:
+            operands += f" ({' '.join(static_operands)})"
+
+        return f"{record.name}{operands}"
+
+    def opcode(self, db):
+        record = self.record(db=db)
+        return f"0x{record.opcode.value:08x}"
+
+    def mask(self, db):
+        record = self.record(db=db)
+        return f"0x{record.opcode.mask:08x}"
+
+    def disassemble(self, db, byteorder="little", verbose=False):
         integer = int(self)
         blob = integer.to_bytes(length=4, byteorder=byteorder)
         blob = " ".join(map(lambda byte: f"{byte:02x}", blob))
 
-        record = db[self]
+        record = self.record(db=db)
         if record is None:
             yield f"{blob}    .long 0x{integer:08x}"
+            return
+
+        operands = []
+        for operand in record.operands.dynamic:
+            operand = " ".join(operand.disassemble(value=self,
+                record=record, verbose=False))
+            operands.append(operand)
+        if operands:
+            operands = ",".join(operands)
+            operands = f" {operands}"
         else:
-            operands = []
-            for operand in record.operands.dynamic:
-                operand = operand.disassemble(self, record)
-                operands.append(operand)
-            if operands:
-                operands = ",".join(operands)
-                operands = f" {operands}"
-            else:
-                operands = ""
-            yield f"{blob}    {record.name}{operands}"
+            operands = ""
+
+        yield f"{blob}    {record.name}{operands}"
+
+        if verbose:
+            indent = (" " * 4)
+            binary = self.binary
+            spec = self.spec(db=db)
+            opcode = self.opcode(db=db)
+            mask = self.mask(db=db)
+            yield f"{indent}spec"
+            yield f"{indent}{indent}{spec}"
+            yield f"{indent}binary"
+            yield f"{indent}{indent}[0:8]   {binary[0:8]}"
+            yield f"{indent}{indent}[8:16]  {binary[8:16]}"
+            yield f"{indent}{indent}[16:24] {binary[16:24]}"
+            yield f"{indent}{indent}[24:32] {binary[24:32]}"
+            yield f"{indent}opcode"
+            yield f"{indent}{indent}{opcode}"
+            yield f"{indent}mask"
+            yield f"{indent}{indent}{mask}"
+            for operand in record.operands:
+                name = operand.name
+                yield f"{indent}{name}"
+                parts = operand.disassemble(value=self,
+                    record=record, verbose=True)
+                for part in parts:
+                    yield f"{indent}{indent}{part}"
+            yield ""
+
 
 class PrefixedInstruction(Instruction):
     class Prefix(WordInstruction.remap(range(0, 32))):
@@ -722,11 +911,12 @@ class PrefixedInstruction(Instruction):
 
 class Mode(_Mapping):
     _: _Field = range(0, 5)
+    sel: _Field = range(0, 2)
 
 
 class NormalMode(Mode):
-    class normal(Mode):
-        """normal mode"""
+    class simple(Mode):
+        """simple mode"""
         dz: Mode[3]
         sz: Mode[4]
 
@@ -790,7 +980,7 @@ class NormalMode(Mode):
         dz: Mode[3]
         sz: Mode[3]
 
-    normal: normal
+    simple: simple
     smr: smr
     pmr: pmr
     svmr: svmr
@@ -804,106 +994,104 @@ class NormalMode(Mode):
     prrc0: prrc0
 
 
-class LDSTMode(Mode):
-    class imm(Mode):
-        class normal(Mode):
-            """normal mode"""
-            zz: Mode[3]
-            els: Mode[4]
-            dz: Mode[3]
-            sz: Mode[3]
-
-        class spu(Mode):
-            """Structured Pack/Unpack"""
-            zz: Mode[3]
-            els: Mode[4]
-            dz: Mode[3]
-            sz: Mode[3]
-
-        class ffrc1(Mode):
-            """Rc=1: ffirst CR sel"""
-            inv: Mode[2]
-            CRbit: Mode[3, 4]
-
-        class ffrc0(Mode):
-            """Rc=0: ffirst z/nonz"""
-            inv: Mode[2]
-            els: Mode[3]
-            RC1: Mode[4]
-
-        class sat(Mode):
-            """sat mode: N=0/1 u/s"""
-            N: Mode[2]
-            zz: Mode[3]
-            els: Mode[4]
-            dz: Mode[3]
-            sz: Mode[3]
-
-        class prrc1(Mode):
-            """Rc=1: pred-result CR sel"""
-            inv: Mode[2]
-            CRbit: Mode[3, 4]
-
-        class prrc0(Mode):
-            """Rc=0: pred-result z/nonz"""
-            inv: Mode[2]
-            els: Mode[3]
-            RC1: Mode[4]
-
-        normal: normal
-        spu: spu
-        ffrc1: ffrc1
-        ffrc0: ffrc0
-        sat: sat
-        prrc1: prrc1
-        prrc0: prrc0
-
-    class idx(Mode):
-        class normal(Mode):
-            """normal mode"""
-            SEA: Mode[2]
-            sz: Mode[3]
-            dz: Mode[3]
-
-        class stride(Mode):
-            """strided (scalar only source)"""
-            SEA: Mode[2]
-            dz: Mode[3]
-            sz: Mode[4]
-
-        class sat(Mode):
-            """sat mode: N=0/1 u/s"""
-            N: Mode[2]
-            dz: Mode[3]
-            sz: Mode[4]
-
-        class prrc1(Mode):
-            """Rc=1: pred-result CR sel"""
-            inv: Mode[2]
-            CRbit: Mode[3, 4]
-
-        class prrc0(Mode):
-            """Rc=0: pred-result z/nonz"""
-            inv: Mode[2]
-            zz: Mode[3]
-            RC1: Mode[4]
-            dz: Mode[3]
-            sz: Mode[3]
-
-        normal: normal
-        stride: stride
-        sat: sat
-        prrc1: prrc1
-        prrc0: prrc0
-
-    imm: imm
-    idx: idx
+class LDSTImmMode(Mode):
+    class simple(Mode):
+        """simple mode"""
+        zz: Mode[3]
+        els: Mode[4]
+        dz: Mode[3]
+        sz: Mode[3]
+
+    class spu(Mode):
+        """Structured Pack/Unpack"""
+        zz: Mode[3]
+        els: Mode[4]
+        dz: Mode[3]
+        sz: Mode[3]
+
+    class ffrc1(Mode):
+        """Rc=1: ffirst CR sel"""
+        inv: Mode[2]
+        CRbit: Mode[3, 4]
+
+    class ffrc0(Mode):
+        """Rc=0: ffirst z/nonz"""
+        inv: Mode[2]
+        els: Mode[3]
+        RC1: Mode[4]
+
+    class sat(Mode):
+        """sat mode: N=0/1 u/s"""
+        N: Mode[2]
+        zz: Mode[3]
+        els: Mode[4]
+        dz: Mode[3]
+        sz: Mode[3]
+
+    class prrc1(Mode):
+        """Rc=1: pred-result CR sel"""
+        inv: Mode[2]
+        CRbit: Mode[3, 4]
+
+    class prrc0(Mode):
+        """Rc=0: pred-result z/nonz"""
+        inv: Mode[2]
+        els: Mode[3]
+        RC1: Mode[4]
+
+    simple: simple
+    spu: spu
+    ffrc1: ffrc1
+    ffrc0: ffrc0
+    sat: sat
+    prrc1: prrc1
+    prrc0: prrc0
+
+
+class LDSTIdxMode(Mode):
+    class simple(Mode):
+        """simple mode"""
+        SEA: Mode[2]
+        sz: Mode[3]
+        dz: Mode[3]
+
+    class stride(Mode):
+        """strided (scalar only source)"""
+        SEA: Mode[2]
+        dz: Mode[3]
+        sz: Mode[4]
+
+    class sat(Mode):
+        """sat mode: N=0/1 u/s"""
+        N: Mode[2]
+        dz: Mode[3]
+        sz: Mode[4]
+
+    class prrc1(Mode):
+        """Rc=1: pred-result CR sel"""
+        inv: Mode[2]
+        CRbit: Mode[3, 4]
+
+    class prrc0(Mode):
+        """Rc=0: pred-result z/nonz"""
+        inv: Mode[2]
+        zz: Mode[3]
+        RC1: Mode[4]
+        dz: Mode[3]
+        sz: Mode[3]
+
+    simple: simple
+    stride: stride
+    sat: sat
+    prrc1: prrc1
+    prrc0: prrc0
 
 
 class RM(_Mapping):
     class Mode(Mode):
         normal: NormalMode
-        ldst: LDSTMode
+        ldst_imm: LDSTImmMode
+        ldst_idx: LDSTIdxMode
 
     _: _Field = range(24)
     mmode: _Field = (0,)
@@ -935,7 +1123,135 @@ class SVP64Instruction(PrefixedInstruction):
 
     prefix: Prefix
 
-    def disassemble(self, db, byteorder="little"):
+    @property
+    def binary(self):
+        bits = []
+        for idx in range(64):
+            bit = int(self[idx])
+            bits.append(bit)
+        return "".join(map(str, bits))
+
+    def spec(self, db):
+        return f"sv.{self.suffix.spec(db=db)}"
+
+    def opcode(self, db):
+        return self.suffix.opcode(db=db)
+
+    def mask(self, db):
+        return self.suffix.mask(db=db)
+
+    def mode(self, db):
+        record = self.record(db=db)
+
+        Rc = False
+        if record.operands["Rc"] is not None:
+            Rc = bool(self[record.fields["Rc"]])
+
+        record = self.record(db=db)
+        subvl = self.prefix.rm.subvl
+        mode = self.prefix.rm.mode
+        sel = mode.sel
+
+        if record.svp64.mode is _SVMode.NORMAL:
+            mode = mode.normal
+            if sel == 0b00:
+                if mode[2] == 0b0:
+                    mode = mode.simple
+                else:
+                    if subvl == 0b00:
+                        if mode[3] == 0b0:
+                            mode = mode.smr
+                        else:
+                            mode = mode.pmr
+                    else:
+                        if mode[4] == 0b0:
+                            mode = mode.svmr
+                        else:
+                            mode = mode.pu
+            elif sel == 0b01:
+                if Rc:
+                    mode = mode.ffrc1
+                else:
+                    mode = mode.ffrc0
+            elif sel == 0b10:
+                if subvl == 0b00:
+                    mode = mode.sat
+                else:
+                    if mode[4]:
+                        mode = mode.satx
+                    else:
+                        mode = mode.satpu
+            elif sel == 0b11:
+                if Rc:
+                    mode = mode.prrc1
+                else:
+                    mode = mode.prrc0
+        elif record.svp64.mode is _SVMode.LDST_IMM:
+            mode = mode.ldst_imm
+            if sel == 0b00:
+                if mode[2] == 0b0:
+                    mode = mode.simple
+                else:
+                    mode = mode.spu
+            elif sel == 0b01:
+                if Rc:
+                    mode = mode.ffrc1
+                else:
+                    mode = mode.ffrc0
+            elif sel == 0b10:
+                mode = mode.sat
+            elif sel == 0b11:
+                if Rc:
+                    mode = mode.prrc1
+                else:
+                    mode = mode.prrc0
+        elif record.svp64.mode is _SVMode.LDST_IMM:
+            mode = mode.ldst_idx
+            if mode.sel == 0b00:
+                mode = mode.simple
+            elif mode.sel == 0b01:
+                mode = mode.stride
+            elif mode.sel == 0b10:
+                mode = mode.sat
+            elif mode.sel == 0b11:
+                if Rc:
+                    mode = mode.prrc1
+                else:
+                    mode = mode.prrc0
+
+        modes = {
+            NormalMode.simple: "normal: simple",
+            NormalMode.smr: "normal: smr",
+            NormalMode.pmr: "normal: pmr",
+            NormalMode.svmr: "normal: svmr",
+            NormalMode.pu: "normal: pu",
+            NormalMode.ffrc1: "normal: ffrc1",
+            NormalMode.ffrc0: "normal: ffrc0",
+            NormalMode.sat: "normal: sat",
+            NormalMode.satx: "normal: satx",
+            NormalMode.satpu: "normal: satpu",
+            NormalMode.prrc1: "normal: prrc1",
+            NormalMode.prrc0: "normal: prrc0",
+            LDSTImmMode.simple: "ld/st imm: simple",
+            LDSTImmMode.spu: "ld/st imm: spu",
+            LDSTImmMode.ffrc1: "ld/st imm: ffrc1",
+            LDSTImmMode.ffrc0: "ld/st imm: ffrc0",
+            LDSTImmMode.sat: "ld/st imm: sat",
+            LDSTImmMode.prrc1: "ld/st imm: prrc1",
+            LDSTImmMode.prrc0: "ld/st imm: prrc0",
+            LDSTIdxMode.simple: "ld/st idx simple",
+            LDSTIdxMode.stride: "ld/st idx stride",
+            LDSTIdxMode.sat: "ld/st idx sat",
+            LDSTIdxMode.prrc1: "ld/st idx prrc1",
+            LDSTIdxMode.prrc0: "ld/st idx prrc0",
+        }
+        for (cls, desc) in modes.items():
+            if isinstance(mode, cls):
+                return (mode, desc)
+
+        raise NotImplementedError
+
+    def disassemble(self, db, byteorder="little", verbose=False):
         integer_prefix = int(self.prefix)
         blob_prefix = integer_prefix.to_bytes(length=4, byteorder=byteorder)
         blob_prefix = " ".join(map(lambda byte: f"{byte:02x}", blob_prefix))
@@ -944,19 +1260,58 @@ class SVP64Instruction(PrefixedInstruction):
         blob_suffix = integer_suffix.to_bytes(length=4, byteorder=byteorder)
         blob_suffix = " ".join(map(lambda byte: f"{byte:02x}", blob_suffix))
 
-        record = db[self.suffix]
+        record = self.record(db=db)
         if record is None or record.svp64 is None:
             yield f"{blob_prefix}    .long 0x{int(self.prefix):08x}"
             yield f"{blob_suffix}    .long 0x{int(self.suffix):08x}"
-        else:
-            yield f"{blob_prefix}    sv.{record.name}"
-            yield f"{blob_suffix}"
+            return
+
+        yield f"{blob_prefix}    sv.{record.name}"
+        yield f"{blob_suffix}"
+
+        (mode, mode_desc) = self.mode(db=db)
+
+        if verbose:
+            indent = (" " * 4)
+            binary = self.binary
+            spec = self.spec(db=db)
+            opcode = self.opcode(db=db)
+            mask = self.mask(db=db)
+            yield f"{indent}spec"
+            yield f"{indent}{indent}{spec}"
+            yield f"{indent}binary"
+            yield f"{indent}{indent}[0:8]   {binary[0:8]}"
+            yield f"{indent}{indent}[8:16]  {binary[8:16]}"
+            yield f"{indent}{indent}[16:24] {binary[16:24]}"
+            yield f"{indent}{indent}[24:32] {binary[24:32]}"
+            yield f"{indent}{indent}[32:40] {binary[32:40]}"
+            yield f"{indent}{indent}[40:48] {binary[40:48]}"
+            yield f"{indent}{indent}[48:56] {binary[48:56]}"
+            yield f"{indent}{indent}[56:64] {binary[56:64]}"
+            yield f"{indent}opcode"
+            yield f"{indent}{indent}{opcode}"
+            yield f"{indent}mask"
+            yield f"{indent}{indent}{mask}"
+            for operand in record.operands:
+                name = operand.name
+                yield f"{indent}{name}"
+                parts = operand.disassemble(value=self,
+                    record=record, verbose=True)
+                for part in parts:
+                    yield f"{indent}{indent}{part}"
+
+            yield f"{indent}mode"
+            yield f"{indent}{indent}{mode_desc}"
+            yield ""
 
 
 def parse(stream, factory):
+    def match(entry):
+        return ("TODO" not in frozenset(entry.values()))
+
     lines = filter(lambda line: not line.strip().startswith("#"), stream)
     entries = _csv.DictReader(lines)
-    entries = filter(lambda entry: "TODO" not in frozenset(entry.values()), entries)
+    entries = filter(match, entries)
     return tuple(map(factory, entries))
 
 
@@ -1001,7 +1356,7 @@ class FieldsDatabase:
 
 
 class PPCDatabase:
-    def __init__(self, root, mdwndb, fieldsdb):
+    def __init__(self, root, mdwndb):
         # The code below groups the instructions by section:identifier.
         # We use the comment as an identifier, there's nothing better.
         # The point is to capture different opcodes for the same instruction.
@@ -1015,47 +1370,19 @@ class PPCDatabase:
                     section.Mode.INTEGER: IntegerOpcode,
                     section.Mode.PATTERN: PatternOpcode,
                 }[section.mode]
-                factory = _functools.partial(PPCRecord.CSV, opcode_cls=opcode_cls)
+                factory = _functools.partial(
+                    PPCRecord.CSV, opcode_cls=opcode_cls)
                 with open(path, "r", encoding="UTF-8") as stream:
                     for insn in parse(stream, factory):
                         records[section][insn.comment].add(insn)
 
-        # Once we collected all instructions with the same identifier,
-        # it's time to merge the different opcodes into the single pattern.
-        # At this point, we only consider masks; the algorithm as follows:
-        # 1. If any of two masks ignores the bit, it's ignored entirely.
-        # 2. If the bit is not equal between masks, it's ignored.
-        # 3. Otherwise the bits are equal and considered.
-        def merge(lhs, rhs):
-            value = 0
-            mask = 0
-            lvalue = lhs.opcode.value
-            rvalue = rhs.opcode.value
-            lmask = lhs.opcode.mask
-            rmask = rhs.opcode.mask
-            bits = max(lmask.bit_length(), rmask.bit_length())
-            for bit in range(bits):
-                lvstate = ((lvalue & (1 << bit)) != 0)
-                rvstate = ((rvalue & (1 << bit)) != 0)
-                lmstate = ((lmask & (1 << bit)) != 0)
-                rmstate = ((rmask & (1 << bit)) != 0)
-                vstate = lvstate
-                mstate = True
-                if (not lmstate or not rmstate) or (lvstate != rvstate):
-                    vstate = 0
-                    mstate = 0
-                value |= (vstate << bit)
-                mask |= (mstate << bit)
-            return _dataclasses.replace(lhs, opcode=Opcode(value=value, mask=mask))
-
         db = dd(set)
         for (section, group) in records.items():
             for records in group.values():
-                db[section].add(_functools.reduce(merge, records))
+                db[section].add(PPCMultiRecord(records))
 
         self.__db = db
         self.__mdwndb = mdwndb
-        self.__fieldsdb = fieldsdb
 
         return super().__init__()
 
@@ -1071,7 +1398,7 @@ class PPCDatabase:
             if not key.endswith("."):
                 return False
 
-            if not record.rc is _RCOE.RC:
+            if not record.Rc is _RCOE.RC:
                 return False
 
             return exact_match(key[:-1], record)
@@ -1100,8 +1427,11 @@ class PPCDatabase:
 
         for (section, records) in self.__db.items():
             for record in records:
-                if (exact_match(key, record) or
-                        Rc_match(key, record) or
+                if exact_match(key, record):
+                    return (section, record)
+
+            for record in records:
+                if (Rc_match(key, record) or
                         LK_match(key, record) or
                         AA_match(key, record)):
                     return (section, record)
@@ -1144,7 +1474,7 @@ class Database:
 
         mdwndb = MarkdownDatabase()
         fieldsdb = FieldsDatabase()
-        ppcdb = PPCDatabase(root=root, mdwndb=mdwndb, fieldsdb=fieldsdb)
+        ppcdb = PPCDatabase(root=root, mdwndb=mdwndb)
         svp64db = SVP64Database(root=root, ppcdb=ppcdb)
 
         db = set()