fpu_test_common.py
1 from coreblocks.func_blocks.fu.fpu.fpu_common import FPUCommonValues, FPUParams, RoundingModes 2 from transactron.testing import * 3 from enum import Enum 4 from contextlib import contextmanager 5 import struct 6 import ctypes 7 8 libm = ctypes.CDLL("libm.so.6") 9 10 11 @contextmanager 12 def python_float_tester(): 13 old_rm = libm.fegetround() 14 try: 15 yield old_rm 16 finally: 17 libm.fesetround(old_rm) 18 19 20 class FPUTester: 21 def __init__(self, params: FPUParams): 22 self.params = params 23 self.converter = ToFloatConverter(params) 24 25 def __compare_results__(self, lhs, rhs): 26 assert lhs["sign"] == rhs["sign"] 27 assert lhs["exp"] == rhs["exp"] 28 assert lhs["sig"] == rhs["sig"] 29 30 async def run_test_set(self, cases, result, common_input, sim: TestbenchContext, request_adapter: TestbenchIO): 31 assert len(cases) == len(result) 32 for num, case in enumerate(cases): 33 input_dict = {} 34 for key, value in common_input.items(): 35 input_dict[key] = value 36 input_dict["op_1"] = self.converter.from_hex(case[0]) 37 input_dict["op_2"] = self.converter.from_hex(case[1]) 38 resp = await request_adapter.call(sim, input_dict) 39 self.__compare_results__(resp, self.converter.from_hex(result[num][0])) 40 41 assert resp["errors"] == int(result[num][1], 16) 42 43 44 def python_to_float(p_float: float) -> float: 45 try: 46 return struct.unpack("f", struct.pack("f", p_float))[0] 47 except OverflowError: 48 if p_float > 3.4028235e38: 49 return float("inf") 50 elif p_float < -3.4028235e38: 51 return float("-inf") 52 else: 53 raise 54 55 56 class ToFloatConverter: 57 def __init__(self, params: FPUParams): 58 self.params = params 59 # Width of the entire floating point number. 60 # 1 is subtracted from sig_width because in memory significand is one bit 61 # shorter than specified. This bit is encoded by exponent. 62 sign_width = 1 63 self.all_width = self.params.sig_width - 1 + self.params.exp_width + sign_width 64 self.sig_width = self.params.sig_width - 1 65 self.exp_mask = (2**self.params.exp_width) - 1 66 self.sig_mask = (2 ** (self.params.sig_width - 1)) - 1 67 self.implicit_one = 1 << (self.params.sig_width - 1) 68 self.cv = FPUCommonValues(self.params) 69 70 def from_float(self, fl): 71 fl_hex = hex(struct.unpack("<I", struct.pack("<f", fl))[0]) 72 return self.from_hex(fl_hex) 73 74 def from_hex(self, hex_float): 75 number = int(hex_float, 16) 76 exp = (number >> self.sig_width) & self.exp_mask 77 sig = number & self.sig_mask 78 if exp != 0: 79 sig = sig | self.implicit_one 80 return { 81 "sign": number >> (self.all_width - 1), 82 "exp": exp, 83 "sig": sig, 84 "is_inf": ((exp == self.cv.max_exp) & ((sig & (~self.implicit_one)) == 0)), 85 "is_nan": ((exp == self.cv.max_exp) & ((sig & (~self.implicit_one)) != 0)), 86 "is_zero": ((exp == 0) & (sig == 0)), 87 } 88 89 90 class FenvRm(Enum): 91 FE_TONEAREST = 0x0000 92 FE_DOWNWARD = 0x400 93 FE_UPWARD = 0x800 94 FE_TOWARDZERO = 0xC00 95 96 97 def fenv_rm_to_fpu_rm(fenv_rm): 98 match fenv_rm: 99 case FenvRm.FE_TONEAREST: 100 return RoundingModes.ROUND_NEAREST_EVEN 101 case FenvRm.FE_DOWNWARD: 102 return RoundingModes.ROUND_DOWN 103 case FenvRm.FE_UPWARD: 104 return RoundingModes.ROUND_UP 105 case FenvRm.FE_TOWARDZERO: 106 return RoundingModes.ROUND_ZERO