fpu: Add conversion routines for OCP FP8 E4M3
Reviewed-by: Chao Liu <chao.liu.zevorn@gmail.com> Signed-off-by: Max Chou <max.chou@sifive.com> [rth: Split out of a larger patch; adjust overflow detection.] Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
Max Chou committed
Feb 5, 2026 at 20:45 UTC
27e989f99c87adc522fc436e1ef3c9cfbdf30d61
4 files changed
+109
-3
fpu/softfloat-parts.c.inc
+42
-3
@@ -242,6 +242,15 @@ static void partsN(canonicalize)(FloatPartsN *p, float_status *status,
242
return;
243
case float_expmax_normal:
244
break;
245
+ case float_expmax_e4m3:
246
+ if (p->frac_hi == 0b111) {
247
+ frac_shl(p, fmt->frac_shift);
248
+ p->cls = (parts_is_snan_frac(p->frac_hi, status)
249
+ ? float_class_snan : float_class_qnan);
250
+ return;
251
+ }
252
+ /* otherwise normal */
253
+ break;
254
default:
255
g_assert_not_reached();
256
}
@@ -262,6 +271,21 @@ static void partsN(canonicalize)(FloatPartsN *p, float_status *status,
271
* The saturate parameter controls saturation behavior for formats that
272
* support it -- when true, overflow produces max normal instead of infinity.
273
*/
274
+
275
+/* Helper for uncanon_normal and uncanon, for FP8 E4M3. */
276
+static void partsN(uncanon_e4m3_overflow)(FloatPartsN *p, float_status *s,
277
+ const FloatFmt *fmt, bool saturate)
278
+{
279
+ assert(N == 64);
280
+ float_raise(float_flag_overflow | float_flag_inexact, s);
281
+ if (saturate) {
282
+ p->exp = fmt->exp_max;
283
+ p->frac_hi = E4M3_NORMAL_FRAC_MAX;
284
+ } else {
285
+ parts_default_nan(p, s);
286
+ }
287
+}
288
+
289
static void partsN(uncanon_normal)(FloatPartsN *p, float_status *s,
290
const FloatFmt *fmt, bool saturate)
291
{
@@ -360,6 +384,12 @@ static void partsN(uncanon_normal)(FloatPartsN *p, float_status *s,
384
}
385
break;
386
387
+ case float_expmax_e4m3:
388
+ if (exp > exp_max || p->frac_hi > E4M3_NORMAL_FRAC_MAX) {
389
+ partsN(uncanon_e4m3_overflow)(p, s, fmt, overflow_norm);
390
+ }
391
+ break;
392
+
393
default:
394
g_assert_not_reached();
395
}
@@ -459,9 +489,18 @@ static void partsN(uncanon)(FloatPartsN *p, float_status *s,
489
frac_clear(p);
490
return;
491
case float_class_inf:
462
- assert(fmt->exp_max_kind == float_expmax_ieee);
463
- p->exp = fmt->exp_max;
464
- frac_clear(p);
492
+ switch (fmt->exp_max_kind) {
493
+ case float_expmax_ieee:
494
+ p->exp = fmt->exp_max;
495
+ frac_clear(p);
496
+ break;
497
+ case float_expmax_e4m3:
498
+ partsN(uncanon_e4m3_overflow)(p, s, fmt, saturate);
499
+ break;
500
+ case float_expmax_normal:
501
+ default:
502
+ g_assert_not_reached();
503
+ }
504
return;
505
case float_class_qnan:
506
case float_class_snan:
fpu/softfloat.c
+62
@@ -528,6 +528,8 @@ typedef enum __attribute__((__packed__)) {
528
float_expmax_ieee,
529
/* exp==max is a normal number; no infinity or nan representation. */
530
float_expmax_normal,
531
+ /* exp==max, frac==max ? nan : normal; no infinity representation. */
532
+ float_expmax_e4m3,
533
} FloatFmtExpMaxKind;
534
535
/*
@@ -572,6 +574,14 @@ typedef struct {
574
.frac_shift = (-F - 1) & 63, \
575
.round_mask = (1ull << ((-F - 1) & 63)) - 1
576
577
+static const FloatFmt float8_e4m3_params = {
578
+ FLOAT_PARAMS(4, 3),
579
+ .exp_max_kind = float_expmax_e4m3
580
+};
581
+
582
+/* 110 << frac_shift, with the implicit bit set */
583
+#define E4M3_NORMAL_FRAC_MAX 0xe000000000000000ull
584
+
585
static const FloatFmt float8_e5m2_params = {
586
FLOAT_PARAMS(5, 2)
587
};
@@ -631,6 +641,11 @@ static void unpack_raw64(FloatParts64 *r, const FloatFmt *fmt, uint64_t raw)
641
};
642
}
643
644
+static void QEMU_FLATTEN float8_e4m3_unpack_raw(FloatParts64 *p, float8_e4m3 f)
645
+{
646
+ unpack_raw64(p, &float8_e4m3_params, f);
647
+}
648
+
649
static void QEMU_FLATTEN float8_e5m2_unpack_raw(FloatParts64 *p, float8_e5m2 f)
650
{
651
unpack_raw64(p, &float8_e5m2_params, f);
@@ -693,6 +708,11 @@ static uint64_t pack_raw64(const FloatParts64 *p, const FloatFmt *fmt)
708
return ret;
709
}
710
711
+static float8_e4m3 QEMU_FLATTEN float8_e4m3_pack_raw(const FloatParts64 *p)
712
+{
713
+ return pack_raw64(p, &float8_e4m3_params);
714
+}
715
+
716
static float8_e5m2 QEMU_FLATTEN float8_e5m2_pack_raw(const FloatParts64 *p)
717
{
718
return pack_raw64(p, &float8_e5m2_params);
@@ -1689,6 +1709,13 @@ static const uint16_t rsqrt_tab[128] = {
1709
* Pack/unpack routines with a specific FloatFmt.
1710
*/
1711
1712
+static void float8_e4m3_unpack_canonical(FloatParts64 *p, float8_e4m3 f,
1713
+ float_status *s)
1714
+{
1715
+ float8_e4m3_unpack_raw(p, f);
1716
+ parts_canonicalize(p, s, &float8_e4m3_params);
1717
+}
1718
+
1719
static void float8_e5m2_unpack_canonical(FloatParts64 *p, float8_e5m2 f,
1720
float_status *s)
1721
{
@@ -1716,6 +1743,14 @@ static void bfloat16_unpack_canonical(FloatParts64 *p, bfloat16 f,
1743
parts_canonicalize(p, s, &bfloat16_params);
1744
}
1745
1746
+static float8_e4m3 float8_e4m3_round_pack_canonical(FloatParts64 *p,
1747
+ float_status *s,
1748
+ bool saturate)
1749
+{
1750
+ parts_uncanon(p, s, &float8_e4m3_params, saturate);
1751
+ return float8_e4m3_pack_raw(p);
1752
+}
1753
+
1754
static float8_e5m2 float8_e5m2_round_pack_canonical(FloatParts64 *p,
1755
float_status *s,
1756
bool saturate)
@@ -2894,6 +2929,15 @@ static void parts_float_to_float_widen(FloatParts128 *a, FloatParts64 *b,
2929
}
2930
}
2931
2932
+bfloat16 float8_e4m3_to_bfloat16(float8_e4m3 a, float_status *s)
2933
+{
2934
+ FloatParts64 p;
2935
+
2936
+ float8_e4m3_unpack_canonical(&p, a, s);
2937
+ parts_float_to_float(&p, s);
2938
+ return bfloat16_round_pack_canonical(&p, s);
2939
+}
2940
+
2941
bfloat16 float8_e5m2_to_bfloat16(float8_e5m2 a, float_status *s)
2942
{
2943
FloatParts64 p;
@@ -2923,6 +2967,15 @@ float64 float16_to_float64(float16 a, bool ieee, float_status *s)
2967
return float64_round_pack_canonical(&p, s);
2968
}
2969
2970
+float8_e4m3 float32_to_float8_e4m3(float32 a, bool saturate, float_status *s)
2971
+{
2972
+ FloatParts64 p;
2973
+
2974
+ float32_unpack_canonical(&p, a, s);
2975
+ parts_float_to_float(&p, s);
2976
+ return float8_e4m3_round_pack_canonical(&p, s, saturate);
2977
+}
2978
+
2979
float8_e5m2 float32_to_float8_e5m2(float32 a, bool saturate, float_status *s)
2980
{
2981
FloatParts64 p;
@@ -2999,6 +3052,15 @@ float32 float64_to_float32(float64 a, float_status *s)
3052
return float32_round_pack_canonical(&p, s);
3053
}
3054
3055
+float8_e4m3 bfloat16_to_float8_e4m3(bfloat16 a, bool saturate, float_status *s)
3056
+{
3057
+ FloatParts64 p;
3058
+
3059
+ bfloat16_unpack_canonical(&p, a, s);
3060
+ parts_float_to_float(&p, s);
3061
+ return float8_e4m3_round_pack_canonical(&p, s, saturate);
3062
+}
3063
+
3064
float8_e5m2 bfloat16_to_float8_e5m2(bfloat16 a, bool saturate, float_status *s)
3065
{
3066
FloatParts64 p;
include/fpu/softfloat-types.h
+1
@@ -122,6 +122,7 @@ typedef uint16_t bfloat16;
122
/*
123
* Open Compute Project (OCP) Microscaling Formats
124
*/
125
+typedef uint8_t float8_e4m3;
126
typedef uint8_t float8_e5m2;
127
128
/*
include/fpu/softfloat.h
+4
@@ -193,6 +193,10 @@ float128 uint128_to_float128(Int128, float_status *status);
193
| OCP FP8 conversion routines.
194
*----------------------------------------------------------------------------*/
195
196
+bfloat16 float8_e4m3_to_bfloat16(float8_e4m3, float_status *status);
197
+float8_e4m3 bfloat16_to_float8_e4m3(bfloat16, bool sat, float_status *status);
198
+float8_e4m3 float32_to_float8_e4m3(float32, bool sat, float_status *status);
199
+
200
bfloat16 float8_e5m2_to_bfloat16(float8_e5m2, float_status *status);
201
float8_e5m2 bfloat16_to_float8_e5m2(bfloat16, bool sat, float_status *status);
202
float8_e5m2 float32_to_float8_e5m2(float32, bool sat, float_status *status);