power_insn: refactor operands; simplify lookups
[openpower-isa.git] / src / openpower / decoder / power_insn.py
index 87b816a24c39dc74bf0b6ff53469c869963afead..a53e2195b7874bacb60475c93322f84ff6f01e5a 100644 (file)
@@ -3,7 +3,6 @@ import csv as _csv
 import dataclasses as _dataclasses
 import enum as _enum
 import functools as _functools
-import itertools as _itertools
 import os as _os
 import operator as _operator
 import pathlib as _pathlib
@@ -29,14 +28,19 @@ from openpower.decoder.power_enums import (
     RCOE as _RCOE,
     CryIn as _CryIn,
     Form as _Form,
-    SVEtype as _SVEtype,
-    SVmask_src as _SVmask_src,
+    SVEType as _SVEType,
+    SVMaskSrc as _SVMaskSrc,
     SVMode as _SVMode,
-    SVPtype as _SVPtype,
+    SVPType as _SVPType,
     SVExtra as _SVExtra,
     RegType as _RegType,
+    SVP64RMMode as _SVP64RMMode,
     SVExtraRegType as _SVExtraRegType,
     SVExtraReg as _SVExtraReg,
+    SVP64SubVL as _SVP64SubVL,
+    SVP64Pred as _SVP64Pred,
+    SVP64PredMode as _SVP64PredMode,
+    SVP64Width as _SVP64Width,
 )
 from openpower.decoder.selectable_int import (
     SelectableInt as _SelectableInt,
@@ -51,7 +55,8 @@ from openpower.decoder.pseudo.pagereader import ISA as _ISA
 
 
 @_functools.total_ordering
-class Verbosity(_enum.Enum):
+class Style(_enum.Enum):
+    LEGACY = _enum.auto()
     SHORT = _enum.auto()
     NORMAL = _enum.auto()
     VERBOSE = _enum.auto()
@@ -162,11 +167,11 @@ class Opcode:
         return "".join(pattern(self.value, self.mask, self.value.bit_length()))
 
     def match(self, key):
-        if isinstance(key, Instruction):
-            key = int(key)
         return ((self.value & self.mask) == (key & self.mask))
 
 
+@_functools.total_ordering
+@_dataclasses.dataclass(eq=True, frozen=True)
 class IntegerOpcode(Opcode):
     def __init__(self, value):
         if value.startswith("0b"):
@@ -180,6 +185,8 @@ class IntegerOpcode(Opcode):
         return super().__init__(value=value, mask=mask)
 
 
+@_functools.total_ordering
+@_dataclasses.dataclass(eq=True, frozen=True)
 class PatternOpcode(Opcode):
     def __init__(self, pattern):
         if not isinstance(pattern, str):
@@ -219,13 +226,13 @@ class PPCRecord:
                 "sgl pipe",
             )
 
-    class Flags(frozenset, metaclass=FlagsMeta):
+    class Flags(tuple, metaclass=FlagsMeta):
         def __new__(cls, flags=frozenset()):
             flags = frozenset(flags)
             diff = (flags - frozenset(cls))
             if diff:
                 raise ValueError(flags)
-            return super().__new__(cls, flags)
+            return super().__new__(cls, sorted(flags))
 
     opcode: Opcode
     comment: str
@@ -294,7 +301,8 @@ class PPCRecord:
 class PPCMultiRecord(tuple):
     def __getattr__(self, attr):
         if attr == "opcode":
-            raise AttributeError(attr)
+            if len(self) != 1:
+                raise AttributeError(attr)
         return getattr(self[0], attr)
 
 
@@ -338,9 +346,9 @@ class SVP64Record:
             return repr({index:self[index] for index in range(0, 4)})
 
     name: str
-    ptype: _SVPtype = _SVPtype.NONE
-    etype: _SVEtype = _SVEtype.NONE
-    msrc: _SVmask_src = _SVmask_src.NO # MASK_SRC is active
+    ptype: _SVPType = _SVPType.NONE
+    etype: _SVEType = _SVEType.NONE
+    msrc: _SVMaskSrc = _SVMaskSrc.NO # MASK_SRC is active
     in1: _In1Sel = _In1Sel.NONE
     in2: _In2Sel = _In2Sel.NONE
     in3: _In3Sel = _In3Sel.NONE
@@ -445,6 +453,26 @@ class SVP64Record:
     extra_reg_cr_in2 = property(_functools.partial(extra_reg, key="cr_in2"))
     extra_reg_cr_out = property(_functools.partial(extra_reg, key="cr_out"))
 
+    @cached_property
+    def extra_CR(self):
+        extra = None
+        for idx in range(0, 4):
+            for entry in self.extra[idx]:
+                if entry.regtype is _SVExtraRegType.DST:
+                    if extra is not None:
+                        raise ValueError(self.svp64)
+                    extra = entry
+                    break
+
+        if _RegType(extra.reg) not in (_RegType.CR_3BIT, _RegType.CR_5BIT):
+            raise ValueError(self.svp64)
+
+        return extra
+
+    @cached_property
+    def extra_CR_3bit(self):
+        return (_RegType(self.extra_CR.reg) is _RegType.CR_3BIT)
+
 
 class BitSel:
     def __init__(self, value=(0, 32)):
@@ -559,6 +587,17 @@ class Fields:
 
 
 class Operands:
+    __GPR_PAIRS = (
+        _SVExtraReg.RTp,
+        _SVExtraReg.RSp,
+    )
+    __FPR_PAIRS = (
+        _SVExtraReg.FRAp,
+        _SVExtraReg.FRBp,
+        _SVExtraReg.FRSp,
+        _SVExtraReg.FRTp,
+    )
+
     def __init__(self, insn, iterable):
         custom_insns = {
             "b": {"target_addr": TargetAddrOperandLI},
@@ -587,6 +626,12 @@ class Operands:
             "SIM": SignedOperand,
             "SVD": SignedOperand,
             "SVDS": SignedOperand,
+            "RSp": GPRPairOperand,
+            "RTp": GPRPairOperand,
+            "FRAp": FPRPairOperand,
+            "FRBp": FPRPairOperand,
+            "FRSp": FPRPairOperand,
+            "FRTp": FPRPairOperand,
         }
         custom_immediates = {
             "DQ": EXTSOperandDQ,
@@ -599,36 +644,46 @@ class Operands:
 
             if "=" in operand:
                 (name, value) = operand.split("=")
-                mapping[name] = (StaticOperand, {"value": int(value)})
+                mapping[name] = (StaticOperand, {
+                    "name": name,
+                    "value": int(value),
+                })
             else:
                 name = operand
                 if name.endswith(")"):
                     name = name.replace("(", " ").replace(")", "")
-                    (immediate, _, name) = name.partition(" ")
+                    (imm_name, _, name) = name.partition(" ")
                 else:
-                    immediate = None
+                    imm_name = None
 
-                if immediate is not None:
-                    cls = custom_immediates.get(immediate, ImmediateOperand)
-                    mapping[name] = (cls, {})
+                if imm_name is not None:
+                    imm_cls = custom_immediates.get(imm_name, ImmediateOperand)
 
                 if insn in custom_insns and name in custom_insns[insn]:
                     cls = custom_insns[insn][name]
                 elif name in custom_fields:
                     cls = custom_fields[name]
 
-                if name in _RegType.__members__:
-                    regtype = _RegType[name]
-                    if regtype is _RegType.GPR:
-                        cls = GPROperand
-                    elif regtype is _RegType.FPR:
-                        cls = FPROperand
-                    if regtype is _RegType.CR_BIT: # 5-bit
-                        cls = CR5Operand
-                    if regtype is _RegType.CR_REG: # actually CR Field, 3-bit
-                        cls = CR3Operand
-
-                mapping[name] = (cls, {})
+                if name in _SVExtraReg.__members__:
+                    reg = _SVExtraReg[name]
+                    if reg in self.__class__.__GPR_PAIRS:
+                        cls = GPRPairOperand
+                    elif reg in self.__class__.__FPR_PAIRS:
+                        cls = FPRPairOperand
+                    else:
+                        regtype = _RegType[name]
+                        if regtype is _RegType.GPR:
+                            cls = GPROperand
+                        elif regtype is _RegType.FPR:
+                            cls = FPROperand
+                        elif regtype is _RegType.CR_3BIT:
+                            cls = CR3Operand
+                        elif regtype is _RegType.CR_5BIT:
+                            cls = CR5Operand
+
+                if imm_name is not None:
+                    mapping[imm_name] = (imm_cls, {"name": imm_name})
+                mapping[name] = (cls, {"name": name})
 
         static = []
         dynamic = []
@@ -648,6 +703,11 @@ class Operands:
 
         return super().__init__()
 
+    def __iter__(self):
+        for (_, items) in self.__mapping.items():
+            (cls, kwargs) = items
+            yield (cls, kwargs)
+
     def __repr__(self):
         return self.__mapping.__repr__()
 
@@ -655,11 +715,7 @@ class Operands:
         return self.__mapping.__contains__(key)
 
     def __getitem__(self, key):
-        (cls, kwargs) = self.__mapping.__getitem__(key)
-        kwargs = dict(kwargs)
-        kwargs["name"] = key
-
-        return (cls, kwargs)
+        return self.__mapping.__getitem__(key)
 
     @property
     def static(self):
@@ -670,6 +726,43 @@ class Operands:
         return self.__dynamic
 
 
+class Arguments(tuple):
+    def __new__(cls, arguments, operands):
+        arguments = iter(tuple(arguments))
+        operands = iter(tuple(operands))
+
+        items = []
+        while True:
+            try:
+                operand = next(operands)
+            except StopIteration:
+                break
+
+            try:
+                argument = next(arguments)
+            except StopIteration:
+                raise ValueError("operands count mismatch")
+
+            if isinstance(operand, ImmediateOperand):
+                argument = argument.replace("(", " ").replace(")", "")
+                (imm_argument, _, argument) = argument.partition(" ")
+                try:
+                    (imm_operand, operand) = (operand, next(operands))
+                except StopIteration:
+                    raise ValueError("operands count mismatch")
+                items.append((imm_argument, imm_operand))
+            items.append((argument, operand))
+
+        try:
+            next(arguments)
+        except StopIteration:
+            pass
+        else:
+            raise ValueError("operands count mismatch")
+
+        return super().__new__(cls, items)
+
+
 class PCode:
     def __init__(self, iterable):
         self.__pcode = tuple(iterable)
@@ -706,74 +799,88 @@ class Record:
         return (lhs < rhs)
 
     @cached_property
-    def PO_XO(self):
-        value = 0
-        mask = 0
-        for ppc in self.ppc:
-            opcode = ppc.opcode
-            value |= opcode.value
-            mask |= opcode.mask
-        value = Opcode.Value(value)
-        mask = Opcode.Mask(mask)
-        XO = Opcode(value=value, mask=mask)
-
-        PO = self.section.opcode
-        if PO is None:
-            PO = XO
-            XO = None
-
-        PO = POStaticOperand(record=self,
-            name="PO", value=int(PO.value), mask=int(PO.mask))
-        if XO is None:
-            XO = XOStaticOperand(record=self,
-                name="XO", value=0, mask=0)
-        else:
-            XO = XOStaticOperand(record=self,
-                name="XO", value=int(XO.value), mask=int(XO.mask))
-
-        return (PO, XO)
-
-    @cached_property
-    def PO(self):
-        return self.PO_XO[0]
+    def operands(self):
+        return (self.static_operands + self.dynamic_operands)
 
     @cached_property
-    def XO(self):
-        return self.PO_XO[1]
-
-    @property
     def static_operands(self):
-        yield from self.PO_XO
-
+        operands = []
+        operands.append(POStaticOperand(record=self, value=self.PO))
+        for ppc in self.ppc:
+            operands.append(XOStaticOperand(
+                record=self,
+                value=ppc.opcode.value,
+                span=self.section.bitsel,
+            ))
         for (cls, kwargs) in self.mdwn.operands.static:
-            yield cls(record=self, **kwargs)
+            operands.append(cls(record=self, **kwargs))
+        return tuple(operands)
 
     @cached_property
     def dynamic_operands(self):
         operands = []
-
         for (cls, kwargs) in self.mdwn.operands.dynamic:
             operands.append(cls(record=self, **kwargs))
-
         return tuple(operands)
 
-    @property
+    @cached_property
     def opcodes(self):
-        def opcode(ppc):
-            value = ([0] * 32)
-            mask = ([0] * 32)
-
-            for operand in self.static_operands:
+        def binary(mapping):
+            return int("".join(str(int(mapping[bit])) for bit in sorted(mapping)), 2)
+
+        def PO_XO(value, mask, opcode, bits):
+            value = dict(value)
+            mask = dict(mask)
+            for (src, dst) in enumerate(reversed(bits)):
+                value[dst] = ((opcode.value & (1 << src)) != 0)
+                mask[dst] = ((opcode.mask & (1 << src)) != 0)
+            return (value, mask)
+
+        def PO(value, mask, opcode, bits):
+            return PO_XO(value=value, mask=mask, opcode=opcode, bits=bits)
+
+        def XO(value, mask, opcode, bits):
+            (value, mask) = PO_XO(value=value, mask=mask, opcode=opcode, bits=bits)
+            for (op_cls, op_kwargs) in self.mdwn.operands.static:
+                operand = op_cls(record=self, **op_kwargs)
                 for (src, dst) in enumerate(reversed(operand.span)):
-                    value[dst] = int((operand.value & (1 << src)) != 0)
-                    mask[dst] = 1
+                    value[dst] = ((operand.value & (1 << src)) != 0)
+                    mask[dst] = True
+            return (value, mask)
+
+        pairs = []
+        value = {bit:False for bit in range(32)}
+        mask = {bit:False for bit in range(32)}
+        if self.section.opcode is not None:
+            (value, mask) = PO(value=value, mask=mask,
+                opcode=self.section.opcode, bits=range(0, 6))
+        for ppc in self.ppc:
+            pairs.append(XO(value=value, mask=mask,
+                opcode=ppc.opcode, bits=self.section.bitsel))
+
+        result = []
+        for (value, mask) in pairs:
+            value = Opcode.Value(binary(value))
+            mask = Opcode.Mask(binary(mask))
+            result.append(Opcode(value=value, mask=mask))
 
-            value = Opcode.Value(int(("".join(map(str, value))), 2))
-            mask = Opcode.Mask(int(("".join(map(str, mask))), 2))
+        return tuple(result)
+
+    @cached_property
+    def PO(self):
+        opcode = self.section.opcode
+        if opcode is None:
+            opcode = self.ppc[0].opcode
+            if isinstance(opcode, PatternOpcode):
+                value = int(opcode.value)
+                bits = opcode.value.bit_length()
+                return int(_SelectableInt(value=value, bits=bits)[0:6])
 
-            return Opcode(value=value, mask=mask)
+        return int(opcode.value)
 
-        return tuple(sorted(map(opcode, self.ppc)))
+    @cached_property
+    def XO(self):
+        return tuple(ppc.opcode for ppc in self.ppc)
 
     def match(self, key):
         for opcode in self.opcodes:
@@ -846,25 +953,44 @@ class Record:
     def Rc(self):
         if "Rc" not in self:
             return False
-        return bool(self["Rc"])
+        return self["Rc"].value
 
 
-@_dataclasses.dataclass(eq=True, frozen=True)
 class Operand:
-    name: str
-    record: Record = _dataclasses.field(repr=False)
+    def __init__(self, record, name):
+        self.__record = record
+        self.__name = name
 
-    def __post_init__(self):
-        pass
+    def __iter__(self):
+        yield ("record", self.record)
+        yield ("name", self.__name)
+
+    def __repr__(self):
+        return f"{self.__class__.__name__}({self.name})"
+
+    @property
+    def name(self):
+        return self.__name
+
+    @property
+    def record(self):
+        return self.__record
 
     @cached_property
     def span(self):
         return self.record.fields[self.name]
 
-    def assemble(self, value, insn):
+    def assemble(self, insn):
+        raise NotImplementedError()
+
+    def disassemble(self, insn,
+            style=Style.NORMAL, indent=""):
+        raise NotImplementedError()
+
+
+class DynamicOperand(Operand):
+    def assemble(self, insn, value):
         span = self.span
-        if isinstance(insn, SVP64Instruction):
-            span = tuple(map(lambda bit: (bit + 32), span))
         if isinstance(value, str):
             value = int(value, 0)
             if value < 0:
@@ -872,20 +998,11 @@ class Operand:
         insn[span] = value
 
     def disassemble(self, insn,
-            verbosity=Verbosity.NORMAL, indent=""):
-        raise NotImplementedError
-
-
-@_dataclasses.dataclass(eq=True, frozen=True)
-class DynamicOperand(Operand):
-    def disassemble(self, insn,
-            verbosity=Verbosity.NORMAL, indent=""):
+            style=Style.NORMAL, indent=""):
         span = self.span
