add in preliminary signed int conversion
[ieee754fpu.git] / src / ieee754 / fcvt / pipeline.py
index 785ab5e7efac8029b55c978785a5dddfc541516d..5487aa6520811e17ff6927b9a9f3458bcf53ff10 100644 (file)
@@ -12,6 +12,7 @@ from ieee754.fpcommon.getop import FPADDBaseData
 from ieee754.fpcommon.pack import FPPackData
 from ieee754.fpcommon.normtopack import FPNormToPack
 from ieee754.fpcommon.postcalc import FPAddStage1Data
+from ieee754.fpcommon.msbhigh import FPMSBHigh
 
 
 from nmigen import Module, Signal, Elaboratable
@@ -28,6 +29,127 @@ from ieee754.fpcommon.fpbase import FPState
 from ieee754.pipeline import PipelineSpec
 
 
+class FPCVTIntToFloatMod(Elaboratable):
+    """ FP integer conversion: copes with 16/32/64 int to 16/32/64 fp.
+
+        self.ctx.i.op & 0x1 == 0x1 : SIGNED int
+        self.ctx.i.op & 0x1 == 0x0 : UNSIGNED int
+    """
+    def __init__(self, in_pspec, out_pspec):
+        self.in_pspec = in_pspec
+        self.out_pspec = out_pspec
+        self.i = self.ispec()
+        self.o = self.ospec()
+
+    def ispec(self):
+        return FPADDBaseData(self.in_pspec)
+
+    def ospec(self):
+        return FPAddStage1Data(self.out_pspec, e_extra=True)
+
+    def setup(self, m, i):
+        """ links module to inputs and outputs
+        """
+        m.submodules.intconvert = self
+        m.d.comb += self.i.eq(i)
+
+    def process(self, i):
+        return self.o
+
+    def elaborate(self, platform):
+        m = Module()
+
+        #m.submodules.sc_out_z = self.o.z
+
+        # decode: XXX really should move to separate stage
+        print("in_width out", self.in_pspec.width,
+              self.out_pspec.width)
+        print("a1", self.in_pspec.width)
+        z1 = self.o.z
+        print("z1", z1.width, z1.rmw, z1.e_width, z1.e_start, z1.e_end)
+
+        me = self.in_pspec.width
+        mz = self.o.z.rmw
+        ms = mz - me
+        print("ms-me", ms, me, mz)
+
+        # 3 extra bits for guard/round/sticky
+        msb = FPMSBHigh(me+3, z1.e_width)
+        m.submodules.norm_msb = msb
+
+        # signed or unsigned, use operator context
+        signed = Signal(reset_less=True)
+        m.d.comb += signed.eq(self.i.ctx.op[0])
+
+        # copy of mantissa (one less bit if signed)
+        mantissa = Signal(me, reset_less=True)
+
+        # detect signed/unsigned.  key case: -ve numbers need inversion
+        # to +ve because the FP sign says if it's -ve or not.
+        with m.If(signed):
+            m.d.comb += z1.s.eq(self.i.a[-1])      # sign in top bit of a
+            with m.If(z1.s):
+                m.d.comb += mantissa.eq(-self.i.a) # invert input if sign -ve
+            with m.Else():
+                m.d.comb += mantissa.eq(self.i.a)  # leave as-is
+        with m.Else():
+            m.d.comb += mantissa.eq(self.i.a)      # unsigned, use full a
+            m.d.comb += z1.s.eq(0)
+
+        # set input from full INT
+        m.d.comb += msb.m_in.eq(Cat(0, 0, 0, mantissa)) # g/r/s + input
+        m.d.comb += msb.e_in.eq(me)                     # exp = int width
+
+        if ms < 0:
+            # larger int to smaller FP (uint32/64 -> fp16 most likely)
+            m.d.comb += z1.e.eq(msb.e_out-1)
+            m.d.comb += z1.m[ms-1:].eq(msb.m_out[-mz-1:])
+        else:
+            # smaller int to larger FP
+            m.d.comb += z1.e.eq(msb.e_out)
+            m.d.comb += z1.m[ms:].eq(msb.m_out[3:])
+        m.d.comb += z1.create(z1.s, z1.e, z1.m) # ... here
+
+        # note: post-normalisation actually appears to be capable of
+        # detecting overflow to infinity (FPPackMod).  so it's ok to
+        # drop the bits into the mantissa (with a fixed exponent),
+        # do some rounding (which might result in exceeding the
+        # range of the target FP by re-increasing the exponent),
+        # and basically *not* have to do any kind of range-checking
+        # here: just set up guard/round/sticky, drop the INT into the
+        # mantissa, and away we go.  XXX TODO: see if FPNormaliseMod
+        # is even necessary.  it probably isn't
+
+        # initialise rounding (but only activate if needed)
+        if ms < 0:
+            # larger int to smaller FP (uint32/64 -> fp16 most likely)
+            m.d.comb += self.o.of.guard.eq(msb.m_out[-mz-2])
+            m.d.comb += self.o.of.round_bit.eq(msb.m_out[-mz-3])
+            m.d.comb += self.o.of.sticky.eq(msb.m_out[:-mz-3].bool())
+            m.d.comb += self.o.of.m0.eq(msb.m_out[-mz-1])
+        else:
+            # smaller int to larger FP
+            m.d.comb += self.o.of.guard.eq(msb.m_out[2])
+            m.d.comb += self.o.of.round_bit.eq(msb.m_out[1])
+            m.d.comb += self.o.of.sticky.eq(msb.m_out[:1].bool())
+            m.d.comb += self.o.of.m0.eq(msb.m_out[3])
+
+        # special cases active by default
+        m.d.comb += self.o.out_do_z.eq(1)
+
+        # detect zero
+        with m.If(~self.i.a.bool()):
+            m.d.comb += self.o.z.zero(0)
+        with m.Else():
+            m.d.comb += self.o.out_do_z.eq(0) # activate normalisation
+
+        # copy the context (muxid, operator)
+        m.d.comb += self.o.oz.eq(self.o.z.v)
+        m.d.comb += self.o.ctx.eq(self.i.ctx)
+
+        return m
+
+
 class FPCVTUpConvertMod(Elaboratable):
     """ FP up-conversion (lower to higher bitwidth)
     """
