Fan-in and Fan-out are combinatorial.
     """
-    def __init__(self, width, num_rows):
+    def __init__(self, width, num_rows, op_wid=None):
         self.width = width
         self.id_wid = num_bits(width)
+        self.op_wid = op_wid
         self.alu = FPADDBasePipe(width, self.id_wid)
         ReservationStations.__init__(self, num_rows)
 
     def i_specfn(self):
-        return FPADDBaseData(self.width, self.id_wid)
+        return FPADDBaseData(self.width, self.id_wid, self.op_wid)
 
     def o_specfn(self):
-        return FPPackData(self.width, self.id_wid)
+        return FPPackData(self.width, self.id_wid, self.op_wid)
 
         https://steve.hollasch.net/cgindex/coding/ieeefloat.html
     """
 
-    def __init__(self, width, id_wid):
+    def __init__(self, width, id_wid, op_wid=None):
         self.width = width
         self.id_wid = id_wid
+        self.op_wid = op_wid
         self.i = self.ispec()
         self.o = self.ospec()
 
     def ispec(self):
-        return FPADDBaseData(self.width, self.id_wid)
+        return FPADDBaseData(self.width, self.id_wid, self.op_wid)
 
     def ospec(self):
-        return FPSCData(self.width, self.id_wid)
+        return FPSCData(self.width, self.id_wid, True, self.op_wid)
 
     def setup(self, m, i):
         """ links module to inputs and outputs
 
         m.d.comb += self.o.oz.eq(self.o.z.v)
         m.d.comb += self.o.mid.eq(self.i.mid)
+        if self.o.op_wid:
+            m.d.comb += self.o.op.eq(self.i.op)
 
         return m
 
         https://steve.hollasch.net/cgindex/coding/ieeefloat.html
     """
 
-    def __init__(self, width, id_wid):
+    def __init__(self, width, id_wid, op_wid=None):
         FPState.__init__(self, "special_cases")
         self.width = width
         self.id_wid = id_wid
+        self.op_wid = op_wid
         SimpleHandshake.__init__(self, self) # pipe is its own stage
         self.out = self.ospec()
 
     def ispec(self):
-        return FPADDBaseData(self.width, self.id_wid) # SpecialCases ispec
+        return FPADDBaseData(self.width, self.id_wid, self.op_wid) # SC ispec
 
     def ospec(self):
