diff --git a/Makefile b/Makefile index ff1d741..a59e0bc 100644 --- a/Makefile +++ b/Makefile @@ -11,15 +11,7 @@ ifeq ($(AGGRESSIVENESS), 0) COVER_FLOAT_FLAGS += --partial-output endif -ifneq ($(PROCESSED_ONLY),) - COVER_FLOAT_FLAGS += --only-processed-vectors -endif - -ifneq ($(SILENT),) - COVER_FLOAT_FLAGS += -qq -endif - -MODELS := B1 B2 B3 B6 B7 B8 B9 B10 B11 B12 B13 B14 B15 B16 B20 B21 B25 B26 B27 B29 +MODELS := B1 B2 B3 B5 B6 B7 B8 B9 B10 B11 B12 B13 B14 B15 B16 B18 B20 B21 B25 B26 B27 B29 .PHONY: build clean sim all $(MODELS) diff --git a/config.svh b/config.svh index db5ad3b..a828d57 100644 --- a/config.svh +++ b/config.svh @@ -16,11 +16,11 @@ // define macros for which models to check coverage for // e.g. `define COVER_B1 for model B1 -`define COVER_B1 -`define COVER_B2 -`define COVER_B3 -`define COVER_B4 -`define COVER_B5 +// `define COVER_B1 +// `define COVER_B2 +// `define COVER_B3 +// `define COVER_B4 +// `define COVER_B5 // `define COVER_B6 // `define COVER_B7 // `define COVER_B8 @@ -36,14 +36,14 @@ `define COVER_B18 `define COVER_B19 // `define COVER_B20 -`define COVER_B21 -`define COVER_B22 -`define COVER_B23 -`define COVER_B24 +// `define COVER_B21 +// `define COVER_B22 +// `define COVER_B23 +// `define COVER_B24 // `define COVER_B25 // `define COVER_B26 // `define COVER_B27 -`define COVER_B28 +// `define COVER_B28 // `define COVER_B29 // define macros for which precisions to check coverage for diff --git a/coverage/coverfloat_pkg.sv b/coverage/coverfloat_pkg.sv index 860ed97..a895d21 100644 --- a/coverage/coverfloat_pkg.sv +++ b/coverage/coverfloat_pkg.sv @@ -101,6 +101,12 @@ package coverfloat_pkg; parameter int F64_M_BITS = 52; parameter int F128_M_BITS = 112; + parameter int F32_FMA_PRE_ADDITION_NF = 2 * F32_M_BITS; + parameter int F64_FMA_PRE_ADDITION_NF = 2 * F64_M_BITS; + parameter int F128_FMA_PRE_ADDITION_NF = 2 * F128_M_BITS; + parameter int F16_FMA_PRE_ADDITION_NF = 2 * F16_M_BITS; + parameter int BF16_FMA_PRE_ADDITION_NF = 2 * BF16_M_BITS; + // Precision (p = number of significand bits + 1 implicit bits) parameter int F16_P = F16_M_BITS + 1; parameter int BF16_P = BF16_M_BITS + 1; @@ -426,7 +432,32 @@ package coverfloat_pkg; longest_seq_of_ones = max_len; end endfunction + function automatic int get_effective_product_exponent( + input logic[127:0] a, + input logic[127:0] b, + input logic[255:0] pre_addition, + input logic [7:0] fmt + ); + int shift_one; + case (fmt) + FMT_BF16: begin + shift_one = pre_addition[BF16_FMA_PRE_ADDITION_NF+1]; + end + FMT_HALF: begin + shift_one = pre_addition[F16_FMA_PRE_ADDITION_NF+1]; + end + FMT_SINGLE: begin + shift_one = pre_addition[F32_FMA_PRE_ADDITION_NF+1]; + end + FMT_DOUBLE: begin + shift_one = pre_addition[F64_FMA_PRE_ADDITION_NF+1]; + end + FMT_QUAD: begin + shift_one = pre_addition[F128_FMA_PRE_ADDITION_NF+1]; + end + endcase + endfunction function automatic int get_product_exponent ( input logic [127:0] a, diff --git a/coverage/covergroups/B29.svh b/coverage/covergroups/B29.svh new file mode 100644 index 0000000..ad5ba38 --- /dev/null +++ b/coverage/covergroups/B29.svh @@ -0,0 +1,141 @@ +covergroup B29_cg (virtual coverfloat_interface CFI); + + option.per_instance = 0; + + F16_input_fmt: coverpoint (CFI.operandFmt == FMT_HALF) { + type_option.weight = 0; + bins f16 = {1}; + } + + BF16_input_fmt: coverpoint (CFI.operandFmt == FMT_BF16) { + type_option.weight = 0; + bins bf16 = {1}; + } + + F32_input_fmt: coverpoint (CFI.operandFmt == FMT_SINGLE) { + type_option.weight = 0; + bins f32 = {1}; + } + + F64_input_fmt: coverpoint (CFI.operandFmt == FMT_DOUBLE) { + type_option.weight = 0; + bins f64 = {1}; + } + + F128_input_fmt: coverpoint (CFI.operandFmt == FMT_QUAD) { + type_option.weight = 0; + bins f128 = {1}; + } + // RFI Instruction + + RFI_op: coverpoint CFI.op { + type_option.weight = 0; + bins rfi = { OP_RFI }; + } + + rounding_mode_all: coverpoint CFI.rm { + type_option.weight = 0; + bins round_near_even = {ROUND_NEAR_EVEN}; + bins round_minmag = {ROUND_MINMAG}; + bins round_min = {ROUND_MIN}; + bins round_max = {ROUND_MAX}; + bins round_near_maxmag = {ROUND_NEAR_MAXMAG}; + } + + F32_sign: coverpoint CFI.result[31] { + type_option.weight = 0; + bins pos = {0}; + bins neg = {1}; + } + + F64_sign: coverpoint CFI.result[63] { + type_option.weight = 0; + bins pos = {0}; + bins neg = {1}; + } + + F128_sign: coverpoint CFI.result[127] { + type_option.weight = 0; + bins pos = {0}; + bins neg = {1}; + } + + F16_sign: coverpoint CFI.result[15] { + type_option.weight = 0; + bins pos = {0}; + bins neg = {1}; + } + + BF16_sign: coverpoint CFI.result[15] { + type_option.weight = 0; + bins pos = {0}; + bins neg = {1}; + } + + BF16_LGS_Combos: coverpoint { + CFI.a[BF16_M_UPPER - $signed(get_unbiased_exponent(CFI.a, FMT_BF16)) + 1], + CFI.a[BF16_M_UPPER - $signed(get_unbiased_exponent(CFI.a, FMT_BF16))], + |(CFI.a & ((64'b1 << (BF16_M_UPPER - $signed(get_unbiased_exponent(CFI.a, FMT_BF16)))) - 1)) + } { + type_option.weight = 0; + bins lgs_combos[] = {[3'b000 : 3'b111]}; + } + + F16_LGS_Combos: coverpoint { + CFI.a[F16_M_UPPER - $signed(get_unbiased_exponent(CFI.a, FMT_HALF)) + 1], + CFI.a[F16_M_UPPER - $signed(get_unbiased_exponent(CFI.a, FMT_HALF))], + |(CFI.a & ((64'b1 << (F16_M_UPPER - $signed(get_unbiased_exponent(CFI.a, FMT_HALF)))) - 1)) + } { + type_option.weight = 0; + bins lgs_combos[] = {[3'b000 : 3'b111]}; + } + + F32_LGS_Combos: coverpoint { + CFI.a[F32_M_UPPER - $signed(get_unbiased_exponent(CFI.a, FMT_SINGLE)) + 1], + CFI.a[F32_M_UPPER - $signed(get_unbiased_exponent(CFI.a, FMT_SINGLE))], + |(CFI.a & ((64'b1 << (F32_M_UPPER - $signed(get_unbiased_exponent(CFI.a, FMT_SINGLE)))) - 1)) + } { + type_option.weight = 0; + bins lgs_combos[] = {[3'b000 : 3'b111]}; + } + + + F64_LGS_Combos: coverpoint { + CFI.a[F64_M_UPPER - $signed(get_unbiased_exponent(CFI.a, FMT_DOUBLE)) + 1], + CFI.a[F64_M_UPPER - $signed(get_unbiased_exponent(CFI.a, FMT_DOUBLE))], + |(CFI.a & ((64'b1 << (F64_M_UPPER - $signed(get_unbiased_exponent(CFI.a, FMT_DOUBLE)))) - 1)) + } { + type_option.weight = 0; + bins lgs_combos[] = {[3'b000 : 3'b111]}; + } + + F128_LGS_Combos: coverpoint { + CFI.a[F128_M_UPPER - $signed(get_unbiased_exponent(CFI.a, FMT_QUAD)) + 1], + CFI.a[F128_M_UPPER - $signed(get_unbiased_exponent(CFI.a, FMT_QUAD))], + |(CFI.a & ((64'b1 << (F128_M_UPPER - $signed(get_unbiased_exponent(CFI.a, FMT_QUAD)))) - 1)) + } { + type_option.weight = 0; + bins lgs_combos[] = {[3'b000 : 3'b111]}; + } + + `ifdef COVER_F32 + B5_F32_LGS: cross F32_sign, RFI_op, F32_input_fmt, F32_LGS_Combos, rounding_mode_all; + `endif + + `ifdef COVER_F64 + B5_F64_LGS: cross F64_sign, RFI_op, F64_input_fmt, F64_LGS_Combos, rounding_mode_all; + `endif + + `ifdef COVER_F128 + B5_F128_LGS: cross F128_sign, RFI_op, F128_input_fmt, F128_LGS_Combos, rounding_mode_all; + `endif + + `ifdef COVER_F16 + B5_F16_LGS: cross F16_sign, RFI_op, F16_input_fmt, F16_LGS_Combos, rounding_mode_all; + `endif + + `ifdef COVER_BF16 + B5_BF16_LGS: cross BF16_sign, RFI_op, BF16_input_fmt, BF16_LGS_Combos, rounding_mode_all; + `endif + +endgroup diff --git a/docs/B18.adoc b/docs/B18.adoc new file mode 100644 index 0000000..4bc21f2 --- /dev/null +++ b/docs/B18.adoc @@ -0,0 +1,93 @@ += B18 Documentation +:toc: +:toclevels: 3 +:sectnums: +:stem: + +== B18: Multiply-Add: Special Events + +Aharoni et al. + +=== Description +This model checks different cases where the multipliccaion causes some event in the product while the addition cancels this event + +Test 1):: Product: Enumerate on all options for LSB, Guard, and Sticky bit. +Intermediate Result: Exact (Guard and Sticky are zero) + +Test 2):: Product: Take overflow values from (B4) "Overflow". +Intermediate Result: No overflow + +Test 3):: Product: Take underflow values from model (B5) "Underflow". +Intermediate Result: No underflow + +*Number of tests:* 1 E 6 + +*Precisions Supported:* `BF_16`, `FP_16`, `FP_32`, `FP_64`, `FP_128` + +*Operations Supported:* `All` + +== Implementation + +=== Definitions +`min_exp`:: The minimum exponent for each precision, where the biased exponent value is 1. +`lp/hp`:: As specified within the operations section, this test suite will be performing high to low precision converts. `hp` refers to the higher precision input operand for these operations, `lp` refers to the lower precision output of the conversion. +`m_bits`:: The number of mantissa bits, dependent on the precision. +`min_sn_exp`:: The smallest possible subnormal value based on the precision, 2^min_exp - m_bits. +`min_sn`:: The smallest possible subnormal input, 2^(min_exp - m_bits) +`hp_exp/lp_exp`:: The exponent value for `hp` and `lp` respectively. +`lp_sn_exp`:: The exponent value corresponding to a subnormal value for `lp`. In any precision, this is 0 when represented as a biased exponent. In most tests, the mathematical operations will be expressed where `lp_sn_exp` is unbiased. +`lsb_exp`:: The exponent value for the least significant bit. +`lrs_int`:: The integer representation of the desired Isb, rounding, and guard bit values. + +== Operations +=== Three operands +* FMADD +* FMSUB +* FNMSUB +* FNMADD + +=== General Implementation +Each test presents a different requirement on the intermediate result and product. Based on this requirement, the appropriate factors for multiplication are chosen and the effective operation is determined. The rounding mode is randomly chosen. + +==== Test 1 Product Mantissa Pattern +[cols="5*"] +|=== +| lrs_int | LSB | Rounding | Sticky | Intermediate Mantissa + +| 1 | 0 | 0 | 1 | pass:[****_****_** 1*] +| 2 | 0 | 1 | 0 | pass:[****_****_** 10] +| 3 | 0 | 1 | 1 | pass:[****_****_** 1*] +| 4 | 1 | 0 | 0 | pass:[****_****_*1 00] +| 5 | 1 | 0 | 1 | pass:[****_****_*1 0*] +| 6 | 1 | 1 | 0 | pass:[****_****_*1 10] +| 7 | 1 | 1 | 1 | pass:[****_****_*1 1*] +| 8 | 0 | 0 | 0 | pass:[****_****_*0 00] + +|=== + +==== Test 1 Intermediate Mantissa Pattern +[cols="4*"] +|=== +|LSB | Rounding | Sticky | Intermediate Mantissa + +| * | 0 | 0 | pass:[****_****_** 00] + +|=== + + +== Specific Test Procedure +=== Test 1 +Multiplication products were extracted from B5, any random number was added or subtracted with a random sign and random operation. Rounding mode is randomly chosen. + +=== Total Test Count +[cols="3*", options="header"] +|=== +| Test # | Breakdown | Count + +| 1 | (188 * 4) | 752 +| 2 | incomplete | incomplete +| 3 | incomplete | incomplete +| *Total Tests* | *700* +|=== + +_*Differs from Aharoni test count_ diff --git a/docs/B5.adoc b/docs/B5.adoc new file mode 100644 index 0000000..276b055 --- /dev/null +++ b/docs/B5.adoc @@ -0,0 +1,312 @@ += B5 Documentation +:toc: +:toclevels: 3 +:sectnums: +:stem: + +== B5: Underflow and Near Underflow + +Aharoni et al. + +=== Description +This model creates a test-case for each of the following constraints on the intermediate results: + +Test 1):: A random positive SubNorm + +Test 2):: A random negative SubNorm + +Test 3):: All numbers in the range [+ MinSubNorm - 3 ulp, + MinSubNorm + 3 ulp] + +Test 4):: All numbers in the range [- MinSubNorm - 3 ulp, - MinSubNorm + 3 ulp] + +Test 5):: All numbers in the range [ + MinNorm - 3 ulp, + MinNorm + 3 ulp] + +Test 6):: All numbers in the range [- MinNorm - 3 ulp, - MinNorm + 3 ulp] + +Test 7):: A random number in the range (0, MinSubNorm) + +Test 8):: A random numnber in the range (-MinSubNorm, -0) + +Test 9):: One number for every exponent in the range [MinNorm.exp, MinNorm.exp + 5] + +*Number of tests:* 2 E 5 + +*Precisions Supported:* `BF_16`, `FP_16`, `FP_32`, `FP_64`, `FP_128` + +*Operations Supported:* `All` + +== Implementation + +=== Definitions +`min_exp`:: The minimum normal exponent for each precision, where the biased exponent value is 1. +`lp/hp`:: This test suite will be performing high to low precision converts to trigger underflow. hp refers to the input operand for these operations, the higher precision input. lp refers to the lower precision output of the conversion. +`hp_exp/lp_exp`:: The exponent value for hp and lp respectively. +`lp_sn_exp`:: The exponent value corresponding to a subnormal value for lp. In any precision, this is 0 when represented as a biased exponent. In most tests, the mathematical operations will be expressed where lp_sn_exp is unbiased. +`lp_max_sn`:: The largest possible subnormal value for the lp, the exponent is lp_sn_exp, the mantissa is all 1s. +`lp_min_sn`:: The smallest representable subnormal value for lp, the exponent is lp_sn_exp, the mantissa is all 0s except for a 1 in the ulp. +`m_bits`:: The number of mantissa bits, depends on the precision. +lp_m_bits/hp_m_bits: The number of m_bits for lp/hp, respectively. +`hp_min_exp`:: The lowest exponent value for hp in order to trigger underflow after a conversion. +`hp_max_exp`:: The highest exponent value for hp in order to trigger underflow after a conversion. +`ulp`:: Aharoni defines 1 ulp as “the value of a 1 in the last place of a number representation. For the binary floating-point number: b0.b1b2…bk * 2m, ulp = 1 * 2m-k” (Aharoni, 10). Within this definition, m represents the bit number within the mantissa and k represents the unbiased exponent. While this definition of ulp is utilized within Aharoni’s description of tests 3 - 6, in the B5 test suite, it is not always possible to achieve a value of -3 ulp. Additionally, B5 is testing various intermediate values and the definition of m used within the Aharoni definition is dependent on the operation being performed and the precision involved. The interesting test cases for underflow are where the values of the effective least significant bit (L’), rounding bit (R’), and fist sticky bit (T’) can alter the result after rounding. Based on the rounding mode, various combinations of L’, R’, and T’ can trigger the RND representable, which truncates the value and adds 1 to L’ (RISC-V System-on-Chip Design, by Harris, Stine, Thompson, and Harris). Therefore, the definition, ulp was added to represent the various representations of L’, R’, and T’ which trigger the RND rounding function based on the rounding mode. ulp is defined as 1 * 2m-k-2 where m represents the exponent and k represents the mantissa bits based on the precision. This is the first bit of the sticky bit, 2 positions beneath the lsb. +`msb_exp`:: The exponent value for the most significant bit. + +== Operations + +=== One operand +* FP_128 -> FP_64 | FP_128 -> FP_32 | FP_128 → FP_16 | FP_128 → BF_16 +* FP_64 -> FP_32 | FP_64 -> FP_16 | FP_64 -> FP_16|FP_64 -> BF_16 +* FP_32 -> FP_16 | FP_32 -> BF_16 +* FP_16 -> BF_16 + +=== Two operands +* Multiply +* Divide + +=== Three operands +* FMADD +* FMSUB +* FNMSUB +* FNMADD + +=== Rounding Modes +* Round Near Even +* Round To Min Mag +* Round To Min +* Round To Max +* Round Near Max Mag +* Round To Odd + +=== Description +This model will generate test-cases which seek to ensure that a RISC-V processor properly sets the Underflow flag. IEEE 754 defines two events in which an underflow flag can occur. One is the creation of a tiny nonzero result. The other is a loss of accuracy in approximating these tiny results. Regarding the creation of a tiny nonzero result, IEEE 754 defines two ways in which this tiny result can be detected: + +* “After rounding - when a nonzero result computed as though the exponent range were unbounded would lie strictly between +- 2Emin” +* “Before rounding - when a nonzero result computed as though both the exponent range and the precision were unbounded would lie strictly between +-2Emin”. + +Regarding loss of accuracy, IEEE 754 defines the two ways in which a loss of accuracy can be detected: + +* “A denormalization loss - When the delivered result differs from what would have been computed were the exponent range unbounded” +* “An Inexact Result - When the delivered result differs from what would have been computed were both exponent range and precision unbounded (This is the condition called inexact in 7.5)”. + +Loss of accuracy will be defined by an inexact result. An inexact result is detected by monitoring R’ and T’, the effective rounding bit and sticky bit after a possible normalization shift, respectively (RISC-V System-on-Chip Design, by Harris, Stine, Thompson, and Harris). This model includes 158 interesting tests which are tiny and inexact before rounding and not tiny after rounding. This results in underflow flag if tininess is detected before rounding, but no underflow flag if tininess is detected after rounding. + +The following includes a half precision binary representation of the desired mantissa combinations where stem:["m_bits" = 10]. * represents any random series of mantissa bits + +[[MinSubNorm]] +==== MinSubNorm +- 3 ulp +[cols="4*"] +|=== +| ulp increment | msb_exp | biased exp | Intermediate Mantissa + +|- 3 ulp | stem:["min_exp" - "m_bits"] | 0 | 0000_0000_00 1* +|- 2 ulp | stem:["min_exp" - "m_bits"] | 0 | 0000_0000_00 10 +|- 1 ulp | stem:["min_exp" - "m_bits"] | 0 | 0000_0000_00 1* +| 0 ulp | stem:["min_exp" - "m_bits" + 1] | 0 | 0000_0000_01 00 +|+ 1 ulp | stem:["min_exp" - "m_bits" + 1] | 0 | 0000_0000_01 0* +|+ 2 ulp | stem:["min_exp" - "m_bits" + 1] | 0 | 0000_0000_01 10 +|+ 3 ulp | stem:["min_exp" - "m_bits" + 1] | 0 | 0000_0000_01 1* + +|=== + +[[MinNorm]] +==== MinNorm +- 3 ulp +[cols="3*"] +|=== +| ulp increment | biased exp | Intermediate Mantissa + +|- 3 ulp | 0 | 1111_1111_11 01 +|- 2 ulp | 0 | 1111_1111_11 10 +|- 1 ulp | 0 | 1111_1111_11 1* +| 0 ulp | 1 | 0000_0000_01 00 +|+ 1 ulp | 1 | 0000_0000_01 0* +|+ 2 ulp | 1 | 0000_0000_01 10 +|+ 3 ulp | 1 | 0000_0000_01 1* + +|=== +[[Factors]] +==== MinNorm +- 3ulp Mantissa Values +[cols="1,2,4", options="header"] +|=== +| Precision | ULP Increment | Product + +| FMT_BF16 | +1 ulp | stem:[(8 + 1) * (8^2 - 8 + 1)] +| FMT_HALF | +1 ulp | stem:[(2^4 + 1) * ((2^4)^2 - 2^4 + 1)] +| FMT_SINGLE | +1 ulp | stem:[(2^5 + 1) * ((2^5)^4 - (2^5)^3 + (2^5)^2 - 2^5 + 1)] +| FMT_DOUBLE | +1 ulp | stem:[(2^(11) + 1) * ((2^(11))^4 - (2^(11))^3 + (2^(11))^2 - 2^(11) + 1)] +| FMT_QUAD | +1 ulp | stem:[(2^(38) + 1) * (2^(76) - 2^(38) + 1)] + +| FMT_BF16 | +2 ulp | Prime +| FMT_HALF | +2 ulp | stem:[2^(11) + 1] +| FMT_SINGLE | +2 ulp | stem:[(2^8 + 1) * ((2^8)^2 - 2^8 + 1)] +| FMT_DOUBLE | +2 ulp | stem:[2^(53) + 1] +| FMT_QUAD | +2 ulp | stem:[2^(113) + 1] + +| FMT_BF16 | +3 ulp | stem:[2^9 + 2 + 1] +| FMT_HALF | +3 ulp | stem:[2^(13) + 4 + 3] +| FMT_SINGLE | +3 ulp | stem:[2^(26) + 7] +| FMT_DOUBLE | +3 ulp | stem:[2^(55) + 7] +| FMT_QUAD | +3 ulp | stem:[(2^(112) + 2) * (2^(113) - 2)] + +| FMT_BF16 | -1 ulp | stem:[2^(14) - 1] +| FMT_HALF | -1 ulp | stem:[2^(20) - 1] +| FMT_SINGLE | -1 ulp | stem:[2^(46) - 1] +| FMT_DOUBLE | -1 ulp | stem:[2^(104) - 1] +| FMT_QUAD | -1 ulp | stem:[2^(224) - 1] + +| FMT_BF16 | -2 ulp | stem:[2^8 - 1] +| FMT_HALF | -2 ulp | stem:[2^(11) - 1] +| FMT_SINGLE | -2 ulp | stem:[2^(24) - 1] +| FMT_DOUBLE | -2 ulp | stem:[2^(53) - 1] +| FMT_QUAD | -2 ulp | stem:[2^(113) - 1] + +| FMT_BF16 | -3 ulp | stem:[19 * 107] +| FMT_HALF | -3 ulp | stem:[233 * 281] +| FMT_SINGLE | -3 ulp | stem:[479 * 70051] +| FMT_DOUBLE | -3 ulp | stem:[497401731493 * 36217] +| FMT_QUAD | -3 ulp | stem:[613 * 33881219305284356466756909162937] +|=== +== Specific Test Procedure + +=== Operation: Multiplication +==== General Procedure +Multiplication of floating point numbers `a` and `b` to get `c` takes the following form: stem:[(1."mantissa_a") * (1."mantissa_b") * 2^("a_exp" + "b_exp") * -1^("a_sign" oplus "b_sign") = "c_mantissa" * 2^"c_exp" * -1^"c_sign"]. Each test has a different requirement on `c` which dictates the possible values for the input values `a` and `b`. + +==== Tests 1, 2, 3, and 4 +For tests 1 and 2, stem:["c_exp" <= "minExp"]. For tests 3 and 4, stem:["c_exp" = "msb_exp"] as listed in xref:MinSubNorm[MinSubNorm ± 3 ulp]. The values `a_exp` and `b_exp` must satisfy the equation: stem:["a_exp" + "b_exp" = "c_exp"]. To satisfy this equation, stem:["a_exp" = "random(min_sn, c_exp - min_sn)"] such that the largest value `b_exp` can be is stem:["target" + "min_sn"]. Therefore, stem:["b_exp" = "c_exp" - "a_exp"]. +Tests 1 and 2 have no requirements on the mantissa, so `a_mantissa` and `b_mantissa` are randomly generated values which do not result in a normalization shift. +`a_mantissa` and `b_mantissa` are randomly generated values which result in multiplication as indicated by xref:MinSubNorm[MinSubNorm ± 3 ulp]. +`a_sign` and `b_sign` are randomly generated based on the test's requirements on the `c_sign`.` + +==== Tests 5 and 6 +`a_exp` and `b_exp` are randomly generated such that stem:["a_exp" + "b_exp" = "c_exp"] where `c_exp` matches the exponent value indicated by xref:MinNorm[MinNorm ± 3 ulp]. +Each `c_mantissa` pattern indicated within xref:MinNorm[MinNorm ± 3 ulp] was determined as specified by xref:Factors[Tests 5 and 6 Specific Products]. BF_16 + 2 ulp was excluded because it is prime. +`a_sign` and `b_sign` are randomly generated based on each test's requirements on `c_sign`. + +==== Tests 7 and 8 +`a_exp` and `b_exp` are randomly generated such that stem:["a_exp" + "b_exp" < "min_exp"]. `a_mantissa` and `b_mantissa` are fully randomized and such that their product, `c_exp` does not require a normalization shift. `a_sign` and `b_sign` are randomized such that for Test 7, stem:["a_sign" oplus "b_sign" = -1] and for Test 8, stem:["a_sign" oplus "b_sign" = +1] + +==== Test 9 +`a_exp` and `b_exp` are randomly generated such that stem:["a_exp" + "b_exp" \exists \in S, S = \{"min_exp",...,"min_exp + 5"}] +`a_mantissa` and `b_mantissa` are randomly generated such that their product does not require a normalization shift. +`a_sign` and `b_sign` generation are fully randomized. + +==== General Form +High precision to low precision floating point converts take an input operand, `hp` and output the result, `lp`. + +==== Determining hp_sign +The desired result's sign, `lp_sign` must match what is defined in <>. Performing a convert preserves the sign, so stem:["hp_sign" = "output_sign"]. + +==== Determining hp_exp +The desired result's exponent, `lp_exp` must match what is defined in <>. Performing a convert preserves the exponent as long as the exponent is normal before and after the convert or subnormal before and after (provided that both `hp` and `lp` share the same `min_exp`). Otherwise, if a transition from normal to subnormal is made, which occurs when stem:["hp_min_exp" < "hp_exp" < "lp_min_exp"], conversions limit the exponent to `lp_min_exp`. Therefore, stem:["hp_min_exp" = max("output_exp", "lp_min_exp")] + +==== Determining hp_mantissa +The desired result's mantissa, `lp_mantissa` must match what is defined in <>. As described in <>, `hp_exp` may represent a subnormal value in `hp`, meaning that the number of bits left to generate the desired mantissa pattern is not `m_bits`. The variable representing the actual number of bits left, `bits_left` is calculated as follows: stem:["bits_left" = "hp_m_bits" - max("hp_min_exp" - "input_exp", 0)]. Because it is impossible to go from subnormal in `hp` to normal in `lp`, I can fill the mantissa bits according to <>. If `hp_exp` is normal in `hp`, the first bit out of the LSB, rounding, and sticky bit will be satisfied after the conversion into `lp`. All bits of `hp_mantissa` will be determined based on the pattern of all following bits. + +=== Operations: Multiplication + +==== General Form +Multiplication of floating point numbers `a` and `b` to get `c` takes the following form: stem:[(1."mantissa_a") * (1."mantissa_b") * 2^("a_exp" + "b_exp") * -1^("a_sign" oplus "b_sign") = "c_mantissa" * 2^"c_exp" * -1^"c_sign"]. Each test has a different requirement on `c` which dictates the possible values for the input values `a` and `b`. + +==== Determining the input signs + +The desired value for `c_sign` matches the desired sign as stated in <>. If `c_sign`=0, `a_sign` is randomly generated and stem:["b_sign" = "a_sign"]. If `c_sign` =1, `a_sign` and `c_sign` are randomly generated and stem:["b_sign" = ("a_sign" + 1) % 2]. + +==== Determining the input exponents +The desired value for `c_exp` matches the exponent as stated in <>. The values `a_exp` and `b_exp` must satisfy the equation: stem:["a_exp"+"b_exp"="c_exp"]. To satisfy this equation, stem:["a_exp" = random("min_sn", "c_exp" - "min_sn")] such that the largest value `b_exp` can be is stem:["target" + "min_sn"]. Therefore, stem:["b_exp" = "c_exp" - "a_exp"]. + +==== Determining the input mantissas +The desired value for `c_mantissa` matches the mantissa as stated in <>. Choosing the appropriate values `a_mantissa` and `b_mantissa` will depend on `lrs_int`. If `lrs_int` = 1, `a_mantissa` and `b_mantissa` are randomly generated within in the range stem:[[1, 2^"bits_left")]. To ensure that `c_mantissa` < 2 and no normalization shift is required, `a_mantissa` and `b_mantissa` are regenerated until the result satisfies the inequality: stem:["a_mantissa" * "b_mantissa" < 2^("a_mantissa_bits" + "b_mantissa_bits" - 1)]. If `lrs_int` = 3, 5, or 7, mantissa generation occurs until the required equality on the lsb and rounding bits are satisfied. If `lrs_int` = 2 or 4, `a_mantissa` or `b_mantissa` is randomly assigned a value in the range stem:[2^("bits left" -1)]. If `lrs_int` = 6, `a_mantissa` or `b_mantissa` is randomly incremented by stem:[2^("bits_left" - 2)]. + +=== Operations: Division + +==== General Form + +Division of floating point numbers `a` and `b` resulting in the floating point value `c` take the following form: stem:["c_mantissa" * 2^"c_exp" * -1^"c_sign" = (1."mantissa_a") / (1."mantissa_b") * 2^("a_exp" - "b_exp") * -1^("a_sign" oplus "b_sign")]. + +==== Determining the input signs +Procedure identical to that listed in <>. + +==== Determining the input exponent +The desired value for `c_exp` matches the exponent as stated in <>. To avoid normalization after division, stem:["a_mantissa" > "b_mantissa"]. Because floating point adds a hidden 1 to normalized values, this means that a cannot be subnormal when `b` is normal. To avoid this condition, `a_exp` and `b_exp` are restricted to normal exponents. To satisfy the equation that stem:["c_exp" = "a_exp" - "b_exp"], the possible range of `a_exp` is first determined. stem:["a_exp" = "random"(max("min_sn", "min_sn" + "c_exp"), min("max_exp", "max_exp" + "c_exp"))]. With `a_exp` determined, stem:["b_exp" = "a_exp" - "c_exp"]. + +==== Determining the input mantissas +The desired value for `c_mantissa` matches the mantissa as stated in <>. If `lrs_int` = 2 or `lrs_int` = 4 the only way to satisfy the requirement on `c_mantissa` is to ensure that the first bit in the mantissa is a 1 and all lower bits are 0. If `lrs_int` = 1, then `a_mantissa` and `b_mantissa` = stem:["random" [1, 2^"m_bits"-1)]. If `lrs_int` = 6, then stem:["a_mantissa" = 2^("m_bits" - 1)] and the first bit in `b_mantissa` is a 1 with all lower bits being 0. If `lrs_int` = 5, then the following inequality must be satisfied: stem:[1 < "a"/"b" <3/2] If `lrs_int` = 7, then the following inequality must be satisfied: stem:[3/2<"a"/"b"<2]. Because `a` and `b` have the same range of possible values inequality around `a`/`b` greater than or less than 3/2 this test simply creates two values, a larger and a smaller value to represent the possible representations of `a`/`b`. The smaller value is within the range: stem:[(1, (2* ((2^"bits_left") -1))/3))]. Based on the smaller value, the large value lies within the range: stem:[(3/2* "small value", (2^"bits_left") - 1)]. If `lrs_int` = 5, `a_mantissa` = the smaller value and `b_mantissa` = the larger value. If `lrs_int` = 7, `a_mantissa` = the larger value and `b_mantissa` = the smaller value. + +=== Operations: All fused multiply operations +==== General Form +Floating point FMA operations of the operands `a1`, `a2`, and `b` take the form: + +* FMADD: stem:["c" = ("a1" * "a2") + "b"] +* FMSUB: stem:["c" = ("a1" * "a2") - "b"] +* FNMSUB: stem:["c" =-("a1" * "a2") + "b"] +* FNMADD: stem:["c" =-("a1" * "a2") - "b"] + +Each test has a different requirement on `c`, which determines the possible values for the inputs, `a1`, `a2`, and `b`. +==== Determining input signs +The value of `c_sign` matches the sign as stated in <>. Each FMA operation results in a different sign as a result of the multiplication and addition operation. Because the multiplication result can reach below `min_sn` but the addition operation is capped at `min_sn`, the sign of the addition dictates the sign of FMA operation. Therefore, based on the FMA operation being performed, the input sign of `b` is altered such that stem:["b_sign" = "c_sign"]. Because the smallest value of `b` is `min_sn`, and each test case seeks results below `min_sn`, the result of the multiplication must be the opposite of `b_sign`. Therefore, based on the FMA operation being performed, `a1_sign` and `a2_sign` are determined such that stem:[("a1_sign" oplus "a2_sign") oplus "multiplication sign" = ! "b_sign"]. Where multiplication sign represents 1 for FNMSUB and FNMADD and 0 for FMSUB and FMADD. + +==== Determining input exponent and mantissa + +The desired output sign and exponent, `c_sign` and `c_exp` match the values as stated in <> and <> respectively. Within tests 1 and 3 where the magnitude of the FMA operation lies between `minSubNorm` and `minSubNorm`/2, stem:["b" = "minSubNorm" * 2]. For tests 2 and 4 where the magnitude of the FMA operation lies between 0 and `minSubNorm`/2, stem:["b" = "minSubNorm"]. As observed in <>, each test has a different desired lsb, rounding bit, and sticky bit pattern, which can be represented as lrs_int. Since the value of `b` is determined, the bit pattern of `b` can also be represented as an integer. Regardless of the test, the integer representation of `b`, `b_int` is 8. The signs of each multiplication operation, as shown in <> are an effective subtraction from the addition operation. We can use the desired output `lrs_int` and `b_int` to determine the desired bit pattern of the multiplication operation, `mul_int`. Using the equation: stem:["b_int" - "lrs_int" = "mul_int"], the desired bit pattern of the multiplication is determined. This value `mul_int` is then passed into the multiplication procedure as `lrs_int` and the results are stored as `a1` and `a2`. + +== Test Count Breakdown +=== Number of Operations +[cols="4,1", options="header"] +|=== +| Operation Type | Count + +| One operand | 10 +| Two operands | 2 +| Three operands | 4 +| *Total* | *16* +|=== + +=== Configurations +[cols="4,1", options="header"] +|=== +| Parameter | Count + +| Number of precisions | 5 +| Rounding Modes | 5 +|=== + +=== Vectors Generated per Test +[cols="3*", options="header"] +|=== +| Test # | Condition | Count | + +1 | A random positive SubNorm | 1 | + +2 | A random negative SubNorm | 1 | + +3 | All numbers in the range [+ MinSubNorm - 3 ulp, + MinSubNorm + 3 ulp] | 7 | + +4 | All numbers in the range [- MinSubNorm - 3 ulp, - MinSubNorm + 3 ulp] | 7 | + +5 | All numbers in the range [ + MinNorm - 3 ulp, + MinNorm + 3 ulp] | 7 | + +6 | All numbers in the range [- MinNorm - 3 ulp, - MinNorm + 3 ulp] | 7 | + +7 | A random number in the range (0, MinSubNorm) | 1 | + +8 | A random numnber in the range (-MinSubNorm, -0) | 1 | + +9 | One number for every exponent in the range [MinNorm.exp, MinNorm.exp + 5] | 6 | + + +| *Total* | *38* +|=== + +=== Total Test Count +[cols="3*", options="header"] +|=== +| Scenario | Breakdown | Count +|Multiplication | (38 * 5 * 5) - (2 * 5) | 940 +|Division | (24 * 5 * 5) | 600 +|Converts | (10 * 5 * 38) | 1900 +|Add/Sub | (12 * 5 * 5) | 300 +|FMA | (38 * 5 * 5 * 4) | 3800 +| *Total Tests* | *7540* +|=== + +_*Differs from Aharoni test count_ diff --git a/docs/B6.adoc b/docs/B6.adoc index 81d4cab..d776bc5 100644 --- a/docs/B6.adoc +++ b/docs/B6.adoc @@ -28,7 +28,7 @@ Test 4):: stem:[+"MinSubNorm" / 2 < "intermediate" < +"MinSubNorm"] == Implementation === Definitions -`min_exp`:: The minimum exponent for each precision, where the biased exponent value is 1. +`min_exp`:: The minimum normal exponent for each precision, where the biased exponent value is 1. `lp/hp`:: As specified within the operations section, this test suite will be performing high to low precision converts. `hp` refers to the higher precision input operand for these operations, `lp` refers to the lower precision output of the conversion. `m_bits`:: The number of mantissa bits, dependent on the precision. `min_sn_exp`:: The smallest possible subnormal value based on the precision, 2^min_exp - m_bits. diff --git a/src/cover_float/common/constants.py b/src/cover_float/common/constants.py index 7ca5dfa..0f705c5 100644 --- a/src/cover_float/common/constants.py +++ b/src/cover_float/common/constants.py @@ -111,10 +111,11 @@ # Defining floating point format constants -MANTISSA_BITS = {FMT_HALF: 10, FMT_SINGLE: 23, FMT_DOUBLE: 52, FMT_QUAD: 112, FMT_BF16: 7} - EXPONENT_BIAS = {FMT_HALF: 15, FMT_SINGLE: 127, FMT_DOUBLE: 1023, FMT_QUAD: 16383, FMT_BF16: 127} +# ROUNDING_MODES = {ROUND_NEAR_EVEN, ROUND_MINMAG, ROUND_MIN, ROUND_MAX, ROUND_NEAR_MAXMAG, ROUND_ODD} + +MANTISSA_BITS = {FMT_HALF: 10, FMT_SINGLE: 23, FMT_DOUBLE: 52, FMT_QUAD: 112, FMT_BF16: 7} EXPONENT_BITS = {FMT_HALF: 5, FMT_SINGLE: 8, FMT_DOUBLE: 11, FMT_QUAD: 15, FMT_BF16: 8} diff --git a/src/cover_float/testgen/B18.py b/src/cover_float/testgen/B18.py new file mode 100644 index 0000000..e3652bc --- /dev/null +++ b/src/cover_float/testgen/B18.py @@ -0,0 +1,336 @@ +# B18 +# Lamarr + +import random +from typing import TextIO + +from cover_float.common.constants import ( + EXPONENT_BIAS, + EXPONENT_BITS, + FLOAT_FMTS, + FMT_BF16, + FMT_DOUBLE, + FMT_HALF, + FMT_QUAD, + FMT_SINGLE, + MANTISSA_BITS, + OP_FMADD, + OP_FMSUB, + OP_FNMADD, + OP_FNMSUB, + ROUND_MAX, + ROUND_MIN, + ROUND_MINMAG, + ROUND_NEAR_EVEN, + ROUND_NEAR_MAXMAG, + UNBIASED_EXP, +) +from cover_float.reference import run_and_store_test_vector +from cover_float.testgen.B5 import getMultiplyTests +from cover_float.testgen.model import register_model + +B5_FMTS = [FMT_QUAD, FMT_DOUBLE, FMT_SINGLE, FMT_BF16, FMT_HALF] +ROUNDING_MODES = [ROUND_NEAR_EVEN, ROUND_MINMAG, ROUND_MIN, ROUND_MAX, ROUND_NEAR_MAXMAG] +FMA_OPS = [OP_FMADD, OP_FMSUB, OP_FNMADD, OP_FNMSUB] + +fma_op_key = { + OP_FMADD: {"mul_sign": 0, "add_sign": 0}, + OP_FMSUB: {"mul_sign": 0, "add_sign": 1}, + OP_FNMADD: {"mul_sign": 1, "add_sign": 1}, + OP_FNMSUB: {"mul_sign": 1, "add_sign": 0}, +} + + +def generate_FP(precision: str, input_sign: str, input_exponent: int, input_mantissa: str) -> str: + input_e_bitwidth = EXPONENT_BITS[precision] + input_bias = EXPONENT_BIAS[precision] + + # 1. Calculate and format the exponent + exp_val = input_exponent + input_bias + exponent = f"{exp_val:0{input_e_bitwidth}b}" + + # 2. Check for Exponent Overflow/Underflow + if len(exponent) != input_e_bitwidth: + raise ValueError( + f"Alignment Error: Exponent binary '{exponent}' is {len(exponent)} bits long. " + f"Expected exactly {input_e_bitwidth} bits. (Calculated value was {exp_val})" + ) + + # 3. Validate Sign Bit + if len(input_sign) != 1 or input_sign not in ("0", "1"): + raise ValueError(f"Alignment Error: Sign bit must be exactly '0' or '1'. Got: '{input_sign}'") + + # 4. Construct the full binary string + complete = input_sign + exponent + input_mantissa + total_bits = len(complete) + + # 5. Validate total bit length is a clean multiple of 4 (for hex conversion) + if total_bits % 4 != 0: + raise ValueError( + f"Alignment Error: Total bit length ({total_bits}) is not a multiple of 4. " + f"Sign: 1, Exp: {input_e_bitwidth}, Mantissa: {len(input_mantissa)}" + ) + + # 6. Convert to Hex AND explicitly pad to the correct number of characters + hex_chars_needed = total_bits // 4 + fp_complete = format(int(complete, 2), "X").zfill(hex_chars_needed) + + return fp_complete + + +def getRandomInt(min_exp: int, max_exp: int, sign: str, precision: str) -> str: + mantissa_bits = MANTISSA_BITS[precision] + + exp = random.randint(min_exp, max_exp + 1) + mantissa = random.randint(1, (1 << mantissa_bits) - 1) + mantissa_str = f"{mantissa:0{mantissa_bits}b}" + return generate_FP(precision, sign, exp, mantissa_str) + + +def genUnderflowTests(test_f: TextIO, cover_f: TextIO) -> None: + + for precision in FLOAT_FMTS: + min_exp = UNBIASED_EXP[precision][0] + 1 + max_exp = UNBIASED_EXP[precision][1] - 1 + rounding_mode = random.choice(ROUNDING_MODES) + for a, b in getMultiplyTests(precision, rounding_mode): + for operation in FMA_OPS: + c = getRandomInt(min_exp, max_exp, str(random.randint(0, 1)), precision) + run_and_store_test_vector( + f"{operation}_{rounding_mode}_{a}_{b}_{c}_{precision}_{32 * '0'}_{precision}_00", + test_f, + cover_f, + ) + + +# def genOverflowTests(test_f: TextIO, cover_f: TextIO) -> None: + + +def get_fma_signs(operation: str) -> tuple[int, int]: + fma_op_key = { + OP_FMADD: {"mul_sign": 0, "add_sign": 0}, + OP_FMSUB: {"mul_sign": 0, "add_sign": 1}, + OP_FNMADD: {"mul_sign": 1, "add_sign": 1}, + OP_FNMSUB: {"mul_sign": 1, "add_sign": 0}, + } + + op_mul_sign = fma_op_key[operation]["mul_sign"] + op_add_sign = fma_op_key[operation]["add_sign"] + + return op_mul_sign, op_add_sign + + +def lsbGuardStickyTests(test_f: TextIO, cover_f: TextIO) -> None: + rounding_mode = random.choice(ROUNDING_MODES) + for precision in FLOAT_FMTS: + for grs_int in range(1, 8): + for operation in FMA_OPS: + mul_sign, add_sign = get_fma_signs(operation) + a, b, c = get_fp_values(precision, f"{grs_int:03b}", mul_sign, add_sign) + run_and_store_test_vector( + f"{operation}_{rounding_mode}_{a}_{b}_{c}_{precision}_{32 * '0'}_{precision}_00", test_f, cover_f + ) + + +def get_fp_values(precision: str, grs_pattern: str, mul_sign: int, addend_sign: int) -> tuple[str, str, str]: + m_bits = MANTISSA_BITS[precision] + e_min = UNBIASED_EXP[precision][0] + e_max = UNBIASED_EXP[precision][1] + + # Determine the input mantissas for each desired value + target_list = { + # Above MinNorm + "001": { # Same as B5, FIX + FMT_BF16: (8 + 1) * (8**2 - 8 + 1), + FMT_HALF: ((2**4) + 1) * ((2**4) ** 2 - (2**4) + 1), + FMT_SINGLE: ((2**5) + 1) * ((2**5) ** 4 - (2**5) ** 3 + (2**5) ** 2 - (2**5) + 1), + FMT_DOUBLE: ((2**11) + 1) * ((2**11) ** 4 - (2**11) ** 3 + (2**11) ** 2 - (2**11) + 1), + FMT_QUAD: ((2**38) + 1) * ((2**76) - (2**38) + 1), + }, + "010": { # Same as B5, FIX + FMT_BF16: 2056, + FMT_HALF: (2**11) + 1, + FMT_SINGLE: ((2**8) + 1) * ((2**8) ** 2 - (2**8) + 1), + FMT_DOUBLE: (2**53) + 1, + FMT_QUAD: (2**113) + 1, + }, + "011": { # Same as B5, FIX + FMT_BF16: 2**9 + 2 + 1, + FMT_HALF: 2**13 + 4 + 3, + FMT_SINGLE: (2**26) + 7, + FMT_DOUBLE: 2**55 + 7, + FMT_QUAD: 2**120 + 125, + }, + "100": { # grs_int = 4 + FMT_BF16: (2**8) - 1, + FMT_HALF: (2**11) - 1, + FMT_SINGLE: (2**24) - 1, + FMT_DOUBLE: (2**53) - 1, + FMT_QUAD: (2**113) - 1, + }, + # Below MinNorm + "111": { # grs_int = 7 + FMT_BF16: (2**10) - 1, + FMT_HALF: (2**14) - 1, + FMT_SINGLE: (2**26) - 1, + FMT_DOUBLE: (2**56) - 1, + FMT_QUAD: (2**116) - 1, + }, # - 1 ulp + "110": { # grs_int = 6 + FMT_BF16: (2**9) - 1, + FMT_HALF: (2**12) - 1, + FMT_SINGLE: (2**25) - 1, + FMT_DOUBLE: (2**54) - 1, + FMT_QUAD: (2**114) - 1, + }, + "101": { # grs_int = 5 + FMT_BF16: 517, + FMT_HALF: (2**12) + 5, + FMT_SINGLE: (2**26) - 3, + FMT_DOUBLE: (2**55) - 3, + FMT_QUAD: 613 * 33881219305284356466756909162937, # FIX + }, + } + + factor_list = { + # Above MinNorm + "001": { + FMT_BF16: (8 + 1), + FMT_HALF: ((2**4) + 1), + FMT_SINGLE: ((2**5) + 1), + FMT_DOUBLE: ((2**11) + 1), + FMT_QUAD: ((2**38) + 1), + }, + "010": {FMT_BF16: 257, FMT_HALF: 683, FMT_SINGLE: ((2**8) + 1), FMT_DOUBLE: 321, FMT_QUAD: 491003369344660409}, + "011": {FMT_BF16: 103, FMT_HALF: 9, FMT_SINGLE: 23 * 29, FMT_DOUBLE: 9 * 25, FMT_QUAD: 1099511627781}, + "100": { + FMT_BF16: (2**4) - 1, + FMT_HALF: 23, + FMT_SINGLE: (2**12) - 1, + FMT_DOUBLE: 6361 * 69431, + FMT_QUAD: 3391 * 23279 * 65993, + }, + # Below MinNorm + "111": { # grs_int = 7 + FMT_BF16: (2**5) - 1, + FMT_HALF: (2**7) - 1, + FMT_SINGLE: (2**13) - 1, + FMT_DOUBLE: (2**28) - 1, + FMT_QUAD: (2**58) - 1, + }, + "110": { # grs_int = 6 + FMT_BF16: 73, + FMT_HALF: (2**4) - 1, + FMT_SINGLE: (2**5) - 1, + FMT_DOUBLE: (2**27) - 1, + FMT_QUAD: (2**57) - 1, + }, + "101": { # grs_int = 5 + FMT_BF16: 11, + FMT_HALF: 1367, + FMT_SINGLE: 37 * 349, + FMT_DOUBLE: 181 * 313 * 431, + FMT_QUAD: 33881219305284356466756909162937, # FIX + }, + } + # Randomize the sign bit + if mul_sign == 0: + a_sign = random.randint(0, 1) + b_sign = a_sign + else: + a_sign = random.randint(0, 1) + b_sign = (a_sign + 1) % 2 + + target_int = target_list[grs_pattern][precision] + factor_1 = factor_list[grs_pattern][precision] + factor_2 = target_int // factor_1 # Integers have unlimited precision, must use integer division + + a_int = int(max(factor_1, factor_2)) # Ensure a is the largest value + b_int = int(min(factor_1, factor_2)) + + a_bin = format(a_int, "b") + b_bin = format(b_int, "b") + + a_bits = len(a_bin) + b_bits = len(b_bin) + + exp_offset = a_bits - b_bits + + # Subtract Hidden 1 + a_int -= 1 << (a_bits - 1) + b_int -= 1 << (b_bits - 1) + + a_trailing_zeros = m_bits - a_bits + 1 + b_trailing_zeros = m_bits - b_bits + 1 + + a_int *= 2 ** max(a_trailing_zeros, 0) + b_int *= 2 ** max(b_trailing_zeros, 0) + + a_bin = f"{a_int:0{m_bits}b}" + b_bin = f"{b_int:0{m_bits}b}" + + e_mul_target = random.randint(e_min + m_bits + 2, e_max) + + a_exp = int((e_mul_target + exp_offset) / 2) + b_exp = int(a_exp - exp_offset) - 1 + if e_min - (a_exp + b_exp) == -1: + b_exp -= 1 + elif e_min - (a_exp + b_exp) == 1: + b_exp += 1 + target_binary = f"{target_int:b}" + target_len = len(target_binary) - 1 # subtract hidden 1 + additional_bits = max(0, target_len - m_bits) + if additional_bits > 0: # If we have bits to remove + c_len = random.randint(additional_bits, m_bits + 1) + + additional_bits_bin = target_binary[m_bits + 1 :] + additional_bits_len = len(additional_bits_bin) + c_prefix_len = c_len - additional_bits_len + c_mantissa_prefix = "" + if c_prefix_len > 0: + c_mantissa_prefix = f"{random.randint(1 << (c_prefix_len - 1), (1 << c_prefix_len) - 1):0{c_prefix_len}b}" + c_mantissa_bin = c_mantissa_prefix + additional_bits_bin + + c_mantissa_bin = c_mantissa_bin.lstrip("0") + if not c_mantissa_bin: + c_mantissa_bin = "1" # Failsafe if the slice was entirely zeros + + # Update c_len to the TRUE length after removing leading zeros + c_len = len(c_mantissa_bin) + + # Calculate precise exponent offset using the corrected c_len + c_exp_offset = a_bits + b_bits - c_len - 1 + else: # If there are no bits to remove + c_len = random.randint(1, m_bits + 1) + c_mantissa = random.randint(1 << (c_len - 1), (1 << c_len) - 1) + c_mantissa_bin = f"{c_mantissa:0{c_len}b}" + c_exp_offset = random.randint(0, m_bits - c_len) if m_bits - c_len > 0 else 0 + + mantissa_len = len(c_mantissa_bin) + c_mantissa_int = int(c_mantissa_bin, 2) + + # handle normal vs subnormals: + c_exp = (a_exp + b_exp) - c_exp_offset + if c_exp < e_min: + c_exp = e_min - 1 + else: # subtract hidden 1 + c_mantissa_int -= 1 << (mantissa_len - 1) + + # Shift the mantissa left to align it to the MSB of the IEEE fraction field + c_trailing_zeros = m_bits - c_len + 1 + c_mantissa_int *= 2 ** max(c_trailing_zeros, 0) + + c_bin = f"{c_mantissa_int:0{m_bits}b}" + + a_fp = generate_FP(precision, str(a_sign), a_exp, a_bin) + b_fp = generate_FP(precision, str(b_sign), b_exp, b_bin) + c_fp = generate_FP(precision, str((addend_sign + 1) % 2), c_exp, c_bin) + + return a_fp, b_fp, c_fp + + +@register_model("B18") +def main(test_f: TextIO, cover_f: TextIO) -> None: + genUnderflowTests(test_f, cover_f) + # overFlowTests(test_f, cover_f) + # lsbGuardStickyTests(test_f, cover_f) diff --git a/src/cover_float/testgen/B5.py b/src/cover_float/testgen/B5.py new file mode 100644 index 0000000..eb0d651 --- /dev/null +++ b/src/cover_float/testgen/B5.py @@ -0,0 +1,1260 @@ +# Lamarr +# B5 Model + + +import random +from collections.abc import Iterator +from random import seed +from typing import TextIO + +from cover_float.common.constants import ( + EXPONENT_BIAS, + EXPONENT_BITS, + FLOAT_FMTS, + FMT_BF16, + FMT_DOUBLE, + FMT_HALF, + FMT_QUAD, + FMT_SINGLE, + MANTISSA_BITS, + OP_ADD, + OP_CFF, + OP_DIV, + OP_FMADD, + OP_FMSUB, + OP_FNMADD, + OP_FNMSUB, + OP_MUL, + OP_SUB, + ROUND_MAX, + ROUND_MIN, + ROUND_MINMAG, + ROUND_NEAR_EVEN, + ROUND_NEAR_MAXMAG, + UNBIASED_EXP, +) +from cover_float.common.util import reproducible_hash +from cover_float.reference import run_and_store_test_vector +from cover_float.testgen.model import register_model + +B5_FMTS = [FMT_QUAD, FMT_DOUBLE, FMT_SINGLE, FMT_BF16, FMT_HALF] +ROUNDING_MODES = [ROUND_NEAR_EVEN, ROUND_MINMAG, ROUND_MIN, ROUND_MAX, ROUND_NEAR_MAXMAG] +FMA_OPS = [OP_FMADD, OP_FMSUB, OP_FNMADD, OP_FNMSUB] + + +def generate_FP( + input_e_bitwidth: int, input_sign: str, input_exponent: int, input_mantissa: str, input_bias: int +) -> str: + # 1. Calculate and format the exponent + exp_val = input_exponent + input_bias + exponent = f"{exp_val:0{input_e_bitwidth}b}" + + # 2. Check for Exponent Overflow/Underflow + if len(exponent) != input_e_bitwidth: + raise ValueError( + f"Alignment Error: Exponent binary '{exponent}' is {len(exponent)} bits long. " + f"Expected exactly {input_e_bitwidth} bits. (Calculated value was {exp_val})" + ) + + # 3. Validate Sign Bit + if len(input_sign) != 1 or input_sign not in ("0", "1"): + raise ValueError(f"Alignment Error: Sign bit must be exactly '0' or '1'. Got: '{input_sign}'") + + # 4. Construct the full binary string + complete = input_sign + exponent + input_mantissa + total_bits = len(complete) + + # 5. Validate total bit length is a clean multiple of 4 (for hex conversion) + if total_bits % 4 != 0: + raise ValueError( + f"Alignment Error: Total bit length ({total_bits}) is not a multiple of 4. " + f"Sign: 1, Exp: {input_e_bitwidth}, Mantissa: {len(input_mantissa)}" + ) + + # 6. Convert to Hex AND explicitly pad to the correct number of characters + hex_chars_needed = total_bits // 4 + fp_complete = format(int(complete, 2), "X").zfill(hex_chars_needed) + + return fp_complete + + +def convert_grs( + hp: str, lp: str, g_exp: int, grs: str, sign: str, rounding_mode: str, test_f: TextIO, cover_f: TextIO +) -> None: + hp_m_bits = MANTISSA_BITS[hp] + hp_e_bits = EXPONENT_BITS[hp] + hp_e_bias = EXPONENT_BIAS[hp] + hp_min_exp = UNBIASED_EXP[hp][0] + hp_minsn_exp = hp_min_exp - hp_m_bits + + # Determine the actual exponent, based on desired grs pattern + grs_int = int(grs, 2) + first_1 = grs.index("1") + + input_exp = g_exp - first_1 if grs_int != 1 else random.randint(hp_minsn_exp, g_exp - 2) + + # Generate the mantissa + input_mant = 0 + bits_left = hp_m_bits - max(hp_min_exp - input_exp, 0) + sn = bits_left < hp_m_bits + # Handle the first bit + + # Like for BF_16 to Single, you're going from sn -> sn + if sn: + input_mant += 1 << bits_left + bits_left -= 1 + if int(grs, 2) != 1: + grs = grs.replace("1", "0", 1) + if grs[1] == "0": + bits_left -= 1 + elif grs[1] == "1": + input_mant += 1 << bits_left + + if grs[2] == "1": + input_mant += random.randint(1, (1 << bits_left) - 1) + + # Normalize exponent + input_exp = max(input_exp, hp_min_exp - 1) + # Make sure exponent has correct padding + input_mant_bin = f"{input_mant:0{hp_m_bits}b}" + + input_fp = generate_FP(hp_e_bits, sign, input_exp, input_mant_bin, hp_e_bias) + run_and_store_test_vector( + f"{OP_CFF}_{rounding_mode}_{input_fp}_{32 * '0'}_{32 * '0'}_{hp}_{32 * '0'}_{lp}_00", test_f, cover_f + ) + + +def tests_conversion_1_2(lp: str, hp: str, rounding_mode: str, test_f: TextIO, cover_f: TextIO) -> None: + hashval = reproducible_hash(OP_CFF + lp + "b5") + seed(hashval) + lp_min_exp = UNBIASED_EXP[lp][0] + + convert_grs(hp, lp, lp_min_exp + 1, "001", "0", rounding_mode, test_f, cover_f) + convert_grs(hp, lp, lp_min_exp + 1, "001", "1", rounding_mode, test_f, cover_f) + + +def genPNTestVectors( + lp: str, + hp: str, + rounding_mode: str, + hp_e_bits: int, + hp_exp: int, + complete_binary_1: str, + complete_binary_2: str, + hp_e_bias: int, + test_f: TextIO, + cover_f: TextIO, +) -> None: + + hashval = reproducible_hash(OP_CFF + lp + "b5") + seed(hashval) + input_value_1 = generate_FP(hp_e_bits, "0", hp_exp, complete_binary_1, hp_e_bias) + input_value_2 = generate_FP(hp_e_bits, "1", hp_exp, complete_binary_2, hp_e_bias) + + run_and_store_test_vector( + f"{OP_CFF}_{rounding_mode}_{input_value_1}_{32 * '0'}_{32 * '0'}_{hp}_{32 * '0'}_{lp}_00", test_f, cover_f + ) # Test 1 + run_and_store_test_vector( + f"{OP_CFF}_{rounding_mode}_{input_value_2}_{32 * '0'}_{32 * '0'}_{hp}_{32 * '0'}_{lp}_00", test_f, cover_f + ) # Test 2 + + +def tests_conversion_3_4(lp: str, hp: str, rounding_mode: str, test_f: TextIO, cover_f: TextIO) -> None: + + hashval = reproducible_hash(OP_CFF + lp + "b5") + seed(hashval) + lp_sn_exp = UNBIASED_EXP[lp][0] - 1 # Account for subnorms + lp_m_bits = MANTISSA_BITS[lp] + + lp_min_exp = lp_sn_exp - lp_m_bits + + grs = ["001", "010", "011", "100", "101", "110", "111"] + for bits in grs: + convert_grs(hp, lp, lp_min_exp + 1, bits, "0", rounding_mode, test_f, cover_f) + convert_grs(hp, lp, lp_min_exp + 1, bits, "1", rounding_mode, test_f, cover_f) + + +def tests_conversion_5_6(lp: str, hp: str, rounding_mode: str, test_f: TextIO, cover_f: TextIO) -> None: + + hashval = reproducible_hash(OP_CFF + lp + "b5") + seed(hashval) + hp_m_bits = MANTISSA_BITS[hp] + lp_sn_exp = UNBIASED_EXP[lp][0] - 1 + hp_e_bits = EXPONENT_BITS[hp] + hp_e_bias = EXPONENT_BIAS[hp] + lp_m_bits = MANTISSA_BITS[lp] + + if ( + hp != FMT_BF16 and lp != FMT_SINGLE + ): # The mantissa bits for bf_16 are smaller than that for single, so you can't do these operations + rem_bits = hp_m_bits - lp_m_bits + + max_rem = (1 << rem_bits) - 1 + + # MinNorm - 3 i_ulp: + hp_m = "1" * (lp_m_bits - 1) + "0" + f"{random.randint(1, max_rem):0{rem_bits}b}" + hp_exp = lp_sn_exp + + genPNTestVectors(lp, hp, rounding_mode, hp_e_bits, hp_exp, hp_m, hp_m, hp_e_bias, test_f, cover_f) + + # MinNorm - 2 i_ulp: + hp_m = "1" * (lp_m_bits - 1) + "1" + "0" * rem_bits + hp_exp = lp_sn_exp + + genPNTestVectors(lp, hp, rounding_mode, hp_e_bits, hp_exp, hp_m, hp_m, hp_e_bias, test_f, cover_f) + + # MinNorm - 1 i_ulp: + hp_m_1 = "1" * (lp_m_bits - 1) + "1" + f"{random.randint(1, max_rem):0{rem_bits}b}" + hp_m_2 = "1" * (lp_m_bits - 1) + "1" + f"{random.randint(1, max_rem):0{rem_bits}b}" + hp_exp = lp_sn_exp + + genPNTestVectors(lp, hp, rounding_mode, hp_e_bits, hp_exp, hp_m_1, hp_m_2, hp_e_bias, test_f, cover_f) + + # # MinNorm: + # hp_m_1 = "0" * (hp_m_bits) + # hp_m_2 = "0" * (hp_m_bits) + # hp_exp = lp_n_exp + + # genPNTestVectors(lp, hp, rounding_mode, hp_e_bits, hp_exp, hp_m_1, hp_m_2, hp_e_bias, test_f, cover_f) + + # # MinNorm + 1 i_ulp: + # hp_m = "0" * (lp_m_bits - 1) + "0" + f"{random.randint(1, max_rem):0{rem_bits}b}" + # hp_exp = lp_n_exp + + # genPNTestVectors(lp, hp, rounding_mode, hp_e_bits, hp_exp, hp_m, hp_m, hp_e_bias, test_f, cover_f) + + # # MinNorm + 2 i_ulp: + # hp_m = "0" * lp_m_bits + "1" + "0" * (rem_bits - 1) + # hp_exp = lp_n_exp + + # genPNTestVectors(lp, hp, rounding_mode, hp_e_bits, hp_exp, hp_m, hp_m, hp_e_bias, test_f, cover_f) + + # # MinNorm + 3 i_ulp: + # hp_m = ("0" * lp_m_bits) + "1" + f"{random.randint(1, (1 << (rem_bits - 1)) - 1):0{(rem_bits - 1)}b}" + # hp_exp = lp_n_exp + + # genPNTestVectors(lp, hp, rounding_mode, hp_e_bits, hp_exp, hp_m, hp_m, hp_e_bias, test_f, cover_f) + + +def tests_conversion_7_8(lp: str, hp: str, rounding_mode: str, test_f: TextIO, cover_f: TextIO) -> None: + hashval = reproducible_hash(OP_CFF + lp + "b5") + seed(hashval) + + lp_sn_exp = UNBIASED_EXP[lp][0] - 1 + + convert_grs(hp, lp, lp_sn_exp + 2, "001", "0", rounding_mode, test_f, cover_f) + convert_grs(hp, lp, lp_sn_exp + 2, "001", "0", rounding_mode, test_f, cover_f) + + +def tests_conversion_9(lp: str, hp: str, rounding_mode: str, test_f: TextIO, cover_f: TextIO) -> None: + hashval = reproducible_hash(OP_CFF + lp + "b5") + seed(hashval) + hp_m_bits = MANTISSA_BITS[hp] + hp_e_bits = EXPONENT_BITS[hp] + hp_e_bias = EXPONENT_BIAS[hp] + lp_sn_exp = UNBIASED_EXP[lp][0] - 1 # Account for subnorms + max_m_value = int("1" * hp_m_bits, 2) + + hp_exp = lp_sn_exp + + for i in range(0, 6): + complete_binary = f"{random.randint(0, max_m_value):0{hp_m_bits}b}" + + input_value_1 = generate_FP(hp_e_bits, f"{random.randint(0, 1)}", hp_exp, complete_binary, hp_e_bias) + run_and_store_test_vector( + f"{OP_CFF}_{rounding_mode}_{input_value_1}_{32 * '0'}_{32 * '0'}_{hp}_{32 * '0'}_{lp}_00", test_f, cover_f + ) # Test 1 + hp_exp = lp_sn_exp + i + 1 + + +def convertTests(test_f: TextIO, cover_f: TextIO) -> None: + # All conversion tests: + for i_hp in range(len(B5_FMTS)): + hp = B5_FMTS[i_hp] + for i_lp in range(i_hp + 1, len(B5_FMTS)): + lp = B5_FMTS[i_lp] + for rounding_mode in ROUNDING_MODES: + tests_conversion_1_2(lp, hp, rounding_mode, test_f, cover_f) + tests_conversion_3_4(lp, hp, rounding_mode, test_f, cover_f) + tests_conversion_5_6(lp, hp, rounding_mode, test_f, cover_f) + tests_conversion_7_8(lp, hp, rounding_mode, test_f, cover_f) + tests_conversion_9(lp, hp, rounding_mode, test_f, cover_f) + + +def genSpecExp_mul(precision: str, target: int, hashString: str) -> tuple[int, int]: + hashval = reproducible_hash(hashString) + seed(hashval) + m_bits = MANTISSA_BITS[precision] + min_exp = UNBIASED_EXP[precision][0] + + min_sn = min_exp - m_bits + + a_exp = random.randint(min_sn, target - min_sn) + b_exp = target - a_exp + return (a_exp, b_exp) + + +def genSpecExp_div(precision: str, target: int, hashString: str, grs_int: int) -> tuple[int, int]: + hashval = reproducible_hash(hashString) + seed(hashval) + m_bits = MANTISSA_BITS[precision] + min_exp = UNBIASED_EXP[precision][0] + max_exp = UNBIASED_EXP[precision][1] + + # Absolute lowest effective exponent (subnormal floor) + min_sn = min_exp - m_bits + 1 + + # Mathematical rule for Division: target = a_exp - b_exp + # Therefore: b_exp = a_exp - target + + lower_bound = max(min_sn, min_sn + target) + + if grs_int == 7 or grs_int == 5: + lower_bound = max(min_exp, min_sn + target) # No Subnormal Values, so mantissa has same # of bits + + upper_bound = min(max_exp, max_exp + target) + + if lower_bound > upper_bound: + lower_bound = upper_bound + + a_exp = random.randint(lower_bound, upper_bound) + b_exp = a_exp - target + + large_exp = max(a_exp, b_exp) + small_exp = min(b_exp, a_exp) + return (small_exp, large_exp) + + +def get_grs_mant(operation: str, precision: str, a_exp: int, b_exp: int, hashString: str, grs: str) -> tuple[str, str]: + m_bits = MANTISSA_BITS[precision] + e_min = UNBIASED_EXP[precision][0] + min_sn = e_min - m_bits + + # Since we're unbiased, if a_exp > e_min, result < 0, meaning it's normal and bits_left = m_bits + a_bits_left = m_bits - max(e_min - a_exp, 0) + b_bits_left = m_bits - max(e_min - b_exp, 0) + + grs_int = int(grs, 2) + + # Loop until we get desired results: + met_conditions = False + + cycles_attempted = 0 + + a_mantissa = 0 + b_mantissa = 0 + + # Set up a_mantissa and b_mantissa, they are different based on the grs pattern: + a_rBit = False + b_rBit = False + if grs_int == 6: + if a_exp == min_sn or b_exp == min_sn: # If exp = min_sn, then you can't have the rBit + if operation == OP_MUL: + if a_exp == min_sn and b_exp == min_sn: + raise ValueError("a_exp and b_exp can't both be min_sn") + elif a_exp == min_sn: + b_rBit = True + elif b_exp == min_sn: + a_rBit = True + else: + a_rBit = True + else: # Random selection otherwise + if operation == OP_MUL: + a_rBit = random.randint(0, 1) == 1 # If 1 is randomly selected, then a_rBit = True + b_rBit = not a_rBit + else: + a_rBit = True + if a_rBit: + a_mantissa += 1 << (a_bits_left - 1) + elif b_rBit: + b_mantissa += 1 << (b_bits_left - 1) + + elif (grs_int == 7 and operation == OP_DIV) or (grs_int == 5 and operation == OP_DIV): + a_min = 1 << m_bits + a_max = 1 << m_bits + if grs_int == 7: + a_min = 7 << (m_bits - 2) + a_max = (1 << (m_bits + 1)) - 1 + a_int = random.randint(a_min, a_max) + b_int = 1 << m_bits + else: + a_min = 5 << (m_bits - 2) + a_max = (1 << m_bits) + ((1 << (m_bits - 1)) - 1) + a_int = random.randint(a_min, a_max) + b_int = random.randint((1 << m_bits), int((a_min / a_int) * a_min)) + + a_mantissa = a_int - (1 << a_bits_left) # Remove hidden 1 + b_mantissa = b_int - (1 << b_bits_left) + + if grs_int == 3 and operation == OP_DIV: + a_mantissa = random.randint(1, (1 << a_bits_left) - 1) + b_mantissa = random.randint(1, a_mantissa) + + if grs_int != 2 and grs_int != 6 and grs_int != 4 and operation == OP_MUL: + while not met_conditions: + seed(cycles_attempted) # Make deterministic + + # Scales down values for test 5 to get a r_bit = 0 + mantissa_scalar = 1 + if grs_int == 5: + mantissa_scalar = (cycles_attempted % 3) + 1 + + a_mantissa = random.randint(0, (1 << a_bits_left) - 1) // mantissa_scalar + b_mantissa = random.randint(0, (1 << b_bits_left) - 1) // mantissa_scalar + + # Add hidden 1 + a_mantissa += 1 << a_bits_left + b_mantissa += 1 << b_bits_left + + product_a_b = a_mantissa * b_mantissa + + # To avoid normalization, the product must be < 2 + product_bits = a_bits_left + b_bits_left + 2 + decimal_bit = a_bits_left + b_bits_left # The first bit where there will be a decimal + maxNorm = 1 << (product_bits - 1) # Really the smallest nonNorm + + if product_a_b < maxNorm: + if grs_int == 5 or grs_int == 7: + subtract_g_bit = product_a_b - (1 << decimal_bit) + subtract_r_bit = subtract_g_bit - (1 << (decimal_bit - 1)) + if grs_int == 5: + met_conditions = subtract_r_bit < 0 + elif grs_int == 7: + met_conditions = subtract_r_bit > 0 + else: + met_conditions = True + + if met_conditions: + # Once a_mantissa and b_mantissa are generated, I can remove the hidden 1 if normal + a_mantissa -= 1 << a_bits_left + b_mantissa -= 1 << b_bits_left + + cycles_attempted += 1 + + # Add leading 1 if SN + if a_bits_left < m_bits: + a_mantissa += 1 << a_bits_left + if b_bits_left < m_bits: + b_mantissa += 1 << b_bits_left + + bin_a_mantissa = f"{a_mantissa:0{m_bits}b}" + bin_b_mantissa = f"{b_mantissa:0{m_bits}b}" + + return (bin_a_mantissa, bin_b_mantissa) + + +def factor_mul_gen(precision: str, rounding_mode: str, grs_pattern: str, sign: str) -> Iterator[tuple[str, str]]: + m_bits = MANTISSA_BITS[precision] + e_min = UNBIASED_EXP[precision][0] - 1 + e_bits = EXPONENT_BITS[precision] + e_bias = EXPONENT_BIAS[precision] + + target_list = { + # Above MinNorm + "001": { + FMT_BF16: (8 + 1) * (8**2 - 8 + 1), + FMT_HALF: ((2**4) + 1) * ((2**4) ** 2 - (2**4) + 1), + FMT_SINGLE: ((2**5) + 1) * ((2**5) ** 4 - (2**5) ** 3 + (2**5) ** 2 - (2**5) + 1), + FMT_DOUBLE: ((2**11) + 1) * ((2**11) ** 4 - (2**11) ** 3 + (2**11) ** 2 - (2**11) + 1), + FMT_QUAD: ((2**38) + 1) * ((2**76) - (2**38) + 1), + }, + "010": { + FMT_BF16: 1, + FMT_HALF: (2**11) + 1, + FMT_SINGLE: ((2**8) + 1) * ((2**8) ** 2 - (2**8) + 1), + FMT_DOUBLE: (2**53) + 1, + FMT_QUAD: (2**113) + 1, + }, + "011": { + FMT_BF16: 2**9 + 2 + 1, + FMT_HALF: 2**13 + 4 + 3, + FMT_SINGLE: (2**26) + 7, + FMT_DOUBLE: 2**55 + 7, + FMT_QUAD: (2**112 + 2) * (2**113 - 2), + }, + # Below MinNorm + "111": { + FMT_BF16: (2**14) - 1, + FMT_HALF: (2**20) - 1, + FMT_SINGLE: (2**46) - 1, + FMT_DOUBLE: (2**104) - 1, + FMT_QUAD: (2**224) - 1, + }, # - 1 ulp + "110": { + FMT_BF16: (2 ** (m_bits + 1)) - 1, + FMT_HALF: (2 ** (m_bits + 1)) - 1, + FMT_SINGLE: (2 ** (m_bits + 1)) - 1, + FMT_DOUBLE: (2 ** (m_bits + 1)) - 1, + FMT_QUAD: (2 ** (m_bits + 1)) - 1, + }, # - 2ulp + "101": { + FMT_BF16: 19 * 107, + FMT_HALF: 233 * 281, + FMT_SINGLE: 479 * 70051, + FMT_DOUBLE: 497401731493 * 36217, + FMT_QUAD: 613 * 33881219305284356466756909162937, + }, # - 3 ulp + } + + factor_list = { + # Above MinNorm + "001": { + FMT_BF16: (8 + 1), + FMT_HALF: ((2**4) + 1), + FMT_SINGLE: ((2**5) + 1), + FMT_DOUBLE: ((2**11) + 1), + FMT_QUAD: ((2**38) + 1), + }, + "010": {FMT_BF16: 1, FMT_HALF: 683, FMT_SINGLE: ((2**8) + 1), FMT_DOUBLE: 321, FMT_QUAD: 491003369344660409}, + "011": {FMT_BF16: 103, FMT_HALF: 9, FMT_SINGLE: 23 * 29, FMT_DOUBLE: 9 * 25, FMT_QUAD: (2**112 + 2)}, + # Below MinNorm + "111": { + FMT_BF16: (2**7) - 1, + FMT_HALF: (2**10) - 1, + FMT_SINGLE: (2**23) - 1, + FMT_DOUBLE: (2**52) - 1, + FMT_QUAD: (2**112) - 1, + }, + "110": { + FMT_BF16: (4**2) - 1, + FMT_HALF: 23, + FMT_SINGLE: (2**12) - 1, + FMT_DOUBLE: 6361, + FMT_QUAD: 1066818132868207, + }, + "101": { + FMT_BF16: 19, + FMT_HALF: 233, + FMT_SINGLE: 479, + FMT_DOUBLE: 36217, + FMT_QUAD: 33881219305284356466756909162937, + }, + } + # Randomize the sign bit + if sign == "0": + a_sign = random.randint(0, 1) + b_sign = a_sign + else: + a_sign = random.randint(0, 1) + b_sign = (a_sign + 1) % 2 + + target_int = target_list[grs_pattern][precision] + factor_1 = factor_list[grs_pattern][precision] + factor_2 = target_int // factor_1 # Integers have unlimited precision, must use integer division + + a_int = int(max(factor_1, factor_2)) # Ensure a is the largest value + b_int = int(min(factor_1, factor_2)) + + a_bin = format(a_int, "b") + b_bin = format(b_int, "b") + + a_bits = len(a_bin) + b_bits = len(b_bin) + # Thow error is bit width is impossibly long + if a_bits > m_bits + 1 or b_bits > m_bits + 1: + raise ValueError(f"a_bits ({a_bits}) or b_bits ({b_bits}) cannot be greater than m_bits + 1 ({m_bits + 1}).") + + exp_offset = a_bits - b_bits + + # Subtract Hidden 1 + a_int -= 1 << (a_bits - 1) + b_int -= 1 << (b_bits - 1) + + a_trailing_zeros = m_bits - a_bits + 1 + b_trailing_zeros = m_bits - b_bits + 1 + + a_int *= 2 ** max(a_trailing_zeros, 0) + b_int *= 2 ** max(b_trailing_zeros, 0) + + a_bin = f"{a_int:0{m_bits}b}" + b_bin = f"{b_int:0{m_bits}b}" + + a_exp = int((e_min + exp_offset) / 2) + b_exp = int(a_exp - exp_offset) - 1 + if e_min - (a_exp + b_exp) == -1: + b_exp -= 1 + elif e_min - (a_exp + b_exp) == 1: + b_exp += 1 + + a_fp = generate_FP(e_bits, str(a_sign), a_exp, a_bin, e_bias) + b_fp = generate_FP(e_bits, str(b_sign), b_exp, b_bin, e_bias) + if not (target_int == 1 and factor_1 == 1): + yield (a_fp, b_fp) + + +def mul_div_grs_gen( + operation: str, + precision: str, + rounding_mode: str, + grs: str, + g_exp: int, + sign: str, + hashEnding: str, +) -> Iterator[tuple[str, str]]: + hashString = "b5" + OP_MUL + precision + rounding_mode + hashEnding + seed(hashString) + + # g_exp is needed for going below normal, getting specific subnorm results + e_bits = EXPONENT_BITS[precision] + m_bits = MANTISSA_BITS[precision] + e_bias = EXPONENT_BIAS[precision] + min_exp = UNBIASED_EXP[precision][0] + sn_exp = min_exp - 1 + + first_bit = g_exp - grs.index("1") + + if sign == "0": + a_sign = random.randint(0, 1) + b_sign = a_sign + else: + a_sign = random.randint(0, 1) + b_sign = (a_sign + 1) % 2 + + grs_int = int(grs, 2) + + target_exp = first_bit + if grs_int == 1: + smallest_res_exp = min_exp - (2 * m_bits) + target_exp = random.randint(smallest_res_exp, target_exp) + + if operation == OP_MUL: + a_exp, b_exp = genSpecExp_mul(precision, target_exp, hashString + grs + sign) + else: # operation == OP_DIV + a_exp, b_exp = genSpecExp_div(precision, target_exp, hashString, grs_int) + + a_mant, b_mant = get_grs_mant(operation, precision, a_exp, b_exp, hashString + grs + sign, grs) + + # Normalize exponents + a_exp = max(a_exp, sn_exp) + b_exp = max(b_exp, sn_exp) + + a = generate_FP(e_bits, str(a_sign), a_exp, a_mant, e_bias) + b = generate_FP(e_bits, str(b_sign), b_exp, b_mant, e_bias) + yield (a, b) + + +def tests_multiply_1_2(precision: str, rounding_mode: str) -> Iterator[tuple[str, str]]: + hashString = "b5" + OP_MUL + precision + rounding_mode + seed(hashString) + + min_exp = UNBIASED_EXP[precision][0] + sn_exp = min_exp - 1 + + yield from mul_div_grs_gen(OP_MUL, precision, rounding_mode, "001", sn_exp, "0", "1/2") + yield from mul_div_grs_gen(OP_MUL, precision, rounding_mode, "001", sn_exp, "1", "1/2") + + +def tests_multiply_3_4(precision: str, rounding_mode: str) -> Iterator[tuple[str, str]]: + hashString = "b5" + OP_MUL + precision + rounding_mode + seed(hashString) + + m_bits = MANTISSA_BITS[precision] + min_exp = UNBIASED_EXP[precision][0] + + minSNPos = min_exp - m_bits # Treating the minSN as normalized + + for grs_int in range(1, 8): + # grs_int = 2 + yield from mul_div_grs_gen( + OP_MUL, precision, rounding_mode, f"{grs_int:03b}", minSNPos, "0", f"{grs_int:03b}" + ) # Positive Test + yield from mul_div_grs_gen( + OP_MUL, precision, rounding_mode, f"{grs_int:03b}", minSNPos, "1", f"{grs_int:03b}" + ) # Negative Test + + +def tests_multiply_5_6(precision: str, rounding_mode: str) -> Iterator[tuple[str, str]]: + min_exp = UNBIASED_EXP[precision][0] + + # MinNorm - 3 ulp + yield from factor_mul_gen(precision, rounding_mode, "101", "0") # Positive Test + yield from factor_mul_gen(precision, rounding_mode, "101", "1") # Negative Test + + # MinNorm - 2 ulp + yield from factor_mul_gen(precision, rounding_mode, "110", "0") # Positive Test + yield from factor_mul_gen(precision, rounding_mode, "110", "1") # Negative Test + + # MinNorm - 1 ulp + yield from factor_mul_gen(precision, rounding_mode, "111", "0") # Positive Test + yield from factor_mul_gen(precision, rounding_mode, "111", "1") # Negative Test + + # MinNorm + yield from mul_div_grs_gen(OP_MUL, precision, rounding_mode, "100", min_exp, "0", "minExp") # Positive Test + yield from mul_div_grs_gen(OP_MUL, precision, rounding_mode, "100", min_exp, "1", "minExp") # Negative Test + + # MinNorm + 1 ulp + yield from factor_mul_gen(precision, rounding_mode, "001", "0") # Positive Test + yield from factor_mul_gen(precision, rounding_mode, "001", "1") # Negative Test + + # MinNorm + 2 ulp + yield from factor_mul_gen(precision, rounding_mode, "010", "0") # Positive Test + yield from factor_mul_gen(precision, rounding_mode, "010", "1") # Negative Test BF_16 factor is impossible + + # MinNorm + 3 ulp + yield from factor_mul_gen(precision, rounding_mode, "011", "0") # Positive Test + yield from factor_mul_gen(precision, rounding_mode, "011", "1") # Negative Test #FP_128 is producing an error + + +def tests_multiply_7_8(precision: str, rounding_mode: str) -> Iterator[tuple[str, str]]: + m_bits = MANTISSA_BITS[precision] + min_exp = UNBIASED_EXP[precision][0] + + target_exp = min_exp - m_bits + + yield from mul_div_grs_gen(OP_MUL, precision, rounding_mode, "011", target_exp, "0", "1/2 pos") + yield from mul_div_grs_gen(OP_MUL, precision, rounding_mode, "011", target_exp, "1", "1/2 neg") + + +def tests_multiply_9(precision: str, rounding_mode: str) -> Iterator[tuple[str, str]]: + min_exp = UNBIASED_EXP[precision][0] + + # all values from minNorm.exp to minNorm.exp + 5 + min_exp_range = min_exp + 1 # minNorm.exp + 1, we want it randomized, so add 1 and lsb = 0, r = 1, s = 1 + max_exp_range = min_exp_range + 5 + + for target_exp in range(min_exp_range, max_exp_range + 1): # Because end is exclusive + seed("b5" + OP_MUL + precision + rounding_mode + str(target_exp)) + sign = str(random.randint(0, 1)) + + yield from mul_div_grs_gen(OP_MUL, precision, rounding_mode, "011", target_exp, sign, str(target_exp)) + + +def getMultiplyTests(precision: str, rounding_mode: str) -> Iterator[tuple[str, str]]: + yield from tests_multiply_1_2(precision, rounding_mode) + yield from tests_multiply_3_4(precision, rounding_mode) + yield from tests_multiply_5_6(precision, rounding_mode) + yield from tests_multiply_7_8(precision, rounding_mode) + yield from tests_multiply_9(precision, rounding_mode) + + +def multiplyTests(test_f: TextIO, cover_f: TextIO) -> None: + for precision in FLOAT_FMTS: + for rounding_mode in ROUNDING_MODES: + for a, b in getMultiplyTests(precision, rounding_mode): + run_and_store_test_vector( + f"{OP_MUL}_{rounding_mode}_{a}_{b}_{32 * '0'}_{precision}_{32 * '0'}_{precision}_00", + test_f, + cover_f, + ) + + +def fma_gen( + operation: str, + precision: str, + rounding_mode: str, + product_sign: int, + product_grs: str, + product_exponent: int, + addend_sign: int, + addend_pattern: str, + test_f: TextIO, + cover_f: TextIO, + hashEnding: str, +) -> None: + m_bits = MANTISSA_BITS[precision] + e_bits = EXPONENT_BITS[precision] + e_bias = EXPONENT_BIAS[precision] + min_exp = UNBIASED_EXP[precision][0] + + fma_op_key = { + OP_FMADD: {"mul_sign": 0, "add_sign": 0}, + OP_FMSUB: {"mul_sign": 0, "add_sign": 1}, + OP_FNMADD: {"mul_sign": 1, "add_sign": 1}, + OP_FNMSUB: {"mul_sign": 1, "add_sign": 0}, + } + + # Determine the desired output signs for multiplication and addition + op_mul_sign = fma_op_key[operation]["mul_sign"] + op_add_sign = fma_op_key[operation]["add_sign"] + + mul_sign = (op_mul_sign + product_sign) % 2 + c_sign = (op_add_sign + addend_sign) % 2 + + # Generate the multiplication testvectors + for a, b in mul_div_grs_gen( + OP_MUL, precision, rounding_mode, product_grs, product_exponent, str(mul_sign), hashEnding + ): + # Generate the addition testvector + c_exp = -1 + c_mant = -1 + if addend_pattern == "1*m-1_0": + c_exp = min_exp - 1 + c_mant = (1 << m_bits) - 2 # all ones throughout the mantissa + elif addend_pattern == "min_n": + c_exp = min_exp + c_mant = 0 + elif addend_pattern == "2*min_sn": + c_exp = min_exp - 1 + c_mant = 2 + elif addend_pattern == "min_sn": + c_exp = min_exp - 1 + c_mant = 1 + elif addend_pattern == "1_0*m-1_1": + c_exp = min_exp + c_mant = 1 + elif addend_pattern == "rand_sn": + c_exp = min_exp - random.randint(1, m_bits) # Upper: Just SN, Lower: Leave 1 spot open on the end + max_mant = (1 << (min_exp - c_exp)) - 1 + c_mant = random.randint(1, max_mant) + c_mant = max_mant + # Normalize c_exp + c_exp = max(min_exp - 1, c_exp) + elif addend_pattern == "rand_n": + c_exp = min_exp + max_mant = 1 << m_bits + c_mant = random.randint(1, max_mant) + elif addend_pattern[0 : addend_pattern.index("_")] == "randexp": + exp_string = addend_pattern[addend_pattern.index("_") + 1 : len(addend_pattern)] + c_exp = 0 - int(exp_string) + max_mant = (1 << m_bits) - 1 + c_mant = random.randint(1, max_mant) + + c_bin_mant = f"{c_mant:0{m_bits}b}" + c = generate_FP(e_bits, str(c_sign), c_exp, c_bin_mant, e_bias) + run_and_store_test_vector( + f"{operation}_{rounding_mode}_{a}_{b}_{c}_{precision}_{32 * '0'}_{precision}_00", test_f, cover_f + ) + + +def tests_fma_1_2(operation: str, precision: str, rounding_mode: str, test_f: TextIO, cover_f: TextIO) -> None: + m_bits = MANTISSA_BITS[precision] + min_exp = UNBIASED_EXP[precision][0] + + minSNPos = min_exp - m_bits + + fma_gen( + operation, precision, rounding_mode, 0, "001", minSNPos, 1, "rand_sn", test_f, cover_f, "pos_1" + ) # adds decreased magnitude, can't be normal + fma_gen( + operation, precision, rounding_mode, 1, "001", minSNPos, 0, "rand_sn", test_f, cover_f, "neg_2" + ) # add decreases magnitude, can't be normal + + +def tests_fma_3_4(operation: str, precision: str, rounding_mode: str, test_f: TextIO, cover_f: TextIO) -> None: + m_bits = MANTISSA_BITS[precision] + min_exp = UNBIASED_EXP[precision][0] + + minSNPos = min_exp - m_bits + + # MinSN - 3 ulp; G = 1, R = 0, S = 1 + fma_gen( + operation, precision, rounding_mode, 0, "101", minSNPos, 1, "min_sn", test_f, cover_f, "pos_-3_ulp_3" + ) # Get rid of G Bit and some more from sticky, result is negative + fma_gen(operation, precision, rounding_mode, 1, "101", minSNPos, 0, "min_sn", test_f, cover_f, "neg_-3_ulp_4") + + # MinSN - 2 ulp; G = 1, R = 1, S = 0 + fma_gen( + operation, precision, rounding_mode, 1, "010", minSNPos, 0, "min_sn", test_f, cover_f, "pos_-2_ulp_3" + ) # Subtract minSN from 1 bit 2*minSN + fma_gen(operation, precision, rounding_mode, 0, "010", minSNPos, 1, "min_sn", test_f, cover_f, "neg_-2_ulp_4") + + # MinSN - 1 ulp; G = 0, R = 1, S = 1 + fma_gen(operation, precision, rounding_mode, 0, "111", minSNPos, 1, "min_sn", test_f, cover_f, "pos_-1_ulp_3") + fma_gen(operation, precision, rounding_mode, 1, "111", minSNPos, 0, "min_sn", test_f, cover_f, "neg_-1_ulp_4") + + # #MinSN; G = 1, R = 0, S = 0 + fma_gen(operation, precision, rounding_mode, 0, "100", minSNPos, 1, "2*min_sn", test_f, cover_f, "pos_minSN_3") + fma_gen(operation, precision, rounding_mode, 1, "100", minSNPos, 0, "2*min_sn", test_f, cover_f, "neg_minSN_4") + + # MinSN + 1 ulp; G = 1, R = 0, S = 1 + fma_gen(operation, precision, rounding_mode, 0, "001", minSNPos, 0, "min_sn", test_f, cover_f, "pos_+1_ulp_3") + fma_gen(operation, precision, rounding_mode, 1, "001", minSNPos, 1, "min_sn", test_f, cover_f, "neg_+1_ulp_4") + + # MinSN + 2 ulp; G = 1, R = 1, S = 0 + fma_gen(operation, precision, rounding_mode, 0, "010", minSNPos, 0, "min_sn", test_f, cover_f, "pos_+2_ulp_3") + fma_gen(operation, precision, rounding_mode, 1, "010", minSNPos, 1, "min_sn", test_f, cover_f, "neg_+2_ulp_4") + + # MinSN + 3 ulp; G = 1, R = 1, S = 1 + fma_gen(operation, precision, rounding_mode, 0, "011", minSNPos, 0, "min_sn", test_f, cover_f, "pos_+3_ulp_3") + fma_gen(operation, precision, rounding_mode, 1, "011", minSNPos, 1, "min_sn", test_f, cover_f, "neg_+3_ulp_4") + + +def tests_fma_5_6(operation: str, precision: str, rounding_mode: str, test_f: TextIO, cover_f: TextIO) -> None: + m_bits = MANTISSA_BITS[precision] + min_exp = UNBIASED_EXP[precision][0] + + minSNPos = min_exp - m_bits + + # MinN - 3 ulp; G = 1, R = 0, S = 1 + fma_gen( + operation, precision, rounding_mode, 0, "101", minSNPos, 0, "1*m-1_0", test_f, cover_f, "pos_-3_ulp_5" + ) # Get rid of G Bit and some more from sticky, result is negative + fma_gen(operation, precision, rounding_mode, 1, "101", minSNPos, 1, "1*m-1_0", test_f, cover_f, "neg_-3_ulp_6") + + # MinN - 2 ulp; G = 1, R = 1, S = 0 + fma_gen( + operation, precision, rounding_mode, 0, "110", minSNPos, 0, "1*m-1_0", test_f, cover_f, "pos_-2_ulp_5" + ) # Get rid of G Bit and some more from sticky, result is negative + fma_gen(operation, precision, rounding_mode, 1, "110", minSNPos, 1, "1*m-1_0", test_f, cover_f, "neg_-2_ulp_6") + + # MinN - 1 ulp; G = 1, R = 1, S = 1 + fma_gen( + operation, precision, rounding_mode, 0, "111", minSNPos, 0, "1*m-1_0", test_f, cover_f, "pos_-2_ulp_5" + ) # Get rid of G Bit and some more from sticky, result is negative + fma_gen(operation, precision, rounding_mode, 1, "111", minSNPos, 1, "1*m-1_0", test_f, cover_f, "neg_-2_ulp_6") + + # MinN + fma_gen(operation, precision, rounding_mode, 1, "100", minSNPos, 0, "1_0*m-1_1", test_f, cover_f, "pos_norm_5") + fma_gen(operation, precision, rounding_mode, 0, "100", minSNPos, 1, "1_0*m-1_1", test_f, cover_f, "neg_norm_6") + + # MinN + 1 ulp + fma_gen(operation, precision, rounding_mode, 0, "001", minSNPos, 0, "min_n", test_f, cover_f, "pos_+1_ulp_5") + fma_gen(operation, precision, rounding_mode, 1, "001", minSNPos, 1, "min_n", test_f, cover_f, "neg_+1_ulp_6") + + # MinN + 2 ulp + fma_gen(operation, precision, rounding_mode, 0, "010", minSNPos, 0, "min_n", test_f, cover_f, "pos_+2_ulp_5") + fma_gen(operation, precision, rounding_mode, 1, "010", minSNPos, 1, "min_n", test_f, cover_f, "neg_+2_ulp_6") + + # MinN + 3 ulp + fma_gen(operation, precision, rounding_mode, 0, "011", minSNPos, 0, "min_n", test_f, cover_f, "pos_+3_ulp_5") + fma_gen(operation, precision, rounding_mode, 1, "011", minSNPos, 1, "min_n", test_f, cover_f, "neg_+3_ulp_6") + + +def tests_fma_7_8(operation: str, precision: str, rounding_mode: str, test_f: TextIO, cover_f: TextIO) -> None: + m_bits = MANTISSA_BITS[precision] + min_exp = UNBIASED_EXP[precision][0] + + minSNPos = min_exp - m_bits + + fma_gen( + operation, precision, rounding_mode, 0, "011", minSNPos + 1, 1, "min_sn", test_f, cover_f, "pos_-3_ulp_3" + ) # Get rid of G Bit and some more from sticky, result is negative + fma_gen(operation, precision, rounding_mode, 1, "011", minSNPos + 1, 0, "min_sn", test_f, cover_f, "neg_-3_ulp_4") + + +def tests_fma_9(operation: str, precision: str, rounding_mode: str, test_f: TextIO, cover_f: TextIO) -> None: + m_bits = MANTISSA_BITS[precision] + min_exp = UNBIASED_EXP[precision][0] + + minSNPos = min_exp - m_bits + for exp in range(min_exp - 1, min_exp + 6): + fma_gen( + operation, + precision, + rounding_mode, + 0, + "001", + minSNPos + 2, + 1, + f"randexp_{abs(exp)}", + test_f, + cover_f, + f"pos_-3_ulp_{exp}", + ) + fma_gen( + operation, + precision, + rounding_mode, + 1, + "001", + minSNPos + 2, + 0, + f"randexp_{abs(exp)}", + test_f, + cover_f, + f"pos_-3_ulp_{exp}", + ) + + +def fmaTests(test_f: TextIO, cover_f: TextIO) -> None: + for operation in FMA_OPS: + for precision in FLOAT_FMTS: + for rounding_mode in ROUNDING_MODES: + tests_fma_1_2(operation, precision, rounding_mode, test_f, cover_f) + tests_fma_3_4(operation, precision, rounding_mode, test_f, cover_f) + tests_fma_5_6(operation, precision, rounding_mode, test_f, cover_f) + tests_fma_7_8(operation, precision, rounding_mode, test_f, cover_f) + tests_fma_9(operation, precision, rounding_mode, test_f, cover_f) + + +def div_grs_mant( + test_f: TextIO, + cover_f: TextIO, + grs: str, + g_bit_pos: int, + precision: str, + rounding_mode: str, + target_exp: int, + sign: str, + hashString: str, +) -> tuple[str, str]: + m_bits = MANTISSA_BITS[precision] + e_bits = EXPONENT_BITS[precision] + e_bias = EXPONENT_BIAS[precision] + min_exp = UNBIASED_EXP[precision][0] + + # Determine exponents, subtract to target_exp + possible_exponents = genSpecExp_div(precision, target_exp, hashString, int(grs, 2)) + + a_exp = max(possible_exponents) + b_exp = min(possible_exponents) + + a_bits_left = m_bits - max(min_exp - a_exp, 0) + b_bits_left = m_bits - max(min_exp - b_exp, 0) + + b_max_mant = (1 << b_bits_left) - 1 + a_max_mant = (1 << a_bits_left) - 1 + + b_mantissa = random.randint(1, b_max_mant - 1) + a_mantissa = random.randint(b_mantissa // 2, a_max_mant) + + if sign == "0": + a_sign = random.randint(0, 1) + b_sign = a_sign + else: + a_sign = random.randint(0, 1) + b_sign = (a_sign + 1) % 2 + + a_exp_norm = max(a_exp, min_exp - 1) + b_exp_norm = max(b_exp, min_exp - 1) + + if a_bits_left < m_bits: + a_mantissa += 1 << a_bits_left + if b_bits_left < m_bits: + b_mantissa += 1 << b_bits_left + + a = f"{a_mantissa:0{m_bits}b}" + b = f"{b_mantissa:0{m_bits}b}" + + a_fp = generate_FP(e_bits, str(a_sign), a_exp_norm, a, e_bias) + b_fp = generate_FP(e_bits, str(b_sign), b_exp_norm, b, e_bias) + + run_and_store_test_vector( + f"{OP_DIV}_{rounding_mode}_{b_fp}_{a_fp}_{32 * '0'}_{precision}_{32 * '0'}_{precision}_00", test_f, cover_f + ) + return a_fp, b_fp + + +def tests_div_1_2(precision: str, rounding_mode: str) -> Iterator[tuple[str, str]]: + min_exp = UNBIASED_EXP[precision][0] + + # Random SN: G = 0, R = 0, S = 1 + yield from mul_div_grs_gen(OP_DIV, precision, rounding_mode, "001", min_exp + 1, "0", "positive") + yield from mul_div_grs_gen(OP_DIV, precision, rounding_mode, "001", min_exp + 1, "1", "positive") + + +def tests_div_3_4(precision: str, rounding_mode: str) -> Iterator[tuple[str, str]]: + m_bits = MANTISSA_BITS[precision] + min_exp = UNBIASED_EXP[precision][0] + + minSNPos = min_exp - m_bits # Treating the minSN as normalized + + # minSN - 3 ulp G = 0, R = 0, S = 1 + yield from mul_div_grs_gen(OP_DIV, precision, rounding_mode, "001", minSNPos, "0", "minSN-3ulppos") + yield from mul_div_grs_gen(OP_DIV, precision, rounding_mode, "001", minSNPos, "1", "minSN-3ulppos") + + # minSN - 2 ulp G = 0, R = 1, S = 0 + yield from mul_div_grs_gen(OP_DIV, precision, rounding_mode, "010", minSNPos, "0", "minSN-2ulppos") + yield from mul_div_grs_gen(OP_DIV, precision, rounding_mode, "010", minSNPos, "1", "minSN-2ulppos") + + # minSN - 1 ulp G = 0, R = 1, S = 1 + yield from mul_div_grs_gen(OP_DIV, precision, rounding_mode, "011", minSNPos, "0", "minSN-1ulppos") + yield from mul_div_grs_gen(OP_DIV, precision, rounding_mode, "011", minSNPos, "1", "minSN-1ulppos") + + # minSN G = 1, R = 0, S = 0 + yield from mul_div_grs_gen(OP_DIV, precision, rounding_mode, "100", minSNPos, "0", "minSNppos") + yield from mul_div_grs_gen(OP_DIV, precision, rounding_mode, "100", minSNPos, "1", "minSNpos") + + # minSN + 1 ulp G = 1, R = 0, S = 1 grs_int = 5 + yield from mul_div_grs_gen(OP_DIV, precision, rounding_mode, "101", minSNPos, "0", "minSN") + yield from mul_div_grs_gen(OP_DIV, precision, rounding_mode, "101", minSNPos, "1", "minSN") + + # minSN + 2 ulp G = 1, R = 1, S = 0 + yield from mul_div_grs_gen(OP_DIV, precision, rounding_mode, "110", minSNPos, "0", "minSN+2ulppos") + yield from mul_div_grs_gen(OP_DIV, precision, rounding_mode, "110", minSNPos, "1", "minSN+2ulppos") + + # minSN + 3 ulp G = 1, R = 1, S = 1 + yield from mul_div_grs_gen(OP_DIV, precision, rounding_mode, "111", minSNPos, "0", "minSN+3ulppos") + yield from mul_div_grs_gen(OP_DIV, precision, rounding_mode, "111", minSNPos, "1", "minSN+3ulppos") + + +def tests_div_7_8(precision: str, rounding_mode: str) -> Iterator[tuple[str, str]]: + m_bits = MANTISSA_BITS[precision] + min_exp = UNBIASED_EXP[precision][0] + + minSNPos = min_exp - m_bits # Treating the minSN as normalized + + yield from mul_div_grs_gen(OP_DIV, precision, rounding_mode, "001", minSNPos, "0", "minSN") + yield from mul_div_grs_gen(OP_DIV, precision, rounding_mode, "001", minSNPos, "1", "minSN") + + +def tests_div_9(precision: str, rounding_mode: str) -> Iterator[tuple[str, str]]: + min_exp = UNBIASED_EXP[precision][0] + + for target_exp in range(min_exp, min_exp + 6): # Because end is exclusive + seed("b5" + OP_DIV + precision + rounding_mode + str(target_exp)) + sign = str(random.randint(0, 1)) + + yield from mul_div_grs_gen(OP_DIV, precision, rounding_mode, "011", target_exp + 1, sign, "min") + + +def getDivTests(precision: str, rounding_mode: str) -> Iterator[tuple[str, str]]: + yield from tests_div_1_2(precision, rounding_mode) + yield from tests_div_3_4(precision, rounding_mode) + yield from tests_div_7_8(precision, rounding_mode) + yield from tests_div_9(precision, rounding_mode) + + +def divTests(test_f: TextIO, cover_f: TextIO) -> None: + for precision in FLOAT_FMTS: + for rounding_mode in ROUNDING_MODES: + for a, b in getDivTests(precision, rounding_mode): + run_and_store_test_vector( + f"{OP_DIV}_{rounding_mode}_{a}_{b}_{32 * '0'}_{precision}_{32 * '0'}_{precision}_00", + test_f, + cover_f, + ) + + +def tests_add_sub_1_2(precision: str, rounding_mode: str, test_f: TextIO, cover_f: TextIO) -> None: + m_bits = MANTISSA_BITS[precision] + e_bits = EXPONENT_BITS[precision] + e_bias = EXPONENT_BIAS[precision] + sn_exp = UNBIASED_EXP[precision][0] - 1 + + maxMant = (1 << m_bits) - 1 + # List for each subNormal: + # operation, sign + # add posSN, add negSN, sub posSN, sub negSN + testList = [["add", 0], ["add", 1], ["sub", 0], ["sub", 1]] + for test in testList: + sign = str(test[1]) + if test[0] == "add": + op = OP_ADD + seed("b5" + op + precision + rounding_mode + "a" + str(test[1])) + a = random.randint(1, maxMant // 2) + seed("b5" + op + precision + rounding_mode + "b" + str(test[1])) + b = random.randint(1, maxMant // 2) + else: + op = OP_SUB + seed("b5" + op + precision + rounding_mode + "b" + str(test[1])) + b = random.randint(1, maxMant // 2) + seed("b5" + op + precision + rounding_mode + "a" + str(test[1])) + a = random.randint(b, maxMant) + a = f"{a:0{m_bits}b}" + b = f"{b:0{m_bits}b}" + a_fp = generate_FP(e_bits, sign, sn_exp, a, e_bias) + b_fp = generate_FP(e_bits, sign, sn_exp, b, e_bias) + run_and_store_test_vector( + f"{op}_{rounding_mode}_{a_fp}_{b_fp}_{32 * '0'}_{precision}_{32 * '0'}_{precision}_00", test_f, cover_f + ) + + +def tests_add_sub_3_4(precision: str, rounding_mode: str, test_f: TextIO, cover_f: TextIO) -> None: + m_bits = MANTISSA_BITS[precision] + e_bits = EXPONENT_BITS[precision] + e_bias = EXPONENT_BIAS[precision] + sn_exp = UNBIASED_EXP[precision][0] - 1 + + maxMant = (1 << m_bits) - 1 + + # The IBM paper only has +minSN for 3 and -minSN for 4 + testList = [["add", 0], ["add", 1], ["sub", 0], ["sub", 1]] + for test in testList: + seed("b5" + str(test[0]) + precision + rounding_mode + "a" + str(test[1])) + a = random.randint(2, maxMant) + seed("b5" + str(test[0]) + precision + rounding_mode + "b" + str(test[1])) + b = a - 1 + if test[0] == "add": + op = OP_ADD + a_sign = test[1] + b_sign = (int(test[1]) + 1) % 2 + else: + op = OP_SUB + a_sign = test[1] + b_sign = a_sign + + a = f"{a:0{m_bits}b}" + b = f"{b:0{m_bits}b}" + + a_fp = generate_FP(e_bits, str(a_sign), sn_exp, a, e_bias) + b_fp = generate_FP(e_bits, str(b_sign), sn_exp, b, e_bias) + + run_and_store_test_vector( + f"{op}_{rounding_mode}_{a_fp}_{b_fp}_{32 * '0'}_{precision}_{32 * '0'}_{precision}_00", test_f, cover_f + ) + + +def tests_add_sub_5_6(precision: str, rounding_mode: str, test_f: TextIO, cover_f: TextIO) -> None: + m_bits = MANTISSA_BITS[precision] + e_bits = EXPONENT_BITS[precision] + e_bias = EXPONENT_BIAS[precision] + sn_exp = UNBIASED_EXP[precision][0] - 1 + + maxMant = (1 << m_bits) - 1 + + # Add operations: + normDist = -3 + while normDist <= 3: + for sign in [0, 1]: + seed("b5" + "OP_ADD" + precision + rounding_mode + "a" + str(normDist) + str(sign)) + a = random.randint(3, maxMant) + b = maxMant - a + 1 + normDist + a_fp = generate_FP(e_bits, str(sign), sn_exp, f"{a:0{m_bits}b}", e_bias) + b_fp = generate_FP(e_bits, str(sign), sn_exp, f"{b:0{m_bits}b}", e_bias) + run_and_store_test_vector( + f"{OP_ADD}_{rounding_mode}_{a_fp}_{b_fp}_{32 * '0'}_{precision}_{32 * '0'}_{precision}_00", + test_f, + cover_f, + ) + normDist += 1 + + # Subtraction operations + normDist = -3 + while normDist <= 3: + for sign in [0, 1]: + seed("b5" + "OP_SUB" + precision + rounding_mode + "a" + str(normDist) + str(sign)) + a = random.randint(3, maxMant - 3) + b = a + normDist + b_exp = sign + sn_exp + a_exp = sn_exp + ((sign + 1) % 2) + a_fp = generate_FP(e_bits, str(0), a_exp, f"{a:0{m_bits}b}", e_bias) + b_fp = generate_FP(e_bits, str(0), b_exp, f"{b:0{m_bits}b}", e_bias) + run_and_store_test_vector( + f"{OP_SUB}_{rounding_mode}_{a_fp}_{b_fp}_{32 * '0'}_{precision}_{32 * '0'}_{precision}_00", + test_f, + cover_f, + ) + normDist += 1 + + +def tests_add_sub_9(precision: str, rounding_mode: str, test_f: TextIO, cover_f: TextIO) -> None: + m_bits = MANTISSA_BITS[precision] + e_bits = EXPONENT_BITS[precision] + e_bias = EXPONENT_BIAS[precision] + sn_exp = UNBIASED_EXP[precision][0] - 1 + + maxMant = (1 << m_bits) - 1 + for i in range(1, 7): + for op in [OP_ADD, OP_SUB]: + a_exp = sn_exp + b_exp = sn_exp + i + seed("b5" + str(op) + precision + rounding_mode + "a" + str(i)) + a_mant = random.randint(0, maxMant) + a_sign = str(random.randint(0, 1)) + seed("b5" + str(op) + precision + rounding_mode + "b" + str(i)) + b_mant = random.randint(a_mant, maxMant) + b_sign = str(random.randint(0, 1)) + a_fp = generate_FP(e_bits, a_sign, a_exp, f"{a_mant:0{m_bits}b}", e_bias) + b_fp = generate_FP(e_bits, b_sign, b_exp, f"{b_mant:0{m_bits}b}", e_bias) + run_and_store_test_vector( + f"{op}_{rounding_mode}_{a_fp}_{b_fp}_{32 * '0'}_{precision}_{32 * '0'}_{precision}_00", test_f, cover_f + ) + + +def addSubTests(test_f: TextIO, cover_f: TextIO) -> None: + for precision in FLOAT_FMTS: + for rounding_mode in ROUNDING_MODES: + # tests_add_sub_1_2(precision, rounding_mode, test_f, cover_f) + # tests_add_sub_3_4(precision, rounding_mode, test_f, cover_f) + tests_add_sub_5_6(precision, rounding_mode, test_f, cover_f) + # tests_add_sub_9(precision, rounding_mode, test_f, cover_f) + + +@register_model("B5") +def main(test_f: TextIO, cover_f: TextIO) -> None: + convertTests(test_f, cover_f) # Generating Too little tests + multiplyTests(test_f, cover_f) + addSubTests(test_f, cover_f) # Generating too many tests + fmaTests(test_f, cover_f) + divTests(test_f, cover_f) diff --git a/src/cover_float/testgen/__init__.py b/src/cover_float/testgen/__init__.py index 542ed81..d36e24c 100644 --- a/src/cover_float/testgen/__init__.py +++ b/src/cover_float/testgen/__init__.py @@ -16,6 +16,7 @@ import cover_float.testgen.B1 as B1 import cover_float.testgen.B2 as B2 import cover_float.testgen.B3 as B3 +import cover_float.testgen.B5 as B5 import cover_float.testgen.B6 as B6 import cover_float.testgen.B7 as B7 import cover_float.testgen.B8 as B8 @@ -27,6 +28,7 @@ import cover_float.testgen.B14 as B14 import cover_float.testgen.B15 as B15 import cover_float.testgen.B16 as B16 +import cover_float.testgen.B18 as B18 import cover_float.testgen.B20 as B20 import cover_float.testgen.B21 as B21 import cover_float.testgen.B25 as B25 @@ -39,6 +41,7 @@ "B1", "B2", "B3", + "B5", "B6", "B7", "B8", @@ -50,6 +53,7 @@ "B14", "B15", "B16", + "B18", "B20", "B21", "B25",