Add V6_vconv_qf16_f8 and V6_vconv_f8_qf16, which use FP8 with exponent bias 7.
Reviewed-by: Marco Liebel <[email protected]> Signed-off-by: Brian Cain <[email protected]> --- target/hexagon/mmvec/hvx_ieee_fp.h | 8 ++ target/hexagon/mmvec/macros.h | 20 ++++ target/hexagon/mmvec/hvx_ieee_fp.c | 21 ++++ tests/tcg/hexagon/check_rev_gating.c | 6 + tests/tcg/hexagon/qfloat_v81.c | 111 +++++++++++++++++++ target/hexagon/tag_rev_info.c.inc | 2 + target/hexagon/imported/mmvec/encode_ext.def | 2 + target/hexagon/imported/mmvec/ext.idef | 6 + 8 files changed, 176 insertions(+) diff --git a/target/hexagon/mmvec/hvx_ieee_fp.h b/target/hexagon/mmvec/hvx_ieee_fp.h index 229ee0ee516..996feae67b3 100644 --- a/target/hexagon/mmvec/hvx_ieee_fp.h +++ b/target/hexagon/mmvec/hvx_ieee_fp.h @@ -52,6 +52,14 @@ float16 f8_to_f16(uint8_t a, float_status *fp_status); */ uint8_t f16_to_f8(float16 a, bool sat, float_status *fp_status); +/* + * The v81 conversions between FP8 and qf16 read the same encoding with + * exponent bias 7 instead of 8, so every value is twice as large. + * NaN is still 0x80 and narrowing never saturates. + */ +float16 f8_bias7_to_f16(uint8_t a, float_status *fp_status); +uint8_t f16_to_f8_bias7(float16 a, float_status *fp_status); + float16 fp_add_hf_f8(uint8_t a1, uint8_t a2, float_status *fp_status); float16 fp_sub_hf_f8(uint8_t a1, uint8_t a2, float_status *fp_status); float16 fp_mult_hf_f8(uint8_t a1, uint8_t a2, float_status *fp_status); diff --git a/target/hexagon/mmvec/macros.h b/target/hexagon/mmvec/macros.h index 0e643b29109..6e908391a48 100644 --- a/target/hexagon/mmvec/macros.h +++ b/target/hexagon/mmvec/macros.h @@ -417,6 +417,26 @@ #define fQF_VILOG2(TYPE, A, AEXT) qf_vilog2(TYPE, A, AEXT) +/* + * Convert an FP8 element to the qf16 element (V).qf16[i]. FP8 is exactly + * representable in hf, so go through that. The one FP8 NaN becomes the + * negative qf16 NaN. + */ +#define fCONVERT_F8_TO_QF16(V, A) \ + do { \ + if ((A) == F8_NAN) { \ + (V).qf16[i] = (int16_t)(extqf16_neg_nan >> 4); \ + set_extended_bits(&(V), i, 16, \ + extqf16_neg_nan & EXT16_BITMASK); \ + } else { \ + unfloat u = fPARSEHF(f8_bias7_to_f16(A, &env->hvx_fp_status)); \ + INIT_UNFLOAT(v) \ + v.exp = E_MIN_EXTQF16; \ + v.sign = 1; \ + fQFADD(16, V, u, v); \ + } \ + } while (0) + #define fCONVERT_QF32_TO_SF(A, AEXT) \ (qfloat_is_extended(env) ? \ conv_sf_extqf32(A, AEXT, CVI_QFRND_MODE) : legacy_conv_sf_qf32(A)) diff --git a/target/hexagon/mmvec/hvx_ieee_fp.c b/target/hexagon/mmvec/hvx_ieee_fp.c index 6394c175a13..be8bc9b9485 100644 --- a/target/hexagon/mmvec/hvx_ieee_fp.c +++ b/target/hexagon/mmvec/hvx_ieee_fp.c @@ -215,6 +215,27 @@ uint8_t f16_to_f8(float16 a, bool sat, float_status *fp_status) return code ? sign | code : 0; } +float16 f8_bias7_to_f16(uint8_t a, float_status *fp_status) +{ + float_status exact = { 0 }; + + /* Preserve NaN's default value from fp_status. */ + return a == F8_NAN ? f8_to_f16(a, fp_status) : + float16_mul(f8_to_f16(a, fp_status), make_float16(0x4000), &exact); +} + +uint8_t f16_to_f8_bias7(float16 a, float_status *fp_status) +{ + float_status halve = { 0 }; + + if (float16_is_any_nan(a)) { + return F8_NAN; + } + /* Halving only loses insignificant bits before FP8 rounding. */ + return f16_to_f8(float16_mul(a, make_float16(0x3800), &halve), false, + fp_status); +} + float16 fp_add_hf_f8(uint8_t a1, uint8_t a2, float_status *fp_status) { return float16_add(f8_to_f16(a1, fp_status), f8_to_f16(a2, fp_status), diff --git a/tests/tcg/hexagon/check_rev_gating.c b/tests/tcg/hexagon/check_rev_gating.c index df6cb426c62..015ce607ec7 100644 --- a/tests/tcg/hexagon/check_rev_gating.c +++ b/tests/tcg/hexagon/check_rev_gating.c @@ -150,6 +150,10 @@ TRY_FUNC(v79hvx_vfmin_f8, TRY_FUNC(v81hvx_veqhf, ".word 0x1f82c11c /* q0 = vcmp.eq(v1.hf, v2.hf) */\n") +TRY_FUNC(v81hvx_vconv_qf16_f8, + ".word 0x1e0ce2a0 /* v1:0.qf16 = v2.f8 */\n") +TRY_FUNC(v81hvx_vconv_f8_qf16, + ".word 0x1e0ce4e3 /* v3.f8 = v4.qf16 */\n") int main(void) { @@ -203,6 +207,8 @@ int main(void) assert(try_v79hvx_vfmin_f8() == SIGILL); assert(try_v81hvx_veqhf() == SIGILL); + assert(try_v81hvx_vconv_qf16_f8() == SIGILL); + assert(try_v81hvx_vconv_f8_qf16() == SIGILL); assert(signals_handled == expected_signals); diff --git a/tests/tcg/hexagon/qfloat_v81.c b/tests/tcg/hexagon/qfloat_v81.c index 4fdd4809071..d7db6ccefe9 100644 --- a/tests/tcg/hexagon/qfloat_v81.c +++ b/tests/tcg/hexagon/qfloat_v81.c @@ -108,11 +108,122 @@ static void test_qf32_bf_conversion(void) check_output_h(__LINE__, 1); } +/* v81 FP8/qf16 conversions use exponent bias 7, unlike v79 vcvt. */ +#define F8_NAN 0x80 +#define QF16_NAN 0x801f + +static void f8_to_qf16_parts(uint8_t f8, int *sig11, int *exp) +{ + int e = (f8 >> 3) & 0xf; + int sig = e ? 8 | (f8 & 7) : f8 & 7; + int p = 31 - __builtin_clz(sig); + + *exp = p + (e ? e : 1) - 10; + *sig11 = sig << (9 - p); +} + +static uint16_t ref_f8_to_qf16(uint8_t f8) +{ + int sig11, exp; + + if (f8 == 0) { + return 0; + } + if (f8 == F8_NAN) { + return QF16_NAN; + } + f8_to_qf16_parts(f8, &sig11, &exp); + /* qf16 negative significands are complemented. */ + if (f8 & 0x80) { + sig11 = ~sig11 & 0x7ff; + } + return sig11 << 5 | (exp + 15); +} + +#define WIDEN_VEC(I) ((I) % 2) +#define WIDEN_IDX(I) (2 * ((I) / 4) + (((I) % 4) / 2)) + +static void test_vconv_qf16_f8(void) +{ + for (int blk = 0; blk < 2; blk++) { + MMVector input; + + for (int i = 0; i < MAX_VEC_SIZE_BYTES; i++) { + input.ub[i] = blk * MAX_VEC_SIZE_BYTES + i; + expect[WIDEN_VEC(i)].uh[WIDEN_IDX(i)] = ref_f8_to_qf16(input.ub[i]); + } + asm volatile( + "v2 = vmemu(%[in])\n" + "v1:0.qf16 = v2.f8\n" + "vmemu(%[out0]) = v0\n" + "vmemu(%[out1]) = v1\n" + : + : [in] "r"(&input), [out0] "r"(&output[0]), + [out1] "r"(&output[1]) + : "v0", "v1", "v2", "memory"); + check_output_uh(__LINE__, 2); + } +} + +/* Round half precision to FP8, nearest-even without saturation. */ +static uint8_t ref_hf_to_f8(uint16_t hf) +{ + _Float16 h; + double x, ax; + int sign = hf >> 15; + int code; + + memcpy(&h, &hf, sizeof(h)); + x = h; + if (x != x || __builtin_isinf(x)) { + return F8_NAN; + } + ax = __builtin_fabs(x); + if (ax < 1.0 / 64) { + /* Subnormal units are 2^-9. */ + code = __builtin_rint(ax * 512); + } else { + int e = __builtin_ilogb(ax); + int m = __builtin_rint(__builtin_scalbn(ax, 3 - e)); + + code = (e + 7) * 8 + m - 8; + } + if (code > 0x7f) { + return F8_NAN; + } + return code ? sign << 7 | code : 0; +} + +/* Form qf16 values from each half-precision input. */ +static void test_vconv_f8_qf16(void) +{ + for (int base = 0; base < 0x10000; base += MAX_VEC_SIZE_BYTES / 2) { + MMVector input; + + for (int i = 0; i < MAX_VEC_SIZE_BYTES / 2; i++) { + input.uh[i] = base + i; + expect[0].uh[i] = ref_hf_to_f8(base + i); + } + asm volatile( + "v2 = vmemu(%[in])\n" + "v3 = vxor(v3, v3)\n" + "v0.qf16 = vadd(v2.hf, v3.hf)\n" + "v1.f8 = v0.qf16\n" + "vmemu(%[out]) = v1\n" + : + : [in] "r"(&input), [out] "r"(&output[0]) + : "v0", "v1", "v2", "v3", "memory"); + check_output_uh(__LINE__, 1); + } +} + int main() { test_qf16(); test_qf32(); test_qf32_bf_conversion(); + test_vconv_qf16_f8(); + test_vconv_f8_qf16(); puts(err ? "FAIL" : "PASS"); return err ? 1 : 0; diff --git a/target/hexagon/tag_rev_info.c.inc b/target/hexagon/tag_rev_info.c.inc index 92559052516..61ada1f7bf9 100644 --- a/target/hexagon/tag_rev_info.c.inc +++ b/target/hexagon/tag_rev_info.c.inc @@ -713,6 +713,8 @@ static const struct tag_rev_info tag_rev_info[XX_LAST_OPCODE] = { [V6_vconv_h_hf_rnd] = { .introduced = HEX_VER_V81, .removed = HEX_VER_NONE }, + [V6_vconv_f8_qf16] = { .introduced = HEX_VER_V81, .removed = HEX_VER_NONE }, + [V6_vconv_qf16_f8] = { .introduced = HEX_VER_V81, .removed = HEX_VER_NONE }, [V6_vconv_bf_qf32] = { .introduced = HEX_VER_V81, .removed = HEX_VER_NONE }, [V2_vallocframe_imm] = { .introduced = HEX_VER_V83, diff --git a/target/hexagon/imported/mmvec/encode_ext.def b/target/hexagon/imported/mmvec/encode_ext.def index 5941e09df93..7139be12e59 100644 --- a/target/hexagon/imported/mmvec/encode_ext.def +++ b/target/hexagon/imported/mmvec/encode_ext.def @@ -928,7 +928,9 @@ DEF_ENC(V6_vconv_bf_qf32,"00011110--0-0110PP1uuuuu111ddddd") DEF_ENC(V6_vconv_hf_qf16,"00011110--0-0100PP1uuuuu011ddddd") DEF_ENC(V6_vconv_hf_qf32,"00011110--0-0100PP1uuuuu110ddddd") DEF_ENC(V6_vconv_qf16_hf,"00011110--0-1100PP1uuuuu100ddddd") +DEF_ENC(V6_vconv_qf16_f8,"00011110--0-1100PP1uuuuu101ddddd") DEF_ENC(V6_vconv_qf16_qf16,"00011110--0-1100PP1uuuuu110ddddd") +DEF_ENC(V6_vconv_f8_qf16,"00011110--0-1100PP1uuuuu111ddddd") DEF_ENC(V6_vconv_qf32_qf32,"00011110--0-1101PP1uuuuu111ddddd") DEF_ENC(V6_vconv_qf32_sf,"00011110--0-1101PP1uuuuu110ddddd") DEF_ENC(V6_vconv_sf_qf32,"00011110--0-0100PP1uuuuu000ddddd") diff --git a/target/hexagon/imported/mmvec/ext.idef b/target/hexagon/imported/mmvec/ext.idef index 1b8afee8b6f..aa74f4f423a 100644 --- a/target/hexagon/imported/mmvec/ext.idef +++ b/target/hexagon/imported/mmvec/ext.idef @@ -3519,6 +3519,12 @@ EXTINSN(V6_vconv_hf_qf32,"Vd32.hf=Vuu32.qf32",ATTRIBS(A_EXTENSION,A_CVI,A_CVI_VS EXTINSN(V6_vconv_qf16_hf,"Vd32.qf16=Vu32.hf",ATTRIBS(A_EXTENSION,A_CVI,A_CVI_VS,A_HVX_FLT),"Vector conversion of qf16 format to ieee HF", { fHIDE(int i;) fVFOREACH(16, i) { fHIDE(unfloat )u = fPARSEHF(VuV.hf[i]); INIT_UNFLOAT(v) v.exp = E_MIN_EXTQF16; v.sign = 1; fQFADD(16,VdV,u,v) ; } }) +EXTINSN(V6_vconv_qf16_f8,"Vdd32.qf16=Vu32.f8",ATTRIBS(A_EXTENSION,A_CVI,A_CVI_VP_VS,A_HVX_FLT),"Vector convert to qf16: f8 widen to qf16", +{ fHIDE(int i;) fVFOREACH(16, i) { fCONVERT_F8_TO_QF16(VddV.v[0], VuV.f8[2*i]); fCONVERT_F8_TO_QF16(VddV.v[1], VuV.f8[2*i+1]); } }) + +EXTINSN(V6_vconv_f8_qf16,"Vd32.f8=Vu32.qf16",ATTRIBS(A_EXTENSION,A_CVI,A_CVI_VS,A_HVX_FLT),"Vector convert qf16 to f8", +{ fHIDE(int i;) fVFOREACH(16, i) { VdV.uh[i] = f16_to_f8_bias7(fCONVERT_QF16_TO_HF(VuV.qf16[i], GET_VEXT(VuV, i, 16)), &env->hvx_fp_status); } }) + EXTINSN(V6_vconv_qf16_qf16,"Vd32.qf16=Vu32.qf16",ATTRIBS(A_EXTENSION,A_CVI,A_CVI_VS,A_HVX_FLT),"Vector conversion of qf16 to qf16", { fHIDE(int i;) fVFOREACH(16, i) { fHIDE(unfloat )u = fPARSEQF_EXT(16,VuV,i); INIT_UNFLOAT(v) v.exp = E_MIN_EXTQF16; v.sign = 1; fQFADD(16,VdV,u,v) ; } }) -- 2.34.1