-        if isinstance(insn, SVP64Instruction):
-            span = tuple(map(lambda bit: (bit + 32), span))
         value = insn[span]
 
-        if verbosity >= Verbosity.VERBOSE:
+        if style >= Style.VERBOSE:
             span = map(str, span)
             yield f"{indent}{self.name}"
             yield f"{indent}{indent}{int(value):0{value.bits}b}"
@@ -894,44 +1011,59 @@ class DynamicOperand(Operand):
             yield str(int(value))
 
 
-@_dataclasses.dataclass(eq=True, frozen=True)
 class SignedOperand(DynamicOperand):
-    def assemble(self, value, insn):
+    def assemble(self, insn, value):
         if isinstance(value, str):
             value = int(value, 0)
         return super().assemble(value=value, insn=insn)
 
+    def assemble(self, insn, value):
+        span = self.span
+        if isinstance(value, str):
+            value = int(value, 0)
+        insn[span] = value
+
     def disassemble(self, insn,
-            verbosity=Verbosity.NORMAL, indent=""):
+            style=Style.NORMAL, indent=""):
         span = self.span
-        if isinstance(insn, SVP64Instruction):
-            span = tuple(map(lambda bit: (bit + 32), span))
-        value = insn[span]
+        value = insn[span].to_signed_int()
+        sign = "-" if (value < 0) else ""
+        value = abs(value)
 
-        if verbosity >= Verbosity.VERBOSE:
+        if style >= Style.VERBOSE:
             span = map(str, span)
             yield f"{indent}{self.name}"
-            yield f"{indent}{indent}{int(value):0{value.bits}b}"
+            yield f"{indent}{indent}{sign}{value}"
             yield f"{indent}{indent}{', '.join(span)}"
         else:
-            yield str(value.to_signed_int())
+            yield f"{sign}{value}"
 
 
-@_dataclasses.dataclass(eq=True, frozen=True)
 class StaticOperand(Operand):
-    value: int
+    def __init__(self, record, name, value):
+        self.__value = value
+        return super().__init__(record=record, name=name)
+
+    def __iter__(self):
+        yield ("value", self.__value)
+        yield from super().__iter__()
+
+    def __repr__(self):
+        return f"{self.__class__.__name__}({self.name}, value={self.value})"
+
+    @property
+    def value(self):
+        return self.__value
 
     def assemble(self, insn):
-        return super().assemble(value=self.value, insn=insn)
+        insn[self.span] = self.value
 
     def disassemble(self, insn,
-            verbosity=Verbosity.NORMAL, indent=""):
+            style=Style.NORMAL, indent=""):
         span = self.span
-        if isinstance(insn, SVP64Instruction):
-            span = tuple(map(lambda bit: (bit + 32), span))
         value = insn[span]
 
-        if verbosity >= Verbosity.VERBOSE:
+        if style >= Style.VERBOSE:
             span = map(str, span)
             yield f"{indent}{self.name}"
             yield f"{indent}{indent}{int(value):0{value.bits}b}"
@@ -940,73 +1072,67 @@ class StaticOperand(Operand):
             yield str(int(value))
 
 
-@_dataclasses.dataclass(eq=True, frozen=True)
-class POStaticOperand(StaticOperand):
-    mask: int
+class SpanStaticOperand(StaticOperand):
+    def __init__(self, record, name, value, span):
+        self.__span = tuple(span)
+        return super().__init__(record=record, name=name, value=value)
 
-    @cached_property
+    def __iter__(self):
+        yield ("span", self.__span)
+        yield from super().__iter__()
+
+    @property
     def span(self):
-        return tuple(range(0, 6))
+        return self.__span
 
 
-@_dataclasses.dataclass(eq=True, frozen=True)
-class XOStaticOperand(StaticOperand):
-    mask: int
-
-    def __post_init__(self):
-        if self.record.section.opcode is None:
-            assert self.value == 0
-            assert self.mask == 0
-            object.__setattr__(self, "span", ())
-            return
+class POStaticOperand(SpanStaticOperand):
+    def __init__(self, record, value):
+        return super().__init__(record=record, name="PO", value=value, span=range(0, 6))
+
+    def __iter__(self):
+        for (key, value) in super().__iter__():
+            if key not in {"name", "span"}:
+                yield (key, value)
 
-        bits = self.record.section.bitsel
-        value = _SelectableInt(value=self.value, bits=len(bits))
+
+class XOStaticOperand(SpanStaticOperand):
+    def __init__(self, record, value, span):
+        bits = record.section.bitsel
+        value = _SelectableInt(value=value, bits=len(bits))
         span = dict(zip(bits, range(len(bits))))
         span_rev = {value:key for (key, value) in span.items()}
 
-        # This part is tricky: we could have used self.record.static_operands,
-        # but this would cause an infinite recursion, since this code is called
-        # from the self.record.static_operands method already.
-        operands = []
-        operands.extend(self.record.mdwn.operands.static)
-        operands.extend(self.record.mdwn.operands.dynamic)
-        for (cls, kwargs) in operands:
-            operand = cls(record=self.record, **kwargs)
+        # This part is tricky: we cannot use record.operands,
+        # as this code is called by record.static_operands method.
+        for (cls, kwargs) in record.mdwn.operands:
+            operand = cls(record=record, **kwargs)
             for idx in operand.span:
                 rev = span.pop(idx, None)
                 if rev is not None:
                     span_rev.pop(rev, None)
 
-        # This part is simpler: we drop bits which are not in the mask.
-        for bit in tuple(span.values()):
-            rev = (len(bits) - bit - 1)
-            if ((self.mask & (1 << bit)) == 0):
-                idx = span_rev.pop(rev, None)
-                if idx is not None:
-                    span.pop(idx, None)
-
         value = int(_selectconcat(*(value[bit] for bit in span.values())))
         span = tuple(span.keys())
-        object.__setattr__(self, "value", value)
-        object.__setattr__(self, "span", span)
 
-        return super().__post_init__()
+        return super().__init__(record=record, name="XO", value=value, span=span)
+
+    def __iter__(self):
+        for (key, value) in super().__iter__():
+            if key not in {"name"}:
+                yield (key, value)
 
 
-@_dataclasses.dataclass(eq=True, frozen=True)
 class ImmediateOperand(DynamicOperand):
     pass
 
 
-@_dataclasses.dataclass(eq=True, frozen=True)
 class SignedImmediateOperand(SignedOperand, ImmediateOperand):
     pass
 
 
