switch to using nmutil's FHDLTestCase
[ieee754fpu.git] / src / ieee754 / fcvt / int2float.py
1 # IEEE Floating Point Conversion
2 # Copyright (C) 2019 Luke Kenneth Casson Leighton <lkcl@lkcl.net>
3
4 from nmigen import Module, Signal, Cat, Mux
5 from nmigen.cli import main, verilog
6
7 from nmutil.pipemodbase import PipeModBase
8 from ieee754.fpcommon.basedata import FPBaseData
9 from ieee754.fpcommon.postcalc import FPPostCalcData
10 from ieee754.fpcommon.msbhigh import FPMSBHigh
11
12 from ieee754.fpcommon.fpbase import FPNumBaseRecord
13
14
15 class FPCVTIntToFloatMod(PipeModBase):
16 """ FP integer conversion: copes with 16/32/64 int to 16/32/64 fp.
17
18 self.ctx.i.op & 0x1 == 0x1 : SIGNED int
19 self.ctx.i.op & 0x1 == 0x0 : UNSIGNED int
20 """
21 def __init__(self, in_pspec, out_pspec):
22 self.in_pspec = in_pspec
23 self.out_pspec = out_pspec
24 super().__init__(in_pspec, "intconvert")
25
26 def ispec(self):
27 return FPBaseData(self.in_pspec)
28
29 def ospec(self):
30 return FPPostCalcData(self.out_pspec, e_extra=True)
31
32 def elaborate(self, platform):
33 m = Module()
34 comb = m.d.comb
35
36 #m.submodules.sc_out_z = self.o.z
37
38 # decode: XXX really should move to separate stage
39 print("in_width out", self.in_pspec.width,
40 self.out_pspec.width)
41 print("a1", self.in_pspec.width)
42 z1 = self.o.z
43 a = self.i.a
44 print("z1", z1.width, z1.rmw, z1.e_width, z1.e_start, z1.e_end)
45
46 me = self.in_pspec.width
47 mz = z1.rmw
48 ms = mz - me
49 print("ms-me", ms, me, mz)
50
51 # 3 extra bits for guard/round/sticky
52 msb = FPMSBHigh(me+3, z1.e_width)
53 m.submodules.norm_msb = msb
54
55 # signed or unsigned, use operator context
56 signed = Signal(reset_less=True)
57 comb += signed.eq(self.i.ctx.op[0])
58
59 # mantissa (one less bit if signed), and sign
60 mantissa = Signal(me, reset_less=True)
61 sign = Signal(reset_less=True)
62
63 # detect signed/unsigned. key case: -ve numbers need inversion
64 # to +ve because the FP sign says if it's -ve or not.
65 comb += sign.eq(Mux(signed, a[-1], 0)) # sign in top bit of a
66 comb += mantissa.eq(Mux(signed,
67 Mux(sign, -a, # invert input if sign -ve
68 a), # leave as-is
69 a)) # unsigned, use full a
70
71 # set input from full INT
72 comb += msb.m_in.eq(Cat(0, 0, 0, mantissa)) # g/r/s + input
73 comb += msb.e_in.eq(me) # exp = int width
74
75 # to do with FP16... not yet resolved why
76 alternative = ms < 0
77
78 if alternative:
79 comb += z1.e.eq(msb.e_out-1)
80 mmsb = msb.m_out[-mz-1:]
81 if mz == 16:
82 # larger int to smaller FP (uint32/64 -> fp16 most likely)
83 comb += z1.m[ms-1:].eq(mmsb)
84 else: # 32? XXX weirdness...
85 comb += z1.m.eq(mmsb)
86 else:
87 # smaller int to larger FP
88 comb += z1.e.eq(msb.e_out)
89 comb += z1.m[ms:].eq(msb.m_out[3:])
90
91 # XXX there is some weirdness involving the sign looping back
92 # see graphviz output
93 # http://bugs.libre-riscv.org/show_bug.cgi?id=135
94 comb += z1.s.eq(sign)
95 comb += z1.create(sign, z1.e, z1.m) # ... here
96
97 # note: post-normalisation actually appears to be capable of
98 # detecting overflow to infinity (FPPackMod). so it's ok to
99 # drop the bits into the mantissa (with a fixed exponent),
100 # do some rounding (which might result in exceeding the
101 # range of the target FP by re-increasing the exponent),
102 # and basically *not* have to do any kind of range-checking
103 # here: just set up guard/round/sticky, drop the INT into the
104 # mantissa, and away we go. XXX TODO: see if FPNormaliseMod
105 # is even necessary. it probably isn't
106
107 # initialise rounding (but only activate if needed)
108 if alternative:
109 # larger int to smaller FP (uint32/64 -> fp16 most likely)
110 comb += self.o.of.guard.eq(msb.m_out[-mz-2])
111 comb += self.o.of.round_bit.eq(msb.m_out[-mz-3])
112 comb += self.o.of.sticky.eq(msb.m_out[:-mz-3].bool())
113 comb += self.o.of.m0.eq(msb.m_out[-mz-1])
114 else:
115 # smaller int to larger FP
116 comb += self.o.of.guard.eq(msb.m_out[2])
117 comb += self.o.of.round_bit.eq(msb.m_out[1])
118 comb += self.o.of.sticky.eq(msb.m_out[:1].bool())
119 comb += self.o.of.m0.eq(msb.m_out[3])
120
121 a_nonzero = Signal(reset_less=True)
122 comb += a_nonzero.eq(~a.bool())
123
124 # prepare zero
125 z_zero = FPNumBaseRecord(z1.width, False, name="z_zero")
126 comb += z_zero.zero(0)
127
128 # special cases?
129 comb += self.o.out_do_z.eq(a_nonzero)
130
131 # detect zero
132 comb += self.o.oz.eq(Mux(a_nonzero, z1.v, z_zero.v))
133
134 # copy the context (muxid, operator)
135 comb += self.o.ctx.eq(self.i.ctx)
136
137 return m
138
139