add WIP fp_working_format.py
[openpower-isa.git] / src / openpower / decoder / fp_working_format.py
1 # SPDX-License-Identifier: LGPLv3+
2 # Funded by NLnet https://nlnet.nl/
3
4 """ implementation of binary floating-point working format as used in:
5 PowerISA v3.1B section 7.6.2.2
6 e.g. bfp_CONVERT_FROM_BFP32() on page 589(615)
7 """
8
9 from openpower.decoder.selectable_int import SelectableInt
10 import operator
11 import math
12 from fractions import Fraction
13
14 # in this file, everything uses properties instead of plain attributes because
15 # we need to convert most SelectableInts we get to Python int
16
17
18 class BFPStateClass:
19 def __init__(self, value=None):
20 self.__snan = 0
21 self.__qnan = 0
22 self.__infinity = 0
23 self.__zero = 0
24 self.__denormal = 0
25 self.__normal = 0
26 if value is not None:
27 self.eq(value)
28
29 def eq(self, rhs):
30 self.SNaN = rhs.SNaN
31 self.QNaN = rhs.QNaN
32 self.Infinity = rhs.Infinity
33 self.Zero = rhs.Zero
34 self.Denormal = rhs.Denormal
35 self.Normal = rhs.Normal
36
37 @property
38 def SNaN(self):
39 return self.__snan
40
41 @SNaN.setter
42 def SNaN(self, value):
43 self.__snan = int(value)
44
45 @property
46 def QNaN(self):
47 return self.__qnan
48
49 @QNaN.setter
50 def QNaN(self, value):
51 self.__qnan = int(value)
52
53 @property
54 def Infinity(self):
55 return self.__infinity
56
57 @Infinity.setter
58 def Infinity(self, value):
59 self.__infinity = int(value)
60
61 @property
62 def Zero(self):
63 return self.__zero
64
65 @Zero.setter
66 def Zero(self, value):
67 self.__zero = int(value)
68
69 @property
70 def Denormal(self):
71 return self.__denormal
72
73 @Denormal.setter
74 def Denormal(self, value):
75 self.__denormal = int(value)
76
77 @property
78 def Normal(self):
79 return self.__normal
80
81 @Normal.setter
82 def Normal(self, value):
83 self.__normal = int(value)
84
85 def __eq__(self, other):
86 if isinstance(other, BFPStateClass):
87 return (self.SNaN == other.SNaN and
88 self.QNaN == other.QNaN and
89 self.Infinity == other.Infinity and
90 self.Zero == other.Zero and
91 self.Denormal == other.Denormal and
92 self.Normal == other.Normal)
93 return NotImplemented
94
95 def _bfp_state_fields(self):
96 return (f"class_.SNaN: {self.SNaN}",
97 f"class_.QNaN: {self.QNaN}",
98 f"class_.Infinity: {self.Infinity}",
99 f"class_.Zero: {self.Zero}",
100 f"class_.Denormal: {self.Denormal}",
101 f"class_.Normal: {self.Normal}")
102
103 def __repr__(self):
104 fields = self._bfp_state_fields()
105 return f"<BFPStateClass {fields}>"
106
107
108 class SelectableMSB0Fraction:
109 """a MSB0 infinite bit string that is really a real number between 0 and 1,
110 but we approximate it using a Fraction.
111
112 this is not just SelectableInt because we need more than 256 bits and
113 because this isn't an integer.
114 """
115
116 def __init__(self, value=None):
117 self.__value = Fraction()
118 self.eq(value)
119
120 @property
121 def value(self):
122 return self.__value
123
124 @value.setter
125 def value(self, v):
126 self.__value = Fraction(v)
127
128 @staticmethod
129 def __get_slice_dimensions(index):
130 if isinstance(index, slice):
131 if index.stop is None or index.step is not None:
132 raise ValueError("unsupported slice kind")
133 # use int() to convert from
134 start = int(0 if index.start is None else index.start)
135 stop = int(index.stop)
136 length = stop - start + 1
137 else:
138 start = int(index)
139 length = 1
140 return start, length
141
142 def __slice_as_int(self, start, length):
143 if start < 0 or length < 0:
144 raise ValueError("slice out of range")
145 end = start + length
146 # shift so bits we want are the lsb bits of the integer part
147 v = math.floor(self.value * (1 << end))
148 return v & ~(~0 << length) # mask off unwanted bits
149
150 def __set_slice(self, start, length, value):
151 if start < 0 or length < 0:
152 raise ValueError("slice out of range")
153 end = start + length
154 shift_factor = 1 << end
155 # shift so bits we want to replace are the lsb bits of the integer part
156 v = self.value * shift_factor
157 mask = ~(~0 << length)
158 # convert any SelectableInts to int and mask
159 value = int(value) & mask
160 # compute how much we need to add
161 offset = value - (math.floor(v) & mask)
162 # shift offset back into position
163 offset /= shift_factor
164 self.value += offset
165
166 def __getitem__(self, index):
167 start, length = self.__get_slice_dimensions(index)
168 return SelectableInt(self.__slice_as_int(start, length), length)
169
170 def __setitem__(self, index, value):
171 start, length = self.__get_slice_dimensions(index)
172 self.__set_slice(start, length, value)
173
174 def __str__(self, *,
175 max_int_digits=4, # don't need much since this is generally
176 # supposed to be in [0, 1]
177 max_fraction_digits=17, # u64 plus 1
178 fraction_sep_period=4, # how many fraction digits between `_`s
179 ):
180 """ convert to a string of the form: `0x3a.bc` or
181 `0x...face.face_face_face_face... (0xa8ef0000 / 0x5555)`"""
182 if max_int_digits < 0 or max_fraction_digits < 0:
183 raise ValueError("invalid digit limit")
184 approx = False
185 int_part = math.floor(self.value)
186 int_part_limit = 0x10 ** max_int_digits
187 if 0 <= int_part < int_part_limit:
188 int_str = hex(int_part)
189 else:
190 approx = True
191 int_part %= int_part_limit
192 int_str = f"0x...{int_part:0{max_int_digits}x}"
193
194 # is the denominator a power of 2?
195 if (self.value.denominator & (self.value.denominator - 1)) == 0:
196 fraction_bits = self.value.denominator.bit_length() - 1
197 fraction_digits = -(-fraction_bits) // 4 # ceil division by 4
198 else:
199 # something bigger than max_fraction_digits
200 fraction_digits = max_fraction_digits + 1
201 if fraction_digits > max_fraction_digits:
202 suffix = "..."
203 approx = True
204 fraction_digits = max_fraction_digits
205 else:
206 suffix = ""
207 factor = 0x10 ** fraction_digits
208 fraction_part = math.floor(self.value * factor)
209 fraction_str = f"{fraction_part:0{fraction_digits}x}"
210 fraction_parts = []
211 if fraction_sep_period is not None and fraction_sep_period > 0:
212 for i in range(0, len(fraction_str), fraction_sep_period):
213 fraction_parts.append(fraction_str[i:i + fraction_sep_period])
214 fraction_str = "_".join(fraction_parts)
215 fraction_str = "." + fraction_str + suffix
216 retval = int_str
217 if self.value.denominator != 1:
218 retval += fraction_str
219 if approx:
220 n = self.value.numerator
221 d = self.value.denominator
222 retval += f" ({n:#x} / {d:#x})"
223 return retval
224
225 def __repr__(self):
226 return "SelectableMSB0Fraction(" + str(self) + ")"
227
228 def eq(self, value):
229 if isinstance(value, (int, Fraction)):
230 self.value = Fraction(value)
231 elif isinstance(value, SelectableMSB0Fraction):
232 self.value = value.value
233 else:
234 raise ValueError("unsupported assignment type")
235
236 def __bool__(self):
237 return self.value != 0
238
239 def __neg__(self):
240 return SelectableMSB0Fraction(-self.value)
241
242 def __pos__(self):
243 return SelectableMSB0Fraction(self)
244
245 @staticmethod
246 def __arith_op(lhs, rhs, op):
247 lhs = SelectableMSB0Fraction(lhs)
248 rhs = SelectableMSB0Fraction(rhs)
249 return SelectableMSB0Fraction(op(lhs.value, rhs.value))
250
251 def __add__(self, other):
252 return self.__arith_op(self, other, operator.add)
253
254 __radd__ = __add__
255
256 def __mul__(self, other):
257 return self.__arith_op(self, other, operator.mul)
258
259 __rmul__ = __mul__
260
261 def __sub__(self, other):
262 return self.__arith_op(self, other, operator.sub)
263
264 def __rsub__(self, other):
265 return self.__arith_op(other, self, operator.sub)
266
267 def __truediv__(self, other):
268 return self.__arith_op(self, other, operator.truediv)
269
270 def __rtruediv__(self, other):
271 return self.__arith_op(other, self, operator.truediv)
272
273 def __lshift__(self, amount):
274 if not isinstance(amount, int):
275 raise TypeError("can't shift by non-int")
276 if amount < 0:
277 return SelectableMSB0Fraction(self.value / (1 << -amount))
278 return SelectableMSB0Fraction(self.value * (1 << amount))
279
280 def __rlshift__(self, other):
281 raise TypeError("can't shift by non-int")
282
283 def __rshift__(self, amount):
284 if not isinstance(amount, int):
285 raise TypeError("can't shift by non-int")
286 return self << -amount
287
288 def __rrshift__(self, other):
289 raise TypeError("can't shift by non-int")
290
291 def __cmp_op(self, other, op):
292 if isinstance(other, (int, Fraction)):
293 pass
294 elif isinstance(other, SelectableMSB0Fraction):
295 other = other.value
296 else:
297 return NotImplemented
298 return op(self.value, other)
299
300 def __eq__(self, other):
301 return self.__cmp_op(self, other, operator.eq)
302
303 def __ne__(self, other):
304 return self.__cmp_op(self, other, operator.ne)
305
306 def __lt__(self, other):
307 return self.__cmp_op(self, other, operator.lt)
308
309 def __le__(self, other):
310 return self.__cmp_op(self, other, operator.le)
311
312 def __gt__(self, other):
313 return self.__cmp_op(self, other, operator.gt)
314
315 def __ge__(self, other):
316 return self.__cmp_op(self, other, operator.ge)
317
318
319 class BFPState:
320 """ implementation of binary floating-point working format as used in:
321 PowerISA v3.1B section 7.6.2.2
322 e.g. bfp_CONVERT_FROM_BFP32() on page 589(615)
323 """
324
325 def __init__(self, value=None):
326 self.__sign = 0
327 self.__exponent = 0
328 self.__significand = SelectableMSB0Fraction()
329 self.__class = BFPStateClass()
330 if value is not None:
331 self.eq(value)
332
333 def eq(self, rhs):
334 self.sign = rhs.sign
335 self.exponent = rhs.exponent
336 self.significand = rhs.significand
337 self.class_ = rhs.class_
338
339 @property
340 def sign(self):
341 return self.__sign
342
343 @sign.setter
344 def sign(self, value):
345 self.__sign = int(value)
346
347 @property
348 def exponent(self):
349 return self.__exponent
350
351 @exponent.setter
352 def exponent(self, value):
353 self.__exponent = int(value)
354
355 @property
356 def significand(self):
357 return self.__significand
358
359 @significand.setter
360 def significand(self, value):
361 self.__significand.eq(value)
362
363 @property
364 def class_(self):
365 return self.__class
366
367 @class_.setter
368 def class_(self, value):
369 self.__class.eq(value)
370
371 def __eq__(self, other):
372 if isinstance(other, BFPStateClass):
373 return self._bfp_state_fields() == other._bfp_state_fields()
374 return NotImplemented
375
376 def _bfp_state_fields(self):
377 class_fields = self.class_._bfp_state_fields()
378 return (f"sign: {self.sign}",
379 f"exponent: {self.exponent}",
380 f"significand: {self.significand}",
381 *self.class_._bfp_state_fields())
382
383 def __repr__(self):
384 fields = self._bfp_state_fields()
385 return f"<BFPState {fields}>"
386
387
388 # TODO: add tests