-@_dataclasses.dataclass(eq=True, frozen=True)
 class NonZeroOperand(DynamicOperand):
-    def assemble(self, value, insn):
+    def assemble(self, insn, value):
         if isinstance(value, str):
             value = int(value, 0)
         if not isinstance(value, int):
@@ -1015,13 +1141,11 @@ class NonZeroOperand(DynamicOperand):
         return super().assemble(value=value, insn=insn)
 
     def disassemble(self, insn,
-            verbosity=Verbosity.NORMAL, indent=""):
+            style=Style.NORMAL, indent=""):
         span = self.span
-        if isinstance(insn, SVP64Instruction):
-            span = tuple(map(lambda bit: (bit + 32), span))
         value = insn[span]
 
-        if verbosity >= Verbosity.VERBOSE:
+        if style >= Style.VERBOSE:
             span = map(str, span)
             yield f"{indent}{self.name}"
             yield f"{indent}{indent}{int(value):0{value.bits}b}"
@@ -1030,7 +1154,6 @@ class NonZeroOperand(DynamicOperand):
             yield str(int(value) + 1)
 
 
-@_dataclasses.dataclass(eq=True, frozen=True)
 class ExtendableOperand(DynamicOperand):
     def sv_spec_enter(self, value, span):
         return (value, span)
@@ -1041,8 +1164,6 @@ class ExtendableOperand(DynamicOperand):
     def spec(self, insn):
         vector = False
         span = self.span
-        if isinstance(insn, SVP64Instruction):
-            span = tuple(map(lambda bit: (bit + 32), span))
         value = insn[span]
         span = tuple(map(str, span))
 
@@ -1054,9 +1175,9 @@ class ExtendableOperand(DynamicOperand):
             if extra_idx is _SVExtra.NONE:
                 return (vector, value, span)
 
-            if self.record.etype is _SVEtype.EXTRA3:
+            if self.record.etype is _SVEType.EXTRA3:
                 spec = insn.prefix.rm.extra3[extra_idx]
-            elif self.record.etype is _SVEtype.EXTRA2:
+            elif self.record.etype is _SVEType.EXTRA2:
                 spec = insn.prefix.rm.extra2[extra_idx]
             else:
                 raise ValueError(self.record.etype)
@@ -1064,10 +1185,10 @@ class ExtendableOperand(DynamicOperand):
             if spec != 0:
                 vector = bool(spec[0])
                 spec_span = spec.__class__
-                if self.record.etype is _SVEtype.EXTRA3:
+                if self.record.etype is _SVEType.EXTRA3:
                     spec_span = tuple(map(str, spec_span[1, 2]))
                     spec = spec[1, 2]
-                elif self.record.etype is _SVEtype.EXTRA2:
+                elif self.record.etype is _SVEType.EXTRA2:
                     spec_span = tuple(map(str, spec_span[1,]))
                     spec = _SelectableInt(value=spec[1].value, bits=2)
                     if vector:
@@ -1103,28 +1224,72 @@ class ExtendableOperand(DynamicOperand):
 
     @property
     def extra_idx(self):
+        pairs = {
+            _SVExtraReg.RSp: _SVExtraReg.RS,
+            _SVExtraReg.RTp: _SVExtraReg.RT,
+            _SVExtraReg.FRAp: _SVExtraReg.FRA,
+            _SVExtraReg.FRBp: _SVExtraReg.FRB,
+            _SVExtraReg.FRSp: _SVExtraReg.FRS,
+            _SVExtraReg.FRTp: _SVExtraReg.FRT,
+        }
+
         for key in frozenset({
                     "in1", "in2", "in3", "cr_in", "cr_in2",
                     "out", "out2", "cr_out",
                 }):
             extra_reg = self.record.svp64.extra_reg(key=key)
-            if extra_reg is self.extra_reg:
+            if pairs.get(extra_reg, extra_reg) is pairs.get(self.extra_reg, self.extra_reg):
                 return self.record.extra_idx(key=key)
 
         return _SVExtra.NONE
 
+    def remap(self, value, vector):
+        raise NotImplementedError()
+
+    def assemble(self, value, insn, prefix):
+        vector = False
+
+        if isinstance(value, str):
+            value = value.lower()
+            if value.startswith("%"):
+                value = value[1:]
+            if value.startswith("*"):
+                if not isinstance(insn, SVP64Instruction):
+                    raise ValueError(value)
+                value = value[1:]
+                vector = True
+            if value.startswith(prefix):
+                value = value[len(prefix):]
+            value = int(value, 0)
+
+        if isinstance(insn, SVP64Instruction):
+            (value, extra) = self.remap(value=value, vector=vector)
+
+            extra_idx = self.extra_idx
+            if extra_idx is _SVExtra.NONE:
+                raise ValueError(self.record)
+
+            if self.record.etype is _SVEType.EXTRA3:
+                insn.prefix.rm.extra3[extra_idx] = extra
+            elif self.record.etype is _SVEType.EXTRA2:
+                insn.prefix.rm.extra2[extra_idx] = extra
+            else:
+                raise ValueError(self.record.etype)
+
+        return super().assemble(value=value, insn=insn)
+
     def disassemble(self, insn,
-            verbosity=Verbosity.NORMAL, prefix="", indent=""):
+            style=Style.NORMAL, prefix="", indent=""):
         (vector, value, span) = self.spec(insn=insn)
 
-        if verbosity >= Verbosity.VERBOSE:
+        if style >= Style.VERBOSE:
             mode = "vector" if vector else "scalar"
             yield f"{indent}{self.name} ({mode})"
             yield f"{indent}{indent}{int(value):0{value.bits}b}"
             yield f"{indent}{indent}{', '.join(span)}"
             if isinstance(insn, SVP64Instruction):
                 extra_idx = self.extra_idx
-                if self.record.etype is _SVEtype.NONE:
+                if self.record.etype is _SVEType.NONE:
                     yield f"{indent}{indent}extra[none]"
                 else:
                     etype = repr(self.record.etype).lower()
@@ -1134,47 +1299,212 @@ class ExtendableOperand(DynamicOperand):
             yield f"{vector}{prefix}{int(value)}"
 
 
-@_dataclasses.dataclass(eq=True, frozen=True)
-class GPROperand(ExtendableOperand):
-    def assemble(self, value, insn):
-        if isinstance(value, str):
-            value = value.lower()
-            if value.startswith("r"):
-                value = value[1:]
-            value = int(value, 0)
-        return super().assemble(value=value, insn=insn)
+class SimpleRegisterOperand(ExtendableOperand):
+    def remap(self, value, vector):
+        if vector:
+            extra = (value & 0b11)
+            value = (value >> 2)
+        else:
+            extra = (value >> 5)
+            value = (value & 0b11111)
+
+        # now sanity-check. EXTRA3 is ok, EXTRA2 has limits
+        # (and shrink to a single bit if ok)
+        if self.record.etype is _SVEType.EXTRA2:
+            if vector:
+                # range is r0-r127 in increments of 2 (r0 r2 ... r126)
+                assert (extra & 0b01) == 0, \
+                    ("vector field %s cannot fit into EXTRA2" % value)
+                extra = (0b10 | (extra >> 1))
+            else:
+                # range is r0-r63 in increments of 1
+                assert (extra >> 1) == 0, \
+                    ("scalar GPR %d cannot fit into EXTRA2" % value)
+                extra &= 0b01
+        elif self.record.etype is _SVEType.EXTRA3:
+            if vector:
+                # EXTRA3 vector bit needs marking
+                extra |= 0b100
+        else:
+            raise ValueError(self.record.etype)
+
+        return (value, extra)
+
+
+class GPROperand(SimpleRegisterOperand):
+    def assemble(self, insn, value):
+        return super().assemble(value=value, insn=insn, prefix="r")
 
     def disassemble(self, insn,
-            verbosity=Verbosity.NORMAL, indent=""):
-        prefix = "" if (verbosity <= Verbosity.SHORT) else "r"
+            style=Style.NORMAL, indent=""):
+        prefix = "" if (style <= Style.SHORT) else "r"
         yield from super().disassemble(prefix=prefix, insn=insn,
-            verbosity=verbosity, indent=indent)
+            style=style, indent=indent)
 
 
-@_dataclasses.dataclass(eq=True, frozen=True)
-class FPROperand(ExtendableOperand):
-    def assemble(self, value, insn):
-        if isinstance(value, str):
-            value = value.lower()
-            if value.startswith("f"):
-                value = value[1:]
-            value = int(value, 0)
-        return super().assemble(value=value, insn=insn)
+class GPRPairOperand(GPROperand):
+    pass
+
+
+class FPROperand(SimpleRegisterOperand):
+    def assemble(self, insn, value):
+        return super().assemble(value=value, insn=insn, prefix="f")
 
     def disassemble(self, insn,
-            verbosity=Verbosity.NORMAL, indent=""):
-        prefix = "" if (verbosity <= Verbosity.SHORT) else "f"
+            style=Style.NORMAL, indent=""):
+        prefix = "" if (style <= Style.SHORT) else "f"
         yield from super().disassemble(prefix=prefix, insn=insn,
-            verbosity=verbosity, indent=indent)
+            style=style, indent=indent)
 
 
-@_dataclasses.dataclass(eq=True, frozen=True)
-class CR3Operand(ExtendableOperand):
+class FPRPairOperand(FPROperand):
     pass
 
 
