From 1b8f7f49df056fc4ffcb8b34bcb82d5a1e3db340 Mon Sep 17 00:00:00 2001 From: Mateusz Marszalek Date: Sun, 8 Sep 2024 23:43:41 +0200 Subject: [PATCH 01/15] Added FPU rounding module --- coreblocks/func_blocks/fu/fpu/__init__.py | 0 coreblocks/func_blocks/fu/fpu/fpu_common.py | 46 ++++ .../func_blocks/fu/fpu/fpu_rounding_module.py | 257 ++++++++++++++++++ 3 files changed, 303 insertions(+) create mode 100644 coreblocks/func_blocks/fu/fpu/__init__.py create mode 100644 coreblocks/func_blocks/fu/fpu/fpu_common.py create mode 100644 coreblocks/func_blocks/fu/fpu/fpu_rounding_module.py diff --git a/coreblocks/func_blocks/fu/fpu/__init__.py b/coreblocks/func_blocks/fu/fpu/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/coreblocks/func_blocks/fu/fpu/fpu_common.py b/coreblocks/func_blocks/fu/fpu/fpu_common.py new file mode 100644 index 000000000..0081c81e5 --- /dev/null +++ b/coreblocks/func_blocks/fu/fpu/fpu_common.py @@ -0,0 +1,46 @@ +from amaranth.lib import enum + + +class RoundingModes(enum.Enum, shape=3): + ROUND_UP = 3 + ROUND_DOWN = 2 + ROUND_ZERO = 1 + ROUND_NEAREST_EVEN = 0 + ROUND_NEAREST_AWAY = 4 + + +class FPUParams: + """FPU parameters + + Parameters + ---------- + sig_width: int + Width of significand + exp_width: int + Width of exponent + """ + + def __init__( + self, + *, + sig_width: int = 24, + exp_width: int = 8, + ): + self.sig_width = sig_width + self.exp_width = exp_width + + +class FPURoundingParams: + """FPU rounding module signature + + Parameters + ----------- + fpu_params: FPUParams + FPU parameters + is_rounded:bool + This flags indicates that the input number was already rounded. This creates simpler version of rounding module that only performs error checking and return corect number. + """ + + def __init__(self, fpu_params: FPUParams, *, is_rounded: bool = False): + self.fpu_params = fpu_params + self.is_rounded = is_rounded diff --git a/coreblocks/func_blocks/fu/fpu/fpu_rounding_module.py b/coreblocks/func_blocks/fu/fpu/fpu_rounding_module.py new file mode 100644 index 000000000..19dc23c10 --- /dev/null +++ b/coreblocks/func_blocks/fu/fpu/fpu_rounding_module.py @@ -0,0 +1,257 @@ +from amaranth import * +from amaranth.lib.wiring import Component, In, Out, Signature +from transactron import TModule, Method, def_method +from coreblocks.func_blocks.fu.fpu.fpu_common import ( + RoundingModes, + FPUParams, + FPURoundingParams, +) + + +class FPURoundingSignature(Signature): + """FPU Rounding module signature + + Parameters + ---------- + fpu_params: FPUParams + FPU parameters + """ + + def __init__(self, *, fpu_params: FPUParams): + super().__init__( + { + "in_sign": In(1), + "in_sig": In(fpu_params.sig_width), + "in_exp": In(fpu_params.exp_width), + "rounding_mode": In(3), + "guard_bit": In(1), + "sticky_bit": In(1), + "in_errors": In(3), + "out_sign": In(1), + "out_sig": Out(fpu_params.sig_width), + "out_exp": Out(fpu_params.exp_width), + "out_error": Out(3), + } + ) + + +class FPURoudningMethodLayout: + """FPU Rounding module layouts for methods + + Parameters + ---------- + fpu_params: FPUParams + FPU parameters + """ + + def __init__(self, *, fpu_params: FPUParams): + self.rounding_in_layout = [ + ("sign", 1), + ("sig", fpu_params.sig_width), + ("exp", fpu_params.exp_width), + ("guard_bit", 1), + ("sticky_bit", 1), + ("rounding_mode", 3), + ("errors", 5), + ("input_nan", 1), + ("input_inf", 1), + ] + self.rounding_out_layout = [ + ("sign", 1), + ("sig", fpu_params.sig_width), + ("exp", fpu_params.exp_width), + ("errors", 5), + ] + + +class FPUrounding(Component): + + fpu_rounding: FPURoundingSignature + + def __init__(self, *, fpu_rounding_params: FPURoundingParams): + super().__init__({"fpu_rounding": Out(FPURoundingSignature(fpu_params=fpu_rounding_params.fpu_params))}) + + self.fpu_rounding_params = fpu_rounding_params + self.method_layouts = FPURoudningMethodLayout(fpu_params=self.fpu_rounding_params.fpu_params) + self.rounding_request = Method( + i=self.method_layouts.rounding_in_layout, + o=self.method_layouts.rounding_out_layout, + ) + self.rtval = {} + self.max_exp = C( + 2 ** (self.fpu_rounding_params.fpu_params.exp_width) - 1, + unsigned(self.fpu_rounding_params.fpu_params.exp_width), + ) + self.max_normal_exp = C( + 2 ** (self.fpu_rounding_params.fpu_params.exp_width) - 2, + unsigned(self.fpu_rounding_params.fpu_params.exp_width), + ) + self.quiet_nan = C( + 2 ** (self.fpu_rounding_params.fpu_params.sig_width - 1), + unsigned(self.fpu_rounding_params.fpu_params.sig_width), + ) + self.max_sig = C( + 2 ** (self.fpu_rounding_params.fpu_params.sig_width) - 1, + unsigned(self.fpu_rounding_params.fpu_params.sig_width), + ) + self.add_one = Signal() + self.inc_rtnte = Signal() + self.inc_rtnta = Signal() + self.inc_rtpi = Signal() + self.inc_rtmi = Signal() + + self.rounded_sig = Signal(self.fpu_rounding_params.fpu_params.sig_width + 1) + self.normalised_sig = Signal(self.fpu_rounding_params.fpu_params.sig_width) + self.rounded_exp = Signal(self.fpu_rounding_params.fpu_params.exp_width + 1) + + self.final_guard_bit = Signal() + self.final_sticky_bit = Signal() + + self.overflow = Signal() + self.underflow = Signal() + self.inexact = Signal() + self.tininess = Signal() + self.is_inf = Signal() + self.is_nan = Signal() + self.input_not_special = Signal() + self.rounded_inexact = Signal() + + self.final_exp = Signal(self.fpu_rounding_params.fpu_params.exp_width) + self.final_sig = Signal(self.fpu_rounding_params.fpu_params.sig_width) + self.final_sign = Signal() + self.final_errors = Signal(5) + + def elaborate(self, platform): + m = TModule() + + @def_method(m, self.rounding_request) + def _(arg): + + m.d.comb += self.inc_rtnte.eq( + (arg.rounding_mode == RoundingModes.ROUND_NEAREST_EVEN) + & (arg.guard_bit & (arg.sticky_bit | arg.sig[0])) + ) + m.d.comb += self.inc_rtnta.eq((arg.rounding_mode == RoundingModes.ROUND_NEAREST_AWAY) & (arg.guard_bit)) + m.d.comb += self.inc_rtpi.eq( + (arg.rounding_mode == RoundingModes.ROUND_UP) & (~arg.sign & (arg.guard_bit | arg.sticky_bit)) + ) + m.d.comb += self.inc_rtmi.eq( + (arg.rounding_mode == RoundingModes.ROUND_DOWN) & (arg.sign & (arg.guard_bit | arg.sticky_bit)) + ) + + m.d.comb += self.add_one.eq(self.inc_rtmi | self.inc_rtnta | self.inc_rtnte | self.inc_rtpi) + + if self.fpu_rounding_params.is_rounded: + + m.d.comb += self.normalised_sig.eq(arg.sig) + m.d.comb += self.final_guard_bit.eq(arg.guard_bit) + m.d.comb += self.final_sticky_bit.eq(arg.sticky_bit) + m.d.comb += self.rounded_exp.eq(arg.exp) + + else: + + m.d.comb += self.rounded_sig.eq(arg.sig + self.add_one) + + with m.If(self.rounded_sig[-1]): + + m.d.comb += self.normalised_sig.eq(self.rounded_sig >> 1) + m.d.comb += self.final_guard_bit.eq(self.rounded_sig[0]) + m.d.comb += self.final_sticky_bit.eq(arg.guard_bit | arg.sticky_bit) + m.d.comb += self.rounded_exp.eq(arg.exp + 1) + + with m.Else(): + m.d.comb += self.normalised_sig.eq(self.rounded_sig) + m.d.comb += self.final_guard_bit.eq(arg.guard_bit) + m.d.comb += self.final_sticky_bit.eq(arg.sticky_bit) + m.d.comb += self.rounded_exp.eq(arg.exp) + + m.d.comb += self.rounded_inexact.eq(self.final_guard_bit | self.final_sticky_bit) + m.d.comb += self.is_nan.eq(arg.errors[0] | arg.input_nan) + m.d.comb += self.is_inf.eq(arg.errors[1] | arg.input_inf) + m.d.comb += self.input_not_special.eq(~(self.is_nan) & ~(self.is_inf)) + m.d.comb += self.overflow.eq(self.input_not_special & (self.rounded_exp >= self.max_exp)) + m.d.comb += self.tininess.eq( + (self.rounded_exp == 0) & (self.rounded_inexact | self.rounded_sig.any()) & (~self.normalised_sig[-1]) + ) + m.d.comb += self.inexact.eq(self.overflow | (self.input_not_special & self.rounded_inexact)) + m.d.comb += self.underflow.eq(self.tininess & self.inexact) + + with m.If(self.is_nan): + + m.d.comb += self.final_exp.eq(self.max_exp) + m.d.comb += self.final_sig.eq(arg.sig) + m.d.comb += self.final_sign.eq(arg.sign) + + with m.Elif(self.is_inf): + + m.d.comb += self.final_exp.eq(self.max_exp) + m.d.comb += self.final_sig.eq(arg.sig) + m.d.comb += self.final_sign.eq(arg.sign) + + with m.Elif(self.overflow): + + with m.If( + (arg.rounding_mode == RoundingModes.ROUND_NEAREST_AWAY) + | (arg.rounding_mode == RoundingModes.ROUND_NEAREST_EVEN) + ): + + m.d.comb += self.final_exp.eq(self.max_exp) + m.d.comb += self.final_sig.eq(0) + m.d.comb += self.final_sign.eq(arg.sign) + + with m.If(arg.rounding_mode == RoundingModes.ROUND_ZERO): + + m.d.comb += self.final_exp.eq(self.max_normal_exp) + m.d.comb += self.final_sig.eq(self.max_sig) + m.d.comb += self.final_sign.eq(arg.sign) + + with m.If(arg.rounding_mode == RoundingModes.ROUND_DOWN): + + with m.If(arg.sign): + + m.d.comb += self.final_exp.eq(self.max_exp) + m.d.comb += self.final_sig.eq(0) + m.d.comb += self.final_sign.eq(arg.sign) + + with m.Else(): + + m.d.comb += self.final_exp.eq(self.max_normal_exp) + m.d.comb += self.final_sig.eq(self.max_sig) + m.d.comb += self.final_sign.eq(arg.sign) + + with m.If(arg.rounding_mode == RoundingModes.ROUND_UP): + + with m.If(arg.sign): + + m.d.comb += self.final_exp.eq(self.max_normal_exp) + m.d.comb += self.final_sig.eq(self.max_sig) + m.d.comb += self.final_sign.eq(arg.sign) + + with m.Else(): + + m.d.comb += self.final_exp.eq(self.max_exp) + m.d.comb += self.final_sig.eq(0) + m.d.comb += self.final_sign.eq(arg.sign) + + with m.Else(): + with m.If((self.rounded_exp == 0) & (self.normalised_sig[-1] == 1)): + m.d.comb += self.final_exp.eq(1) + with m.Else(): + m.d.comb += self.final_exp.eq(self.rounded_exp) + m.d.comb += self.final_sig.eq(self.normalised_sig) + m.d.comb += self.final_sign.eq(arg.sign) + + m.d.comb += self.final_errors[0].eq(arg.errors[0]) + m.d.comb += self.final_errors[1].eq(arg.errors[1]) + m.d.comb += self.final_errors[2].eq(self.overflow) + m.d.comb += self.final_errors[3].eq(self.underflow) + m.d.comb += self.final_errors[4].eq(self.inexact) + + self.rtval["exp"] = self.final_exp + self.rtval["sig"] = self.final_sig + self.rtval["sign"] = self.final_sign + self.rtval["errors"] = self.final_errors + + return self.rtval + + return m From b68f547aaaf2e085b29375c9d356efe2c893f457 Mon Sep 17 00:00:00 2001 From: Mateusz Marszalek Date: Sun, 8 Sep 2024 23:44:48 +0200 Subject: [PATCH 02/15] Added FPU rounding module tests --- test/func_blocks/fu/test_fpu_rounding.py | 393 +++++++++++++++++++++++ 1 file changed, 393 insertions(+) create mode 100644 test/func_blocks/fu/test_fpu_rounding.py diff --git a/test/func_blocks/fu/test_fpu_rounding.py b/test/func_blocks/fu/test_fpu_rounding.py new file mode 100644 index 000000000..042806224 --- /dev/null +++ b/test/func_blocks/fu/test_fpu_rounding.py @@ -0,0 +1,393 @@ +from coreblocks.func_blocks.fu.fpu.fpu_rounding_module import * +from coreblocks.func_blocks.fu.fpu.fpu_common import ( + RoundingModes, + FPUParams, + FPURoundingParams, +) +from transactron import TModule +from transactron.lib import AdapterTrans +from transactron.testing import * +from amaranth import * + + +class TestFPURounding(TestCaseWithSimulator): + class FPURoundingModule(Elaboratable): + def __init__(self, params: FPUParams): + self.params = params + self.input_not_rounded = FPURoundingParams(fpu_params=self.params, is_rounded=False) + self.input_rounded_params = FPURoundingParams( + fpu_params=self.params, + is_rounded=True, + ) + + def elaborate(self, platform): + m = TModule() + m.submodules.fpur = fpur = self.fpu_rounding = FPUrounding(fpu_rounding_params=self.input_not_rounded) + m.submodules.fpur_rounded = fpur_rounded = self.fpu_rounding_input_rounded = FPUrounding( + fpu_rounding_params=self.input_rounded_params + ) + m.submodules.rounding = self.rounding_request_adapter = TestbenchIO(AdapterTrans(fpur.rounding_request)) + m.submodules.input_rounded_rounding = self.input_rounded_rounding_request_adapter = TestbenchIO( + AdapterTrans(fpur_rounded.rounding_request) + ) + + return m + + class HelpValues: + def __init__(self, params: FPUParams): + self.params = params + self.max_exp = (2**self.params.exp_width) - 1 + self.max_norm_exp = (2**self.params.exp_width) - 2 + self.not_max_norm_exp = (2**self.params.exp_width) - 3 + self.max_sig = (2**params.sig_width) - 1 + self.not_max_norm_sig = 1 << (self.params.sig_width - 1) | 1 + self.not_max_norm_even_sig = 1 << (self.params.sig_width - 1) + self.sub_norm_sig = 3 + self.max_sub_norm_sig = (2 ** (self.params.sig_width - 1)) - 1 + self.qnan = 3 << (self.params.sig_width - 2) | 1 + + def test_manual(self): + params = FPUParams(sig_width=24, exp_width=8) + fpurt = TestFPURounding.FPURoundingModule(params) + help_values = TestFPURounding.HelpValues(params) + + def other_cases_test(request_adapter: TestbenchIO, is_input_not_rounded: bool): + input_values_dict = {} + input_values_dict["sign"] = 0 + input_values_dict["sig"] = help_values.not_max_norm_even_sig + input_values_dict["exp"] = help_values.not_max_norm_exp + input_values_dict["guard_bit"] = 0 + input_values_dict["sticky_bit"] = 0 + input_values_dict["rounding_mode"] = RoundingModes.ROUND_NEAREST_AWAY + input_values_dict["errors"] = 0 + input_values_dict["input_nan"] = 0 + input_values_dict["input_inf"] = 0 + + # No errors + resp = yield from request_adapter.call(input_values_dict) + + assert resp["sign"] == 0 + assert resp["exp"] == input_values_dict["exp"] + assert resp["sig"] == input_values_dict["sig"] + assert resp["errors"] == 0 + + # inexact + input_values_dict["sticky_bit"] = 1 + + resp = yield from request_adapter.call(input_values_dict) + + assert resp["sign"] == 0 + assert resp["exp"] == input_values_dict["exp"] + assert resp["sig"] == input_values_dict["sig"] + assert resp["errors"] == 16 + + # underflow rounding + input_values_dict["exp"] = 0 + input_values_dict["sig"] = ( + help_values.sub_norm_sig if is_input_not_rounded else help_values.sub_norm_sig + 1 + ) + input_values_dict["guard_bit"] = 1 + + resp = yield from request_adapter.call(input_values_dict) + + assert resp["sign"] == 0 + assert resp["exp"] == 0 + assert resp["sig"] == help_values.sub_norm_sig + 1 + assert resp["errors"] == 24 + + # underflow no rounding + + input_values_dict["guard_bit"] = 0 + input_values_dict["sig"] = help_values.sub_norm_sig + + resp = yield from request_adapter.call(input_values_dict) + + assert resp["sign"] == 0 + assert resp["exp"] == 0 + assert resp["sig"] == help_values.sub_norm_sig + assert resp["errors"] == 24 + + # invalid operation + + input_values_dict["exp"] = help_values.max_exp + input_values_dict["sig"] = help_values.qnan + input_values_dict["errors"] = 1 + + resp = yield from request_adapter.call(input_values_dict) + + assert resp["sign"] == 0 + assert resp["exp"] == input_values_dict["exp"] + assert resp["sig"] == input_values_dict["sig"] + assert resp["errors"] == 1 + + # division by zero + + input_values_dict["exp"] = help_values.max_exp + input_values_dict["sig"] = 0 + input_values_dict["errors"] = 2 + + resp = yield from request_adapter.call(input_values_dict) + assert resp["sign"] == 0 + assert resp["exp"] == input_values_dict["exp"] + assert resp["sig"] == input_values_dict["sig"] + assert resp["errors"] == 2 + + # overflow but no guard and sticky bits + + input_values_dict["guard_bit"] = 0 + input_values_dict["sticky_bit"] = 0 + input_values_dict["errors"] = 0 + + resp = yield from request_adapter.call(input_values_dict) + + assert resp["sign"] == 0 + assert resp["exp"] == input_values_dict["exp"] + assert resp["sig"] == input_values_dict["sig"] + assert resp["errors"] == 20 + + # tininess but no underflow + + input_values_dict["exp"] = 0 + input_values_dict["sig"] = help_values.sub_norm_sig + + resp = yield from request_adapter.call(input_values_dict) + + assert resp["sign"] == 0 + assert resp["exp"] == input_values_dict["exp"] + assert resp["sig"] == input_values_dict["sig"] + assert resp["errors"] == 0 + + # one of inputs was qnan + + input_values_dict["exp"] = help_values.max_exp + input_values_dict["sig"] = help_values.qnan + input_values_dict["sticky_bit"] = 1 + input_values_dict["input_nan"] = 1 + input_values_dict["input_inf"] = 0 + + resp = yield from request_adapter.call(input_values_dict) + + assert resp["sign"] == 0 + assert resp["exp"] == input_values_dict["exp"] + assert resp["sig"] == input_values_dict["sig"] + assert resp["errors"] == 0 + + # one of inputs was inf + + input_values_dict["sign"] = 1 + input_values_dict["exp"] = help_values.max_exp + input_values_dict["sig"] = 0 + input_values_dict["input_nan"] = 0 + input_values_dict["input_inf"] = 1 + + resp = yield from request_adapter.call(input_values_dict) + + assert resp["sign"] == 1 + assert resp["exp"] == input_values_dict["exp"] + assert resp["sig"] == input_values_dict["sig"] + assert resp["errors"] == 0 + + # subnormal number become normalized after rounding + + if is_input_not_rounded: + input_values_dict["exp"] = 0 + input_values_dict["sig"] = help_values.max_sub_norm_sig + input_values_dict["sticky_bit"] = 1 + input_values_dict["guard_bit"] = 1 + input_values_dict["rounding_mode"] = RoundingModes.ROUND_NEAREST_AWAY + input_values_dict["input_nan"] = 0 + input_values_dict["input_inf"] = 0 + + resp = yield from request_adapter.call(input_values_dict) + assert resp["sign"] == 1 + assert resp["exp"] == 1 + assert resp["sig"] == input_values_dict["sig"] + 1 + assert resp["errors"] == 16 + + def one_rounding_mode_test( + rm: RoundingModes, + inc_arr: list, + plus_oveflow_sig: int, + plus_overflow_exp: int, + minus_overflow_sig: int, + minus_overflow_exp: int, + request_adapter: TestbenchIO, + is_input_not_rounded: bool, + ): + input_values_dict = {} + input_values_dict["sign"] = 0 + input_values_dict["sig"] = ( + help_values.max_sig + if is_input_not_rounded and rm != RoundingModes.ROUND_DOWN and rm != RoundingModes.ROUND_ZERO + else 0 + ) + input_values_dict["exp"] = ( + help_values.max_norm_exp + if is_input_not_rounded and rm != RoundingModes.ROUND_DOWN and rm != RoundingModes.ROUND_ZERO + else help_values.max_exp + ) + input_values_dict["guard_bit"] = 1 + input_values_dict["sticky_bit"] = 1 + input_values_dict["rounding_mode"] = rm + input_values_dict["errors"] = 0 + input_values_dict["input_nan"] = 0 + input_values_dict["input_inf"] = 0 + + # overflow detection + resp = yield from request_adapter.call(input_values_dict) + assert resp["sign"] == 0 + assert resp["exp"] == plus_overflow_exp + assert resp["sig"] == plus_oveflow_sig + assert resp["errors"] == 20 + + input_values_dict["sign"] = 1 + input_values_dict["sig"] = ( + help_values.max_sig + if is_input_not_rounded and rm != RoundingModes.ROUND_UP and rm != RoundingModes.ROUND_ZERO + else 0 + ) + input_values_dict["exp"] = ( + help_values.max_norm_exp + if is_input_not_rounded and rm != RoundingModes.ROUND_UP and rm != RoundingModes.ROUND_ZERO + else help_values.max_exp + ) + + resp = yield from request_adapter.call(input_values_dict) + assert resp["sign"] == 1 + assert resp["exp"] == minus_overflow_exp + assert resp["sig"] == minus_overflow_sig + assert resp["errors"] == 20 + + # no overflow + input_values_dict["exp"] = help_values.not_max_norm_exp + + for i in range(4): + input_values_dict["sign"] = 0 + input_values_dict["guard_bit"] = i & 1 + input_values_dict["sticky_bit"] = (i >> 1) & 1 + input_values_dict["sig"] = ( + help_values.not_max_norm_sig if is_input_not_rounded else help_values.not_max_norm_sig + inc_arr[i] + ) + + resp = yield from request_adapter.call(input_values_dict) + assert resp["sign"] == 0 + assert resp["exp"] == help_values.not_max_norm_exp + assert resp["sig"] == help_values.not_max_norm_sig + inc_arr[i] + if i: + assert resp["errors"] == 16 + else: + assert resp["errors"] == 0 + + input_values_dict["sign"] = 1 + input_values_dict["sig"] = ( + help_values.not_max_norm_sig + if is_input_not_rounded + else help_values.not_max_norm_sig + inc_arr[4 + i] + ) + + resp = yield from request_adapter.call(input_values_dict) + assert resp["sign"] == 1 + assert resp["exp"] == help_values.not_max_norm_exp + assert resp["sig"] == help_values.not_max_norm_sig + inc_arr[4 + i] + if i: + assert resp["errors"] == 16 + else: + assert resp["errors"] == 0 + + if rm == RoundingModes.ROUND_NEAREST_EVEN: + input_values_dict["sticky_bit"] = 0 + input_values_dict["guard_bit"] = 1 + + # tie, no increment + input_values_dict["sign"] = 1 + input_values_dict["sig"] = help_values.not_max_norm_even_sig + + resp = yield from request_adapter.call(input_values_dict) + assert resp["sign"] == 1 + assert resp["exp"] == help_values.not_max_norm_exp + assert resp["sig"] == help_values.not_max_norm_even_sig + assert resp["errors"] == 16 + + input_values_dict["sign"] = 0 + + resp = yield from request_adapter.call(input_values_dict) + assert resp["sign"] == 0 + assert resp["exp"] == help_values.not_max_norm_exp + assert resp["sig"] == help_values.not_max_norm_even_sig + assert resp["errors"] == 16 + + def all_rounding_modes_test(request_adapter: TestbenchIO, is_input_not_rounded: bool): + tie_to_even_inc_array = [ + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + ] + tie_to_away_inc_array = [0, 1, 0, 1, 0, 1, 0, 1] + round_up_inc_array = [0, 1, 1, 1, 0, 0, 0, 0] + round_down_inc_array = [0, 0, 0, 0, 0, 1, 1, 1] + round_zero_inc_array = [0, 0, 0, 0, 0, 0, 0, 0] + + yield from one_rounding_mode_test( + RoundingModes.ROUND_NEAREST_EVEN, + tie_to_even_inc_array, + 0, + help_values.max_exp, + 0, + help_values.max_exp, + request_adapter, + is_input_not_rounded, + ) + yield from one_rounding_mode_test( + RoundingModes.ROUND_NEAREST_AWAY, + tie_to_away_inc_array, + 0, + help_values.max_exp, + 0, + help_values.max_exp, + request_adapter, + is_input_not_rounded, + ) + yield from one_rounding_mode_test( + RoundingModes.ROUND_UP, + round_up_inc_array, + 0, + help_values.max_exp, + help_values.max_sig, + help_values.max_norm_exp, + request_adapter, + is_input_not_rounded, + ) + yield from one_rounding_mode_test( + RoundingModes.ROUND_DOWN, + round_down_inc_array, + help_values.max_sig, + help_values.max_norm_exp, + 0, + help_values.max_exp, + request_adapter, + is_input_not_rounded, + ) + yield from one_rounding_mode_test( + RoundingModes.ROUND_ZERO, + round_zero_inc_array, + help_values.max_sig, + help_values.max_norm_exp, + help_values.max_sig, + help_values.max_norm_exp, + request_adapter, + is_input_not_rounded, + ) + + def test_process(): + yield from all_rounding_modes_test(fpurt.rounding_request_adapter, True) + yield from other_cases_test(fpurt.rounding_request_adapter, True) + yield from all_rounding_modes_test(fpurt.input_rounded_rounding_request_adapter, False) + yield from other_cases_test(fpurt.input_rounded_rounding_request_adapter, False) + + with self.run_simulation(fpurt) as sim: + sim.add_sync_process(test_process) From 27a86d8547666dcf3de19484b2aafc6d4b456492 Mon Sep 17 00:00:00 2001 From: Mateusz Marszalek Date: Tue, 10 Sep 2024 01:29:14 +0200 Subject: [PATCH 03/15] Fixed small spelling mistake --- coreblocks/func_blocks/fu/fpu/fpu_common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coreblocks/func_blocks/fu/fpu/fpu_common.py b/coreblocks/func_blocks/fu/fpu/fpu_common.py index 0081c81e5..9cb7f7b35 100644 --- a/coreblocks/func_blocks/fu/fpu/fpu_common.py +++ b/coreblocks/func_blocks/fu/fpu/fpu_common.py @@ -38,7 +38,7 @@ class FPURoundingParams: fpu_params: FPUParams FPU parameters is_rounded:bool - This flags indicates that the input number was already rounded. This creates simpler version of rounding module that only performs error checking and return corect number. + This flags indicates that the input number was already rounded. This creates simpler version of rounding module that only performs error checking and returns correct number. """ def __init__(self, fpu_params: FPUParams, *, is_rounded: bool = False): From 9c9f760da7aca5c79ce8c50f0a5f21846503cd8e Mon Sep 17 00:00:00 2001 From: Mateusz Marszalek Date: Tue, 10 Sep 2024 02:22:30 +0200 Subject: [PATCH 04/15] Fixed formatting in fpu_common --- coreblocks/func_blocks/fu/fpu/fpu_common.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/coreblocks/func_blocks/fu/fpu/fpu_common.py b/coreblocks/func_blocks/fu/fpu/fpu_common.py index 9cb7f7b35..7a7bee520 100644 --- a/coreblocks/func_blocks/fu/fpu/fpu_common.py +++ b/coreblocks/func_blocks/fu/fpu/fpu_common.py @@ -38,7 +38,8 @@ class FPURoundingParams: fpu_params: FPUParams FPU parameters is_rounded:bool - This flags indicates that the input number was already rounded. This creates simpler version of rounding module that only performs error checking and returns correct number. + This flags indicates that the input number was already rounded. + This creates simpler version of rounding module that only performs error checking and returns correct number. """ def __init__(self, fpu_params: FPUParams, *, is_rounded: bool = False): From 6ba84c370de45c72916bade4e8c35db8de8f37f3 Mon Sep 17 00:00:00 2001 From: Mateusz Marszalek Date: Tue, 1 Oct 2024 00:37:02 +0200 Subject: [PATCH 05/15] Review changes for fpu_rounding_module and fpu_common --- coreblocks/func_blocks/fu/fpu/fpu_common.py | 2 +- .../func_blocks/fu/fpu/fpu_rounding_module.py | 287 ++++++++---------- 2 files changed, 127 insertions(+), 162 deletions(-) diff --git a/coreblocks/func_blocks/fu/fpu/fpu_common.py b/coreblocks/func_blocks/fu/fpu/fpu_common.py index 7a7bee520..44241e623 100644 --- a/coreblocks/func_blocks/fu/fpu/fpu_common.py +++ b/coreblocks/func_blocks/fu/fpu/fpu_common.py @@ -34,7 +34,7 @@ class FPURoundingParams: """FPU rounding module signature Parameters - ----------- + ---------- fpu_params: FPUParams FPU parameters is_rounded:bool diff --git a/coreblocks/func_blocks/fu/fpu/fpu_rounding_module.py b/coreblocks/func_blocks/fu/fpu/fpu_rounding_module.py index 19dc23c10..45078a359 100644 --- a/coreblocks/func_blocks/fu/fpu/fpu_rounding_module.py +++ b/coreblocks/func_blocks/fu/fpu/fpu_rounding_module.py @@ -1,5 +1,4 @@ from amaranth import * -from amaranth.lib.wiring import Component, In, Out, Signature from transactron import TModule, Method, def_method from coreblocks.func_blocks.fu.fpu.fpu_common import ( RoundingModes, @@ -8,33 +7,6 @@ ) -class FPURoundingSignature(Signature): - """FPU Rounding module signature - - Parameters - ---------- - fpu_params: FPUParams - FPU parameters - """ - - def __init__(self, *, fpu_params: FPUParams): - super().__init__( - { - "in_sign": In(1), - "in_sig": In(fpu_params.sig_width), - "in_exp": In(fpu_params.exp_width), - "rounding_mode": In(3), - "guard_bit": In(1), - "sticky_bit": In(1), - "in_errors": In(3), - "out_sign": In(1), - "out_sig": Out(fpu_params.sig_width), - "out_exp": Out(fpu_params.exp_width), - "out_error": Out(3), - } - ) - - class FPURoudningMethodLayout: """FPU Rounding module layouts for methods @@ -49,11 +21,11 @@ def __init__(self, *, fpu_params: FPUParams): ("sign", 1), ("sig", fpu_params.sig_width), ("exp", fpu_params.exp_width), - ("guard_bit", 1), + ("round_bit", 1), ("sticky_bit", 1), - ("rounding_mode", 3), - ("errors", 5), - ("input_nan", 1), + ("rounding_mode", Shape.cast(RoundingModes)), + ("invalid_operation", 1), + ("division_by_zero", 1), ("input_inf", 1), ] self.rounding_out_layout = [ @@ -64,12 +36,23 @@ def __init__(self, *, fpu_params: FPUParams): ] -class FPUrounding(Component): +class FPUrounding(Elaboratable): + """FPU Rounding module + + Parameters + ---------- + fpu_rounding_params: FPURoundingParams + FPU rounding module parameters - fpu_rounding: FPURoundingSignature + Attributes + ---------- + rounding_request: Method + Transactional method for initiating rounding of a floating point number. + Takes 'rounding_in_layout' as argument + Returns rounded number and errors as 'rounding_out_layout' + """ def __init__(self, *, fpu_rounding_params: FPURoundingParams): - super().__init__({"fpu_rounding": Out(FPURoundingSignature(fpu_params=fpu_rounding_params.fpu_params))}) self.fpu_rounding_params = fpu_rounding_params self.method_layouts = FPURoudningMethodLayout(fpu_params=self.fpu_rounding_params.fpu_params) @@ -77,181 +60,163 @@ def __init__(self, *, fpu_rounding_params: FPURoundingParams): i=self.method_layouts.rounding_in_layout, o=self.method_layouts.rounding_out_layout, ) - self.rtval = {} - self.max_exp = C( + + def elaborate(self, platform): + m = TModule() + + max_exp = C( 2 ** (self.fpu_rounding_params.fpu_params.exp_width) - 1, unsigned(self.fpu_rounding_params.fpu_params.exp_width), ) - self.max_normal_exp = C( + max_normal_exp = C( 2 ** (self.fpu_rounding_params.fpu_params.exp_width) - 2, unsigned(self.fpu_rounding_params.fpu_params.exp_width), ) - self.quiet_nan = C( - 2 ** (self.fpu_rounding_params.fpu_params.sig_width - 1), - unsigned(self.fpu_rounding_params.fpu_params.sig_width), - ) - self.max_sig = C( + max_sig = C( 2 ** (self.fpu_rounding_params.fpu_params.sig_width) - 1, unsigned(self.fpu_rounding_params.fpu_params.sig_width), ) - self.add_one = Signal() - self.inc_rtnte = Signal() - self.inc_rtnta = Signal() - self.inc_rtpi = Signal() - self.inc_rtmi = Signal() - - self.rounded_sig = Signal(self.fpu_rounding_params.fpu_params.sig_width + 1) - self.normalised_sig = Signal(self.fpu_rounding_params.fpu_params.sig_width) - self.rounded_exp = Signal(self.fpu_rounding_params.fpu_params.exp_width + 1) - - self.final_guard_bit = Signal() - self.final_sticky_bit = Signal() - - self.overflow = Signal() - self.underflow = Signal() - self.inexact = Signal() - self.tininess = Signal() - self.is_inf = Signal() - self.is_nan = Signal() - self.input_not_special = Signal() - self.rounded_inexact = Signal() - - self.final_exp = Signal(self.fpu_rounding_params.fpu_params.exp_width) - self.final_sig = Signal(self.fpu_rounding_params.fpu_params.sig_width) - self.final_sign = Signal() - self.final_errors = Signal(5) + add_one = Signal() + inc_rtnte = Signal() + inc_rtnta = Signal() + inc_rtpi = Signal() + inc_rtmi = Signal() - def elaborate(self, platform): - m = TModule() + rounded_sig = Signal(self.fpu_rounding_params.fpu_params.sig_width + 1) + normalised_sig = Signal(self.fpu_rounding_params.fpu_params.sig_width) + rounded_exp = Signal(self.fpu_rounding_params.fpu_params.exp_width + 1) + + final_round_bit = Signal() + final_sticky_bit = Signal() + + overflow = Signal() + underflow = Signal() + inexact = Signal() + tininess = Signal() + + final_exp = Signal(self.fpu_rounding_params.fpu_params.exp_width) + final_sig = Signal(self.fpu_rounding_params.fpu_params.sig_width) + final_sign = Signal() + final_errors = Signal(5) @def_method(m, self.rounding_request) def _(arg): - m.d.comb += self.inc_rtnte.eq( + m.d.av_comb += inc_rtnte.eq( (arg.rounding_mode == RoundingModes.ROUND_NEAREST_EVEN) - & (arg.guard_bit & (arg.sticky_bit | arg.sig[0])) + & (arg.round_bit & (arg.sticky_bit | arg.sig[0])) ) - m.d.comb += self.inc_rtnta.eq((arg.rounding_mode == RoundingModes.ROUND_NEAREST_AWAY) & (arg.guard_bit)) - m.d.comb += self.inc_rtpi.eq( - (arg.rounding_mode == RoundingModes.ROUND_UP) & (~arg.sign & (arg.guard_bit | arg.sticky_bit)) + m.d.av_comb += inc_rtnta.eq((arg.rounding_mode == RoundingModes.ROUND_NEAREST_AWAY) & (arg.round_bit)) + m.d.av_comb += inc_rtpi.eq( + (arg.rounding_mode == RoundingModes.ROUND_UP) & (~arg.sign & (arg.round_bit | arg.sticky_bit)) ) - m.d.comb += self.inc_rtmi.eq( - (arg.rounding_mode == RoundingModes.ROUND_DOWN) & (arg.sign & (arg.guard_bit | arg.sticky_bit)) + m.d.av_comb += inc_rtmi.eq( + (arg.rounding_mode == RoundingModes.ROUND_DOWN) & (arg.sign & (arg.round_bit | arg.sticky_bit)) ) - m.d.comb += self.add_one.eq(self.inc_rtmi | self.inc_rtnta | self.inc_rtnte | self.inc_rtpi) + m.d.av_comb += add_one.eq(inc_rtmi | inc_rtnta | inc_rtnte | inc_rtpi) if self.fpu_rounding_params.is_rounded: - m.d.comb += self.normalised_sig.eq(arg.sig) - m.d.comb += self.final_guard_bit.eq(arg.guard_bit) - m.d.comb += self.final_sticky_bit.eq(arg.sticky_bit) - m.d.comb += self.rounded_exp.eq(arg.exp) + m.d.av_comb += normalised_sig.eq(arg.sig) + m.d.av_comb += final_round_bit.eq(arg.round_bit) + m.d.av_comb += final_sticky_bit.eq(arg.sticky_bit) + m.d.av_comb += rounded_exp.eq(arg.exp) else: - m.d.comb += self.rounded_sig.eq(arg.sig + self.add_one) + m.d.av_comb += rounded_sig.eq(arg.sig + add_one) - with m.If(self.rounded_sig[-1]): + with m.If(rounded_sig[-1]): - m.d.comb += self.normalised_sig.eq(self.rounded_sig >> 1) - m.d.comb += self.final_guard_bit.eq(self.rounded_sig[0]) - m.d.comb += self.final_sticky_bit.eq(arg.guard_bit | arg.sticky_bit) - m.d.comb += self.rounded_exp.eq(arg.exp + 1) + m.d.av_comb += normalised_sig.eq(rounded_sig >> 1) + m.d.av_comb += final_round_bit.eq(rounded_sig[0]) + m.d.av_comb += final_sticky_bit.eq(arg.round_bit | arg.sticky_bit) + m.d.av_comb += rounded_exp.eq(arg.exp + 1) with m.Else(): - m.d.comb += self.normalised_sig.eq(self.rounded_sig) - m.d.comb += self.final_guard_bit.eq(arg.guard_bit) - m.d.comb += self.final_sticky_bit.eq(arg.sticky_bit) - m.d.comb += self.rounded_exp.eq(arg.exp) - - m.d.comb += self.rounded_inexact.eq(self.final_guard_bit | self.final_sticky_bit) - m.d.comb += self.is_nan.eq(arg.errors[0] | arg.input_nan) - m.d.comb += self.is_inf.eq(arg.errors[1] | arg.input_inf) - m.d.comb += self.input_not_special.eq(~(self.is_nan) & ~(self.is_inf)) - m.d.comb += self.overflow.eq(self.input_not_special & (self.rounded_exp >= self.max_exp)) - m.d.comb += self.tininess.eq( - (self.rounded_exp == 0) & (self.rounded_inexact | self.rounded_sig.any()) & (~self.normalised_sig[-1]) - ) - m.d.comb += self.inexact.eq(self.overflow | (self.input_not_special & self.rounded_inexact)) - m.d.comb += self.underflow.eq(self.tininess & self.inexact) + m.d.av_comb += normalised_sig.eq(rounded_sig) + m.d.av_comb += final_round_bit.eq(arg.round_bit) + m.d.av_comb += final_sticky_bit.eq(arg.sticky_bit) + m.d.av_comb += rounded_exp.eq(arg.exp) - with m.If(self.is_nan): + rounded_inexact = final_round_bit | final_sticky_bit + is_nan = arg.invalid_operation | ((arg.exp == max_exp) & (arg.sig.any())) + is_inf = arg.division_by_zero | arg.input_inf + input_not_special = ~(is_nan) & ~(is_inf) + m.d.av_comb += overflow.eq(input_not_special & (rounded_exp >= max_exp)) + m.d.av_comb += tininess.eq((rounded_exp == 0) & (~normalised_sig[-1])) + m.d.av_comb += inexact.eq(overflow | (input_not_special & rounded_inexact)) + m.d.av_comb += underflow.eq(tininess & inexact) - m.d.comb += self.final_exp.eq(self.max_exp) - m.d.comb += self.final_sig.eq(arg.sig) - m.d.comb += self.final_sign.eq(arg.sign) + with m.If(is_nan | is_inf): - with m.Elif(self.is_inf): + m.d.av_comb += final_exp.eq(arg.exp) + m.d.av_comb += final_sig.eq(arg.sig) + m.d.av_comb += final_sign.eq(arg.sign) - m.d.comb += self.final_exp.eq(self.max_exp) - m.d.comb += self.final_sig.eq(arg.sig) - m.d.comb += self.final_sign.eq(arg.sign) + with m.Elif(overflow): - with m.Elif(self.overflow): + with m.Switch(arg.rounding_mode): + with m.Case(RoundingModes.ROUND_NEAREST_AWAY, RoundingModes.ROUND_NEAREST_EVEN): - with m.If( - (arg.rounding_mode == RoundingModes.ROUND_NEAREST_AWAY) - | (arg.rounding_mode == RoundingModes.ROUND_NEAREST_EVEN) - ): + m.d.av_comb += final_exp.eq(max_exp) + m.d.av_comb += final_sig.eq(0) + m.d.av_comb += final_sign.eq(arg.sign) - m.d.comb += self.final_exp.eq(self.max_exp) - m.d.comb += self.final_sig.eq(0) - m.d.comb += self.final_sign.eq(arg.sign) + with m.Case(RoundingModes.ROUND_ZERO): - with m.If(arg.rounding_mode == RoundingModes.ROUND_ZERO): + m.d.av_comb += final_exp.eq(max_normal_exp) + m.d.av_comb += final_sig.eq(max_sig) + m.d.av_comb += final_sign.eq(arg.sign) - m.d.comb += self.final_exp.eq(self.max_normal_exp) - m.d.comb += self.final_sig.eq(self.max_sig) - m.d.comb += self.final_sign.eq(arg.sign) + with m.Case(RoundingModes.ROUND_DOWN): - with m.If(arg.rounding_mode == RoundingModes.ROUND_DOWN): + with m.If(arg.sign): - with m.If(arg.sign): + m.d.av_comb += final_exp.eq(max_exp) + m.d.av_comb += final_sig.eq(0) + m.d.av_comb += final_sign.eq(arg.sign) - m.d.comb += self.final_exp.eq(self.max_exp) - m.d.comb += self.final_sig.eq(0) - m.d.comb += self.final_sign.eq(arg.sign) + with m.Else(): - with m.Else(): + m.d.av_comb += final_exp.eq(max_normal_exp) + m.d.av_comb += final_sig.eq(max_sig) + m.d.av_comb += final_sign.eq(arg.sign) - m.d.comb += self.final_exp.eq(self.max_normal_exp) - m.d.comb += self.final_sig.eq(self.max_sig) - m.d.comb += self.final_sign.eq(arg.sign) + with m.Case(RoundingModes.ROUND_UP): - with m.If(arg.rounding_mode == RoundingModes.ROUND_UP): + with m.If(arg.sign): - with m.If(arg.sign): + m.d.av_comb += final_exp.eq(max_normal_exp) + m.d.av_comb += final_sig.eq(max_sig) + m.d.av_comb += final_sign.eq(arg.sign) - m.d.comb += self.final_exp.eq(self.max_normal_exp) - m.d.comb += self.final_sig.eq(self.max_sig) - m.d.comb += self.final_sign.eq(arg.sign) + with m.Else(): - with m.Else(): - - m.d.comb += self.final_exp.eq(self.max_exp) - m.d.comb += self.final_sig.eq(0) - m.d.comb += self.final_sign.eq(arg.sign) + m.d.av_comb += final_exp.eq(max_exp) + m.d.av_comb += final_sig.eq(0) + m.d.av_comb += final_sign.eq(arg.sign) with m.Else(): - with m.If((self.rounded_exp == 0) & (self.normalised_sig[-1] == 1)): - m.d.comb += self.final_exp.eq(1) + with m.If((rounded_exp == 0) & (normalised_sig[-1] == 1)): + m.d.av_comb += final_exp.eq(1) with m.Else(): - m.d.comb += self.final_exp.eq(self.rounded_exp) - m.d.comb += self.final_sig.eq(self.normalised_sig) - m.d.comb += self.final_sign.eq(arg.sign) - - m.d.comb += self.final_errors[0].eq(arg.errors[0]) - m.d.comb += self.final_errors[1].eq(arg.errors[1]) - m.d.comb += self.final_errors[2].eq(self.overflow) - m.d.comb += self.final_errors[3].eq(self.underflow) - m.d.comb += self.final_errors[4].eq(self.inexact) - - self.rtval["exp"] = self.final_exp - self.rtval["sig"] = self.final_sig - self.rtval["sign"] = self.final_sign - self.rtval["errors"] = self.final_errors - - return self.rtval + m.d.av_comb += final_exp.eq(rounded_exp) + m.d.av_comb += final_sig.eq(normalised_sig) + m.d.av_comb += final_sign.eq(arg.sign) + + m.d.av_comb += final_errors[0].eq(arg.invalid_operation) + m.d.av_comb += final_errors[1].eq(arg.division_by_zero) + m.d.av_comb += final_errors[2].eq(overflow) + m.d.av_comb += final_errors[3].eq(underflow) + m.d.av_comb += final_errors[4].eq(inexact) + + return { + "exp": final_exp, + "sig": final_sig, + "sign": final_sign, + "errors": final_errors, + } return m From d038fac487a99187c9d54c7ad62196eaa6b74734 Mon Sep 17 00:00:00 2001 From: Mateusz Marszalek Date: Tue, 1 Oct 2024 00:39:12 +0200 Subject: [PATCH 06/15] Review changes for test_fpu_rounding --- test/func_blocks/fu/test_fpu_rounding.py | 221 ++++++++++++----------- 1 file changed, 119 insertions(+), 102 deletions(-) diff --git a/test/func_blocks/fu/test_fpu_rounding.py b/test/func_blocks/fu/test_fpu_rounding.py index 042806224..4f10007e0 100644 --- a/test/func_blocks/fu/test_fpu_rounding.py +++ b/test/func_blocks/fu/test_fpu_rounding.py @@ -6,6 +6,7 @@ ) from transactron import TModule from transactron.lib import AdapterTrans +from parameterized import parameterized from transactron.testing import * from amaranth import * @@ -46,21 +47,38 @@ def __init__(self, params: FPUParams): self.max_sub_norm_sig = (2 ** (self.params.sig_width - 1)) - 1 self.qnan = 3 << (self.params.sig_width - 2) | 1 - def test_manual(self): - params = FPUParams(sig_width=24, exp_width=8) + params = FPUParams(sig_width=24, exp_width=8) + help_values = HelpValues(params) + + tie_to_even_inc_array = [ + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + ] + tie_to_away_inc_array = [0, 1, 0, 1, 0, 1, 0, 1] + round_up_inc_array = [0, 1, 1, 1, 0, 0, 0, 0] + round_down_inc_array = [0, 0, 0, 0, 0, 1, 1, 1] + round_zero_inc_array = [0, 0, 0, 0, 0, 0, 0, 0] + + @parameterized.expand([(params, help_values)]) + def test_special_cases(self, params: FPUParams, help_values: HelpValues): fpurt = TestFPURounding.FPURoundingModule(params) - help_values = TestFPURounding.HelpValues(params) def other_cases_test(request_adapter: TestbenchIO, is_input_not_rounded: bool): input_values_dict = {} input_values_dict["sign"] = 0 input_values_dict["sig"] = help_values.not_max_norm_even_sig input_values_dict["exp"] = help_values.not_max_norm_exp - input_values_dict["guard_bit"] = 0 + input_values_dict["round_bit"] = 0 input_values_dict["sticky_bit"] = 0 input_values_dict["rounding_mode"] = RoundingModes.ROUND_NEAREST_AWAY - input_values_dict["errors"] = 0 - input_values_dict["input_nan"] = 0 + input_values_dict["invalid_operation"] = 0 + input_values_dict["division_by_zero"] = 0 input_values_dict["input_inf"] = 0 # No errors @@ -86,7 +104,7 @@ def other_cases_test(request_adapter: TestbenchIO, is_input_not_rounded: bool): input_values_dict["sig"] = ( help_values.sub_norm_sig if is_input_not_rounded else help_values.sub_norm_sig + 1 ) - input_values_dict["guard_bit"] = 1 + input_values_dict["round_bit"] = 1 resp = yield from request_adapter.call(input_values_dict) @@ -97,25 +115,26 @@ def other_cases_test(request_adapter: TestbenchIO, is_input_not_rounded: bool): # underflow no rounding - input_values_dict["guard_bit"] = 0 - input_values_dict["sig"] = help_values.sub_norm_sig + input_values_dict["round_bit"] = 0 + input_values_dict["sig"] = 0 resp = yield from request_adapter.call(input_values_dict) assert resp["sign"] == 0 assert resp["exp"] == 0 - assert resp["sig"] == help_values.sub_norm_sig + assert resp["sig"] == 0 assert resp["errors"] == 24 # invalid operation input_values_dict["exp"] = help_values.max_exp input_values_dict["sig"] = help_values.qnan - input_values_dict["errors"] = 1 + input_values_dict["invalid_operation"] = 1 + input_values_dict["division_by_zero"] = 0 resp = yield from request_adapter.call(input_values_dict) - assert resp["sign"] == 0 + assert resp["sign"] == input_values_dict["sign"] assert resp["exp"] == input_values_dict["exp"] assert resp["sig"] == input_values_dict["sig"] assert resp["errors"] == 1 @@ -124,19 +143,21 @@ def other_cases_test(request_adapter: TestbenchIO, is_input_not_rounded: bool): input_values_dict["exp"] = help_values.max_exp input_values_dict["sig"] = 0 - input_values_dict["errors"] = 2 + input_values_dict["invalid_operation"] = 0 + input_values_dict["division_by_zero"] = 1 resp = yield from request_adapter.call(input_values_dict) - assert resp["sign"] == 0 + assert resp["sign"] == input_values_dict["sign"] assert resp["exp"] == input_values_dict["exp"] assert resp["sig"] == input_values_dict["sig"] assert resp["errors"] == 2 # overflow but no guard and sticky bits - input_values_dict["guard_bit"] = 0 + input_values_dict["round_bit"] = 0 input_values_dict["sticky_bit"] = 0 - input_values_dict["errors"] = 0 + input_values_dict["invalid_operation"] = 0 + input_values_dict["division_by_zero"] = 0 resp = yield from request_adapter.call(input_values_dict) @@ -162,7 +183,6 @@ def other_cases_test(request_adapter: TestbenchIO, is_input_not_rounded: bool): input_values_dict["exp"] = help_values.max_exp input_values_dict["sig"] = help_values.qnan input_values_dict["sticky_bit"] = 1 - input_values_dict["input_nan"] = 1 input_values_dict["input_inf"] = 0 resp = yield from request_adapter.call(input_values_dict) @@ -177,7 +197,6 @@ def other_cases_test(request_adapter: TestbenchIO, is_input_not_rounded: bool): input_values_dict["sign"] = 1 input_values_dict["exp"] = help_values.max_exp input_values_dict["sig"] = 0 - input_values_dict["input_nan"] = 0 input_values_dict["input_inf"] = 1 resp = yield from request_adapter.call(input_values_dict) @@ -193,9 +212,8 @@ def other_cases_test(request_adapter: TestbenchIO, is_input_not_rounded: bool): input_values_dict["exp"] = 0 input_values_dict["sig"] = help_values.max_sub_norm_sig input_values_dict["sticky_bit"] = 1 - input_values_dict["guard_bit"] = 1 + input_values_dict["round_bit"] = 1 input_values_dict["rounding_mode"] = RoundingModes.ROUND_NEAREST_AWAY - input_values_dict["input_nan"] = 0 input_values_dict["input_inf"] = 0 resp = yield from request_adapter.call(input_values_dict) @@ -204,13 +222,81 @@ def other_cases_test(request_adapter: TestbenchIO, is_input_not_rounded: bool): assert resp["sig"] == input_values_dict["sig"] + 1 assert resp["errors"] == 16 + def test_process(): + yield from other_cases_test(fpurt.rounding_request_adapter, True) + yield from other_cases_test(fpurt.input_rounded_rounding_request_adapter, False) + + with self.run_simulation(fpurt) as sim: + sim.add_sync_process(test_process) + + @parameterized.expand( + [ + ( + params, + help_values, + RoundingModes.ROUND_NEAREST_EVEN, + tie_to_away_inc_array, + 0, + help_values.max_exp, + 0, + help_values.max_exp, + ), + ( + params, + help_values, + RoundingModes.ROUND_NEAREST_AWAY, + tie_to_away_inc_array, + 0, + help_values.max_exp, + 0, + help_values.max_exp, + ), + ( + params, + help_values, + RoundingModes.ROUND_UP, + round_up_inc_array, + 0, + help_values.max_exp, + help_values.max_sig, + help_values.max_norm_exp, + ), + ( + params, + help_values, + RoundingModes.ROUND_DOWN, + round_down_inc_array, + help_values.max_sig, + help_values.max_norm_exp, + 0, + help_values.max_exp, + ), + ( + params, + help_values, + RoundingModes.ROUND_ZERO, + round_zero_inc_array, + help_values.max_sig, + help_values.max_norm_exp, + help_values.max_sig, + help_values.max_norm_exp, + ), + ] + ) + def test_rounding( + self, + params: FPUParams, + help_values: HelpValues, + rm: RoundingModes, + inc_arr: list, + plus_oveflow_sig: int, + plus_overflow_exp: int, + minus_overflow_sig: int, + minus_overflow_exp: int, + ): + fpurt = TestFPURounding.FPURoundingModule(params) + def one_rounding_mode_test( - rm: RoundingModes, - inc_arr: list, - plus_oveflow_sig: int, - plus_overflow_exp: int, - minus_overflow_sig: int, - minus_overflow_exp: int, request_adapter: TestbenchIO, is_input_not_rounded: bool, ): @@ -226,11 +312,11 @@ def one_rounding_mode_test( if is_input_not_rounded and rm != RoundingModes.ROUND_DOWN and rm != RoundingModes.ROUND_ZERO else help_values.max_exp ) - input_values_dict["guard_bit"] = 1 + input_values_dict["round_bit"] = 1 input_values_dict["sticky_bit"] = 1 input_values_dict["rounding_mode"] = rm - input_values_dict["errors"] = 0 - input_values_dict["input_nan"] = 0 + input_values_dict["invalid_operation"] = 0 + input_values_dict["division_by_zero"] = 0 input_values_dict["input_inf"] = 0 # overflow detection @@ -263,7 +349,7 @@ def one_rounding_mode_test( for i in range(4): input_values_dict["sign"] = 0 - input_values_dict["guard_bit"] = i & 1 + input_values_dict["round_bit"] = i & 1 input_values_dict["sticky_bit"] = (i >> 1) & 1 input_values_dict["sig"] = ( help_values.not_max_norm_sig if is_input_not_rounded else help_values.not_max_norm_sig + inc_arr[i] @@ -296,7 +382,7 @@ def one_rounding_mode_test( if rm == RoundingModes.ROUND_NEAREST_EVEN: input_values_dict["sticky_bit"] = 0 - input_values_dict["guard_bit"] = 1 + input_values_dict["round_bit"] = 1 # tie, no increment input_values_dict["sign"] = 1 @@ -316,78 +402,9 @@ def one_rounding_mode_test( assert resp["sig"] == help_values.not_max_norm_even_sig assert resp["errors"] == 16 - def all_rounding_modes_test(request_adapter: TestbenchIO, is_input_not_rounded: bool): - tie_to_even_inc_array = [ - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - ] - tie_to_away_inc_array = [0, 1, 0, 1, 0, 1, 0, 1] - round_up_inc_array = [0, 1, 1, 1, 0, 0, 0, 0] - round_down_inc_array = [0, 0, 0, 0, 0, 1, 1, 1] - round_zero_inc_array = [0, 0, 0, 0, 0, 0, 0, 0] - - yield from one_rounding_mode_test( - RoundingModes.ROUND_NEAREST_EVEN, - tie_to_even_inc_array, - 0, - help_values.max_exp, - 0, - help_values.max_exp, - request_adapter, - is_input_not_rounded, - ) - yield from one_rounding_mode_test( - RoundingModes.ROUND_NEAREST_AWAY, - tie_to_away_inc_array, - 0, - help_values.max_exp, - 0, - help_values.max_exp, - request_adapter, - is_input_not_rounded, - ) - yield from one_rounding_mode_test( - RoundingModes.ROUND_UP, - round_up_inc_array, - 0, - help_values.max_exp, - help_values.max_sig, - help_values.max_norm_exp, - request_adapter, - is_input_not_rounded, - ) - yield from one_rounding_mode_test( - RoundingModes.ROUND_DOWN, - round_down_inc_array, - help_values.max_sig, - help_values.max_norm_exp, - 0, - help_values.max_exp, - request_adapter, - is_input_not_rounded, - ) - yield from one_rounding_mode_test( - RoundingModes.ROUND_ZERO, - round_zero_inc_array, - help_values.max_sig, - help_values.max_norm_exp, - help_values.max_sig, - help_values.max_norm_exp, - request_adapter, - is_input_not_rounded, - ) - def test_process(): - yield from all_rounding_modes_test(fpurt.rounding_request_adapter, True) - yield from other_cases_test(fpurt.rounding_request_adapter, True) - yield from all_rounding_modes_test(fpurt.input_rounded_rounding_request_adapter, False) - yield from other_cases_test(fpurt.input_rounded_rounding_request_adapter, False) + yield from one_rounding_mode_test(fpurt.rounding_request_adapter, True) + yield from one_rounding_mode_test(fpurt.input_rounded_rounding_request_adapter, False) with self.run_simulation(fpurt) as sim: sim.add_sync_process(test_process) From cb09f6527e0e6fbb2c5f83c83a5c2ba29df606ff Mon Sep 17 00:00:00 2001 From: Mateusz Marszalek Date: Tue, 1 Oct 2024 19:23:11 +0200 Subject: [PATCH 07/15] Small change to RoundingModes definition --- coreblocks/func_blocks/fu/fpu/fpu_common.py | 2 +- coreblocks/func_blocks/fu/fpu/fpu_rounding_module.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/coreblocks/func_blocks/fu/fpu/fpu_common.py b/coreblocks/func_blocks/fu/fpu/fpu_common.py index 44241e623..580207a9f 100644 --- a/coreblocks/func_blocks/fu/fpu/fpu_common.py +++ b/coreblocks/func_blocks/fu/fpu/fpu_common.py @@ -1,7 +1,7 @@ from amaranth.lib import enum -class RoundingModes(enum.Enum, shape=3): +class RoundingModes(enum.Enum): ROUND_UP = 3 ROUND_DOWN = 2 ROUND_ZERO = 1 diff --git a/coreblocks/func_blocks/fu/fpu/fpu_rounding_module.py b/coreblocks/func_blocks/fu/fpu/fpu_rounding_module.py index 45078a359..755bafa6c 100644 --- a/coreblocks/func_blocks/fu/fpu/fpu_rounding_module.py +++ b/coreblocks/func_blocks/fu/fpu/fpu_rounding_module.py @@ -23,7 +23,7 @@ def __init__(self, *, fpu_params: FPUParams): ("exp", fpu_params.exp_width), ("round_bit", 1), ("sticky_bit", 1), - ("rounding_mode", Shape.cast(RoundingModes)), + ("rounding_mode", RoundingModes), ("invalid_operation", 1), ("division_by_zero", 1), ("input_inf", 1), From deb43e341a74e3bc0e5b97d14a7f7ed321fd3e2d Mon Sep 17 00:00:00 2001 From: Mateusz Marszalek Date: Sun, 6 Oct 2024 04:04:31 +0200 Subject: [PATCH 08/15] Refactored tests --- test/func_blocks/fu/test_fpu_rounding.py | 697 +++++++++++++++-------- 1 file changed, 444 insertions(+), 253 deletions(-) diff --git a/test/func_blocks/fu/test_fpu_rounding.py b/test/func_blocks/fu/test_fpu_rounding.py index 4f10007e0..e7045bd53 100644 --- a/test/func_blocks/fu/test_fpu_rounding.py +++ b/test/func_blocks/fu/test_fpu_rounding.py @@ -70,157 +70,180 @@ def test_special_cases(self, params: FPUParams, help_values: HelpValues): fpurt = TestFPURounding.FPURoundingModule(params) def other_cases_test(request_adapter: TestbenchIO, is_input_not_rounded: bool): - input_values_dict = {} - input_values_dict["sign"] = 0 - input_values_dict["sig"] = help_values.not_max_norm_even_sig - input_values_dict["exp"] = help_values.not_max_norm_exp - input_values_dict["round_bit"] = 0 - input_values_dict["sticky_bit"] = 0 - input_values_dict["rounding_mode"] = RoundingModes.ROUND_NEAREST_AWAY - input_values_dict["invalid_operation"] = 0 - input_values_dict["division_by_zero"] = 0 - input_values_dict["input_inf"] = 0 - - # No errors - resp = yield from request_adapter.call(input_values_dict) - - assert resp["sign"] == 0 - assert resp["exp"] == input_values_dict["exp"] - assert resp["sig"] == input_values_dict["sig"] - assert resp["errors"] == 0 - - # inexact - input_values_dict["sticky_bit"] = 1 - - resp = yield from request_adapter.call(input_values_dict) - - assert resp["sign"] == 0 - assert resp["exp"] == input_values_dict["exp"] - assert resp["sig"] == input_values_dict["sig"] - assert resp["errors"] == 16 - - # underflow rounding - input_values_dict["exp"] = 0 - input_values_dict["sig"] = ( - help_values.sub_norm_sig if is_input_not_rounded else help_values.sub_norm_sig + 1 - ) - input_values_dict["round_bit"] = 1 - - resp = yield from request_adapter.call(input_values_dict) - - assert resp["sign"] == 0 - assert resp["exp"] == 0 - assert resp["sig"] == help_values.sub_norm_sig + 1 - assert resp["errors"] == 24 - - # underflow no rounding - - input_values_dict["round_bit"] = 0 - input_values_dict["sig"] = 0 - - resp = yield from request_adapter.call(input_values_dict) - - assert resp["sign"] == 0 - assert resp["exp"] == 0 - assert resp["sig"] == 0 - assert resp["errors"] == 24 - - # invalid operation - - input_values_dict["exp"] = help_values.max_exp - input_values_dict["sig"] = help_values.qnan - input_values_dict["invalid_operation"] = 1 - input_values_dict["division_by_zero"] = 0 - - resp = yield from request_adapter.call(input_values_dict) - - assert resp["sign"] == input_values_dict["sign"] - assert resp["exp"] == input_values_dict["exp"] - assert resp["sig"] == input_values_dict["sig"] - assert resp["errors"] == 1 - - # division by zero - - input_values_dict["exp"] = help_values.max_exp - input_values_dict["sig"] = 0 - input_values_dict["invalid_operation"] = 0 - input_values_dict["division_by_zero"] = 1 - - resp = yield from request_adapter.call(input_values_dict) - assert resp["sign"] == input_values_dict["sign"] - assert resp["exp"] == input_values_dict["exp"] - assert resp["sig"] == input_values_dict["sig"] - assert resp["errors"] == 2 - - # overflow but no guard and sticky bits - - input_values_dict["round_bit"] = 0 - input_values_dict["sticky_bit"] = 0 - input_values_dict["invalid_operation"] = 0 - input_values_dict["division_by_zero"] = 0 - - resp = yield from request_adapter.call(input_values_dict) - - assert resp["sign"] == 0 - assert resp["exp"] == input_values_dict["exp"] - assert resp["sig"] == input_values_dict["sig"] - assert resp["errors"] == 20 - - # tininess but no underflow - - input_values_dict["exp"] = 0 - input_values_dict["sig"] = help_values.sub_norm_sig - - resp = yield from request_adapter.call(input_values_dict) - - assert resp["sign"] == 0 - assert resp["exp"] == input_values_dict["exp"] - assert resp["sig"] == input_values_dict["sig"] - assert resp["errors"] == 0 - - # one of inputs was qnan - - input_values_dict["exp"] = help_values.max_exp - input_values_dict["sig"] = help_values.qnan - input_values_dict["sticky_bit"] = 1 - input_values_dict["input_inf"] = 0 - - resp = yield from request_adapter.call(input_values_dict) - - assert resp["sign"] == 0 - assert resp["exp"] == input_values_dict["exp"] - assert resp["sig"] == input_values_dict["sig"] - assert resp["errors"] == 0 - - # one of inputs was inf - - input_values_dict["sign"] = 1 - input_values_dict["exp"] = help_values.max_exp - input_values_dict["sig"] = 0 - input_values_dict["input_inf"] = 1 - - resp = yield from request_adapter.call(input_values_dict) - - assert resp["sign"] == 1 - assert resp["exp"] == input_values_dict["exp"] - assert resp["sig"] == input_values_dict["sig"] - assert resp["errors"] == 0 - - # subnormal number become normalized after rounding - - if is_input_not_rounded: - input_values_dict["exp"] = 0 - input_values_dict["sig"] = help_values.max_sub_norm_sig - input_values_dict["sticky_bit"] = 1 - input_values_dict["round_bit"] = 1 - input_values_dict["rounding_mode"] = RoundingModes.ROUND_NEAREST_AWAY - input_values_dict["input_inf"] = 0 - - resp = yield from request_adapter.call(input_values_dict) - assert resp["sign"] == 1 - assert resp["exp"] == 1 - assert resp["sig"] == input_values_dict["sig"] + 1 - assert resp["errors"] == 16 + test_cases = [ + # No errors + { + "sign": 0, + "sig": help_values.not_max_norm_even_sig, + "exp": help_values.not_max_norm_exp, + "round_bit": 0, + "sticky_bit": 0, + "rounding_mode": RoundingModes.ROUND_NEAREST_AWAY, + "invalid_operation": 0, + "division_by_zero": 0, + "input_inf": 0, + }, + # inexact + { + "sign": 0, + "sig": help_values.not_max_norm_even_sig, + "exp": help_values.not_max_norm_exp, + "round_bit": 0, + "sticky_bit": 1, + "rounding_mode": RoundingModes.ROUND_NEAREST_AWAY, + "invalid_operation": 0, + "division_by_zero": 0, + "input_inf": 0, + }, + # underflow rounding + { + "sign": 0, + "sig": help_values.sub_norm_sig if is_input_not_rounded else help_values.sub_norm_sig + 1, + "exp": 0, + "round_bit": 1, + "sticky_bit": 1, + "rounding_mode": RoundingModes.ROUND_NEAREST_AWAY, + "invalid_operation": 0, + "division_by_zero": 0, + "input_inf": 0, + }, + # underflow no rounding + { + "sign": 0, + "sig": 0, + "exp": 0, + "round_bit": 0, + "sticky_bit": 1, + "rounding_mode": RoundingModes.ROUND_NEAREST_AWAY, + "invalid_operation": 0, + "division_by_zero": 0, + "input_inf": 0, + }, + # invalid operation + { + "sign": 0, + "sig": help_values.qnan, + "exp": help_values.max_exp, + "round_bit": 0, + "sticky_bit": 1, + "rounding_mode": RoundingModes.ROUND_NEAREST_AWAY, + "invalid_operation": 1, + "division_by_zero": 0, + "input_inf": 0, + }, + # division by zero + { + "sign": 0, + "sig": 0, + "exp": help_values.max_exp, + "round_bit": 0, + "sticky_bit": 1, + "rounding_mode": RoundingModes.ROUND_NEAREST_AWAY, + "invalid_operation": 0, + "division_by_zero": 1, + "input_inf": 0, + }, + # overflow but no round and sticky bits + { + "sign": 0, + "sig": 0, + "exp": help_values.max_exp, + "round_bit": 0, + "sticky_bit": 0, + "rounding_mode": RoundingModes.ROUND_NEAREST_AWAY, + "invalid_operation": 0, + "division_by_zero": 0, + "input_inf": 0, + }, + # tininess but no underflow + { + "sign": 0, + "sig": help_values.sub_norm_sig, + "exp": 0, + "round_bit": 0, + "sticky_bit": 0, + "rounding_mode": RoundingModes.ROUND_NEAREST_AWAY, + "invalid_operation": 0, + "division_by_zero": 0, + "input_inf": 0, + }, + # one of inputs was qnan + { + "sign": 0, + "sig": help_values.qnan, + "exp": help_values.max_exp, + "round_bit": 1, + "sticky_bit": 0, + "rounding_mode": RoundingModes.ROUND_NEAREST_AWAY, + "invalid_operation": 0, + "division_by_zero": 0, + "input_inf": 0, + }, + # one of inputs was inf + { + "sign": 1, + "sig": 0, + "exp": help_values.max_exp, + "round_bit": 1, + "sticky_bit": 0, + "rounding_mode": RoundingModes.ROUND_NEAREST_AWAY, + "invalid_operation": 0, + "division_by_zero": 0, + "input_inf": 1, + }, + # subnormal number become normalized after rounding + { + "sign": 1, + "sig": help_values.max_sub_norm_sig, + "exp": 0, + "round_bit": 1, + "sticky_bit": 1, + "rounding_mode": RoundingModes.ROUND_NEAREST_AWAY, + "invalid_operation": 0, + "division_by_zero": 0, + "input_inf": 0, + }, + ] + + expected_results = [ + # No errors + {"sign": 0, "sig": help_values.not_max_norm_even_sig, "exp": help_values.not_max_norm_exp, "errors": 0}, + # inexact + { + "sign": 0, + "sig": help_values.not_max_norm_even_sig, + "exp": help_values.not_max_norm_exp, + "errors": 16, + }, + # underflow rounding + {"sign": 0, "sig": help_values.sub_norm_sig + 1, "exp": 0, "errors": 24}, + # underflow no rounding + {"sign": 0, "sig": 0, "exp": 0, "errors": 24}, + # invalid operation + {"sign": 0, "sig": help_values.qnan, "exp": help_values.max_exp, "errors": 1}, + # division by zero + {"sign": 0, "sig": 0, "exp": help_values.max_exp, "errors": 2}, + # overflow but no round and sticky bits + {"sign": 0, "sig": 0, "exp": help_values.max_exp, "errors": 20}, + # tininess but no underflow + {"sign": 0, "sig": help_values.sub_norm_sig, "exp": 0, "errors": 0}, + # one of inputs was qnan + {"sign": 0, "sig": help_values.qnan, "exp": help_values.max_exp, "errors": 0}, + # one of inputs was inf + {"sign": 1, "sig": 0, "exp": help_values.max_exp, "errors": 0}, + # subnormal number become normalized after rounding + {"sign": 1, "sig": help_values.max_sub_norm_sig + 1, "exp": 1, "errors": 16}, + ] + + num_of_test_cases = len(test_cases) if is_input_not_rounded else len(test_cases) - 1 + + for i in range(num_of_test_cases): + + resp = yield from request_adapter.call(test_cases[i]) + assert resp["sign"] == expected_results[i]["sign"] + assert resp["exp"] == expected_results[i]["exp"] + assert resp["sig"] == expected_results[i]["sig"] + assert resp["errors"] == expected_results[i]["errors"] def test_process(): yield from other_cases_test(fpurt.rounding_request_adapter, True) @@ -289,7 +312,7 @@ def test_rounding( help_values: HelpValues, rm: RoundingModes, inc_arr: list, - plus_oveflow_sig: int, + plus_overflow_sig: int, plus_overflow_exp: int, minus_overflow_sig: int, minus_overflow_exp: int, @@ -300,107 +323,275 @@ def one_rounding_mode_test( request_adapter: TestbenchIO, is_input_not_rounded: bool, ): - input_values_dict = {} - input_values_dict["sign"] = 0 - input_values_dict["sig"] = ( - help_values.max_sig - if is_input_not_rounded and rm != RoundingModes.ROUND_DOWN and rm != RoundingModes.ROUND_ZERO - else 0 - ) - input_values_dict["exp"] = ( - help_values.max_norm_exp - if is_input_not_rounded and rm != RoundingModes.ROUND_DOWN and rm != RoundingModes.ROUND_ZERO - else help_values.max_exp - ) - input_values_dict["round_bit"] = 1 - input_values_dict["sticky_bit"] = 1 - input_values_dict["rounding_mode"] = rm - input_values_dict["invalid_operation"] = 0 - input_values_dict["division_by_zero"] = 0 - input_values_dict["input_inf"] = 0 - - # overflow detection - resp = yield from request_adapter.call(input_values_dict) - assert resp["sign"] == 0 - assert resp["exp"] == plus_overflow_exp - assert resp["sig"] == plus_oveflow_sig - assert resp["errors"] == 20 - - input_values_dict["sign"] = 1 - input_values_dict["sig"] = ( - help_values.max_sig - if is_input_not_rounded and rm != RoundingModes.ROUND_UP and rm != RoundingModes.ROUND_ZERO - else 0 - ) - input_values_dict["exp"] = ( - help_values.max_norm_exp - if is_input_not_rounded and rm != RoundingModes.ROUND_UP and rm != RoundingModes.ROUND_ZERO - else help_values.max_exp - ) - - resp = yield from request_adapter.call(input_values_dict) - assert resp["sign"] == 1 - assert resp["exp"] == minus_overflow_exp - assert resp["sig"] == minus_overflow_sig - assert resp["errors"] == 20 - - # no overflow - input_values_dict["exp"] = help_values.not_max_norm_exp - - for i in range(4): - input_values_dict["sign"] = 0 - input_values_dict["round_bit"] = i & 1 - input_values_dict["sticky_bit"] = (i >> 1) & 1 - input_values_dict["sig"] = ( - help_values.not_max_norm_sig if is_input_not_rounded else help_values.not_max_norm_sig + inc_arr[i] - ) - - resp = yield from request_adapter.call(input_values_dict) - assert resp["sign"] == 0 - assert resp["exp"] == help_values.not_max_norm_exp - assert resp["sig"] == help_values.not_max_norm_sig + inc_arr[i] - if i: - assert resp["errors"] == 16 - else: - assert resp["errors"] == 0 - - input_values_dict["sign"] = 1 - input_values_dict["sig"] = ( - help_values.not_max_norm_sig - if is_input_not_rounded - else help_values.not_max_norm_sig + inc_arr[4 + i] - ) - - resp = yield from request_adapter.call(input_values_dict) - assert resp["sign"] == 1 - assert resp["exp"] == help_values.not_max_norm_exp - assert resp["sig"] == help_values.not_max_norm_sig + inc_arr[4 + i] - if i: - assert resp["errors"] == 16 - else: - assert resp["errors"] == 0 - - if rm == RoundingModes.ROUND_NEAREST_EVEN: - input_values_dict["sticky_bit"] = 0 - input_values_dict["round_bit"] = 1 - - # tie, no increment - input_values_dict["sign"] = 1 - input_values_dict["sig"] = help_values.not_max_norm_even_sig - - resp = yield from request_adapter.call(input_values_dict) - assert resp["sign"] == 1 - assert resp["exp"] == help_values.not_max_norm_exp - assert resp["sig"] == help_values.not_max_norm_even_sig - assert resp["errors"] == 16 - - input_values_dict["sign"] = 0 - - resp = yield from request_adapter.call(input_values_dict) - assert resp["sign"] == 0 - assert resp["exp"] == help_values.not_max_norm_exp - assert resp["sig"] == help_values.not_max_norm_even_sig - assert resp["errors"] == 16 + test_cases = [ + # overflow detection + { + "sign": 0, + "sig": ( + help_values.max_sig + if is_input_not_rounded and rm != RoundingModes.ROUND_DOWN and rm != RoundingModes.ROUND_ZERO + else 0 + ), + "exp": ( + help_values.max_norm_exp + if is_input_not_rounded and rm != RoundingModes.ROUND_DOWN and rm != RoundingModes.ROUND_ZERO + else help_values.max_exp + ), + "round_bit": 1, + "sticky_bit": 1, + "rounding_mode": rm, + "invalid_operation": 0, + "division_by_zero": 0, + "input_inf": 0, + }, + { + "sign": 1, + "sig": ( + help_values.max_sig + if is_input_not_rounded and rm != RoundingModes.ROUND_UP and rm != RoundingModes.ROUND_ZERO + else 0 + ), + "exp": ( + help_values.max_norm_exp + if is_input_not_rounded and rm != RoundingModes.ROUND_UP and rm != RoundingModes.ROUND_ZERO + else help_values.max_exp + ), + "round_bit": 1, + "sticky_bit": 1, + "rounding_mode": rm, + "invalid_operation": 0, + "division_by_zero": 0, + "input_inf": 0, + }, + # no overflow 00 + { + "sign": 0, + "sig": ( + help_values.not_max_norm_sig + if is_input_not_rounded + else help_values.not_max_norm_sig + inc_arr[0] + ), + "exp": help_values.not_max_norm_exp, + "round_bit": 0, + "sticky_bit": 0, + "rounding_mode": rm, + "invalid_operation": 0, + "division_by_zero": 0, + "input_inf": 0, + }, + { + "sign": 1, + "sig": ( + help_values.not_max_norm_sig + if is_input_not_rounded + else help_values.not_max_norm_sig + inc_arr[4] + ), + "exp": help_values.not_max_norm_exp, + "round_bit": 0, + "sticky_bit": 0, + "rounding_mode": rm, + "invalid_operation": 0, + "division_by_zero": 0, + "input_inf": 0, + }, + # no overflow 10 + { + "sign": 0, + "sig": ( + help_values.not_max_norm_sig + if is_input_not_rounded + else help_values.not_max_norm_sig + inc_arr[1] + ), + "exp": help_values.not_max_norm_exp, + "round_bit": 1, + "sticky_bit": 0, + "rounding_mode": rm, + "invalid_operation": 0, + "division_by_zero": 0, + "input_inf": 0, + }, + { + "sign": 1, + "sig": ( + help_values.not_max_norm_sig + if is_input_not_rounded + else help_values.not_max_norm_sig + inc_arr[5] + ), + "exp": help_values.not_max_norm_exp, + "round_bit": 1, + "sticky_bit": 0, + "rounding_mode": rm, + "invalid_operation": 0, + "division_by_zero": 0, + "input_inf": 0, + }, + # no overflow 01 + { + "sign": 0, + "sig": ( + help_values.not_max_norm_sig + if is_input_not_rounded + else help_values.not_max_norm_sig + inc_arr[2] + ), + "exp": help_values.not_max_norm_exp, + "round_bit": 0, + "sticky_bit": 1, + "rounding_mode": rm, + "invalid_operation": 0, + "division_by_zero": 0, + "input_inf": 0, + }, + { + "sign": 1, + "sig": ( + help_values.not_max_norm_sig + if is_input_not_rounded + else help_values.not_max_norm_sig + inc_arr[6] + ), + "exp": help_values.not_max_norm_exp, + "round_bit": 0, + "sticky_bit": 1, + "rounding_mode": rm, + "invalid_operation": 0, + "division_by_zero": 0, + "input_inf": 0, + }, + # no overflow 11 + { + "sign": 0, + "sig": ( + help_values.not_max_norm_sig + if is_input_not_rounded + else help_values.not_max_norm_sig + inc_arr[3] + ), + "exp": help_values.not_max_norm_exp, + "round_bit": 1, + "sticky_bit": 1, + "rounding_mode": rm, + "invalid_operation": 0, + "division_by_zero": 0, + "input_inf": 0, + }, + { + "sign": 1, + "sig": ( + help_values.not_max_norm_sig + if is_input_not_rounded + else help_values.not_max_norm_sig + inc_arr[7] + ), + "exp": help_values.not_max_norm_exp, + "round_bit": 1, + "sticky_bit": 1, + "rounding_mode": rm, + "invalid_operation": 0, + "division_by_zero": 0, + "input_inf": 0, + }, + # Round to nearest tie to even + { + "sign": 1, + "sig": help_values.not_max_norm_even_sig, + "exp": help_values.not_max_norm_exp, + "round_bit": 1, + "sticky_bit": 0, + "rounding_mode": rm, + "invalid_operation": 0, + "division_by_zero": 0, + "input_inf": 0, + }, + { + "sign": 0, + "sig": help_values.not_max_norm_even_sig, + "exp": help_values.not_max_norm_exp, + "round_bit": 1, + "sticky_bit": 0, + "rounding_mode": rm, + "invalid_operation": 0, + "division_by_zero": 0, + "input_inf": 0, + }, + ] + expected_results = [ + # overflow detection + {"sign": 0, "sig": plus_overflow_sig, "exp": plus_overflow_exp, "errors": 20}, + {"sign": 1, "sig": minus_overflow_sig, "exp": minus_overflow_exp, "errors": 20}, + # no overflow 00 + { + "sign": 0, + "sig": help_values.not_max_norm_sig + inc_arr[0], + "exp": help_values.not_max_norm_exp, + "errors": 0, + }, + { + "sign": 1, + "sig": help_values.not_max_norm_sig + inc_arr[4], + "exp": help_values.not_max_norm_exp, + "errors": 0, + }, + # no overflow 01 + { + "sign": 0, + "sig": help_values.not_max_norm_sig + inc_arr[1], + "exp": help_values.not_max_norm_exp, + "errors": 16, + }, + { + "sign": 1, + "sig": help_values.not_max_norm_sig + inc_arr[5], + "exp": help_values.not_max_norm_exp, + "errors": 16, + }, + # no overflow 10 + { + "sign": 0, + "sig": help_values.not_max_norm_sig + inc_arr[2], + "exp": help_values.not_max_norm_exp, + "errors": 16, + }, + { + "sign": 1, + "sig": help_values.not_max_norm_sig + inc_arr[6], + "exp": help_values.not_max_norm_exp, + "errors": 16, + }, + # no overflow 11 + { + "sign": 0, + "sig": help_values.not_max_norm_sig + inc_arr[3], + "exp": help_values.not_max_norm_exp, + "errors": 16, + }, + { + "sign": 1, + "sig": help_values.not_max_norm_sig + inc_arr[7], + "exp": help_values.not_max_norm_exp, + "errors": 16, + }, + # Round to nearest tie to even + { + "sign": 1, + "sig": help_values.not_max_norm_even_sig, + "exp": help_values.not_max_norm_exp, + "errors": 16, + }, + { + "sign": 0, + "sig": help_values.not_max_norm_even_sig, + "exp": help_values.not_max_norm_exp, + "errors": 16, + }, + ] + + num_of_test_cases = len(test_cases) if rm == RoundingModes.ROUND_NEAREST_EVEN else len(test_cases) - 2 + + for i in range(num_of_test_cases): + + resp = yield from request_adapter.call(test_cases[i]) + print(i) + assert resp["sign"] == expected_results[i]["sign"] + assert resp["exp"] == expected_results[i]["exp"] + assert resp["sig"] == expected_results[i]["sig"] + assert resp["errors"] == expected_results[i]["errors"] def test_process(): yield from one_rounding_mode_test(fpurt.rounding_request_adapter, True) From d6aa8fe44ac7344b017fe9fdf6e494308160fe53 Mon Sep 17 00:00:00 2001 From: Mateusz Marszalek Date: Mon, 14 Oct 2024 23:47:56 +0200 Subject: [PATCH 09/15] Split rounding module into rounding part and error checking part --- coreblocks/func_blocks/fu/fpu/fpu_common.py | 17 - .../func_blocks/fu/fpu/fpu_error_module.py | 167 ++++++++ .../func_blocks/fu/fpu/fpu_rounding_module.py | 151 +------ test/func_blocks/fu/test_fpu_error.py | 305 +++++++++++++ test/func_blocks/fu/test_fpu_rounding.py | 399 ++---------------- 5 files changed, 532 insertions(+), 507 deletions(-) create mode 100644 coreblocks/func_blocks/fu/fpu/fpu_error_module.py create mode 100644 test/func_blocks/fu/test_fpu_error.py diff --git a/coreblocks/func_blocks/fu/fpu/fpu_common.py b/coreblocks/func_blocks/fu/fpu/fpu_common.py index 580207a9f..2f3655faf 100644 --- a/coreblocks/func_blocks/fu/fpu/fpu_common.py +++ b/coreblocks/func_blocks/fu/fpu/fpu_common.py @@ -28,20 +28,3 @@ def __init__( ): self.sig_width = sig_width self.exp_width = exp_width - - -class FPURoundingParams: - """FPU rounding module signature - - Parameters - ---------- - fpu_params: FPUParams - FPU parameters - is_rounded:bool - This flags indicates that the input number was already rounded. - This creates simpler version of rounding module that only performs error checking and returns correct number. - """ - - def __init__(self, fpu_params: FPUParams, *, is_rounded: bool = False): - self.fpu_params = fpu_params - self.is_rounded = is_rounded diff --git a/coreblocks/func_blocks/fu/fpu/fpu_error_module.py b/coreblocks/func_blocks/fu/fpu/fpu_error_module.py new file mode 100644 index 000000000..09264d7b5 --- /dev/null +++ b/coreblocks/func_blocks/fu/fpu/fpu_error_module.py @@ -0,0 +1,167 @@ +from amaranth import * +from transactron import TModule, Method, def_method +from coreblocks.func_blocks.fu.fpu.fpu_common import ( + RoundingModes, + FPUParams, +) + + +class FPUErrorMethodLayout: + """FPU error checking module layouts for methods + Parameters + ---------- + fpu_params: FPUParams + FPU parameters + """ + + def __init__(self, *, fpu_params: FPUParams): + self.error_in_layout = [ + ("sign", 1), + ("sig", fpu_params.sig_width), + ("exp", fpu_params.exp_width), + ("rounding_mode", RoundingModes), + ("inexact", 1), + ("invalid_operation", 1), + ("division_by_zero", 1), + ("input_inf", 1), + ] + self.error_out_layout = [ + ("sign", 1), + ("sig", fpu_params.sig_width), + ("exp", fpu_params.exp_width), + ("errors", 5), + ] + + +class FPUErrorModule(Elaboratable): + """FPU error checking module + + Parameters + ---------- + fpu_params: FPUParams + FPU rounding module parameters + + Attributes + ---------- + error_checking_request: Method + Transactional method for initiating error checking of a floating point number. + Takes 'error_in_layout' as argument + Returns final number and errors as 'error_out_layout' + """ + + def __init__(self, *, fpu_params: FPUParams): + + self.fpu_errors_params = fpu_params + self.method_layouts = FPUErrorMethodLayout(fpu_params=self.fpu_errors_params) + self.error_checking_request = Method( + i=self.method_layouts.error_in_layout, + o=self.method_layouts.error_out_layout, + ) + + def elaborate(self, platform): + m = TModule() + + max_exp = C( + 2 ** (self.fpu_errors_params.exp_width) - 1, + unsigned(self.fpu_errors_params.exp_width), + ) + max_normal_exp = C( + 2 ** (self.fpu_errors_params.exp_width) - 2, + unsigned(self.fpu_errors_params.exp_width), + ) + max_sig = C( + 2 ** (self.fpu_errors_params.sig_width) - 1, + unsigned(self.fpu_errors_params.sig_width), + ) + + overflow = Signal() + underflow = Signal() + inexact = Signal() + tininess = Signal() + + final_exp = Signal(self.fpu_errors_params.exp_width) + final_sig = Signal(self.fpu_errors_params.sig_width) + final_sign = Signal() + final_errors = Signal(5) + + @def_method(m, self.error_checking_request) + def _(arg): + is_nan = arg.invalid_operation | ((arg.exp == max_exp) & (arg.sig.any())) + is_inf = arg.division_by_zero | arg.input_inf + input_not_special = ~(is_nan) & ~(is_inf) + m.d.av_comb += overflow.eq(input_not_special & (arg.exp == max_exp)) + m.d.av_comb += tininess.eq((arg.exp == 0) & (~arg.sig[-1])) + m.d.av_comb += inexact.eq(overflow | (input_not_special & arg.inexact)) + m.d.av_comb += underflow.eq(tininess & inexact) + + with m.If(is_nan | is_inf): + + m.d.av_comb += final_exp.eq(arg.exp) + m.d.av_comb += final_sig.eq(arg.sig) + m.d.av_comb += final_sign.eq(arg.sign) + + with m.Elif(overflow): + + with m.Switch(arg.rounding_mode): + with m.Case(RoundingModes.ROUND_NEAREST_AWAY, RoundingModes.ROUND_NEAREST_EVEN): + + m.d.av_comb += final_exp.eq(max_exp) + m.d.av_comb += final_sig.eq(0) + m.d.av_comb += final_sign.eq(arg.sign) + + with m.Case(RoundingModes.ROUND_ZERO): + + m.d.av_comb += final_exp.eq(max_normal_exp) + m.d.av_comb += final_sig.eq(max_sig) + m.d.av_comb += final_sign.eq(arg.sign) + + with m.Case(RoundingModes.ROUND_DOWN): + + with m.If(arg.sign): + + m.d.av_comb += final_exp.eq(max_exp) + m.d.av_comb += final_sig.eq(0) + m.d.av_comb += final_sign.eq(arg.sign) + + with m.Else(): + + m.d.av_comb += final_exp.eq(max_normal_exp) + m.d.av_comb += final_sig.eq(max_sig) + m.d.av_comb += final_sign.eq(arg.sign) + + with m.Case(RoundingModes.ROUND_UP): + + with m.If(arg.sign): + + m.d.av_comb += final_exp.eq(max_normal_exp) + m.d.av_comb += final_sig.eq(max_sig) + m.d.av_comb += final_sign.eq(arg.sign) + + with m.Else(): + + m.d.av_comb += final_exp.eq(max_exp) + m.d.av_comb += final_sig.eq(0) + m.d.av_comb += final_sign.eq(arg.sign) + + with m.Else(): + with m.If((arg.exp == 0) & (arg.sig[-1] == 1)): + m.d.av_comb += final_exp.eq(1) + with m.Else(): + m.d.av_comb += final_exp.eq(arg.exp) + m.d.av_comb += final_sig.eq(arg.sig) + m.d.av_comb += final_sign.eq(arg.sign) + + m.d.av_comb += final_errors[0].eq(arg.invalid_operation) + m.d.av_comb += final_errors[1].eq(arg.division_by_zero) + m.d.av_comb += final_errors[2].eq(overflow) + m.d.av_comb += final_errors[3].eq(underflow) + m.d.av_comb += final_errors[4].eq(inexact) + + return { + "exp": final_exp, + "sig": final_sig, + "sign": final_sign, + "errors": final_errors, + } + + return m diff --git a/coreblocks/func_blocks/fu/fpu/fpu_rounding_module.py b/coreblocks/func_blocks/fu/fpu/fpu_rounding_module.py index 755bafa6c..3c4dfa310 100644 --- a/coreblocks/func_blocks/fu/fpu/fpu_rounding_module.py +++ b/coreblocks/func_blocks/fu/fpu/fpu_rounding_module.py @@ -3,7 +3,6 @@ from coreblocks.func_blocks.fu.fpu.fpu_common import ( RoundingModes, FPUParams, - FPURoundingParams, ) @@ -24,15 +23,11 @@ def __init__(self, *, fpu_params: FPUParams): ("round_bit", 1), ("sticky_bit", 1), ("rounding_mode", RoundingModes), - ("invalid_operation", 1), - ("division_by_zero", 1), - ("input_inf", 1), ] self.rounding_out_layout = [ - ("sign", 1), ("sig", fpu_params.sig_width), ("exp", fpu_params.exp_width), - ("errors", 5), + ("inexact", 1), ] @@ -41,8 +36,8 @@ class FPUrounding(Elaboratable): Parameters ---------- - fpu_rounding_params: FPURoundingParams - FPU rounding module parameters + fpu_params: FPUParams + FPU parameters Attributes ---------- @@ -52,10 +47,10 @@ class FPUrounding(Elaboratable): Returns rounded number and errors as 'rounding_out_layout' """ - def __init__(self, *, fpu_rounding_params: FPURoundingParams): + def __init__(self, *, fpu_params: FPUParams): - self.fpu_rounding_params = fpu_rounding_params - self.method_layouts = FPURoudningMethodLayout(fpu_params=self.fpu_rounding_params.fpu_params) + self.fpu_rounding_params = fpu_params + self.method_layouts = FPURoudningMethodLayout(fpu_params=self.fpu_rounding_params) self.rounding_request = Method( i=self.method_layouts.rounding_in_layout, o=self.method_layouts.rounding_out_layout, @@ -64,40 +59,20 @@ def __init__(self, *, fpu_rounding_params: FPURoundingParams): def elaborate(self, platform): m = TModule() - max_exp = C( - 2 ** (self.fpu_rounding_params.fpu_params.exp_width) - 1, - unsigned(self.fpu_rounding_params.fpu_params.exp_width), - ) - max_normal_exp = C( - 2 ** (self.fpu_rounding_params.fpu_params.exp_width) - 2, - unsigned(self.fpu_rounding_params.fpu_params.exp_width), - ) - max_sig = C( - 2 ** (self.fpu_rounding_params.fpu_params.sig_width) - 1, - unsigned(self.fpu_rounding_params.fpu_params.sig_width), - ) add_one = Signal() inc_rtnte = Signal() inc_rtnta = Signal() inc_rtpi = Signal() inc_rtmi = Signal() - rounded_sig = Signal(self.fpu_rounding_params.fpu_params.sig_width + 1) - normalised_sig = Signal(self.fpu_rounding_params.fpu_params.sig_width) - rounded_exp = Signal(self.fpu_rounding_params.fpu_params.exp_width + 1) + rounded_sig = Signal(self.fpu_rounding_params.sig_width + 1) + normalised_sig = Signal(self.fpu_rounding_params.sig_width) + rounded_exp = Signal(self.fpu_rounding_params.exp_width) final_round_bit = Signal() final_sticky_bit = Signal() - overflow = Signal() - underflow = Signal() inexact = Signal() - tininess = Signal() - - final_exp = Signal(self.fpu_rounding_params.fpu_params.exp_width) - final_sig = Signal(self.fpu_rounding_params.fpu_params.sig_width) - final_sign = Signal() - final_errors = Signal(5) @def_method(m, self.rounding_request) def _(arg): @@ -116,107 +91,27 @@ def _(arg): m.d.av_comb += add_one.eq(inc_rtmi | inc_rtnta | inc_rtnte | inc_rtpi) - if self.fpu_rounding_params.is_rounded: + m.d.av_comb += rounded_sig.eq(arg.sig + add_one) + + with m.If(rounded_sig[-1]): - m.d.av_comb += normalised_sig.eq(arg.sig) + m.d.av_comb += normalised_sig.eq(rounded_sig >> 1) + m.d.av_comb += final_round_bit.eq(rounded_sig[0]) + m.d.av_comb += final_sticky_bit.eq(arg.round_bit | arg.sticky_bit) + m.d.av_comb += rounded_exp.eq(arg.exp + 1) + + with m.Else(): + m.d.av_comb += normalised_sig.eq(rounded_sig) m.d.av_comb += final_round_bit.eq(arg.round_bit) m.d.av_comb += final_sticky_bit.eq(arg.sticky_bit) m.d.av_comb += rounded_exp.eq(arg.exp) - else: - - m.d.av_comb += rounded_sig.eq(arg.sig + add_one) - - with m.If(rounded_sig[-1]): - - m.d.av_comb += normalised_sig.eq(rounded_sig >> 1) - m.d.av_comb += final_round_bit.eq(rounded_sig[0]) - m.d.av_comb += final_sticky_bit.eq(arg.round_bit | arg.sticky_bit) - m.d.av_comb += rounded_exp.eq(arg.exp + 1) - - with m.Else(): - m.d.av_comb += normalised_sig.eq(rounded_sig) - m.d.av_comb += final_round_bit.eq(arg.round_bit) - m.d.av_comb += final_sticky_bit.eq(arg.sticky_bit) - m.d.av_comb += rounded_exp.eq(arg.exp) - - rounded_inexact = final_round_bit | final_sticky_bit - is_nan = arg.invalid_operation | ((arg.exp == max_exp) & (arg.sig.any())) - is_inf = arg.division_by_zero | arg.input_inf - input_not_special = ~(is_nan) & ~(is_inf) - m.d.av_comb += overflow.eq(input_not_special & (rounded_exp >= max_exp)) - m.d.av_comb += tininess.eq((rounded_exp == 0) & (~normalised_sig[-1])) - m.d.av_comb += inexact.eq(overflow | (input_not_special & rounded_inexact)) - m.d.av_comb += underflow.eq(tininess & inexact) - - with m.If(is_nan | is_inf): - - m.d.av_comb += final_exp.eq(arg.exp) - m.d.av_comb += final_sig.eq(arg.sig) - m.d.av_comb += final_sign.eq(arg.sign) - - with m.Elif(overflow): - - with m.Switch(arg.rounding_mode): - with m.Case(RoundingModes.ROUND_NEAREST_AWAY, RoundingModes.ROUND_NEAREST_EVEN): - - m.d.av_comb += final_exp.eq(max_exp) - m.d.av_comb += final_sig.eq(0) - m.d.av_comb += final_sign.eq(arg.sign) - - with m.Case(RoundingModes.ROUND_ZERO): - - m.d.av_comb += final_exp.eq(max_normal_exp) - m.d.av_comb += final_sig.eq(max_sig) - m.d.av_comb += final_sign.eq(arg.sign) - - with m.Case(RoundingModes.ROUND_DOWN): - - with m.If(arg.sign): - - m.d.av_comb += final_exp.eq(max_exp) - m.d.av_comb += final_sig.eq(0) - m.d.av_comb += final_sign.eq(arg.sign) - - with m.Else(): - - m.d.av_comb += final_exp.eq(max_normal_exp) - m.d.av_comb += final_sig.eq(max_sig) - m.d.av_comb += final_sign.eq(arg.sign) - - with m.Case(RoundingModes.ROUND_UP): - - with m.If(arg.sign): - - m.d.av_comb += final_exp.eq(max_normal_exp) - m.d.av_comb += final_sig.eq(max_sig) - m.d.av_comb += final_sign.eq(arg.sign) - - with m.Else(): - - m.d.av_comb += final_exp.eq(max_exp) - m.d.av_comb += final_sig.eq(0) - m.d.av_comb += final_sign.eq(arg.sign) - - with m.Else(): - with m.If((rounded_exp == 0) & (normalised_sig[-1] == 1)): - m.d.av_comb += final_exp.eq(1) - with m.Else(): - m.d.av_comb += final_exp.eq(rounded_exp) - m.d.av_comb += final_sig.eq(normalised_sig) - m.d.av_comb += final_sign.eq(arg.sign) - - m.d.av_comb += final_errors[0].eq(arg.invalid_operation) - m.d.av_comb += final_errors[1].eq(arg.division_by_zero) - m.d.av_comb += final_errors[2].eq(overflow) - m.d.av_comb += final_errors[3].eq(underflow) - m.d.av_comb += final_errors[4].eq(inexact) + m.d.av_comb += inexact.eq(final_round_bit | final_sticky_bit) return { - "exp": final_exp, - "sig": final_sig, - "sign": final_sign, - "errors": final_errors, + "exp": rounded_exp, + "sig": normalised_sig, + "inexact": inexact, } return m diff --git a/test/func_blocks/fu/test_fpu_error.py b/test/func_blocks/fu/test_fpu_error.py new file mode 100644 index 000000000..d17924037 --- /dev/null +++ b/test/func_blocks/fu/test_fpu_error.py @@ -0,0 +1,305 @@ +from coreblocks.func_blocks.fu.fpu.fpu_error_module import * +from coreblocks.func_blocks.fu.fpu.fpu_common import ( + RoundingModes, + FPUParams, +) +from transactron import TModule +from transactron.lib import AdapterTrans +from parameterized import parameterized +from transactron.testing import * +from amaranth import * + + +class TestFPUError(TestCaseWithSimulator): + class FPUErrorModule(Elaboratable): + def __init__(self, params: FPUParams): + self.params = params + + def elaborate(self, platform): + m = TModule() + m.submodules.fpue = fpue = self.fpu_error_module = FPUErrorModule(fpu_params=self.params) + m.submodules.error_checking = self.error_checking_request_adapter = TestbenchIO( + AdapterTrans(fpue.error_checking_request) + ) + return m + + class HelpValues: + def __init__(self, params: FPUParams): + self.params = params + self.max_exp = (2**self.params.exp_width) - 1 + self.max_norm_exp = (2**self.params.exp_width) - 2 + self.not_max_norm_exp = (2**self.params.exp_width) - 3 + self.max_sig = (2**params.sig_width) - 1 + self.not_max_norm_sig = 1 << (self.params.sig_width - 1) | 1 + self.not_max_norm_even_sig = 1 << (self.params.sig_width - 1) + self.sub_norm_sig = 3 + self.min_norm_sig = 1 << (self.params.sig_width - 1) + self.max_sub_norm_sig = (2 ** (self.params.sig_width - 1)) - 1 + self.qnan = 3 << (self.params.sig_width - 2) | 1 + + params = FPUParams(sig_width=24, exp_width=8) + help_values = HelpValues(params) + + @parameterized.expand([(params, help_values)]) + def test_special_cases(self, params: FPUParams, help_values: HelpValues): + fpue = TestFPUError.FPUErrorModule(params) + + def other_cases_test(): + test_cases = [ + # No errors + { + "sign": 0, + "sig": help_values.not_max_norm_even_sig, + "exp": help_values.not_max_norm_exp, + "inexact": 0, + "rounding_mode": RoundingModes.ROUND_NEAREST_AWAY, + "invalid_operation": 0, + "division_by_zero": 0, + "input_inf": 0, + }, + # inexact + { + "sign": 0, + "sig": help_values.not_max_norm_even_sig, + "exp": help_values.not_max_norm_exp, + "inexact": 1, + "rounding_mode": RoundingModes.ROUND_NEAREST_AWAY, + "invalid_operation": 0, + "division_by_zero": 0, + "input_inf": 0, + }, + # underflow + { + "sign": 0, + "sig": help_values.sub_norm_sig, + "exp": 0, + "inexact": 1, + "rounding_mode": RoundingModes.ROUND_NEAREST_AWAY, + "invalid_operation": 0, + "division_by_zero": 0, + "input_inf": 0, + }, + # invalid operation + { + "sign": 0, + "sig": help_values.qnan, + "exp": help_values.max_exp, + "inexact": 1, + "rounding_mode": RoundingModes.ROUND_NEAREST_AWAY, + "invalid_operation": 1, + "division_by_zero": 0, + "input_inf": 0, + }, + # division by zero + { + "sign": 0, + "sig": 0, + "exp": help_values.max_exp, + "inexact": 1, + "rounding_mode": RoundingModes.ROUND_NEAREST_AWAY, + "invalid_operation": 0, + "division_by_zero": 1, + "input_inf": 0, + }, + # overflow but no round and sticky bits + { + "sign": 0, + "sig": 0, + "exp": help_values.max_exp, + "inexact": 0, + "rounding_mode": RoundingModes.ROUND_NEAREST_AWAY, + "invalid_operation": 0, + "division_by_zero": 0, + "input_inf": 0, + }, + # tininess but no underflow + { + "sign": 0, + "sig": help_values.sub_norm_sig, + "exp": 0, + "inexact": 0, + "rounding_mode": RoundingModes.ROUND_NEAREST_AWAY, + "invalid_operation": 0, + "division_by_zero": 0, + "input_inf": 0, + }, + # one of inputs was qnan + { + "sign": 0, + "sig": help_values.qnan, + "exp": help_values.max_exp, + "inexact": 1, + "rounding_mode": RoundingModes.ROUND_NEAREST_AWAY, + "invalid_operation": 0, + "division_by_zero": 0, + "input_inf": 0, + }, + # one of inputs was inf + { + "sign": 1, + "sig": 0, + "exp": help_values.max_exp, + "inexact": 1, + "rounding_mode": RoundingModes.ROUND_NEAREST_AWAY, + "invalid_operation": 0, + "division_by_zero": 0, + "input_inf": 1, + }, + # subnormal number become normalized after rounding + { + "sign": 1, + "sig": help_values.min_norm_sig, + "exp": 0, + "inexact": 1, + "rounding_mode": RoundingModes.ROUND_NEAREST_AWAY, + "invalid_operation": 0, + "division_by_zero": 0, + "input_inf": 0, + }, + ] + + expected_results = [ + # No errors + {"sign": 0, "sig": help_values.not_max_norm_even_sig, "exp": help_values.not_max_norm_exp, "errors": 0}, + # inexact + { + "sign": 0, + "sig": help_values.not_max_norm_even_sig, + "exp": help_values.not_max_norm_exp, + "errors": 16, + }, + # underflow + {"sign": 0, "sig": help_values.sub_norm_sig, "exp": 0, "errors": 24}, + # invalid operation + {"sign": 0, "sig": help_values.qnan, "exp": help_values.max_exp, "errors": 1}, + # division by zero + {"sign": 0, "sig": 0, "exp": help_values.max_exp, "errors": 2}, + # overflow but no round and sticky bits + {"sign": 0, "sig": 0, "exp": help_values.max_exp, "errors": 20}, + # tininess but no underflow + {"sign": 0, "sig": help_values.sub_norm_sig, "exp": 0, "errors": 0}, + # one of inputs was qnan + {"sign": 0, "sig": help_values.qnan, "exp": help_values.max_exp, "errors": 0}, + # one of inputs was inf + {"sign": 1, "sig": 0, "exp": help_values.max_exp, "errors": 0}, + # subnormal number become normalized after rounding + {"sign": 1, "sig": help_values.min_norm_sig, "exp": 1, "errors": 16}, + ] + for i in range(len(test_cases)): + + resp = yield from fpue.error_checking_request_adapter.call(test_cases[i]) + print(i) + assert resp["sign"] == expected_results[i]["sign"] + assert resp["exp"] == expected_results[i]["exp"] + assert resp["sig"] == expected_results[i]["sig"] + assert resp["errors"] == expected_results[i]["errors"] + + def test_process(): + yield from other_cases_test() + + with self.run_simulation(fpue) as sim: + sim.add_sync_process(test_process) + + @parameterized.expand( + [ + ( + params, + help_values, + RoundingModes.ROUND_NEAREST_EVEN, + 0, + help_values.max_exp, + 0, + help_values.max_exp, + ), + ( + params, + help_values, + RoundingModes.ROUND_NEAREST_AWAY, + 0, + help_values.max_exp, + 0, + help_values.max_exp, + ), + ( + params, + help_values, + RoundingModes.ROUND_UP, + 0, + help_values.max_exp, + help_values.max_sig, + help_values.max_norm_exp, + ), + ( + params, + help_values, + RoundingModes.ROUND_DOWN, + help_values.max_sig, + help_values.max_norm_exp, + 0, + help_values.max_exp, + ), + ( + params, + help_values, + RoundingModes.ROUND_ZERO, + help_values.max_sig, + help_values.max_norm_exp, + help_values.max_sig, + help_values.max_norm_exp, + ), + ] + ) + def test_rounding( + self, + params: FPUParams, + help_values: HelpValues, + rm: RoundingModes, + plus_overflow_sig: int, + plus_overflow_exp: int, + minus_overflow_sig: int, + minus_overflow_exp: int, + ): + fpue = TestFPUError.FPUErrorModule(params) + + def one_rounding_mode_test(): + test_cases = [ + # overflow detection + { + "sign": 0, + "sig": 0, + "exp": help_values.max_exp, + "rounding_mode": rm, + "inexact": 0, + "invalid_operation": 0, + "division_by_zero": 0, + "input_inf": 0, + }, + { + "sign": 1, + "sig": 0, + "exp": help_values.max_exp, + "rounding_mode": rm, + "inexact": 0, + "invalid_operation": 0, + "division_by_zero": 0, + "input_inf": 0, + }, + ] + expected_results = [ + # overflow detection + {"sign": 0, "sig": plus_overflow_sig, "exp": plus_overflow_exp, "errors": 20}, + {"sign": 1, "sig": minus_overflow_sig, "exp": minus_overflow_exp, "errors": 20}, + ] + + for i in range(len(test_cases)): + resp = yield from fpue.error_checking_request_adapter.call(test_cases[i]) + assert resp["sign"] == expected_results[i]["sign"] + assert resp["exp"] == expected_results[i]["exp"] + assert resp["sig"] == expected_results[i]["sig"] + assert resp["errors"] == expected_results[i]["errors"] + + def test_process(): + yield from one_rounding_mode_test() + + with self.run_simulation(fpue) as sim: + sim.add_sync_process(test_process) diff --git a/test/func_blocks/fu/test_fpu_rounding.py b/test/func_blocks/fu/test_fpu_rounding.py index e7045bd53..60651321e 100644 --- a/test/func_blocks/fu/test_fpu_rounding.py +++ b/test/func_blocks/fu/test_fpu_rounding.py @@ -2,7 +2,6 @@ from coreblocks.func_blocks.fu.fpu.fpu_common import ( RoundingModes, FPUParams, - FPURoundingParams, ) from transactron import TModule from transactron.lib import AdapterTrans @@ -15,23 +14,11 @@ class TestFPURounding(TestCaseWithSimulator): class FPURoundingModule(Elaboratable): def __init__(self, params: FPUParams): self.params = params - self.input_not_rounded = FPURoundingParams(fpu_params=self.params, is_rounded=False) - self.input_rounded_params = FPURoundingParams( - fpu_params=self.params, - is_rounded=True, - ) def elaborate(self, platform): m = TModule() - m.submodules.fpur = fpur = self.fpu_rounding = FPUrounding(fpu_rounding_params=self.input_not_rounded) - m.submodules.fpur_rounded = fpur_rounded = self.fpu_rounding_input_rounded = FPUrounding( - fpu_rounding_params=self.input_rounded_params - ) + m.submodules.fpur = fpur = self.fpu_rounding = FPUrounding(fpu_params=self.params) m.submodules.rounding = self.rounding_request_adapter = TestbenchIO(AdapterTrans(fpur.rounding_request)) - m.submodules.input_rounded_rounding = self.input_rounded_rounding_request_adapter = TestbenchIO( - AdapterTrans(fpur_rounded.rounding_request) - ) - return m class HelpValues: @@ -65,193 +52,6 @@ def __init__(self, params: FPUParams): round_down_inc_array = [0, 0, 0, 0, 0, 1, 1, 1] round_zero_inc_array = [0, 0, 0, 0, 0, 0, 0, 0] - @parameterized.expand([(params, help_values)]) - def test_special_cases(self, params: FPUParams, help_values: HelpValues): - fpurt = TestFPURounding.FPURoundingModule(params) - - def other_cases_test(request_adapter: TestbenchIO, is_input_not_rounded: bool): - test_cases = [ - # No errors - { - "sign": 0, - "sig": help_values.not_max_norm_even_sig, - "exp": help_values.not_max_norm_exp, - "round_bit": 0, - "sticky_bit": 0, - "rounding_mode": RoundingModes.ROUND_NEAREST_AWAY, - "invalid_operation": 0, - "division_by_zero": 0, - "input_inf": 0, - }, - # inexact - { - "sign": 0, - "sig": help_values.not_max_norm_even_sig, - "exp": help_values.not_max_norm_exp, - "round_bit": 0, - "sticky_bit": 1, - "rounding_mode": RoundingModes.ROUND_NEAREST_AWAY, - "invalid_operation": 0, - "division_by_zero": 0, - "input_inf": 0, - }, - # underflow rounding - { - "sign": 0, - "sig": help_values.sub_norm_sig if is_input_not_rounded else help_values.sub_norm_sig + 1, - "exp": 0, - "round_bit": 1, - "sticky_bit": 1, - "rounding_mode": RoundingModes.ROUND_NEAREST_AWAY, - "invalid_operation": 0, - "division_by_zero": 0, - "input_inf": 0, - }, - # underflow no rounding - { - "sign": 0, - "sig": 0, - "exp": 0, - "round_bit": 0, - "sticky_bit": 1, - "rounding_mode": RoundingModes.ROUND_NEAREST_AWAY, - "invalid_operation": 0, - "division_by_zero": 0, - "input_inf": 0, - }, - # invalid operation - { - "sign": 0, - "sig": help_values.qnan, - "exp": help_values.max_exp, - "round_bit": 0, - "sticky_bit": 1, - "rounding_mode": RoundingModes.ROUND_NEAREST_AWAY, - "invalid_operation": 1, - "division_by_zero": 0, - "input_inf": 0, - }, - # division by zero - { - "sign": 0, - "sig": 0, - "exp": help_values.max_exp, - "round_bit": 0, - "sticky_bit": 1, - "rounding_mode": RoundingModes.ROUND_NEAREST_AWAY, - "invalid_operation": 0, - "division_by_zero": 1, - "input_inf": 0, - }, - # overflow but no round and sticky bits - { - "sign": 0, - "sig": 0, - "exp": help_values.max_exp, - "round_bit": 0, - "sticky_bit": 0, - "rounding_mode": RoundingModes.ROUND_NEAREST_AWAY, - "invalid_operation": 0, - "division_by_zero": 0, - "input_inf": 0, - }, - # tininess but no underflow - { - "sign": 0, - "sig": help_values.sub_norm_sig, - "exp": 0, - "round_bit": 0, - "sticky_bit": 0, - "rounding_mode": RoundingModes.ROUND_NEAREST_AWAY, - "invalid_operation": 0, - "division_by_zero": 0, - "input_inf": 0, - }, - # one of inputs was qnan - { - "sign": 0, - "sig": help_values.qnan, - "exp": help_values.max_exp, - "round_bit": 1, - "sticky_bit": 0, - "rounding_mode": RoundingModes.ROUND_NEAREST_AWAY, - "invalid_operation": 0, - "division_by_zero": 0, - "input_inf": 0, - }, - # one of inputs was inf - { - "sign": 1, - "sig": 0, - "exp": help_values.max_exp, - "round_bit": 1, - "sticky_bit": 0, - "rounding_mode": RoundingModes.ROUND_NEAREST_AWAY, - "invalid_operation": 0, - "division_by_zero": 0, - "input_inf": 1, - }, - # subnormal number become normalized after rounding - { - "sign": 1, - "sig": help_values.max_sub_norm_sig, - "exp": 0, - "round_bit": 1, - "sticky_bit": 1, - "rounding_mode": RoundingModes.ROUND_NEAREST_AWAY, - "invalid_operation": 0, - "division_by_zero": 0, - "input_inf": 0, - }, - ] - - expected_results = [ - # No errors - {"sign": 0, "sig": help_values.not_max_norm_even_sig, "exp": help_values.not_max_norm_exp, "errors": 0}, - # inexact - { - "sign": 0, - "sig": help_values.not_max_norm_even_sig, - "exp": help_values.not_max_norm_exp, - "errors": 16, - }, - # underflow rounding - {"sign": 0, "sig": help_values.sub_norm_sig + 1, "exp": 0, "errors": 24}, - # underflow no rounding - {"sign": 0, "sig": 0, "exp": 0, "errors": 24}, - # invalid operation - {"sign": 0, "sig": help_values.qnan, "exp": help_values.max_exp, "errors": 1}, - # division by zero - {"sign": 0, "sig": 0, "exp": help_values.max_exp, "errors": 2}, - # overflow but no round and sticky bits - {"sign": 0, "sig": 0, "exp": help_values.max_exp, "errors": 20}, - # tininess but no underflow - {"sign": 0, "sig": help_values.sub_norm_sig, "exp": 0, "errors": 0}, - # one of inputs was qnan - {"sign": 0, "sig": help_values.qnan, "exp": help_values.max_exp, "errors": 0}, - # one of inputs was inf - {"sign": 1, "sig": 0, "exp": help_values.max_exp, "errors": 0}, - # subnormal number become normalized after rounding - {"sign": 1, "sig": help_values.max_sub_norm_sig + 1, "exp": 1, "errors": 16}, - ] - - num_of_test_cases = len(test_cases) if is_input_not_rounded else len(test_cases) - 1 - - for i in range(num_of_test_cases): - - resp = yield from request_adapter.call(test_cases[i]) - assert resp["sign"] == expected_results[i]["sign"] - assert resp["exp"] == expected_results[i]["exp"] - assert resp["sig"] == expected_results[i]["sig"] - assert resp["errors"] == expected_results[i]["errors"] - - def test_process(): - yield from other_cases_test(fpurt.rounding_request_adapter, True) - yield from other_cases_test(fpurt.input_rounded_rounding_request_adapter, False) - - with self.run_simulation(fpurt) as sim: - sim.add_sync_process(test_process) - @parameterized.expand( [ ( @@ -259,50 +59,30 @@ def test_process(): help_values, RoundingModes.ROUND_NEAREST_EVEN, tie_to_away_inc_array, - 0, - help_values.max_exp, - 0, - help_values.max_exp, ), ( params, help_values, RoundingModes.ROUND_NEAREST_AWAY, tie_to_away_inc_array, - 0, - help_values.max_exp, - 0, - help_values.max_exp, ), ( params, help_values, RoundingModes.ROUND_UP, round_up_inc_array, - 0, - help_values.max_exp, - help_values.max_sig, - help_values.max_norm_exp, ), ( params, help_values, RoundingModes.ROUND_DOWN, round_down_inc_array, - help_values.max_sig, - help_values.max_norm_exp, - 0, - help_values.max_exp, ), ( params, help_values, RoundingModes.ROUND_ZERO, round_zero_inc_array, - help_values.max_sig, - help_values.max_norm_exp, - help_values.max_sig, - help_values.max_norm_exp, ), ] ) @@ -312,180 +92,87 @@ def test_rounding( help_values: HelpValues, rm: RoundingModes, inc_arr: list, - plus_overflow_sig: int, - plus_overflow_exp: int, - minus_overflow_sig: int, - minus_overflow_exp: int, ): fpurt = TestFPURounding.FPURoundingModule(params) - def one_rounding_mode_test( - request_adapter: TestbenchIO, - is_input_not_rounded: bool, - ): + def one_rounding_mode_test(): test_cases = [ - # overflow detection - { - "sign": 0, - "sig": ( - help_values.max_sig - if is_input_not_rounded and rm != RoundingModes.ROUND_DOWN and rm != RoundingModes.ROUND_ZERO - else 0 - ), - "exp": ( - help_values.max_norm_exp - if is_input_not_rounded and rm != RoundingModes.ROUND_DOWN and rm != RoundingModes.ROUND_ZERO - else help_values.max_exp - ), - "round_bit": 1, - "sticky_bit": 1, - "rounding_mode": rm, - "invalid_operation": 0, - "division_by_zero": 0, - "input_inf": 0, - }, + # carry after increment { - "sign": 1, - "sig": ( - help_values.max_sig - if is_input_not_rounded and rm != RoundingModes.ROUND_UP and rm != RoundingModes.ROUND_ZERO - else 0 - ), - "exp": ( - help_values.max_norm_exp - if is_input_not_rounded and rm != RoundingModes.ROUND_UP and rm != RoundingModes.ROUND_ZERO - else help_values.max_exp - ), + "sign": 0 if rm != RoundingModes.ROUND_DOWN else 1, + "sig": help_values.max_sig, + "exp": help_values.not_max_norm_exp, "round_bit": 1, "sticky_bit": 1, "rounding_mode": rm, - "invalid_operation": 0, - "division_by_zero": 0, - "input_inf": 0, }, # no overflow 00 { "sign": 0, - "sig": ( - help_values.not_max_norm_sig - if is_input_not_rounded - else help_values.not_max_norm_sig + inc_arr[0] - ), + "sig": help_values.not_max_norm_sig, "exp": help_values.not_max_norm_exp, "round_bit": 0, "sticky_bit": 0, "rounding_mode": rm, - "invalid_operation": 0, - "division_by_zero": 0, - "input_inf": 0, }, { "sign": 1, - "sig": ( - help_values.not_max_norm_sig - if is_input_not_rounded - else help_values.not_max_norm_sig + inc_arr[4] - ), + "sig": help_values.not_max_norm_sig, "exp": help_values.not_max_norm_exp, "round_bit": 0, "sticky_bit": 0, "rounding_mode": rm, - "invalid_operation": 0, - "division_by_zero": 0, - "input_inf": 0, }, # no overflow 10 { "sign": 0, - "sig": ( - help_values.not_max_norm_sig - if is_input_not_rounded - else help_values.not_max_norm_sig + inc_arr[1] - ), + "sig": help_values.not_max_norm_sig, "exp": help_values.not_max_norm_exp, "round_bit": 1, "sticky_bit": 0, "rounding_mode": rm, - "invalid_operation": 0, - "division_by_zero": 0, - "input_inf": 0, }, { "sign": 1, - "sig": ( - help_values.not_max_norm_sig - if is_input_not_rounded - else help_values.not_max_norm_sig + inc_arr[5] - ), + "sig": help_values.not_max_norm_sig, "exp": help_values.not_max_norm_exp, "round_bit": 1, "sticky_bit": 0, "rounding_mode": rm, - "invalid_operation": 0, - "division_by_zero": 0, - "input_inf": 0, }, # no overflow 01 { "sign": 0, - "sig": ( - help_values.not_max_norm_sig - if is_input_not_rounded - else help_values.not_max_norm_sig + inc_arr[2] - ), + "sig": help_values.not_max_norm_sig, "exp": help_values.not_max_norm_exp, "round_bit": 0, "sticky_bit": 1, "rounding_mode": rm, - "invalid_operation": 0, - "division_by_zero": 0, - "input_inf": 0, }, { "sign": 1, - "sig": ( - help_values.not_max_norm_sig - if is_input_not_rounded - else help_values.not_max_norm_sig + inc_arr[6] - ), + "sig": help_values.not_max_norm_sig, "exp": help_values.not_max_norm_exp, "round_bit": 0, "sticky_bit": 1, "rounding_mode": rm, - "invalid_operation": 0, - "division_by_zero": 0, - "input_inf": 0, }, # no overflow 11 { "sign": 0, - "sig": ( - help_values.not_max_norm_sig - if is_input_not_rounded - else help_values.not_max_norm_sig + inc_arr[3] - ), + "sig": help_values.not_max_norm_sig, "exp": help_values.not_max_norm_exp, "round_bit": 1, "sticky_bit": 1, "rounding_mode": rm, - "invalid_operation": 0, - "division_by_zero": 0, - "input_inf": 0, }, { "sign": 1, - "sig": ( - help_values.not_max_norm_sig - if is_input_not_rounded - else help_values.not_max_norm_sig + inc_arr[7] - ), + "sig": help_values.not_max_norm_sig, "exp": help_values.not_max_norm_exp, "round_bit": 1, "sticky_bit": 1, "rounding_mode": rm, - "invalid_operation": 0, - "division_by_zero": 0, - "input_inf": 0, }, # Round to nearest tie to even { @@ -495,9 +182,6 @@ def one_rounding_mode_test( "round_bit": 1, "sticky_bit": 0, "rounding_mode": rm, - "invalid_operation": 0, - "division_by_zero": 0, - "input_inf": 0, }, { "sign": 0, @@ -506,79 +190,73 @@ def one_rounding_mode_test( "round_bit": 1, "sticky_bit": 0, "rounding_mode": rm, - "invalid_operation": 0, - "division_by_zero": 0, - "input_inf": 0, }, ] expected_results = [ - # overflow detection - {"sign": 0, "sig": plus_overflow_sig, "exp": plus_overflow_exp, "errors": 20}, - {"sign": 1, "sig": minus_overflow_sig, "exp": minus_overflow_exp, "errors": 20}, + # carry after increment + { + "sig": (help_values.max_sig + 1) >> 1 if rm != RoundingModes.ROUND_ZERO else help_values.max_sig, + "exp": ( + help_values.not_max_norm_exp + 1 + if rm != RoundingModes.ROUND_ZERO + else help_values.not_max_norm_exp + ), + "inexact": 1, + }, # no overflow 00 { - "sign": 0, "sig": help_values.not_max_norm_sig + inc_arr[0], "exp": help_values.not_max_norm_exp, - "errors": 0, + "inexact": 0, }, { - "sign": 1, "sig": help_values.not_max_norm_sig + inc_arr[4], "exp": help_values.not_max_norm_exp, - "errors": 0, + "inexact": 0, }, # no overflow 01 { - "sign": 0, "sig": help_values.not_max_norm_sig + inc_arr[1], "exp": help_values.not_max_norm_exp, - "errors": 16, + "inexact": 1, }, { - "sign": 1, "sig": help_values.not_max_norm_sig + inc_arr[5], "exp": help_values.not_max_norm_exp, - "errors": 16, + "inexact": 1, }, # no overflow 10 { - "sign": 0, "sig": help_values.not_max_norm_sig + inc_arr[2], "exp": help_values.not_max_norm_exp, - "errors": 16, + "inexact": 1, }, { - "sign": 1, "sig": help_values.not_max_norm_sig + inc_arr[6], "exp": help_values.not_max_norm_exp, - "errors": 16, + "inexact": 1, }, # no overflow 11 { - "sign": 0, "sig": help_values.not_max_norm_sig + inc_arr[3], "exp": help_values.not_max_norm_exp, - "errors": 16, + "inexact": 1, }, { - "sign": 1, "sig": help_values.not_max_norm_sig + inc_arr[7], "exp": help_values.not_max_norm_exp, - "errors": 16, + "inexact": 1, }, # Round to nearest tie to even { - "sign": 1, "sig": help_values.not_max_norm_even_sig, "exp": help_values.not_max_norm_exp, - "errors": 16, + "inexact": 1, }, { - "sign": 0, "sig": help_values.not_max_norm_even_sig, "exp": help_values.not_max_norm_exp, - "errors": 16, + "inexact": 1, }, ] @@ -586,16 +264,13 @@ def one_rounding_mode_test( for i in range(num_of_test_cases): - resp = yield from request_adapter.call(test_cases[i]) - print(i) - assert resp["sign"] == expected_results[i]["sign"] + resp = yield from fpurt.rounding_request_adapter.call(test_cases[i]) assert resp["exp"] == expected_results[i]["exp"] assert resp["sig"] == expected_results[i]["sig"] - assert resp["errors"] == expected_results[i]["errors"] + assert resp["inexact"] == expected_results[i]["inexact"] def test_process(): - yield from one_rounding_mode_test(fpurt.rounding_request_adapter, True) - yield from one_rounding_mode_test(fpurt.input_rounded_rounding_request_adapter, False) + yield from one_rounding_mode_test() with self.run_simulation(fpurt) as sim: sim.add_sync_process(test_process) From c4ce728ef623d4698b23203b416abb87a80406e6 Mon Sep 17 00:00:00 2001 From: Mateusz Marszalek Date: Tue, 15 Oct 2024 23:52:35 +0200 Subject: [PATCH 10/15] Fixed docstring --- coreblocks/func_blocks/fu/fpu/fpu_error_module.py | 1 + 1 file changed, 1 insertion(+) diff --git a/coreblocks/func_blocks/fu/fpu/fpu_error_module.py b/coreblocks/func_blocks/fu/fpu/fpu_error_module.py index 09264d7b5..b8e41dff5 100644 --- a/coreblocks/func_blocks/fu/fpu/fpu_error_module.py +++ b/coreblocks/func_blocks/fu/fpu/fpu_error_module.py @@ -8,6 +8,7 @@ class FPUErrorMethodLayout: """FPU error checking module layouts for methods + Parameters ---------- fpu_params: FPUParams From 2276d9b6f025295f6b664f3a5a76283c4e7ae2d1 Mon Sep 17 00:00:00 2001 From: Mateusz Marszalek Date: Tue, 15 Oct 2024 23:56:26 +0200 Subject: [PATCH 11/15] Fixed formatting --- coreblocks/func_blocks/fu/fpu/fpu_error_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coreblocks/func_blocks/fu/fpu/fpu_error_module.py b/coreblocks/func_blocks/fu/fpu/fpu_error_module.py index b8e41dff5..c62c776ae 100644 --- a/coreblocks/func_blocks/fu/fpu/fpu_error_module.py +++ b/coreblocks/func_blocks/fu/fpu/fpu_error_module.py @@ -8,7 +8,7 @@ class FPUErrorMethodLayout: """FPU error checking module layouts for methods - + Parameters ---------- fpu_params: FPUParams From d519a1b4a5e8fed46bff291fa2048b49b1fff942 Mon Sep 17 00:00:00 2001 From: Mateusz Marszalek Date: Tue, 29 Oct 2024 10:42:14 +0100 Subject: [PATCH 12/15] Cleaned tests --- test/func_blocks/fu/test_fpu_error.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/func_blocks/fu/test_fpu_error.py b/test/func_blocks/fu/test_fpu_error.py index d17924037..eefa35140 100644 --- a/test/func_blocks/fu/test_fpu_error.py +++ b/test/func_blocks/fu/test_fpu_error.py @@ -188,7 +188,6 @@ def other_cases_test(): for i in range(len(test_cases)): resp = yield from fpue.error_checking_request_adapter.call(test_cases[i]) - print(i) assert resp["sign"] == expected_results[i]["sign"] assert resp["exp"] == expected_results[i]["exp"] assert resp["sig"] == expected_results[i]["sig"] From ae4553d46c10b285b8fee4ebd539d384b5f8ed67 Mon Sep 17 00:00:00 2001 From: Mateusz Marszalek Date: Sun, 10 Nov 2024 04:10:41 +0100 Subject: [PATCH 13/15] review changes --- coreblocks/func_blocks/fu/fpu/fpu_common.py | 11 ++++++++++- coreblocks/func_blocks/fu/fpu/fpu_error_module.py | 3 ++- coreblocks/func_blocks/fu/fpu/fpu_rounding_module.py | 2 +- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/coreblocks/func_blocks/fu/fpu/fpu_common.py b/coreblocks/func_blocks/fu/fpu/fpu_common.py index 2f3655faf..48bc6d022 100644 --- a/coreblocks/func_blocks/fu/fpu/fpu_common.py +++ b/coreblocks/func_blocks/fu/fpu/fpu_common.py @@ -9,13 +9,22 @@ class RoundingModes(enum.Enum): ROUND_NEAREST_AWAY = 4 +class Errors(enum.IntFlag): + INVALID_OPERATION = 0 + DIVISION_BY_ZERO = 1 + OVERFLOW = 2 + UNDERFLOW = 3 + INEXACT = 4 + + + class FPUParams: """FPU parameters Parameters ---------- sig_width: int - Width of significand + Width of significand, including implicit bit exp_width: int Width of exponent """ diff --git a/coreblocks/func_blocks/fu/fpu/fpu_error_module.py b/coreblocks/func_blocks/fu/fpu/fpu_error_module.py index c62c776ae..1d9a9d46c 100644 --- a/coreblocks/func_blocks/fu/fpu/fpu_error_module.py +++ b/coreblocks/func_blocks/fu/fpu/fpu_error_module.py @@ -3,6 +3,7 @@ from coreblocks.func_blocks.fu.fpu.fpu_common import ( RoundingModes, FPUParams, + Errors, ) @@ -30,7 +31,7 @@ def __init__(self, *, fpu_params: FPUParams): ("sign", 1), ("sig", fpu_params.sig_width), ("exp", fpu_params.exp_width), - ("errors", 5), + ("errors",len(Errors)), ] diff --git a/coreblocks/func_blocks/fu/fpu/fpu_rounding_module.py b/coreblocks/func_blocks/fu/fpu/fpu_rounding_module.py index 3c4dfa310..267d8557d 100644 --- a/coreblocks/func_blocks/fu/fpu/fpu_rounding_module.py +++ b/coreblocks/func_blocks/fu/fpu/fpu_rounding_module.py @@ -31,7 +31,7 @@ def __init__(self, *, fpu_params: FPUParams): ] -class FPUrounding(Elaboratable): +class FPURounding(Elaboratable): """FPU Rounding module Parameters From 517c6942ae9b5e6b488bf23ac45e36ad0fe93468 Mon Sep 17 00:00:00 2001 From: Mateusz Marszalek Date: Sun, 10 Nov 2024 04:31:57 +0100 Subject: [PATCH 14/15] Fixed formatting --- coreblocks/func_blocks/fu/fpu/fpu_common.py | 3 +-- coreblocks/func_blocks/fu/fpu/fpu_error_module.py | 12 ++++++------ test/func_blocks/fu/test_fpu_rounding.py | 2 +- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/coreblocks/func_blocks/fu/fpu/fpu_common.py b/coreblocks/func_blocks/fu/fpu/fpu_common.py index 48bc6d022..2e9d912ba 100644 --- a/coreblocks/func_blocks/fu/fpu/fpu_common.py +++ b/coreblocks/func_blocks/fu/fpu/fpu_common.py @@ -9,7 +9,7 @@ class RoundingModes(enum.Enum): ROUND_NEAREST_AWAY = 4 -class Errors(enum.IntFlag): +class Errors(enum.IntFlag, shape=5): INVALID_OPERATION = 0 DIVISION_BY_ZERO = 1 OVERFLOW = 2 @@ -17,7 +17,6 @@ class Errors(enum.IntFlag): INEXACT = 4 - class FPUParams: """FPU parameters diff --git a/coreblocks/func_blocks/fu/fpu/fpu_error_module.py b/coreblocks/func_blocks/fu/fpu/fpu_error_module.py index 1d9a9d46c..3501f8688 100644 --- a/coreblocks/func_blocks/fu/fpu/fpu_error_module.py +++ b/coreblocks/func_blocks/fu/fpu/fpu_error_module.py @@ -31,7 +31,7 @@ def __init__(self, *, fpu_params: FPUParams): ("sign", 1), ("sig", fpu_params.sig_width), ("exp", fpu_params.exp_width), - ("errors",len(Errors)), + ("errors", Shape.cast(Errors)), ] @@ -153,11 +153,11 @@ def _(arg): m.d.av_comb += final_sig.eq(arg.sig) m.d.av_comb += final_sign.eq(arg.sign) - m.d.av_comb += final_errors[0].eq(arg.invalid_operation) - m.d.av_comb += final_errors[1].eq(arg.division_by_zero) - m.d.av_comb += final_errors[2].eq(overflow) - m.d.av_comb += final_errors[3].eq(underflow) - m.d.av_comb += final_errors[4].eq(inexact) + m.d.av_comb += final_errors[Errors.INVALID_OPERATION].eq(arg.invalid_operation) + m.d.av_comb += final_errors[Errors.DIVISION_BY_ZERO].eq(arg.division_by_zero) + m.d.av_comb += final_errors[Errors.OVERFLOW].eq(overflow) + m.d.av_comb += final_errors[Errors.UNDERFLOW].eq(underflow) + m.d.av_comb += final_errors[Errors.INEXACT].eq(inexact) return { "exp": final_exp, diff --git a/test/func_blocks/fu/test_fpu_rounding.py b/test/func_blocks/fu/test_fpu_rounding.py index 60651321e..a27137da3 100644 --- a/test/func_blocks/fu/test_fpu_rounding.py +++ b/test/func_blocks/fu/test_fpu_rounding.py @@ -17,7 +17,7 @@ def __init__(self, params: FPUParams): def elaborate(self, platform): m = TModule() - m.submodules.fpur = fpur = self.fpu_rounding = FPUrounding(fpu_params=self.params) + m.submodules.fpur = fpur = self.fpu_rounding = FPURounding(fpu_params=self.params) m.submodules.rounding = self.rounding_request_adapter = TestbenchIO(AdapterTrans(fpur.rounding_request)) return m From 57ba7bf8a1fcb3fe78b141bb89ec7038ebd2efa9 Mon Sep 17 00:00:00 2001 From: Mateusz Marszalek Date: Mon, 11 Nov 2024 20:17:14 +0100 Subject: [PATCH 15/15] Review changes --- coreblocks/func_blocks/fu/fpu/fpu_common.py | 12 ++++++------ .../func_blocks/fu/fpu/fpu_error_module.py | 19 +++++++++++++------ .../fu/{ => fpu}/test_fpu_error.py | 17 +++++++++-------- .../fu/{ => fpu}/test_fpu_rounding.py | 2 +- 4 files changed, 29 insertions(+), 21 deletions(-) rename test/func_blocks/fu/{ => fpu}/test_fpu_error.py (96%) rename test/func_blocks/fu/{ => fpu}/test_fpu_rounding.py (99%) diff --git a/coreblocks/func_blocks/fu/fpu/fpu_common.py b/coreblocks/func_blocks/fu/fpu/fpu_common.py index 2e9d912ba..14ad02739 100644 --- a/coreblocks/func_blocks/fu/fpu/fpu_common.py +++ b/coreblocks/func_blocks/fu/fpu/fpu_common.py @@ -9,12 +9,12 @@ class RoundingModes(enum.Enum): ROUND_NEAREST_AWAY = 4 -class Errors(enum.IntFlag, shape=5): - INVALID_OPERATION = 0 - DIVISION_BY_ZERO = 1 - OVERFLOW = 2 - UNDERFLOW = 3 - INEXACT = 4 +class Errors(enum.IntFlag): + INVALID_OPERATION = enum.auto() + DIVISION_BY_ZERO = enum.auto() + OVERFLOW = enum.auto() + UNDERFLOW = enum.auto() + INEXACT = enum.auto() class FPUParams: diff --git a/coreblocks/func_blocks/fu/fpu/fpu_error_module.py b/coreblocks/func_blocks/fu/fpu/fpu_error_module.py index 3501f8688..5759f34f5 100644 --- a/coreblocks/func_blocks/fu/fpu/fpu_error_module.py +++ b/coreblocks/func_blocks/fu/fpu/fpu_error_module.py @@ -17,6 +17,11 @@ class FPUErrorMethodLayout: """ def __init__(self, *, fpu_params: FPUParams): + """ + input_inf is a flag that comes from previous stage. + Its purpose is to indicate that the infinity on input + is a result of infinity arithmetic and not a result of overflow + """ self.error_in_layout = [ ("sign", 1), ("sig", fpu_params.sig_width), @@ -31,7 +36,7 @@ def __init__(self, *, fpu_params: FPUParams): ("sign", 1), ("sig", fpu_params.sig_width), ("exp", fpu_params.exp_width), - ("errors", Shape.cast(Errors)), + ("errors", Errors), ] @@ -153,11 +158,13 @@ def _(arg): m.d.av_comb += final_sig.eq(arg.sig) m.d.av_comb += final_sign.eq(arg.sign) - m.d.av_comb += final_errors[Errors.INVALID_OPERATION].eq(arg.invalid_operation) - m.d.av_comb += final_errors[Errors.DIVISION_BY_ZERO].eq(arg.division_by_zero) - m.d.av_comb += final_errors[Errors.OVERFLOW].eq(overflow) - m.d.av_comb += final_errors[Errors.UNDERFLOW].eq(underflow) - m.d.av_comb += final_errors[Errors.INEXACT].eq(inexact) + m.d.av_comb += final_errors.eq( + Mux(arg.invalid_operation, Errors.INVALID_OPERATION, 0) + | Mux(arg.division_by_zero, Errors.DIVISION_BY_ZERO, 0) + | Mux(overflow, Errors.OVERFLOW, 0) + | Mux(underflow, Errors.UNDERFLOW, 0) + | Mux(inexact, Errors.INEXACT, 0) + ) return { "exp": final_exp, diff --git a/test/func_blocks/fu/test_fpu_error.py b/test/func_blocks/fu/fpu/test_fpu_error.py similarity index 96% rename from test/func_blocks/fu/test_fpu_error.py rename to test/func_blocks/fu/fpu/test_fpu_error.py index eefa35140..6938bfd17 100644 --- a/test/func_blocks/fu/test_fpu_error.py +++ b/test/func_blocks/fu/fpu/test_fpu_error.py @@ -2,6 +2,7 @@ from coreblocks.func_blocks.fu.fpu.fpu_common import ( RoundingModes, FPUParams, + Errors, ) from transactron import TModule from transactron.lib import AdapterTrans @@ -166,16 +167,16 @@ def other_cases_test(): "sign": 0, "sig": help_values.not_max_norm_even_sig, "exp": help_values.not_max_norm_exp, - "errors": 16, + "errors": Errors.INEXACT, }, # underflow - {"sign": 0, "sig": help_values.sub_norm_sig, "exp": 0, "errors": 24}, + {"sign": 0, "sig": help_values.sub_norm_sig, "exp": 0, "errors": Errors.UNDERFLOW | Errors.INEXACT}, # invalid operation - {"sign": 0, "sig": help_values.qnan, "exp": help_values.max_exp, "errors": 1}, + {"sign": 0, "sig": help_values.qnan, "exp": help_values.max_exp, "errors": Errors.INVALID_OPERATION}, # division by zero - {"sign": 0, "sig": 0, "exp": help_values.max_exp, "errors": 2}, + {"sign": 0, "sig": 0, "exp": help_values.max_exp, "errors": Errors.DIVISION_BY_ZERO}, # overflow but no round and sticky bits - {"sign": 0, "sig": 0, "exp": help_values.max_exp, "errors": 20}, + {"sign": 0, "sig": 0, "exp": help_values.max_exp, "errors": Errors.INEXACT | Errors.OVERFLOW}, # tininess but no underflow {"sign": 0, "sig": help_values.sub_norm_sig, "exp": 0, "errors": 0}, # one of inputs was qnan @@ -183,7 +184,7 @@ def other_cases_test(): # one of inputs was inf {"sign": 1, "sig": 0, "exp": help_values.max_exp, "errors": 0}, # subnormal number become normalized after rounding - {"sign": 1, "sig": help_values.min_norm_sig, "exp": 1, "errors": 16}, + {"sign": 1, "sig": help_values.min_norm_sig, "exp": 1, "errors": Errors.INEXACT}, ] for i in range(len(test_cases)): @@ -197,7 +198,7 @@ def test_process(): yield from other_cases_test() with self.run_simulation(fpue) as sim: - sim.add_sync_process(test_process) + sim.add_process(test_process) @parameterized.expand( [ @@ -301,4 +302,4 @@ def test_process(): yield from one_rounding_mode_test() with self.run_simulation(fpue) as sim: - sim.add_sync_process(test_process) + sim.add_process(test_process) diff --git a/test/func_blocks/fu/test_fpu_rounding.py b/test/func_blocks/fu/fpu/test_fpu_rounding.py similarity index 99% rename from test/func_blocks/fu/test_fpu_rounding.py rename to test/func_blocks/fu/fpu/test_fpu_rounding.py index a27137da3..0b1e40865 100644 --- a/test/func_blocks/fu/test_fpu_rounding.py +++ b/test/func_blocks/fu/fpu/test_fpu_rounding.py @@ -273,4 +273,4 @@ def test_process(): yield from one_rounding_mode_test() with self.run_simulation(fpurt) as sim: - sim.add_sync_process(test_process) + sim.add_process(test_process)