-        return FPSCData(self.width, self.id_wid) # DeNorm ospec
+        return FPSCData(self.width, self.id_wid, True, self.op_wid) # DeNorm
 
     def setup(self, m, i):
         """ links module to inputs and outputs
 
 
 class FPCorrectionsMod(Elaboratable):
 
-    def __init__(self, width, id_wid):
+    def __init__(self, width, id_wid, op_wid=None):
         self.width = width
         self.id_wid = id_wid
+        self.op_wid = op_wid # operand width
         self.i = self.ispec()
         self.out_z = self.ospec()
 
     def ispec(self):
-        return FPRoundData(self.width, self.id_wid)
+        return FPRoundData(self.width, self.id_wid, self.op_wid)
 
     def ospec(self):
-        return FPRoundData(self.width, self.id_wid)
+        return FPRoundData(self.width, self.id_wid, self.op_wid)
 
     def process(self, i):
         return self.out_z
 
 
 class FPSCData:
 
-    def __init__(self, width, id_wid, m_extra=True):
+    def __init__(self, width, id_wid, m_extra=True, op_wid=None):
 
         # NOTE: difference between z and oz is that oz is created by
         # special-cases module(s) and will propagate, along with its
         self.oz = Signal(width, reset_less=True)   # "finished" (bypass) result
         self.out_do_z = Signal(reset_less=True)    # "bypass" enabled
         self.mid = Signal(id_wid, reset_less=True) # multiplexer ID
+        self.op_wid = op_wid
+        if op_wid:
+            self.op = Signal(op_wid, reset_less=True) # operand
+
 
     def __iter__(self):
         yield from self.a
         yield self.oz
         yield self.out_do_z
         yield self.mid
+        if op_wid:
+            yield self.op
 
     def eq(self, i):
-        return [self.z.eq(i.z), self.out_do_z.eq(i.out_do_z), self.oz.eq(i.oz),
+        ret = [self.z.eq(i.z), self.out_do_z.eq(i.out_do_z), self.oz.eq(i.oz),
                 self.a.eq(i.a), self.b.eq(i.b), self.mid.eq(i.mid)]
+        if self.op_wid:
+            ret.append(self.op.eq(i.op))
+        return ret
 
 
 class FPAddDeNormMod(FPState, Elaboratable):
 
-    def __init__(self, width, id_wid, m_extra=True):
+    def __init__(self, width, id_wid, m_extra=True, op_wid=None):
         self.width = width
         self.id_wid = id_wid
         self.m_extra = m_extra
+        self.op_wid = op_wid
         self.i = self.ispec()
         self.o = self.ospec()
 
     def ispec(self):
-        return FPSCData(self.width, self.id_wid, self.m_extra)
+        return FPSCData(self.width, self.id_wid, self.m_extra, self.op_wid)
 
     def ospec(self):
-        return FPSCData(self.width, self.id_wid, self.m_extra)
+        return FPSCData(self.width, self.id_wid, self.m_extra, self.op_wid)
 
     def process(self, i):
         return self.o
 
         return [self.a, self.b, self.mid]
 
 
-class FPADDBaseData:
+class FPBaseData:
 
-    def __init__(self, width, id_wid):
+    def __init__(self, n_ops, width, id_wid, op_wid):
         self.width = width
         self.id_wid = id_wid
-        self.a  = Signal(width)                      # operand a
-        self.b  = Signal(width)                      # operand b
+        self.op_wid = op_wid
+        ops = []
+        for i in range(n_ops):
+            name = chr(ord("a")+i)
+            operand = Signal(width, name=name)
+            setattr(self, name, operand)
+            ops.append(operand)
+        self.ops = ops
         self.mid = Signal(id_wid, reset_less=True)   # RS multiplex ID
+        self.op = Signal(op_wid, reset_less=True)
 
     def eq(self, i):
-        return [self.a.eq(i.a), self.b.eq(i.b), self.mid.eq(i.mid)]
+        ret = []
+        for op1, op2 in zip(self.ops, i.ops):
+            ret.append(op1.eq(op2))
+        ret.append(self.mid.eq(i.mid))
+        if self.op_wid:
+            ret.append(self.op.eq(i.op))
+        return ret
+
+    def __iter__(self):
+        if self.ops:
+            yield from self.ops
+        yield self.mid
+        if self.id_wid:
+            yield self.op
 
     def ports(self):
-        return [self.a, self.b, self.mid]
+        return list(self)
+
+class FPADDBaseData(FPBaseData):
+
+    def __init__(self, width, id_wid, op_wid):
+        FPBaseData.__init__(self, 2, width, id_wid, op_wid)
 
 
 class FPGet2OpMod(PrevControl):
-    def __init__(self, width, id_wid):
+    def __init__(self, width, id_wid, op_wid=None):
         PrevControl.__init__(self)
         self.width = width
         self.id_wid = id_wid
         self.o = self.ospec()
 
     def ispec(self):
-        return FPADDBaseData(self.width, self.id_wid)
+        return FPADDBaseData(self.width, self.id_wid, self.op_wid)
 
     def ospec(self):
-        return FPADDBaseData(self.width, self.id_wid)
+        return FPADDBaseData(self.width, self.id_wid, self.op_wid)
 
     def process(self, i):
         return self.o
     """ gets operands
     """
 
-    def __init__(self, in_state, out_state, width, id_wid):
+    def __init__(self, in_state, out_state, width, id_wid, op_wid=None):
         FPState.__init__(self, in_state)
         self.out_state = out_state
-        self.mod = FPGet2OpMod(width, id_wid)
+        self.mod = FPGet2OpMod(width, id_wid, op_wid)
         self.o = self.ospec()
         self.in_stb = Signal(reset_less=True)
         self.out_ack = Signal(reset_less=True)
 
 
 class FPNormToPack(FPState, SimpleHandshake):
 
-    def __init__(self, width, id_wid):
+    def __init__(self, width, id_wid, op_wid=None):
         FPState.__init__(self, "normalise_1")
         self.id_wid = id_wid
+        self.op_wid = op_wid
         self.width = width
         SimpleHandshake.__init__(self, self) # pipeline is its own stage
 
     def ispec(self):
-        return FPAddStage1Data(self.width, self.id_wid) # Norm1ModSingle ispec
+        return FPAddStage1Data(self.width, self.id_wid, self.op_wid)
 
     def ospec(self):
-        return FPPackData(self.width, self.id_wid) # FPPackMod ospec
+        return FPPackData(self.width, self.id_wid, self.op_wid) # FPPackMod
 
     def setup(self, m, i):
         """ links module to inputs and outputs
 
 from ieee754.fpcommon.fpbase import FPState
 from .roundz import FPRoundData
 from nmutil.singlepipe import Object