-@_dataclasses.dataclass(eq=True, frozen=True)
-class CR5Operand(ExtendableOperand):
+class ConditionRegisterFieldOperand(ExtendableOperand):
+    def pattern(name_pattern):
+        (name, pattern) = name_pattern
+        return (name, _re.compile(f"^{pattern}$", _re.S))
+
+    CONDS = {
+        "lt": 0,
+        "gt": 1,
+        "eq": 2,
+        "so": 3,
+        "un": 3,
+    }
+    CR = r"(?:CR|cr)([0-9]+)"
+    N = r"([0-9]+)"
+    BIT = rf"({'|'.join(CONDS.keys())})"
+    LBIT = fr"{BIT}\s*\+\s*"  # BIT+
+    RBIT = fr"\s*\+\s*{BIT}"  # +BIT
+    CRN = fr"{CR}\s*\*\s*{N}" # CR*N
+    NCR = fr"{N}\s*\*\s*{CR}" # N*CR
+    XCR = fr"{CR}\.{BIT}"
+    PATTERNS = tuple(map(pattern, (
+        ("CR", CR),
+        ("XCR", XCR),
+        ("CR*N", CRN),
+        ("N*CR", NCR),
+        ("BIT+CR", (LBIT + CR)),
+        ("CR+BIT", (CR + RBIT)),
+        ("BIT+CR*N", (LBIT + CRN)),
+        ("CR*N+BIT", (CRN + RBIT)),
+        ("BIT+N*CR", (LBIT + NCR)),
+        ("N*CR+BIT", (NCR + RBIT)),
+    )))
+
+    def remap(self, value, vector, regtype):
+        if regtype is _RegType.CR_5BIT:
+            subvalue = (value & 0b11)
+            value >>= 2
+
+        if vector:
+            extra = (value & 0b1111)
+            value >>= 4
+        else:
+            extra = (value >> 3)
+            value &= 0b111
+
+        if self.record.etype is _SVEType.EXTRA2:
+            if vector:
+                assert (extra & 0b111) == 0, \
+                    "vector CR cannot fit into EXTRA2"
+                extra = (0b10 | (extra >> 3))
+            else:
+                assert (extra >> 1) == 0, \
+                    "scalar CR cannot fit into EXTRA2"
+                extra &= 0b01
+        elif self.record.etype is _SVEType.EXTRA3:
+            if vector:
+                assert (extra & 0b11) == 0, \
+                    "vector CR cannot fit into EXTRA3"
+                extra = (0b100 | (extra >> 2))
+            else:
+                assert (extra >> 2) == 0, \
+                    "scalar CR cannot fit into EXTRA3"
+                extra &= 0b11
+
+        if regtype is _RegType.CR_5BIT:
+            value = ((value << 2) | subvalue)
+
+        return (value, extra)
+
+    def assemble(self, insn, value):
+        if isinstance(value, str):
+            vector = False
+
+            if value.startswith("*"):
+                if not isinstance(insn, SVP64Instruction):
+                    raise ValueError(value)
+                value = value[1:]
+                vector = True
+
+            for (name, pattern) in reversed(self.__class__.PATTERNS):
+                match = pattern.match(value)
+                if match is not None:
+                    keys = name.replace("+", "_").replace("*", "_").split("_")
+                    values = match.groups()
+                    match = dict(zip(keys, values))
+                    CR = int(match["CR"])
+                    if name == "XCR":
+                        N = 4
+                    else:
+                        N = int(match.get("N", "1"))
+                    BIT = self.__class__.CONDS[match.get("BIT", "lt")]
+                    value = ((CR * N) + BIT)
+                    break
+
+            value = str(value)
+            if vector:
+                value = f"*{value}"
+
+        return super().assemble(value=value, insn=insn, prefix="cr")
+
+    def disassemble(self, insn,
+            style=Style.NORMAL, prefix="", indent=""):
+        (vector, value, span) = self.spec(insn=insn)
+
+        if style >= Style.VERBOSE:
+            mode = "vector" if vector else "scalar"
+            yield f"{indent}{self.name} ({mode})"
+            yield f"{indent}{indent}{int(value):0{value.bits}b}"
+            yield f"{indent}{indent}{', '.join(span)}"
+            if isinstance(insn, SVP64Instruction):
+                extra_idx = self.extra_idx
+                if self.record.etype is _SVEType.NONE:
+                    yield f"{indent}{indent}extra[none]"
+                else:
+                    etype = repr(self.record.etype).lower()
+                    yield f"{indent}{indent}{etype}{extra_idx!r}"
+        else:
+            vector = "*" if vector else ""
+            CR = int(value >> 2)
+            CC = int(value & 3)
+            cond = ("lt", "gt", "eq", "so")[CC]
+            if style >= Style.NORMAL:
+                if CR != 0:
+                    if isinstance(insn, SVP64Instruction):
+                        yield f"{vector}cr{CR}.{cond}"
+                    else:
+                        yield f"4*cr{CR}+{cond}"
+                else:
+                    yield cond
+            else:
+                yield f"{vector}{prefix}{int(value)}"
+
+
+class CR3Operand(ConditionRegisterFieldOperand):
+    def remap(self, value, vector):
+        return super().remap(value=value, vector=vector,
+            regtype=_RegType.CR_3BIT)
+
+
+class CR5Operand(ConditionRegisterFieldOperand):
+    def remap(self, value, vector):
+        return super().remap(value=value, vector=vector,
+            regtype=_RegType.CR_5BIT)
+
     def sv_spec_enter(self, value, span):
         value = _SelectableInt(value=(value.value >> 2), bits=3)
         return (value, span)
@@ -1185,70 +1515,83 @@ class CR5Operand(ExtendableOperand):
         return (value, span)
 
 
-@_dataclasses.dataclass(eq=True, frozen=True)
-class EXTSOperand(DynamicOperand):
+class EXTSOperand(SignedOperand):
     field: str # real name to report
     nz: int = 0 # number of zeros
     fmt: str = "d" # integer formatter
 
-    def __post_init__(self):
-        if not self.field:
-            object.__setattr__(self, "field", self.name)
+    def __init__(self, record, name, field, nz=0, fmt="d"):
+        self.__field = field
+        self.__nz = nz
+        self.__fmt = fmt
+        return super().__init__(record=record, name=name)
+
+    @property
+    def field(self):
+        return self.__field
+
+    @property
+    def nz(self):
+        return self.__nz
+
+    @property
+    def fmt(self):
+        return self.__fmt
 
     @cached_property
     def span(self):
         return self.record.fields[self.field]
 
+    def assemble(self, insn, value):
+        span = self.span
+        if isinstance(value, str):
+            value = int(value, 0)
+        insn[span] = (value >> self.nz)
+
     def disassemble(self, insn,
-            verbosity=Verbosity.NORMAL, indent=""):
+            style=Style.NORMAL, indent=""):
         span = self.span
-        if isinstance(insn, SVP64Instruction):
-            span = tuple(map(lambda bit: (bit + 32), span))
-        value = insn[span]
+        value = insn[span].to_signed_int()
+        sign = "-" if (value < 0) else ""
+        value = (abs(value) << self.nz)
 
-        if verbosity >= Verbosity.VERBOSE:
+        if style >= Style.VERBOSE:
             span = (tuple(map(str, span)) + (("{0}",) * self.nz))
             zeros = ("0" * self.nz)
             hint = f"{self.name} = EXTS({self.field} || {zeros})"
             yield f"{indent * 1}{hint}"
             yield f"{indent * 2}{self.field}"
-            yield f"{indent * 3}{int(value):0{value.bits}b}{zeros}"
+            yield f"{indent * 3}{sign}{value:{self.fmt}}"
             yield f"{indent * 3}{', '.join(span)}"
         else:
-            value = _selectconcat(value,
-                _SelectableInt(value=0, bits=self.nz)).to_signed_int()
-            yield f"{value:{self.fmt}}"
+            yield f"{sign}{value:{self.fmt}}"
 
 
-@_dataclasses.dataclass(eq=True, frozen=True)
 class TargetAddrOperand(EXTSOperand):
-    nz: int = 2
-    fmt: str = "#x"
+    def __init__(self, record, name, field):
+        return super().__init__(record=record, name=name, field=field, nz=2, fmt="#x")
 
 
-@_dataclasses.dataclass(eq=True, frozen=True)
 class TargetAddrOperandLI(TargetAddrOperand):
-    field: str = "LI"
+    def __init__(self, record, name):
+        return super().__init__(record=record, name=name, field="LI")
 
 
-@_dataclasses.dataclass(eq=True, frozen=True)
 class TargetAddrOperandBD(TargetAddrOperand):
-    field: str = "BD"
+    def __init__(self, record, name):
+        return super().__init__(record=record, name=name, field="BD")
 
 
-@_dataclasses.dataclass(eq=True, frozen=True)
 class EXTSOperandDS(EXTSOperand, ImmediateOperand):
-    field: str = "DS"
-    nz: int = 2
+    def __init__(self, record, name):
+        return super().__init__(record=record, name=name, field="DS", nz=2)
 
 
-@_dataclasses.dataclass(eq=True, frozen=True)
 class EXTSOperandDQ(EXTSOperand, ImmediateOperand):
-    field: str = "DQ"
-    nz: int = 4
+    def __init__(self, record, name):
+        return super().__init__(record=record, name=name, field="DQ", nz=4)
 
 
-@_dataclasses.dataclass(eq=True, frozen=True)
 class DOperandDX(SignedOperand):
     @cached_property
     def span(self):
@@ -1258,13 +1601,13 @@ class DOperandDX(SignedOperand):
         return sum(spans, tuple())
 
     def disassemble(self, insn,
-            verbosity=Verbosity.NORMAL, indent=""):
+            style=Style.NORMAL, indent=""):
         span = self.span
-        if isinstance(insn, SVP64Instruction):
-            span = tuple(map(lambda bit: (bit + 32), span))
-        value = insn[span]
+        value = insn[span].to_signed_int()
+        sign = "-" if (value < 0) else ""
+        value = abs(value)
 
-        if verbosity >= Verbosity.VERBOSE:
+        if style >= Style.VERBOSE:
             yield f"{indent}D"
             mapping = {
                 "d0": "[0:9]",
@@ -1274,15 +1617,12 @@ class DOperandDX(SignedOperand):
             for (subname, subspan) in mapping.items():
                 operand = DynamicOperand(name=subname)
                 span = operand.span
-                if isinstance(insn, SVP64Instruction):
-                    span = tuple(map(lambda bit: (bit + 32), span))
-                value = insn[span]
                 span = map(str, span)
                 yield f"{indent}{indent}{operand.name} = D{subspan}"
-                yield f"{indent}{indent}{indent}{int(value):0{value.bits}b}"
+                yield f"{indent}{indent}{indent}{sign}{value}"
                 yield f"{indent}{indent}{indent}{', '.join(span)}"
         else:
-            yield str(value.to_signed_int())
+            yield f"{sign}{value}"
 
 
 class Instruction(_Mapping):
@@ -1322,23 +1662,36 @@ class Instruction(_Mapping):
         return self.storage.__setitem__(key, value)
 
     def bytes(self, byteorder="little"):
-        nr_bytes = (self.storage.bits // 8)
+        nr_bytes = (len(self.__class__) // 8)
         return int(self).to_bytes(nr_bytes, byteorder=byteorder)
 
-    def record(self, db):
-        record = db[self]
+    @classmethod
+    def record(cls, db, entry):
+        record = db[entry]
         if record is None:
-            raise KeyError(self)
+            raise KeyError(entry)
         return record
 
-    def spec(self, db, prefix):
-        record = self.record(db=db)
+    @classmethod
+    def operands(cls, record):
+        yield from record.operands
+
+    @classmethod
+    def static_operands(cls, record):
+        return filter(lambda operand: isinstance(operand, StaticOperand),
+            cls.operands(record=record))
+
+    @classmethod
+    def dynamic_operands(cls, record):
+        return filter(lambda operand: isinstance(operand, DynamicOperand),
+            cls.operands(record=record))
 
+    def spec(self, record, prefix):
         dynamic_operands = tuple(map(_operator.itemgetter(0),
-            self.dynamic_operands(db=db)))
+            self.spec_dynamic_operands(record=record)))
 
         static_operands = []
-        for (name, value) in self.static_operands(db=db):
+        for (name, value) in self.spec_static_operands(record=record):
             static_operands.append(f"{name}={value}")
 
         operands = ""
@@ -1351,16 +1704,19 @@ class Instruction(_Mapping):
 
         return f"{prefix}{record.name}{operands}"
 
-    def dynamic_operands(self, db, verbosity=Verbosity.NORMAL):
-        record = self.record(db=db)
+    def spec_static_operands(self, record):
+        for operand in self.static_operands(record=record):
+            if not isinstance(operand, (POStaticOperand, XOStaticOperand)):
+                yield (operand.name, operand.value)
 
+    def spec_dynamic_operands(self, record, style=Style.NORMAL):
         imm = False
         imm_name = ""
         imm_value = ""
-        for operand in record.dynamic_operands:
+        for operand in self.dynamic_operands(record=record):
             name = operand.name
             value = " ".join(operand.disassemble(insn=self,
-                verbosity=min(verbosity, Verbosity.NORMAL)))
+                style=min(style, Style.NORMAL)))
             if imm:
                 name = f"{imm_name}({name})"
                 value = f"{imm_value}({value})"
@@ -1372,40 +1728,32 @@ class Instruction(_Mapping):
             if not imm:
                 yield (name, value)
 
-    def static_operands(self, db):
-        record = self.record(db=db)
-        for operand in record.static_operands:
-            yield (operand.name, operand.value)
-
     @classmethod
-    def assemble(cls, db, opcode, arguments):
-        raise NotImplementedError(f"{cls.__name__}.assemble")
+    def assemble(cls, record, arguments=None):
+        if arguments is None:
+            arguments = ()
+
+        insn = cls.integer(value=0)
 
