/* Copyright (C) 2012 IBM Author: Maynard Johnson This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. The GNU General Public License is contained in the file COPYING. */ #include #include #include #if defined(HAS_DFP) register double f14 __asm__ ("fr14"); register double f15 __asm__ ("fr15"); register double f16 __asm__ ("fr16"); register double f17 __asm__ ("fr17"); register double f18 __asm__ ("fr18"); register double f19 __asm__ ("fr19"); typedef unsigned char Bool; #define True 1 #define False 0 #define ALLCR "cr0","cr1","cr2","cr3","cr4","cr5","cr6","cr7" #define SET_CR(_arg) \ __asm__ __volatile__ ("mtcr %0" : : "b"(_arg) : ALLCR ); #define SET_XER(_arg) \ __asm__ __volatile__ ("mtxer %0" : : "b"(_arg) : "xer" ); #define GET_CR(_lval) \ __asm__ __volatile__ ("mfcr %0" : "=b"(_lval) ) #define GET_XER(_lval) \ __asm__ __volatile__ ("mfxer %0" : "=b"(_lval) ) #define GET_CR_XER(_lval_cr,_lval_xer) \ do { GET_CR(_lval_cr); GET_XER(_lval_xer); } while (0) #define SET_CR_ZERO \ SET_CR(0) #define SET_XER_ZERO \ SET_XER(0) #define SET_CR_XER_ZERO \ do { SET_CR_ZERO; SET_XER_ZERO; } while (0) #define SET_FPSCR_ZERO \ do { double _d = 0.0; \ __asm__ __volatile__ ("mtfsf 0xFF, %0" : : "f"(_d) ); \ } while (0) #define GET_FPSCR(_arg) \ __asm__ __volatile__ ("mffs %0" : "=f"(_arg) ) #define SET_FPSCR_DRN \ __asm__ __volatile__ ("mtfsf 1, %0, 0, 1" : : "f"(f14) ) // The assembly-level instructions being tested static Bool do_dot; static void _test_dadd (void) { if (do_dot) __asm__ __volatile__ ("dadd. %0, %1, %2" : "=f" (f18) : "f" (f14),"f" (f16)); else __asm__ __volatile__ ("dadd %0, %1, %2" : "=f" (f18) : "f" (f14),"f" (f16)); } static void _test_dsub (void) { if (do_dot) __asm__ __volatile__ ("dsub. %0, %1, %2" : "=f" (f18) : "f" (f14),"f" (f16)); else __asm__ __volatile__ ("dsub %0, %1, %2" : "=f" (f18) : "f" (f14),"f" (f16)); } static void _test_dmul (void) { if (do_dot) __asm__ __volatile__ ("dmul. %0, %1, %2" : "=f" (f18) : "f" (f14),"f" (f16)); else __asm__ __volatile__ ("dmul %0, %1, %2" : "=f" (f18) : "f" (f14),"f" (f16)); } static void _test_ddiv (void) { if (do_dot) __asm__ __volatile__ ("ddiv. %0, %1, %2" : "=f" (f18) : "f" (f14),"f" (f16)); else __asm__ __volatile__ ("ddiv %0, %1, %2" : "=f" (f18) : "f" (f14),"f" (f16)); } // Quad DFP arith instructions static void _test_daddq (void) { if (do_dot) __asm__ __volatile__ ("daddq. %0, %1, %2" : "=f" (f18) : "f" (f14),"f" (f16)); else __asm__ __volatile__ ("daddq %0, %1, %2" : "=f" (f18) : "f" (f14),"f" (f16)); } static void _test_dsubq (void) { if (do_dot) __asm__ __volatile__ ("dsubq. %0, %1, %2" : "=f" (f18) : "f" (f14),"f" (f16)); else __asm__ __volatile__ ("dsubq %0, %1, %2" : "=f" (f18) : "f" (f14),"f" (f16)); } static void _test_dmulq (void) { if (do_dot) __asm__ __volatile__ ("dmulq. %0, %1, %2" : "=f" (f18) : "f" (f14),"f" (f16)); else __asm__ __volatile__ ("dmulq %0, %1, %2" : "=f" (f18) : "f" (f14),"f" (f16)); } static void _test_ddivq (void) { if (do_dot) __asm__ __volatile__ ("ddivq. %0, %1, %2" : "=f" (f18) : "f" (f14),"f" (f16)); else __asm__ __volatile__ ("ddivq %0, %1, %2" : "=f" (f18) : "f" (f14),"f" (f16)); } static void _test_mffs (void) { __asm__ __volatile__ ("mffs %0" : "=f"(f14)); } static void _test_mtfsf (int upper) { if (upper) __asm__ __volatile__ ("mtfsf 1, %0, 0, 1" : : "f"(f14) ); else __asm__ __volatile__ ("mtfsf 1, %0, 0, 0" : : "f"(f14) ); } typedef void (*test_func_t)(void); typedef struct test_table { test_func_t test_category; char * name; } test_table_t; /* * 345.0DD (0x2207c00000000000 0xe50) * 1.2300e+5DD (0x2207c00000000000 0x14c000) * -16.0DD (0xa207c00000000000 0xe0) * 0.00189DD (0x2206c00000000000 0xcf) * -4.1235DD (0xa205c00000000000 0x10a395bcf) * 9.8399e+20DD (0x2209400000000000 0x253f1f534acdd4) * 0DD (0x2208000000000000 0x0) * 0DD (0x2208000000000000 0x0) * infDD (0x7800000000000000 0x0) * nanDD (0x7c00000000000000 0x0 */ static unsigned long long dfp128_vals[] = { // Some finite numbers 0x2207c00000000000ULL, 0x0000000000000e50ULL, 0x2207c00000000000ULL, 0x000000000014c000ULL, 0xa207c00000000000ULL, 0x00000000000000e0ULL, 0x2206c00000000000ULL, 0x00000000000000cfULL, 0xa205c00000000000ULL, 0x000000010a395bcfULL, 0x6209400000fd0000ULL, 0x00253f1f534acdd4ULL, // huge number 0x000400000089b000ULL, 0x0a6000d000000049ULL, // very small number // flavors of zero 0x2208000000000000ULL, 0x0000000000000000ULL, 0xa208000000000000ULL, 0x0000000000000000ULL, // negative 0xa248000000000000ULL, 0x0000000000000000ULL, // flavors of NAN 0x7c00000000000000ULL, 0x0000000000000000ULL, // quiet 0xfc00000000000000ULL, 0xc00100035b007700ULL, 0x7e00000000000000ULL, 0xfe000000d0e0a0d0ULL, // signaling // flavors of Infinity 0x7800000000000000ULL, 0x0000000000000000ULL, 0xf800000000000000ULL, 0x0000000000000000ULL, // negative 0xf900000000000000ULL, 0x0000000000000000ULL }; static unsigned long long dfp64_vals[] = { // various finite numbers 0x2234000000000e50ULL, 0x223400000014c000ULL, 0xa2340000000000e0ULL,// negative 0x22240000000000cfULL, 0xa21400010a395bcfULL,// negative 0x6e4d3f1f534acdd4ULL,// huge number 0x000400000089b000ULL,// very small number // flavors of zero 0x2238000000000000ULL, 0xa238000000000000ULL, 0x4248000000000000ULL, // flavors of NAN 0x7e34000000000111ULL, 0xfe000000d0e0a0d0ULL,//signaling 0xfc00000000000000ULL,//quiet // flavors of Infinity 0x7800000000000000ULL, 0xf800000000000000ULL,//negative 0x7a34000000000000ULL, }; typedef struct dfp_test_args { int fra_idx; int frb_idx; } dfp_test_args_t; // Index pairs from dfp64_vals or dfp128_vals array to be used with dfp_two_arg_tests static dfp_test_args_t dfp_2args_x2[] = { {0, 1}, {2, 1}, {3, 4}, {0, 6}, {2, 4}, {5, 1}, {5, 2}, {7, 8}, {7, 1}, {9, 15}, {8, 12}, {7, 11}, {13, 2}, {13, 14}, {15, 12}, {14, 11}, {12, 12}, {12, 11}, {11, 11} }; // Index pairs from dfp64_vals array to be used with dfp_two_arg_tests static dfp_test_args_t dfp_2args_x1[] = { {0, 1}, {2, 1}, {3, 4}, {0, 6}, {2, 4}, {5, 1}, {5, 2}, {7, 1}, {7, 2}, {8, 0}, {8, 1}, {8, 2}, {7, 8}, {12, 14}, {12, 1}, {12, 13}, {12, 12}, {12, 11}, {11, 14}, {11, 0}, {11, 13}, {11, 11}, {14, 14}, {14, 3}, {14, 15}, }; typedef enum { LONG_TEST, QUAD_TEST } precision_type_t; typedef struct dfp_test { test_func_t test_func; const char * name; dfp_test_args_t * targs; int num_tests; precision_type_t precision; const char * op; Bool cr_supported; } dfp_test_t; static dfp_test_t dfp_two_arg_tests[] = { { &_test_dadd, "dadd", dfp_2args_x1, 25, LONG_TEST, "+", False}, { &_test_dsub, "dsub", dfp_2args_x1, 25, LONG_TEST, "-", False}, { &_test_dmul, "dmul", dfp_2args_x2, 19, LONG_TEST, "*", False}, { &_test_ddiv, "ddiv", dfp_2args_x2, 19, LONG_TEST, "/", False}, { &_test_daddq, "daddq", dfp_2args_x1, 25, QUAD_TEST, "+", False}, { &_test_dsubq, "dsubq", dfp_2args_x1, 25, QUAD_TEST, "-", False}, { &_test_dmulq, "dmulq", dfp_2args_x2, 19, QUAD_TEST, "*", False}, { &_test_ddivq, "ddivq", dfp_2args_x2, 19, QUAD_TEST, "/", False}, { NULL, NULL, NULL, 0, 0, NULL} }; static void test_dfp_two_arg_ops(void) { test_func_t func; unsigned long long u0, u0x, u1, u1x; double res, d0, d1, *d0p, *d1p; double d0x, d1x, *d0xp, *d1xp; int k = 0; u0x = u1x = 0; d0p = &d0; d0xp = &d0x; d1p = &d1; d1xp = &d1x; while ((func = dfp_two_arg_tests[k].test_func)) { int i, repeat = 1; dfp_test_t test_group = dfp_two_arg_tests[k]; do_dot = False; again: for (i = 0; i < test_group.num_tests; i++) { unsigned int condreg; unsigned int flags; if (test_group.precision == LONG_TEST) { u0 = dfp64_vals[test_group.targs[i].fra_idx]; u1 = dfp64_vals[test_group.targs[i].frb_idx]; } else { u0 = dfp128_vals[test_group.targs[i].fra_idx * 2]; u0x = dfp128_vals[(test_group.targs[i].fra_idx * 2) + 1]; u1 = dfp128_vals[test_group.targs[i].frb_idx * 2]; u1x = dfp128_vals[(test_group.targs[i].frb_idx * 2) + 1]; } *(unsigned long long *)d0p = u0; *(unsigned long long *)d1p = u1; f14 = d0; f16 = d1; if (test_group.precision == QUAD_TEST) { *(unsigned long long *)d0xp = u0x; *(unsigned long long *)d1xp = u1x; f15 = d0x; f17 = d1x; } SET_FPSCR_ZERO; SET_CR_XER_ZERO; (*func)(); GET_CR(flags); res = f18; condreg = (flags & 0x000000f0) >> 4; printf("%s%s %016llx", test_group.name, do_dot? "." : "", u0); if (test_group.precision == LONG_TEST) { printf(" %s %016llx => %016llx", test_group.op, u1, *((unsigned long long *)(&res))); } else { double resx = f19; printf(" %016llx %s %016llx %016llx ==> %016llx %016llx", u0x, test_group.op, u1, u1x, *((unsigned long long *)(&res)), *((unsigned long long *)(&resx))); } if (test_group.cr_supported) printf(" (cr = %08x)\n", condreg); else printf("\n"); } printf("\n"); if (repeat) { repeat = 0; do_dot = True; goto again; } k++; printf( "\n" ); } } void test_move_toFrom_fpscr(void) { #define BFP_MAX_RM 3 int shift = 0; unsigned long long i, max_rm, expected_val; double fpscr_in, fpscr_out; unsigned long long * hex_fpscr_in = (unsigned long long *)&fpscr_in; unsigned long long * hex_fpscr_out = (unsigned long long *)&fpscr_out; max_rm = 4; again: /* NOTE: The first time through this loop is for setting the binary * floating point rounding mode (bits 62:63 of FPSCR). The second time * through is for setting the decimal floating point rounding mode * (bits 29:31 of FPSCR). In the second time through this loop, the value * returned should include the final binary FP rounding mode, along with * the decimal FP rounding modes. */ for (i = 0; i < max_rm; i++) { *hex_fpscr_in = (i << shift); f14 = fpscr_in; _test_mtfsf(max_rm/8); *hex_fpscr_in = 0ULL; f14= fpscr_in; _test_mffs(); fpscr_out = f14; if (max_rm == 4) { *hex_fpscr_out &= (max_rm - 1) << shift; expected_val = i << shift; } else { *hex_fpscr_out &= BFP_MAX_RM | ((max_rm - 1) << shift); expected_val = (i << shift) | BFP_MAX_RM; } printf("FPSCR %s floating point rounding mode %016llx == %016llx? %s\n", (max_rm == 8) ? "decimal" : "binary", *hex_fpscr_out, expected_val, (expected_val == *hex_fpscr_out) ? "yes" : "no"); } if (max_rm == 4) { max_rm = 8; shift = 32; goto again; } } void test_rounding_modes(void) { int j; unsigned long long u0, u1, rm_idx; double res, d0, d1, *d0p, *d1p, fpscr; unsigned long long * hex_fpscr = (unsigned long long *)&fpscr; u0 = 0x26cc3f1f534acdd4ULL; u1 = 0x27feff197a42ba06ULL; d0p = &d0; d1p = &d1; for (j = 0; j < 12; j++) { for (rm_idx = 0; rm_idx < 8; rm_idx++) { *hex_fpscr = 0ULL; __asm__ __volatile__ ("mffs %0" : "=f"(f14)); fpscr = f14; *hex_fpscr &= 0xFFFFFFF0FFFFFFFFULL; *hex_fpscr |= (rm_idx << 32); f14 = fpscr; SET_FPSCR_DRN; *(unsigned long long *)d0p = u0; *(unsigned long long *)d1p = u1; f14 = d0; f16 = d1; _test_dmul(); res = f18; printf("test #%d: dmul with rounding mode %d: %016llx * %016llx => %016llx\n", j, (int)rm_idx, u0, u1, *((unsigned long long *)(&res))); printf("\n"); } // Changing the least significant bit of one of the dmul arguments give us more // opportunities for different rounding modes to yield different results which // can then be validated. u0++; } } static test_table_t all_tests[] = { { &test_dfp_two_arg_ops, "Test DFP arithmetic instructions"}, { &test_rounding_modes, "Test DFP rounding modes"}, { &test_move_toFrom_fpscr, "Test move to/from FPSCR"}, { NULL, NULL } }; #endif // HAS_DFP int main() { #if defined(HAS_DFP) test_table_t aTest; test_func_t func; int i = 0; while ((func = all_tests[i].test_category)) { aTest = all_tests[i]; printf( "%s\n", aTest.name ); (*func)(); i++; } #endif // HAS_DFP return 0; }