+from ieee754.fpcommon.getop import FPBaseData
 
 
 class FPPackData(Object):
 
-    def __init__(self, width, id_wid):
+    def __init__(self, width, id_wid, op_wid):
         Object.__init__(self)
         self.z = Signal(width, reset_less=True)    # result
         self.mid = Signal(id_wid, reset_less=True) # multiplex ID
+        self.op = Signal(op_wid or 0, reset_less=True) # operand width
 
 
 class FPPackMod(Elaboratable):
 
-    def __init__(self, width, id_wid):
+    def __init__(self, width, id_wid, op_wid=None):
         self.width = width
         self.id_wid = id_wid
+        self.op_wid = op_wid
         self.i = self.ispec()
         self.o = self.ospec()
 
     def ispec(self):
-        return FPRoundData(self.width, self.id_wid)
+        return FPRoundData(self.width, self.id_wid, self.op_wid)
 
     def ospec(self):
-        return FPPackData(self.width, self.id_wid)
+        return FPPackData(self.width, self.id_wid, self.op_wid)
 
     def process(self, i):
         return self.o
         m.submodules.pack_in_z = in_z = FPNumBase(self.i.z)
         #m.submodules.pack_out_z = out_z = FPNumOut(z)
         m.d.comb += self.o.mid.eq(self.i.mid)
+        if self.i.op_wid:
+            m.d.comb += self.o.op.eq(self.i.op)
         with m.If(~self.i.out_do_z):
             with m.If(in_z.is_overflowed):
                 m.d.comb += z.inf(self.i.z.s)
 
 
 from nmigen import Signal
 from ieee754.fpcommon.fpbase import Overflow, FPNumBaseRecord
+from ieee754.fpcommon.getop import FPBaseData
 
-class FPAddStage1Data:
+class FPAddStage1Data(FPBaseData):
 
-    def __init__(self, width, id_wid):
+    def __init__(self, width, id_wid, op_wid=None):
+        FPBaseData.__init__(self, 0, width, id_wid, op_wid)
         self.z = FPNumBaseRecord(width, False)
         self.out_do_z = Signal(reset_less=True)
         self.oz = Signal(width, reset_less=True)
         self.of = Overflow()
-        self.mid = Signal(id_wid, reset_less=True)
 
     def __iter__(self):
         yield from self.z
         yield self.out_do_z
         yield self.oz
         yield from self.of
-        yield self.mid
+        yield from FPBaseData.__iter__(self)
 
     def eq(self, i):
-        return [self.z.eq(i.z), self.out_do_z.eq(i.out_do_z), self.oz.eq(i.oz),
-                self.of.eq(i.of), self.mid.eq(i.mid)]
+        ret = [self.z.eq(i.z), self.out_do_z.eq(i.out_do_z), self.oz.eq(i.oz),
+              self.of.eq(i.of),] + FPBaseData.eq(self, i)
+
+        return ret
 
 
 class FPNorm1Data:
 
-    def __init__(self, width, id_wid):
+    def __init__(self, width, id_wid, op_wid=None):
         self.roundz = Signal(reset_less=True, name="norm1_roundz")
         self.z = FPNumBaseRecord(width, False)
         self.out_do_z = Signal(reset_less=True)
         self.oz = Signal(width, reset_less=True)
         self.mid = Signal(id_wid, reset_less=True)
+        self.op_wid = op_wid
+        if op_wid:
+            self.op = Signal(op_wid, reset_less=True) # operand
 
     def eq(self, i):