-    def disassemble(self, db,
+        for operand in cls.static_operands(record=record):
+            operand.assemble(insn=insn)
+
+        dynamic_operands = tuple(cls.dynamic_operands(record=record))
+        for (value, operand) in Arguments(arguments, dynamic_operands):
+            operand.assemble(insn=insn, value=value)
+
+        return insn
+
+    def disassemble(self, record,
             byteorder="little",
-            verbosity=Verbosity.NORMAL):
-        raise NotImplementedError
+            style=Style.NORMAL):
+        raise NotImplementedError()
 
 
 class WordInstruction(Instruction):
     _: _Field = range(0, 32)
     PO: _Field = range(0, 6)
 
-    @classmethod
-    def assemble(cls, db, opcode, arguments):
-        record = db[opcode]
-        insn = cls.integer(value=0)
-        for operand in record.static_operands:
-            operand.assemble(insn=insn)
-
-        dynamic_operands = tuple(record.dynamic_operands)
-        if len(dynamic_operands) != len(arguments):
-            raise ValueError("operands count mismatch")
-        for (value, operand) in zip(arguments, dynamic_operands):
-            operand.assemble(value=value, insn=insn)
-
-        return insn
-
     @classmethod
     def integer(cls, value, byteorder="little"):
         return super().integer(bits=32, value=value, byteorder=byteorder)
@@ -1418,34 +1766,42 @@ class WordInstruction(Instruction):
             bits.append(bit)
         return "".join(map(str, bits))
 
-    def disassemble(self, db,
+    def disassemble(self, record,
             byteorder="little",
-            verbosity=Verbosity.NORMAL):
-        integer = int(self)
-        if verbosity <= Verbosity.SHORT:
+            style=Style.NORMAL):
+        if style <= Style.SHORT:
             blob = ""
         else:
-            blob = integer.to_bytes(length=4, byteorder=byteorder)
+            blob = self.bytes(byteorder=byteorder)
             blob = " ".join(map(lambda byte: f"{byte:02x}", blob))
             blob += "    "
 
-        record = db[self]
         if record is None:
-            yield f"{blob}.long 0x{integer:08x}"
+            yield f"{blob}.long 0x{int(self):08x}"
             return
 
-        operands = tuple(map(_operator.itemgetter(1),
-            self.dynamic_operands(db=db, verbosity=verbosity)))
-        if operands:
-            operands = ",".join(operands)
-            yield f"{blob}{record.name} {operands}"
+        paired = False
+        if style is Style.LEGACY:
+            paired = False
+            for operand in self.dynamic_operands(record=record):
+                if isinstance(operand, (GPRPairOperand, FPRPairOperand)):
+                    paired = True
+
+        if style is Style.LEGACY and (paired or record.ppc.unofficial):
+            yield f"{blob}.long 0x{int(self):08x}"
         else:
-            yield f"{blob}{record.name}"
+            operands = tuple(map(_operator.itemgetter(1),
+                self.spec_dynamic_operands(record=record, style=style)))
+            if operands:
+                operands = ",".join(operands)
+                yield f"{blob}{record.name} {operands}"
+            else:
+                yield f"{blob}{record.name}"
 
-        if verbosity >= Verbosity.VERBOSE:
+        if style >= Style.VERBOSE:
             indent = (" " * 4)
             binary = self.binary
-            spec = self.spec(db=db, prefix="")
+            spec = self.spec(record=record, prefix="")
             yield f"{indent}spec"
             yield f"{indent}{indent}{spec}"
             yield f"{indent}pcode"
@@ -1459,10 +1815,9 @@ class WordInstruction(Instruction):
             yield f"{indent}opcodes"
             for opcode in record.opcodes:
                 yield f"{indent}{indent}{opcode!r}"
-            for (cls, kwargs) in record.mdwn.operands:
-                operand = cls(record=record, **kwargs)
+            for operand in self.operands(record=record):
                 yield from operand.disassemble(insn=self,
-                    verbosity=verbosity, indent=indent)
+                    style=style, indent=indent)
             yield ""
 
 
@@ -1496,6 +1851,7 @@ class PrefixedInstruction(Instruction):
 
 class Mode(_Mapping):
     _: _Field = range(0, 5)
+    sel: _Field = (0, 1)
 
 
 class Extra(_Mapping):
@@ -1565,8 +1921,8 @@ class BaseRM(_Mapping):
                 3: "vec4",
             }[subvl]
 
-    def disassemble(self, verbosity=Verbosity.NORMAL):
-        if verbosity >= Verbosity.VERBOSE:
+    def disassemble(self, style=Style.NORMAL):
+        if style >= Style.VERBOSE:
             indent = (" " * 4)
             for (name, span) in self.traverse(path="RM"):
                 value = self.storage[span]
@@ -1613,17 +1969,29 @@ class ZZBaseRM(BaseRM):
         yield from super().specifiers(record=record)
 
 
-class DZBaseRM(BaseRM):
+class ZZCombinedBaseRM(BaseRM):
     def specifiers(self, record):
-        if self.dz:
+        if self.sz and self.dz:
+            yield "zz"
+        elif self.sz:
+            yield "sz"
+        elif self.dz:
             yield "dz"
 
         yield from super().specifiers(record=record)
 
 
-class SZBaseRM(BaseRM):
+class DZBaseRM(BaseRM):
     def specifiers(self, record):
-        if self.sz:
+        if self.dz:
+            yield "dz"
+
+        yield from super().specifiers(record=record)
+
+
+class SZBaseRM(BaseRM):
+    def specifiers(self, record):
+        if self.sz:
             yield "sz"
 
         yield from super().specifiers(record=record)
@@ -1707,7 +2075,7 @@ class PredicateBaseRM(BaseRM):
         CR = (int(self.mmode) == 1)
         mask = int(self.mask)
         sm = dm = PredicateBaseRM.predicate(CR, mask)
-        if record.svp64.ptype is _SVPtype.P2:
+        if record.svp64.ptype is _SVPType.P2:
             smask = int(self.smask)
             sm = PredicateBaseRM.predicate(CR, smask)
         if sm == dm and dm:
@@ -1749,7 +2117,7 @@ class NormalBaseRM(PredicateWidthBaseRM):
     pass
 
 
-class NormalSimpleRM(DZBaseRM, SZBaseRM, NormalBaseRM):
+class NormalSimpleRM(ZZCombinedBaseRM, NormalBaseRM):
     """normal: simple mode"""
     dz: BaseRM.mode[3]
     sz: BaseRM.mode[4]
@@ -1782,7 +2150,7 @@ class NormalFFRc0RM(FFPRRc0BaseRM, VLiBaseRM, NormalBaseRM):
         yield from super().specifiers(record=record, mode="ff")
 
 
-class NormalSatRM(SatBaseRM, DZBaseRM, SZBaseRM, NormalBaseRM):
+class NormalSatRM(SatBaseRM, ZZCombinedBaseRM, NormalBaseRM):
     """normal: sat mode: N=0/1 u/s, SUBVL=1"""
     N: BaseRM.mode[2]
     dz: BaseRM.mode[3]
@@ -1913,14 +2281,14 @@ class LDSTIdxBaseRM(PredicateWidthBaseRM):
     pass
 
 
-class LDSTIdxSimpleRM(SEABaseRM, DZBaseRM, SZBaseRM, LDSTIdxBaseRM):
+class LDSTIdxSimpleRM(SEABaseRM, ZZCombinedBaseRM, LDSTIdxBaseRM):
     """ld/st index: simple mode"""
     SEA: BaseRM.mode[2]
     dz: BaseRM.mode[3]
     sz: BaseRM.mode[4]
 
 
-class LDSTIdxStrideRM(SEABaseRM, DZBaseRM, SZBaseRM, LDSTIdxBaseRM):
+class LDSTIdxStrideRM(SEABaseRM, ZZCombinedBaseRM, LDSTIdxBaseRM):
     """ld/st index: strided (scalar only source)"""
     SEA: BaseRM.mode[2]
     dz: BaseRM.mode[3]
@@ -1932,7 +2300,7 @@ class LDSTIdxStrideRM(SEABaseRM, DZBaseRM, SZBaseRM, LDSTIdxBaseRM):
         yield from super().specifiers(record=record)
 
 
-class LDSTIdxSatRM(SatBaseRM, DZBaseRM, SZBaseRM, LDSTIdxBaseRM):
+class LDSTIdxSatRM(SatBaseRM, ZZCombinedBaseRM, LDSTIdxBaseRM):
     """ld/st index: sat mode: N=0/1 u/s"""
     N: BaseRM.mode[2]
     dz: BaseRM.mode[3]
@@ -1977,8 +2345,8 @@ class CROpBaseRM(BaseRM):
     SNZ: BaseRM[7]
 
 
-class CROpSimpleRM(PredicateBaseRM, DZBaseRM, SZBaseRM, CROpBaseRM):
-    """cr_op: simple mode"""
+class CROpSimpleRM(PredicateBaseRM, ZZCombinedBaseRM, CROpBaseRM):
+    """crop: simple mode"""
     RG: BaseRM[20]
     dz: BaseRM[22]
     sz: BaseRM[23]
@@ -1989,32 +2357,34 @@ class CROpSimpleRM(PredicateBaseRM, DZBaseRM, SZBaseRM, CROpBaseRM):
 
         yield from super().specifiers(record=record)
 
-class CROpMRRM(MRBaseRM, DZBaseRM, SZBaseRM, CROpBaseRM):
-    """cr_op: scalar reduce mode (mapreduce), SUBVL=1"""
+
+class CROpMRRM(MRBaseRM, ZZCombinedBaseRM, CROpBaseRM):
+    """crop: scalar reduce mode (mapreduce), SUBVL=1"""
     RG: BaseRM[20]
     dz: BaseRM[22]
     sz: BaseRM[23]
 
 
-class CROpFF3RM(FFPRRc1BaseRM, VLiBaseRM, ZZBaseRM, PredicateBaseRM, CROpBaseRM):
-    """cr_op: ffirst 3-bit mode"""
+class CROpFF3RM(FFPRRc0BaseRM, PredicateBaseRM, VLiBaseRM, DZBaseRM, SZBaseRM, CROpBaseRM):
+    """crop: ffirst 3-bit mode"""
+    RC1: BaseRM[19]
     VLi: BaseRM[20]
     inv: BaseRM[21]
-    CR: BaseRM[22, 23]
-    zz: BaseRM[6]
-    sz: BaseRM[6]
-    dz: BaseRM[6]
+    dz: BaseRM[22]
+    sz: BaseRM[23]
 
     def specifiers(self, record):
         yield from super().specifiers(record=record, mode="ff")
 
 
-class CROpFF5RM(FFPRRc0BaseRM, PredicateBaseRM,
-                VLiBaseRM, DZBaseRM, SZBaseRM, CROpBaseRM):
+# FIXME: almost everything in this class contradicts the specs.
+# However, this is the direct translation of the pysvp64asm code.
+# Please revisit this code; there is an inactive sketch below.
+class CROpFF5RM(FFPRRc1BaseRM, PredicateBaseRM, VLiBaseRM, CROpBaseRM):
     """cr_op: ffirst 5-bit mode"""
     VLi: BaseRM[20]
     inv: BaseRM[21]
-    RC1: BaseRM[19] # cheat: set RC=1 based on ffirst mode being set
+    CR: BaseRM[22, 23]
     dz: BaseRM[22]
     sz: BaseRM[23]
 
@@ -2127,21 +2497,829 @@ class RM(BaseRM):
     normal: NormalRM
     ldst_imm: LDSTImmRM
     ldst_idx: LDSTIdxRM
-    cr_op: CROpRM
+    crop: CROpRM
     branch: BranchRM
 