@@ -41,7 +163,7 @@ class FPCVTUpConvertMod(Elaboratable):
         return FPADDBaseData(self.in_pspec)
 
     def ospec(self):
-        return FPAddStage1Data(self.out_pspec, e_extra=True)
+        return FPAddStage1Data(self.out_pspec, e_extra=False)
 
     def setup(self, m, i):
         """ links module to inputs and outputs
@@ -69,35 +191,37 @@ class FPCVTUpConvertMod(Elaboratable):
 
         me = a1.rmw
         ms = self.o.z.rmw - a1.rmw
-        print("ms-me", ms, me)
-
-        # intermediaries
-        exp_sub_n126 = Signal((a1.e_width, True), reset_less=True)
-        exp_gt127 = Signal(reset_less=True)
-        # constants from z1, at the bit-width of a1.
-        N126 = Const(z1.fp.N126.value, (a1.e_width, True))
-        P127 = Const(z1.fp.P127.value, (a1.e_width, True))
-        m.d.comb += exp_sub_n126.eq(a1.e - N126)
-        m.d.comb += exp_gt127.eq(a1.e > P127)
+        print("ms-me", ms, me, self.o.z.rmw, a1.rmw)
 
+        # conversion can mostly be done manually...
         m.d.comb += self.o.z.s.eq(a1.s)
         m.d.comb += self.o.z.e.eq(a1.e)
-        m.d.comb += self.o.z.m[-self.o.z.rmw-1:].eq(a1.m)
-        m.d.comb += self.o.z.create(a1.s, a1.e, self.o.z.m)
+        m.d.comb += self.o.z.m[ms:].eq(a1.m)
+        m.d.comb += self.o.z.create(a1.s, a1.e, self.o.z.m) # ... here
 
-        # if exp == top
+        # initialise rounding to all zeros (deactivate)
+        m.d.comb += self.o.of.guard.eq(0)
+        m.d.comb += self.o.of.round_bit.eq(0)
+        m.d.comb += self.o.of.sticky.eq(0)
+        m.d.comb += self.o.of.m0.eq(a1.m[0])
+
+        # most special cases active (except tiny-number normalisation, below)
+        m.d.comb += self.o.out_do_z.eq(1)
+
+        # detect NaN/Inf first
         with m.If(a1.exp_128):
-            m.d.comb += self.o.z.create(a1.s, self.o.z.P128, self.o.z.m)
-            m.d.comb += self.o.out_do_z.eq(1)
+            with m.If(~a1.m_zero):
+                m.d.comb += self.o.z.nan(0) # RISC-V wants normalised NaN
+            with m.Else():
+                m.d.comb += self.o.z.inf(a1.s) # RISC-V wants signed INF
         with m.Else():
-            with m.If(a1.exp_zero):
-                with m.If(a1.m[:a1.rmw].bool()):
-                    m.d.comb += self.o.of.guard.eq(a1.m[a1.rmw-1])
-                    m.d.comb += self.o.of.round_bit.eq(a1.m[a1.rmw-2])
-                    m.d.comb += self.o.of.sticky.eq(1)
-                    m.d.comb += self.o.of.m0.eq(a1.m[a1.rmw])  # bit of a1
+            with m.If(a1.exp_n127):
+                with m.If(~a1.m_zero):
+                    m.d.comb += self.o.z.m[ms:].eq(Cat(0, a1.m))
+                    m.d.comb += self.o.out_do_z.eq(0) # activate normalisation
                 with m.Else():
-                    m.d.comb += self.o.out_do_z.eq(1)
+                    # RISC-V zero needs actual zero
+                    m.d.comb += self.o.z.zero(a1.s)
 
         # copy the context (muxid, operator)
         m.d.comb += self.o.oz.eq(self.o.z.v)
@@ -219,6 +343,31 @@ class FPCVTDownConvertMod(Elaboratable):
         return m
 
 
+class FPCVTIntToFloat(FPState):
+    """ Up-conversion
+    """
+
+    def __init__(self, in_width, out_width, id_wid):
+        FPState.__init__(self, "inttofloat")
+        self.mod = FPCVTIntToFloatMod(in_width, out_width)
+        self.out_z = self.mod.ospec()
+        self.out_do_z = Signal(reset_less=True)
+
+    def setup(self, m, i):
+        """ links module to inputs and outputs
+        """
+        self.mod.setup(m, i, self.out_do_z)
+        m.d.sync += self.out_z.v.eq(self.mod.out_z.v)  # only take the output
+        m.d.sync += self.out_z.ctx.eq(self.mod.o.ctx)  # (and context)
+
+    def action(self, m):
+        self.idsync(m)
+        with m.If(self.out_do_z):
+            m.next = "put_z"
+        with m.Else():
+            m.next = "denormalise"
+
+
 class FPCVTUpConvert(FPState):
     """ Up-conversion
     """
@@ -269,6 +418,17 @@ class FPCVTDownConvert(FPState):
             m.next = "denormalise"
 
 
+class FPCVTIntToFloatDeNorm(FPState, SimpleHandshake):
+    """ Upconvert
+    """
+
+    def __init__(self, in_pspec, out_pspec):
+        FPState.__init__(self, "inttofloat")
+        sc = FPCVTIntToFloatMod(in_pspec, out_pspec)
+        SimpleHandshake.__init__(self, sc)
+        self.out = self.ospec(None)
+
+
 class FPCVTUpConvertDeNorm(FPState, SimpleHandshake):
     """ Upconvert
     """
@@ -291,11 +451,27 @@ class FPCVTDownConvertDeNorm(FPState, SimpleHandshake):
         self.out = self.ospec(None)
 
 
+class FPCVTIntBasePipe(ControlBase):
+    def __init__(self, in_pspec, out_pspec):
+        ControlBase.__init__(self)
+        self.pipe1 = FPCVTIntToFloatDeNorm(in_pspec, out_pspec)
+        self.pipe2 = FPNormToPack(out_pspec, e_extra=True)
+
+        self._eqs = self.connect([self.pipe1, self.pipe2])
+
+    def elaborate(self, platform):
+        m = ControlBase.elaborate(self, platform)
+        m.submodules.toint = self.pipe1
+        m.submodules.normpack = self.pipe2
+        m.d.comb += self._eqs
+        return m
+
+
 class FPCVTUpBasePipe(ControlBase):
     def __init__(self, in_pspec, out_pspec):
         ControlBase.__init__(self)
         self.pipe1 = FPCVTUpConvertDeNorm(in_pspec, out_pspec)
-        self.pipe2 = FPNormToPack(out_pspec, e_extra=True)
+        self.pipe2 = FPNormToPack(out_pspec, e_extra=False)
 
         self._eqs = self.connect([self.pipe1, self.pipe2])
 
@@ -323,6 +499,34 @@ class FPCVTDownBasePipe(ControlBase):
         return m
 
 
+class FPCVTIntMuxInOut(ReservationStations):
+    """ Reservation-Station version of FPCVT int-to-float pipeline.
+
+        * fan-in on inputs (an array of FPADDBaseData: a,b,mid)
+        * 2-stage multiplier pipeline
+        * fan-out on outputs (an array of FPPackData: z,mid)
+
+        Fan-in and Fan-out are combinatorial.
+    """
+
+    def __init__(self, in_width, out_width, num_rows, op_wid=0):
+        self.op_wid = op_wid
+        self.id_wid = num_bits(in_width)
+        self.out_id_wid = num_bits(out_width)
+
+        self.in_pspec = PipelineSpec(in_width, self.id_wid, self.op_wid)
+        self.out_pspec = PipelineSpec(out_width, self.out_id_wid, op_wid)
+
+        self.alu = FPCVTIntBasePipe(self.in_pspec, self.out_pspec)
+        ReservationStations.__init__(self, num_rows)
+
+    def i_specfn(self):
+        return FPADDBaseData(self.in_pspec)
+
+    def o_specfn(self):
+        return FPPackData(self.out_pspec)
+
+
 class FPCVTUpMuxInOut(ReservationStations):
     """ Reservation-Station version of FPCVT up pipeline.
 
@@ -350,6 +554,7 @@ class FPCVTUpMuxInOut(ReservationStations):
     def o_specfn(self):
         return FPPackData(self.out_pspec)
 
+
 class FPCVTDownMuxInOut(ReservationStations):
     """ Reservation-Station version of FPCVT pipeline.