-        return [self.z.eq(i.z), self.out_do_z.eq(i.out_do_z), self.oz.eq(i.oz),
+        ret = [self.z.eq(i.z), self.out_do_z.eq(i.out_do_z), self.oz.eq(i.oz),
                 self.roundz.eq(i.roundz), self.mid.eq(i.mid)]
+        if self.op_wid:
+            ret.append(self.op.eq(i.op))
+        return ret
 
 
 class FPNorm1ModSingle(Elaboratable):
 
-    def __init__(self, width, id_wid):
+    def __init__(self, width, id_wid, op_wid=None):
         self.width = width
         self.id_wid = id_wid
+        self.op_wid = op_wid
         self.i = self.ispec()
         self.o = self.ospec()
 
     def ispec(self):
-        return FPAddStage1Data(self.width, self.id_wid)
+        return FPAddStage1Data(self.width, self.id_wid, self.op_wid)
 
     def ospec(self):
-        return FPNorm1Data(self.width, self.id_wid)
+        return FPNorm1Data(self.width, self.id_wid, self.op_wid)
 
     def setup(self, m, i):
         """ links module to inputs and outputs
 
 
 class FPRoundData:
 
-    def __init__(self, width, id_wid):
+    def __init__(self, width, id_wid, op_wid=None):
         self.z = FPNumBaseRecord(width, False)
         self.mid = Signal(id_wid, reset_less=True) # multiplex ID
         # pipeline bypass [data comes from specialcases]
         self.out_do_z = Signal(reset_less=True)
         self.oz = Signal(width, reset_less=True)
+        self.op_wid = op_wid
+        if op_wid:
+            self.op = Signal(op_wid, reset_less=True)
 
     def eq(self, i):
-        return [self.z.eq(i.z), self.out_do_z.eq(i.out_do_z), self.oz.eq(i.oz),
+        ret = [self.z.eq(i.z), self.out_do_z.eq(i.out_do_z), self.oz.eq(i.oz),
                 self.mid.eq(i.mid)]
+        if self.op_wid:
+            ret.append(self.op.eq(i.op))
+        return ret
+
 
 
 class FPRoundMod(Elaboratable):
 
-    def __init__(self, width, id_wid):
+    def __init__(self, width, id_wid, op_wid=None):
         self.width = width
         self.id_wid = id_wid
+        self.op_wid = op_wid
         self.i = self.ispec()
         self.out_z = self.ospec()
 
     def ispec(self):
-        return FPNorm1Data(self.width, self.id_wid)
+        return FPNorm1Data(self.width, self.id_wid, self.op_wid)
 
     def ospec(self):
-        return FPRoundData(self.width, self.id_wid)
+        return FPRoundData(self.width, self.id_wid, self.op_wid)
 
     def process(self, i):
         return self.out_z
 
 from ieee754.fpcommon.fpbase import FPNumBaseRecord
 from ieee754.fpcommon.fpbase import FPState
 from ieee754.fpcommon.denorm import FPSCData
+from ieee754.fpcommon.getop import FPBaseData
 
 
 class FPMulStage0Data:
 
-    def __init__(self, width, id_wid):
+    def __init__(self, width, id_wid, op_wid=None):
+        FPBaseData.__init__(self, 0, width, id_wid, op_wid)
         self.z = FPNumBaseRecord(width, False)
         self.out_do_z = Signal(reset_less=True)
         self.oz = Signal(width, reset_less=True)
         mw = (self.z.m_width)*2 - 1 + 3 # sticky/round/guard bits + (2*mant) - 1
         self.product = Signal(mw, reset_less=True)
-        self.mid = Signal(id_wid, reset_less=True)
 
     def eq(self, i):
         return [self.z.eq(i.z), self.out_do_z.eq(i.out_do_z), self.oz.eq(i.oz),
-                self.product.eq(i.product), self.mid.eq(i.mid)]
+                self.product.eq(i.product)] + FPBaseData.eq(self, i)
 
 
 class FPMulStage0Mod(Elaboratable):
 
-    def __init__(self, width, id_wid):
+    def __init__(self, width, id_wid, op_wid=None):
         self.width = width
         self.id_wid = id_wid
+        self.op_wid = op_wid
         self.i = self.ispec()
         self.o = self.ospec()
 
     def ispec(self):
-        return FPSCData(self.width, self.id_wid, False)
+        return FPSCData(self.width, self.id_wid, False, self.op_wid)
 
     def ospec(self):
-        return FPMulStage0Data(self.width, self.id_wid)
+        return FPMulStage0Data(self.width, self.id_wid, self.op_wid)
 
     def process(self, i):
         return self.o
         m.d.comb += self.o.oz.eq(self.i.oz)
         m.d.comb += self.o.out_do_z.eq(self.i.out_do_z)
         m.d.comb += self.o.mid.eq(self.i.mid)
+        if self.o.op_wid:
+            m.d.comb += self.o.op.eq(self.i.op)
         return m
 
 
 
     """ Second stage of mul: preparation for normalisation.
     """
 
-    def __init__(self, width, id_wid):
+    def __init__(self, width, id_wid, op_wid=None):
         self.width = width
         self.id_wid = id_wid
+        self.op_wid = op_wid
         self.i = self.ispec()
         self.o = self.ospec()
 
     def ispec(self):
-        return FPMulStage0Data(self.width, self.id_wid)
+        return FPMulStage0Data(self.width, self.id_wid, self.op_wid)
 
     def ospec(self):
-        return FPAddStage1Data(self.width, self.id_wid)
+        return FPAddStage1Data(self.width, self.id_wid, self.op_wid)
 
     def process(self, i):
         return self.o
         m.d.comb += self.o.out_do_z.eq(self.i.out_do_z)
         m.d.comb += self.o.oz.eq(self.i.oz)
         m.d.comb += self.o.mid.eq(self.i.mid)
+        if self.o.op_wid:
+            m.d.comb += self.o.op.eq(self.i.op)
 
         return m
 
 
 
 class FPMulStages(FPState, SimpleHandshake):
 
-    def __init__(self, width, id_wid):
+    def __init__(self, width, id_wid, op_wid=None):
         FPState.__init__(self, "align")
         self.width = width
         self.id_wid = id_wid
+        self.op_wid = op_wid
         SimpleHandshake.__init__(self, self) # pipeline is its own stage
         self.m1o = self.ospec()
 
     def ispec(self):
-        return FPSCData(self.width, self.id_wid, False)
+        return FPSCData(self.width, self.id_wid, False, self.op_wid)
 
     def ospec(self):
-        return FPAddStage1Data(self.width, self.id_wid) # AddStage1 ospec
+        return FPAddStage1Data(self.width, self.id_wid, self.op_wid)
 
     def setup(self, m, i):
         """ links module to inputs and outputs
         """
 
         # chain MulStage0 and MulStage1
-        m0mod = FPMulStage0Mod(self.width, self.id_wid)
-        m1mod = FPMulStage1Mod(self.width, self.id_wid)
+        m0mod = FPMulStage0Mod(self.width, self.id_wid, self.op_wid)
+        m1mod = FPMulStage1Mod(self.width, self.id_wid, self.op_wid)
 
         chain = StageChain([m0mod, m1mod])
         chain.setup(m, i)
 
 
         Fan-in and Fan-out are combinatorial.
     """
-    def __init__(self, width, num_rows):
+    def __init__(self, width, num_rows, op_wid=0):
         self.width = width
         self.id_wid = num_bits(width)
+        self.op_wid = op_wid
         self.alu = FPMULBasePipe(width, self.id_wid)
         ReservationStations.__init__(self, num_rows)
 
     def i_specfn(self):
-        return FPADDBaseData(self.width, self.id_wid)
+        return FPADDBaseData(self.width, self.id_wid, self.op_wid)
 
     def o_specfn(self):
-        return FPPackData(self.width, self.id_wid)
+        return FPPackData(self.width, self.id_wid, self.op_wid)
 
         https://steve.hollasch.net/cgindex/coding/ieeefloat.html
     """
 
-    def __init__(self, width, id_wid):
+    def __init__(self, width, id_wid, op_wid=None):
         self.width = width
         self.id_wid = id_wid
+        self.op_wid = op_wid
         self.i = self.ispec()
         self.o = self.ospec()
 
     def ispec(self):
-        return FPADDBaseData(self.width, self.id_wid)
+        return FPADDBaseData(self.width, self.id_wid, self.op_wid)
 
     def ospec(self):
-        return FPSCData(self.width, self.id_wid, False)
+        return FPSCData(self.width, self.id_wid, False, self.op_wid)
 
     def setup(self, m, i):
         """ links module to inputs and outputs
     """ special cases: NaNs, infs, zeros, denormalised
     """
 
-    def __init__(self, width, id_wid):
+    def __init__(self, width, id_wid, op_wid=None):
         FPState.__init__(self, "special_cases")
         self.width = width
         self.id_wid = id_wid
+        self.op_wid = op_wid
         SimpleHandshake.__init__(self, self) # pipe is its own stage
         self.out = self.ospec()
 
     def ispec(self):
-        return FPADDBaseData(self.width, self.id_wid) # SpecialCases ispec
+        return FPADDBaseData(self.width, self.id_wid, self.op_wid)
 
     def ospec(self):
-        return FPSCData(self.width, self.id_wid, False) # DeNorm ospec
+        return FPSCData(self.width, self.id_wid, False, self.op_wid)
 
     def setup(self, m, i):
         """ links module to inputs and outputs