-    def select(self, record):
-        rm = self
-        Rc = record.Rc
 
-        # the idea behind these tables is that they are now literally
+@_dataclasses.dataclass(eq=True, frozen=True)
+class Specifier:
+    record: Record
+
+    @classmethod
+    def match(cls, desc, record):
+        raise NotImplementedError()
+
+    def validate(self, others):
+        pass
+
+    def assemble(self, insn):
+        raise NotImplementedError()
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class SpecifierWidth(Specifier):
+    width: _SVP64Width
+
+    @classmethod
+    def match(cls, desc, record, etalon):
+        (mode, _, value) = desc.partition("=")
+        mode = mode.strip()
+        value = value.strip()
+        if mode != etalon:
+            return None
+        width = _SVP64Width(value)
+
+        return cls(record=record, width=width)
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class SpecifierW(SpecifierWidth):
+    @classmethod
+    def match(cls, desc, record):
+        return super().match(desc=desc, record=record, etalon="w")
+
+    def assemble(self, insn):
+        selector = insn.select(record=self.record)
+        selector.ewsrc = self.width.value
+        selector.elwidth = self.width.value
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class SpecifierSW(SpecifierWidth):
+    @classmethod
+    def match(cls, desc, record):
+        return super().match(desc=desc, record=record, etalon="sw")
+
+    def assemble(self, insn):
+        selector = insn.select(record=self.record)
+        selector.ewsrc = self.width.value
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class SpecifierDW(SpecifierWidth):
+    @classmethod
+    def match(cls, desc, record):
+        return super().match(desc=desc, record=record, etalon="dw")
+
+    def assemble(self, insn):
+        selector = insn.select(record=self.record)
+        selector.elwidth = self.width.value
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class SpecifierSubVL(Specifier):
+    value: _SVP64SubVL
+
+    @classmethod
+    def match(cls, desc, record):
+        try:
+            value = _SVP64SubVL(desc)
+        except ValueError:
+            return None
+
+        return cls(record=record, value=value)
+
+    def assemble(self, insn):
+        selector = insn.select(record=self.record)
+        selector.subvl = int(self.value.value)
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class SpecifierPredicate(Specifier):
+    mode: str
+    pred: _SVP64Pred
+
+    @classmethod
+    def match(cls, desc, record, mode_match, pred_match):
+        (mode, _, pred) = desc.partition("=")
+
+        mode = mode.strip()
+        if not mode_match(mode):
+            return None
+
+        pred = _SVP64Pred(pred.strip())
+        if not pred_match(pred):
+            raise ValueError(pred)
+
+        return cls(record=record, mode=mode, pred=pred)
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class SpecifierFFPR(SpecifierPredicate):
+    @classmethod
+    def match(cls, desc, record, mode):
+        return super().match(desc=desc, record=record,
+            mode_match=lambda mode_arg: mode_arg == mode,
+            pred_match=lambda pred_arg: pred_arg.mode in (
+                _SVP64PredMode.CR,
+                _SVP64PredMode.RC1,
+            ))
+
+    def validate(self, others):
+        if self.record.svp64.mode is _SVMode.CROP:
+            if self.mode == "pr":
+                raise ValueError("crop: 'pr' mode not supported")
+            if (self.record.svp64.extra_CR_3bit and
+                    (self.pred.mode is not _SVP64PredMode.RC1)):
+                raise ValueError("3-bit CRs only support RC1/~RC1 BO")
+
+    def assemble(self, insn):
+        selector = insn.select(record=self.record)
+        if selector.mode.sel != 0:
+            raise ValueError("cannot override mode")
+        if self.record.svp64.mode is _SVMode.CROP:
+            selector.mode.sel = 0b10
+            selector.inv = self.pred.inv
+            if not self.record.svp64.extra_CR_3bit:
+                selector.CR = self.pred.state
+        else:
+            selector.mode.sel = 0b01 if self.mode == "ff" else 0b11
+            selector.inv = self.pred.inv
+            if self.record.Rc:
+                selector.CR = self.pred.state
+            else:
+                selector.RC1 = self.pred.state
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class SpecifierFF(SpecifierFFPR):
+    @classmethod
+    def match(cls, desc, record):
+        return super().match(desc=desc, record=record, mode="ff")
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class SpecifierPR(SpecifierFFPR):
+    @classmethod
+    def match(cls, desc, record):
+        return super().match(desc=desc, record=record, mode="pr")
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class SpecifierMask(SpecifierPredicate):
+    @classmethod
+    def match(cls, desc, record, mode):
+        return super().match(desc=desc, record=record,
+            mode_match=lambda mode_arg: mode_arg == mode,
+            pred_match=lambda pred_arg: pred_arg.mode in (
+                _SVP64PredMode.INT,
+                _SVP64PredMode.CR,
+            ))
+
+    def assemble(self, insn):
+        raise NotImplementedError()
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class SpecifierM(SpecifierMask):
+    @classmethod
+    def match(cls, desc, record):
+        return super().match(desc=desc, record=record, mode="m")
+
+    def validate(self, others):
+        for spec in others:
+            if isinstance(spec, SpecifierSM):
+                raise ValueError("source-mask and predicate mask conflict")
+            elif isinstance(spec, SpecifierDM):
+                raise ValueError("dest-mask and predicate mask conflict")
+
+    def assemble(self, insn):
+        selector = insn.select(record=self.record)
+        selector.mask = int(self.pred)
+        if ((self.record.ptype is _SVPType.P2) and
+                (self.record.svp64.mode is not _SVMode.BRANCH)):
+            selector.smask = int(self.pred)
+        selector.mmode = (self.pred.mode is _SVP64PredMode.CR)
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class SpecifierSM(SpecifierMask):
+    @classmethod
+    def match(cls, desc, record):
+        return super().match(desc=desc, record=record, mode="sm")
+
+    def validate(self, others):
+        if self.record.svp64.ptype is _SVPType.P1:
+            raise ValueError("source-mask on non-twin predicate")
+
+        if self.pred.mode is _SVP64PredMode.CR:
+            twin = None
+            for spec in others:
+                if isinstance(spec, SpecifierDM):
+                    twin = spec
+
+            if twin is None:
+                raise ValueError("missing dest-mask in CR twin predication")
+            if self.pred.mode != twin.pred.mode:
+                raise ValueError(f"predicate masks mismatch: {self.pred!r} vs {twin.pred!r}")
+
+    def assemble(self, insn):
+        selector = insn.select(record=self.record)
+        selector.smask = int(self.pred)
+        selector.mmode = (self.pred.mode is _SVP64PredMode.CR)
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class SpecifierDM(SpecifierMask):
+    @classmethod
+    def match(cls, desc, record):
+        return super().match(desc=desc, record=record, mode="dm")
+
+    def validate(self, others):
+        if self.record.svp64.ptype is _SVPType.P1:
+            raise ValueError("dest-mask on non-twin predicate")
+
+        if self.pred.mode is _SVP64PredMode.CR:
+            twin = None
+            for spec in others:
+                if isinstance(spec, SpecifierSM):
+                    twin = spec
+
+            if twin is None:
+                raise ValueError("missing source-mask in CR twin predication")
+            if self.pred.mode != twin.pred.mode:
+                raise ValueError(f"predicate masks mismatch: {self.pred!r} vs {twin.pred!r}")
+
+    def assemble(self, insn):
+        selector = insn.select(record=self.record)
+        selector.mask = int(self.pred)
+        selector.mmode = (self.pred.mode is _SVP64PredMode.CR)
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class SpecifierZZ(Specifier):
+    @classmethod
+    def match(cls, desc, record):
+        if desc != "zz":
+            return None
+
+        return cls(record=record)
+
+    def validate(self, others):
+        for spec in others:
+            # Since zz takes precedence (overrides) sz and dz,
+            # treat them as mutually exclusive.
+            if isinstance(spec, (SpecifierSZ, SpecifierDZ)):
+                raise ValueError("mutually exclusive predicate masks")
+
+    def assemble(self, insn):
+        selector = insn.select(record=self.record)
+        if hasattr(selector, "zz"): # this should be done in a different way
+            selector.zz = 1
+        else:
+            selector.sz = 1
+            selector.dz = 1
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class SpecifierXZ(Specifier):
+    desc: str
+    hint: str = _dataclasses.field(repr=False)
+
+    @classmethod
+    def match(cls, desc, record, etalon, hint):
+        if desc != etalon:
+            return None
+
+        return cls(desc=desc, record=record, hint=hint)
+
+    def validate(self, others):
+        if self.record.svp64.ptype is _SVPType.P1:
+            raise ValueError(f"{self.hint} on non-twin predicate")
+
+        if self.pred.mode is _SVP64PredMode.CR:
+            twin = None
+            for spec in others:
+                if isinstance(spec, SpecifierXZ):
+                    twin = spec
+
+            if twin is None:
+                raise ValueError(f"missing {self.hint} in CR twin predication")
+            if self.pred != twin.pred:
+                raise ValueError(f"predicate masks mismatch: {self.pred!r} vs {twin.pred!r}")
+
+    def assemble(self, insn):
+        selector = insn.select(record=self.record)
+        setattr(selector, self.desc, 1)
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class SpecifierSZ(SpecifierXZ):
+    @classmethod
+    def match(cls, desc, record):
+        return super().match(desc=desc, record=record,
+            etalon="sz", hint="source-mask")
+
+    def validate(self, others):
+        for spec in others:
+            if self.record.svp64.mode is not _SVMode.CROP:
+                if isinstance(spec, SpecifierFF):
+                    raise ValueError("source-zero not allowed in ff mode")
+                elif isinstance(spec, SpecifierPR):
+                    raise ValueError("source-zero not allowed in pr mode")
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class SpecifierDZ(SpecifierXZ):
+    @classmethod
+    def match(cls, desc, record):
+        return super().match(desc=desc, record=record,
+            etalon="dz", hint="dest-mask")
+
+    def validate(self, others):
+        for spec in others:
+            if ((self.record.svp64.mode is not _SVMode.CROP) and
+                    isinstance(spec, (SpecifierFF, SpecifierPR)) and
+                    (spec.pred.mode is _SVP64PredMode.RC1)):
+                mode = "ff" if isinstance(spec, SpecifierFF) else "pr"
+                raise ValueError(f"dest-zero not allowed in {mode} mode BO")
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class SpecifierEls(Specifier):
+    @classmethod
+    def match(cls, desc, record):
+        if desc != "els":
+            return None
+
+        if record.svp64.mode not in (_SVMode.LDST_IMM, _SVMode.LDST_IDX):
+            raise ValueError("els is only valid in ld/st modes")
+
+        return cls(record=record)
+
+    def assemble(self, insn):
+        if self.record.svp64.mode is _SVMode.LDST_IDX: # stride mode
+            insn.prefix.rm.mode[0] = 0
+            insn.prefix.rm.mode[1] = 1
+
+        selector = insn.select(record=self.record)
+        if self.record.svp64.mode is not _SVMode.LDST_IDX: # stride mode
+            selector.els = 1
+
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class SpecifierSEA(Specifier):
+    @classmethod
+    def match(cls, desc, record):
+        if desc != "sea":
+            return None
+
+        return cls(record=record)
+
+    def validate(self, others):
+        if self.record.svp64.mode is not _SVMode.LDST_IDX:
+            raise ValueError("sea is only valid in ld/st modes")
+
+        for spec in others:
+            if isinstance(spec, SpecifierFF):
+                raise ValueError(f"sea cannot be used in ff mode")
+
+    def assemble(self, insn):
+        selector = insn.select(record=self.record)
+        if selector.mode.sel not in (0b00, 0b01):
+            raise ValueError("sea is only valid for normal and els modes")
+        selector.SEA = 1
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class SpecifierSat(Specifier):
+    desc: str
+    sign: bool
+
+    @classmethod
+    def match(cls, desc, record, etalon, sign):
+        if desc != etalon:
+            return None
+
+        if record.svp64.mode not in (_SVMode.NORMAL, _SVMode.LDST_IMM, _SVMode.LDST_IDX):
+            raise ValueError("only normal, ld/st imm and ld/st idx modes supported")
+
+        return cls(record=record, desc=desc, sign=sign)
+
+    def assemble(self, insn):
+        selector = insn.select(record=self.record)
+        selector.mode[0] = 0b1
+        selector.mode[1] = 0b0
+        selector.N = int(self.sign)
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class SpecifierSatS(SpecifierSat):
+    @classmethod
+    def match(cls, desc, record):
+        return super().match(desc=desc, record=record,
+            etalon="sats", sign=True)
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class SpecifierSatU(SpecifierSat):
+    @classmethod
+    def match(cls, desc, record):
+        return super().match(desc=desc, record=record,
+            etalon="satu", sign=False)
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class SpecifierMapReduce(Specifier):
+    RG: bool
+
+    @classmethod
+    def match(cls, record, RG):
+        if record.svp64.mode not in (_SVMode.NORMAL, _SVMode.CROP):
+            raise ValueError("only normal and crop modes supported")
+
+        return cls(record=record, RG=RG)
+
+    def assemble(self, insn):
+        selector = insn.select(record=self.record)
+        if self.record.svp64.mode not in (_SVMode.NORMAL, _SVMode.CROP):
+            raise ValueError("only normal and crop modes supported")
+        selector.mode[0] = 0
+        selector.mode[1] = 0
+        selector.mode[2] = 1
+        selector.RG = self.RG
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class SpecifierMR(SpecifierMapReduce):
+    @classmethod
+    def match(cls, desc, record):
+        if desc != "mr":
+            return None
+
+        return super().match(record=record, RG=False)
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class SpecifierMRR(SpecifierMapReduce):
+    @classmethod
+    def match(cls, desc, record):
+        if desc != "mrr":
+            return None
+
+        return super().match(record=record, RG=True)
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class SpecifierBranch(Specifier):
+    @classmethod
+    def match(cls, desc, record, etalon):
+        if desc != etalon:
+            return None
+
+        if record.svp64.mode is not _SVMode.BRANCH:
+            raise ValueError("only branch modes supported")
+
+        return cls(record=record)
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class SpecifierAll(SpecifierBranch):
+    @classmethod
+    def match(cls, desc, record):
+        return super().match(desc=desc, record=record, etalon="all")
+
+    def assemble(self, insn):
+        selector = insn.select(record=self.record)
+        selector.ALL = 1
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class SpecifierSNZ(Specifier):
+    @classmethod
+    def match(cls, desc, record):
+        if desc != "snz":
+            return None
+
+        if record.svp64.mode not in (_SVMode.BRANCH, _SVMode.CROP):
+            raise ValueError("only branch and crop modes supported")
+
+        return cls(record=record)
+
+    def assemble(self, insn):
+        selector = insn.select(record=self.record)
+        if self.record.svp64.mode in (_SVMode.CROP, _SVMode.BRANCH):
+            selector.SNZ = 1
+            if self.record.svp64.mode is _SVMode.BRANCH:
+                selector.sz = 1
+        else:
+            raise ValueError("only branch and crop modes supported")
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class SpecifierSL(SpecifierBranch):
+    @classmethod
+    def match(cls, desc, record):
+        return super().match(desc=desc, record=record, etalon="sl")
+
+    def assemble(self, insn):
+        selector = insn.select(record=self.record)
+        selector.SL = 1
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class SpecifierSLu(SpecifierBranch):
+    @classmethod
+    def match(cls, desc, record):
+        return super().match(desc=desc, record=record, etalon="slu")
+
+    def assemble(self, insn):
+        selector = insn.select(record=self.record)
+        selector.SLu = 1
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class SpecifierLRu(SpecifierBranch):
+    @classmethod
+    def match(cls, desc, record):
+        return super().match(desc=desc, record=record, etalon="lru")
+
+    def assemble(self, insn):
+        selector = insn.select(record=self.record)
+        selector.LRu = 1
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class SpecifierVSXX(SpecifierBranch):
+    VSb: bool
+    VLi: bool
+
+    @classmethod
+    def match(cls, desc, record, etalon, VSb, VLi):
+        if desc != etalon:
+            return None
+
+        if record.svp64.mode is not _SVMode.BRANCH:
+            raise ValueError("only branch modes supported")
+
+        return cls(record=record, VSb=VSb, VLi=VLi)
+
+    def assemble(self, insn):
+        selector = insn.select(record=self.record)
+        selector.VLS = 1
+        selector.VSb = int(self.VSb)
+        selector.VLi = int(self.VLi)
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class SpecifierVS(SpecifierVSXX):
+    @classmethod
+    def match(cls, desc, record):
+        return super().match(desc=desc, record=record,
+            etalon="vs", VSb=False, VLi=False)
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class SpecifierVSi(SpecifierVSXX):
+    @classmethod
+    def match(cls, desc, record):
+        return super().match(desc=desc, record=record,
+            etalon="vsi", VSb=False, VLi=True)
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class SpecifierVSb(SpecifierVSXX):
+    @classmethod
+    def match(cls, desc, record):
+        return super().match(desc=desc, record=record,
+            etalon="vsb", VSb=True, VLi=False)
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class SpecifierVSbi(SpecifierVSXX):
+    @classmethod
+    def match(cls, desc, record):
+        return super().match(desc=desc, record=record,
+            etalon="vsbi", VSb=True, VLi=True)
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class SpecifierCTX(Specifier):
+    CTi: bool
+
+    @classmethod
+    def match(cls, desc, record, etalon, CTi):
+        if desc != etalon:
+            return None
+
+        if record.svp64.mode is not _SVMode.BRANCH:
+            raise ValueError("only branch modes supported")
+
+        return cls(record=record, CTi=CTi)
+
+    def assemble(self, insn):
+        selector = insn.select(record=self.record)
+        selector.CTR = 1
+        selector.CTi = int(self.CTi)
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class SpecifierCTR(SpecifierCTX):
+    @classmethod
+    def match(cls, desc, record):
+        return super().match(desc=desc, record=record,
+            etalon="ctr", CTi=False)
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class SpecifierCTi(SpecifierCTX):
+    @classmethod
+    def match(cls, desc, record):
+        return super().match(desc=desc, record=record,
+            etalon="cti", CTi=True)
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class SpecifierPI(Specifier):
+    @classmethod
+    def match(cls, desc, record):
+        if desc != "pi":
+            return None
+
+        if record.svp64.mode is not _SVMode.LDST_IMM:
+            raise ValueError("only ld/st imm mode supported")
+
+        return cls(record=record)
+
+    def assemble(self, insn):
+        selector = insn.select(record=self.record)
+        selector.mode[0] = 0b0
+        selector.mode[1] = 0b0
+        selector.mode[2] = 0b1
+        selector.pi = 0b1
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class SpecifierLF(Specifier):
+    @classmethod
+    def match(cls, desc, record):
+        if desc != "lf":
+            return None
+
+        if record.svp64.mode is not _SVMode.LDST_IMM:
+            raise ValueError("only ld/st imm mode supported")
+
+        return cls(record=record)
+
+    def assemble(self, insn):
+        selector = insn.select(record=self.record)
+        selector.mode[2] = 1
+        selector.lf = 0b1
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class SpecifierVLi(Specifier):
+    @classmethod
+    def match(cls, desc, record):
+        if desc != "vli":
+            return None
+
+        return cls(record=record)
+
+    def validate(self, others):
+        for spec in others:
+            if isinstance(spec, SpecifierFF):
+                return
+
+        raise ValueError("VLi only allowed in failfirst")
+
+    def assemble(self, insn):
+        selector = insn.select(record=self.record)
+        selector.VLi = 1
+
+
+class Specifiers(tuple):
+    SPECS = (
+        SpecifierW,
+        SpecifierSW,
+        SpecifierDW,
+        SpecifierSubVL,
+        SpecifierFF,
+        SpecifierPR,
+        SpecifierM,
+        SpecifierSM,
+        SpecifierDM,
+        SpecifierZZ,
+        SpecifierSZ,
+        SpecifierDZ,
+        SpecifierEls,
+        SpecifierSEA,
+        SpecifierSatS,
+        SpecifierSatU,
+        SpecifierMR,
+        SpecifierMRR,
+        SpecifierAll,
+        SpecifierSNZ,
+        SpecifierSL,
+        SpecifierSLu,
+        SpecifierLRu,
+        SpecifierVS,
+        SpecifierVSi,
+        SpecifierVSb,
+        SpecifierVSbi,
+        SpecifierVLi,
+        SpecifierCTR,
+        SpecifierCTi,
+        SpecifierPI,
+        SpecifierLF,
+    )
+
+    def __new__(cls, items, record):
+        def transform(item):
+            for spec_cls in cls.SPECS:
+                spec = spec_cls.match(item, record=record)
+                if spec is not None:
+                    return spec
+            raise ValueError(item)
+
+        # TODO: remove this hack
+        items = dict.fromkeys(items)
+        if "vli" in items:
+            del items["vli"]
+            items["vli"] = None
+        items = tuple(items)
+
+        specs = tuple(map(transform, items))
+        for (index, spec) in enumerate(specs):
+            head = specs[:index]
+            tail = specs[index + 1:]
+            spec.validate(others=(head + tail))
+
+        return super().__new__(cls, specs)
+
+
+class SVP64OperandMeta(type):
+    class SVP64NonZeroOperand(NonZeroOperand):
+        def assemble(self, insn, value):
+            if isinstance(value, str):
+                value = int(value, 0)
+            if not isinstance(value, int):
+                raise ValueError("non-integer operand")
+
+            # FIXME: this is really weird
+            if self.record.name in ("svstep", "svstep."):
+                value += 1 # compensation
+
+            return super().assemble(value=value, insn=insn)
+
+    class SVP64XOStaticOperand(SpanStaticOperand):
+        def __init__(self, record, value, span):
+            return super().__init__(record=record, name="XO", value=value, span=span)
+
+    __TRANSFORM = {
+        NonZeroOperand: SVP64NonZeroOperand,
+        XOStaticOperand: SVP64XOStaticOperand,
+    }
+
+    def __new__(metacls, name, bases, ns):
+        bases = list(bases)
+        for (index, base_cls) in enumerate(bases):
+            bases[index] = metacls.__TRANSFORM.get(base_cls, base_cls)
+
+        bases = tuple(bases)
+
+        return super().__new__(metacls, name, bases, ns)
+
+
+class SVP64Operand(Operand, metaclass=SVP64OperandMeta):
+    @property
+    def span(self):
+        return tuple(map(lambda bit: (bit + 32), super().span))
+
+
+class RMSelector:
+    def __init__(self, insn, record):
+        self.__insn = insn
+        self.__record = record
+        return super().__init__()
+
+    def __str__(self):
+        return self.rm.__doc__
+
+    def __repr__(self):
+        return repr(self.rm)
+
+    @property
+    def insn(self):
+        return self.__insn
+
+    @property
+    def record(self):
+        return self.__record
+
+    @property
+    def rm(self):
+        rm = getattr(self.insn.prefix.rm, self.record.svp64.mode.name.lower())
+
+        # The idea behind these tables is that they are now literally
         # in identical format to insndb.csv and minor_xx.csv and can
-        # be done precisely as that.  the only thing to watch out for
+        # be done precisely as that. The only thing to watch out for
         # is the insertion of Rc=1 as a "mask/value" bit and likewise
         # regtype detection (3-bit BF/BFA, 5-bit BA/BB/BT) also inserted
         # as the LSB.
         table = None
-        if record.svp64.mode is _SVMode.NORMAL:
+        if self.record.svp64.mode is _SVMode.NORMAL:
             # concatenate mode 5-bit with Rc (LSB) then do a mask/map search
             #    mode  Rc  mask  Rc  member
             table = (
@@ -2153,10 +3331,9 @@ class RM(BaseRM):
                 (0b110000, 0b110001, "prrc0"),  # predicate,  Rc=0
                 (0b110001, 0b110001, "prrc1"),  # predicate,  Rc=1
             )
-            rm = rm.normal
-            search = ((int(rm.mode) << 1) | Rc)
+            search = ((int(self.insn.prefix.rm.normal.mode) << 1) | self.record.Rc)
 
-        elif record.svp64.mode is _SVMode.LDST_IMM:
+        elif self.record.svp64.mode is _SVMode.LDST_IMM:
             # concatenate mode 5-bit with Rc (LSB) then do a mask/map search
             #    mode  Rc  mask  Rc  member
             # ironically/coincidentally this table is identical to NORMAL
@@ -2170,10 +3347,9 @@ class RM(BaseRM):
                 (0b110001, 0b110001, "prrc1"),  # predicate,  Rc=1
                 (0b110000, 0b110001, "prrc0"),  # predicate,  Rc=0
             )
-            rm = rm.ldst_imm
-            search = ((int(rm.mode) << 1) | Rc)
+            search = ((int(self.insn.prefix.rm.ldst_imm.mode) << 1) | self.record.Rc)
 
-        elif record.svp64.mode is _SVMode.LDST_IDX:
+        elif self.record.svp64.mode is _SVMode.LDST_IDX:
             # concatenate mode 5-bit with Rc (LSB) then do a mask/map search
             #    mode  Rc  mask  Rc  member
             table = (
@@ -2183,37 +3359,20 @@ class RM(BaseRM):
                 (0b110001, 0b110001, "prrc1"),  # predicate,  Rc=1
                 (0b110000, 0b110001, "prrc0"),  # predicate,  Rc=0
             )
-            rm = rm.ldst_idx
-            search = ((int(rm.mode) << 1) | Rc)
+            search = ((int(self.insn.prefix.rm.ldst_idx.mode) << 1) | self.record.Rc)
 
-        elif record.svp64.mode is _SVMode.CROP:
+        elif self.record.svp64.mode is _SVMode.CROP:
             # concatenate mode 5-bit with regtype (LSB) then do mask/map search
             #    mode  3b  mask  3b  member
             table = (
                 (0b000000, 0b111000, "simple"), # simple
                 (0b001000, 0b111000, "mr"),     # mapreduce
-                (0b100001, 0b100001, "ff3"),    # failfirst, 3-bit CR
-                (0b100000, 0b100000, "ff5"),    # failfirst, 5-bit CR
+                (0b100001, 0b100001, "ff3"),    # ffirst, 3-bit CR
+                (0b100000, 0b100000, "ff5"),    # ffirst, 5-bit CR
             )
-            # determine CR type, 5-bit (BA/BB/BT) or 3-bit Field (BF/BFA)
-            regtype = None
-            for idx in range(0, 4):
-                for entry in record.svp64.extra[idx]:
-                    if entry.regtype is _SVExtraRegType.DST:
-                        if regtype is not None:
-                            raise ValueError(record.svp64)
-                        regtype = _RegType(entry.reg)
-            if regtype is _RegType.CR_REG:
-                regtype = 0 # 5-bit
-            elif regtype is _RegType.CR_BIT:
-                regtype = 1 # 3-bit
-            else:
-                raise ValueError(record.svp64)
-            # finally provide info for search
-            rm = rm.cr_op
-            search = ((int(rm.mode) << 1) | (regtype or 0))
+            search = ((int(self.insn.prefix.rm.crop.mode) << 1) | int(self.record.svp64.extra_CR_3bit))
 
-        elif record.svp64.mode is _SVMode.BRANCH:
+        elif self.record.svp64.mode is _SVMode.BRANCH:
             # just mode 2-bit
             #    mode  mask  member
             table = (
@@ -2223,21 +3382,32 @@ class RM(BaseRM):
                 (0b11, 0b11, "ctrvls"), # CTR+VLset mode
             )
             # slightly weird: doesn't have a 5-bit "mode" field like others
-            rm = rm.branch
-            search = int(rm.mode[0, 1])
+            search = int(self.insn.prefix.rm.branch.mode.sel)
 
         # look up in table
         if table is not None:
-            for (value, mask, member) in table:
+            for (value, mask, field) in table:
                 if ((value & mask) == (search & mask)):
-                    rm = getattr(rm, member)
-                    break
-
-        if rm.__class__ is self.__class__:
-            raise ValueError(self)
+                    return getattr(rm, field)
 
         return rm
 
+    def __getattr__(self, key):
+        if key.startswith(f"_{self.__class__.__name__}__"):
+            return super().__getattribute__(key)
+
+        return getattr(self.rm, key)
+
+    def __setattr__(self, key, value):
+        if key.startswith(f"_{self.__class__.__name__}__"):
+            return super().__setattr__(key, value)
+
+        rm = self.rm
+        if not hasattr(rm, key):
+            raise AttributeError(key)
+
+        return setattr(rm, key, value)
+
 
 class SVP64Instruction(PrefixedInstruction):
     """SVP64 instruction: https://libre-soc.org/openpower/sv/svp64/"""
@@ -2247,11 +3417,8 @@ class SVP64Instruction(PrefixedInstruction):
 
     prefix: Prefix
 
-    def record(self, db):
-        record = db[self.suffix]
-        if record is None:
-            raise KeyError(self)
-        return record
+    def select(self, record):
+        return RMSelector(insn=self, record=record)
 
     @property
     def binary(self):
@@ -2261,28 +3428,42 @@ class SVP64Instruction(PrefixedInstruction):
             bits.append(bit)
         return "".join(map(str, bits))
 
-    def disassemble(self, db,
+    @classmethod
+    def assemble(cls, record, arguments=None, specifiers=None):
+        insn = super().assemble(record=record, arguments=arguments)
+
+        specifiers = Specifiers(items=specifiers, record=record)
+        for specifier in specifiers:
+            specifier.assemble(insn=insn)
+
+        insn.prefix.PO = 0x1
+        insn.prefix.id = 0x3
+
+        return insn
+
+    def disassemble(self, record,
             byteorder="little",
-            verbosity=Verbosity.NORMAL):
-        def blob(integer):
-            if verbosity <= Verbosity.SHORT:
+            style=Style.NORMAL):
+        def blob(insn):
+            if style <= Style.SHORT:
                 return ""
             else:
-                blob = integer.to_bytes(length=4, byteorder=byteorder)
+                blob = insn.bytes(byteorder=byteorder)
                 blob = " ".join(map(lambda byte: f"{byte:02x}", blob))
                 return f"{blob}    "
 
-        record = self.record(db=db)
-        blob_prefix = blob(int(self.prefix))
-        blob_suffix = blob(int(self.suffix))
-        if record is None or record.svp64 is None:
+        blob_prefix = blob(self.prefix)
+        blob_suffix = blob(self.suffix)
+        if record is None:
             yield f"{blob_prefix}.long 0x{int(self.prefix):08x}"
             yield f"{blob_suffix}.long 0x{int(self.suffix):08x}"
             return
 
+        assert record.svp64 is not None
+
         name = f"sv.{record.name}"
 
-        rm = self.prefix.rm.select(record=record)
+        rm = self.select(record=record)
 
         # convert specifiers to /x/y/z (sorted lexicographically)
         specifiers = sorted(rm.specifiers(record=record))
@@ -2292,19 +3473,25 @@ class SVP64Instruction(PrefixedInstruction):
 
         # convert operands to " ,x,y,z"
         operands = tuple(map(_operator.itemgetter(1),
-            self.dynamic_operands(db=db, verbosity=verbosity)))
+            self.spec_dynamic_operands(record=record, style=style)))
         operands = ",".join(operands)
         if len(operands) > 0: # if any separate with a space
             operands = (" " + operands)
 
-        yield f"{blob_prefix}{name}{specifiers}{operands}"
-        if blob_suffix:
-            yield f"{blob_suffix}"
+        if style <= Style.LEGACY:
+            yield f"{blob_prefix}.long 0x{int(self.prefix):08x}"
+            suffix = WordInstruction.integer(value=int(self.suffix))
+            yield from suffix.disassemble(record=record,
+                byteorder=byteorder, style=style)
+        else:
+            yield f"{blob_prefix}{name}{specifiers}{operands}"
+            if blob_suffix:
+                yield f"{blob_suffix}"
 
-        if verbosity >= Verbosity.VERBOSE:
+        if style >= Style.VERBOSE:
             indent = (" " * 4)
             binary = self.binary
-            spec = self.spec(db=db, prefix="sv.")
+            spec = self.spec(record=record, prefix="sv.")
 
             yield f"{indent}spec"
             yield f"{indent}{indent}{spec}"
@@ -2323,16 +3510,24 @@ class SVP64Instruction(PrefixedInstruction):
             yield f"{indent}opcodes"
             for opcode in record.opcodes:
                 yield f"{indent}{indent}{opcode!r}"
-            for (cls, kwargs) in record.mdwn.operands:
-                operand = cls(record=record, **kwargs)
-                yield from operand.disassemble(insn=self, record=record,
-                    verbosity=verbosity, indent=indent)
+            for operand in self.operands(record=record):
+                yield from operand.disassemble(insn=self,
+                    style=style, indent=indent)
             yield f"{indent}RM"
-            yield f"{indent}{indent}{rm.__doc__}"
-            for line in rm.disassemble(verbosity=verbosity):
+            yield f"{indent}{indent}{str(rm)}"
+            for line in rm.disassemble(style=style):
                 yield f"{indent}{indent}{line}"
             yield ""
 
+    @classmethod
+    def operands(cls, record):
+        for operand in super().operands(record=record):
+            parent = operand.__class__
+            name = f"SVP64{parent.__name__}"
+            bases = (SVP64Operand, parent)
+            child = type(name, bases, {})
+            yield child(**dict(operand))
+
 
 def parse(stream, factory):
     def match(entry):
@@ -2396,7 +3591,6 @@ class PPCDatabase:
         # The code below groups the instructions by name:section.
         # There can be multiple names for the same instruction.
         # The point is to capture different opcodes for the same instruction.
-        dd = _collections.defaultdict
         sections = {}
         records = _collections.defaultdict(set)
         path = (root / "insndb.csv")
@@ -2542,10 +3736,7 @@ class Database:
                 mdwn=mdwn, fields=fields)
             db.add(record)
             names[record.name] = record
-            PO = section.opcode
-            if PO is None:
-                PO = ppc[0].opcode
-            opcodes[section][PO.value].add(record)
+            opcodes[section][record.PO].add(record)
 
         self.__db = sorted(db)
         self.__names = dict(sorted(names.items()))
@@ -2565,9 +3756,15 @@ class Database:
 
     @_functools.lru_cache(maxsize=None)
     def __getitem__(self, key):
-        if isinstance(key, WordInstruction):
+        if isinstance(key, SVP64Instruction):
+            key = key.suffix
+
+        if isinstance(key, Instruction):
             PO = int(key.PO)
-            for (section, group) in self.__opcodes.items():
+            key = int(key)
+            sections = sorted(self.__opcodes)
+            for section in sections:
+                group = self.__opcodes[section]
                 for record in group[PO]:
                     if record.match(key=key):
                         return record