You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nuttx.apache.org by xi...@apache.org on 2026/10/13 00:58:08 UTC
(nuttx) 06/07: riscv/erbium: Emulate FDIV.S and FSQRT.S.
This is an automated email from the ASF dual-hosted git repository.
xiaoxiang781216 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git
commit 8a16107796f23f8f28025ab01022b7d228f6a2e3
Author: Afonso Oliveira <af...@gmail.com>
AuthorDate: Mon Oct 12 13:05:11 2026 +0100
riscv/erbium: Emulate FDIV.S and FSQRT.S.
The port compiles with -mno-fdiv, but prebuilt code does not: the
divisions and square roots in a toolchain's libm, for example in expf(),
logf(), powf() and sqrtf(), use FDIV.S and FSQRT.S, which ET-Minion has
no hardware for. They raise exception 30 (M-code emulation) and stop the
system, so a program that links the toolchain's libm, as PX4 does, could
not call those functions.
Emulate both instructions in erbium_mcode_emulate(), like the 64-bit
conversions. The result is correctly rounded in every RISC-V rounding
mode, with the RISC-V flags and canonical NaN, and follows the subnormal
rules that ET-Minion applies to its other F instructions: a subnormal
input reads as zero and sets fflags bit 31, and a result that is tiny
after rounding is flushed to zero with underflow and inexact. Division
uses a 64-bit integer divide and square root a bit-by-bit integer root;
the code stays integer-only.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Afonso Oliveira <af...@gmail.com>
---
arch/risc-v/src/erbium/Kconfig | 26 ++-
arch/risc-v/src/erbium/erbium_mcode.c | 401 +++++++++++++++++++++++++++++++++-
2 files changed, 406 insertions(+), 21 deletions(-)
diff --git a/arch/risc-v/src/erbium/Kconfig b/arch/risc-v/src/erbium/Kconfig
index dc1db90d3ba..cdc62984635 100644
--- a/arch/risc-v/src/erbium/Kconfig
+++ b/arch/risc-v/src/erbium/Kconfig
@@ -22,18 +22,20 @@ config ERBIUM_MTIMER_FREQ
timer prescaler. The ET-platform emulator defaults to 2 MHz.
config ERBIUM_MCODE_EMULATION
- bool "Emulate the 64-bit integer FCVT instructions"
+ bool "Emulate the M-code floating-point instructions"
default y
depends on ARCH_FPU && !ARCH_USE_S_MODE
---help---
- ET-Minion has no hardware for FCVT.L.S, FCVT.LU.S, FCVT.S.L and
- FCVT.S.LU, which GCC emits for every conversion between float and
- a 64-bit integer type (long, int64_t, size_t, time_t). They raise
- exception 30 (M-code emulation). With this option the trap handler
- performs them in software, with the ET-Minion rule that a subnormal
- input reads as zero and sets fflags bit 31, and resumes after the
- instruction. Each conversion then costs a trap, about 260
- instructions in erbium_emu. Without this option they stop the
- system with an "M-code emulation" exception report. FDIV.S,
- FSQRT.S, FENCE.I, SFENCE.VMA and the ET packed instructions are
- never emulated.
+ ET-Minion has no hardware for FCVT.L.S, FCVT.LU.S, FCVT.S.L,
+ FCVT.S.LU, FDIV.S and FSQRT.S. They raise exception 30 (M-code
+ emulation). GCC emits the conversions for every conversion between
+ float and a 64-bit integer type (long, int64_t, size_t, time_t),
+ and prebuilt code, such as a toolchain's libm, contains FDIV.S and
+ FSQRT.S. With this option the trap handler performs them in
+ software, with the RISC-V rounding and flag rules and the
+ ET-Minion subnormal rules: a subnormal input reads as zero and
+ sets fflags bit 31, and a result that is tiny after rounding is
+ flushed to zero. Each instruction then costs a trap of a few
+ hundred instructions. Without this option they stop the system
+ with an "M-code emulation" exception report. FENCE.I, SFENCE.VMA
+ and the ET packed instructions are never emulated.
diff --git a/arch/risc-v/src/erbium/erbium_mcode.c b/arch/risc-v/src/erbium/erbium_mcode.c
index b5cc3b00ae5..92db61e138a 100644
--- a/arch/risc-v/src/erbium/erbium_mcode.c
+++ b/arch/risc-v/src/erbium/erbium_mcode.c
@@ -21,10 +21,11 @@
****************************************************************************/
/* ET-Minion raises exception 30 (M-code emulation) for FCVT.L.S,
- * FCVT.LU.S, FCVT.S.L and FCVT.S.LU instead of executing them. This file
- * performs them on the trapped context with RISC-V F semantics and the
- * ET-Minion input rule for subnormals (read as zero, flagged in fflags
- * bit 31), as the hardware does for FCVT.W.S.
+ * FCVT.LU.S, FCVT.S.L, FCVT.S.LU, FDIV.S and FSQRT.S instead of executing
+ * them. This file performs them on the trapped context with RISC-V F
+ * semantics and the ET-Minion subnormal rules of its other F instructions:
+ * a subnormal input reads as zero and sets fflags bit 31, and a result that
+ * is tiny after rounding is flushed to zero with underflow and inexact.
*
* Keep this file integer-only: a float, a double or a conversion between
* floating point and a 64-bit integer would compile to the instructions it
@@ -61,9 +62,16 @@
#define FCVT_S_LU 0xd0300053
#define FCVT_UNSIGNED (1u << 20) /* rs2 bit 0: the LU variants */
+/* OP-FP, fmt S: FSQRT.S has rs2 0 and uses FCVT_MASK; FDIV.S keeps funct7 */
+
+#define FSQRT_S 0x58000053
+#define FDIV_MASK 0xfe00007f
+#define FDIV_S 0x18000053
+
#define INSN_RD(i) (((i) >> 7) & 0x1f)
#define INSN_RM(i) (((i) >> 12) & 0x7)
#define INSN_RS1(i) (((i) >> 15) & 0x1f)
+#define INSN_RS2(i) (((i) >> 20) & 0x1f)
#define XREG_ZERO 0
#define XREG_GP 3
@@ -76,6 +84,9 @@
#define FRM_DYN 7
#define FFLAG_NX 0x01u
+#define FFLAG_UF 0x02u
+#define FFLAG_OF 0x04u
+#define FFLAG_DZ 0x08u
#define FFLAG_NV 0x10u
#define FFLAG_INPUTDENORM 0x80000000u /* ET-Minion fflags bit 31 */
@@ -91,6 +102,10 @@
#define F32_EXP(x) (((x) >> F32_FRAC_BITS) & 0xff)
#define F32_FRAC(x) ((x) & 0x7fffff)
#define F32_SIGN_BIT 0x80000000u
+#define F32_INF 0x7f800000u
+#define F32_QNAN 0x7fc00000u /* RISC-V canonical NaN */
+#define F32_IS_SNAN(x) (((x) & 0x7fc00000) == 0x7f800000 && \
+ F32_FRAC(x) != 0)
#define I64_MIN_BITS UINT64_C(0x8000000000000000)
#define I64_MAX_BITS UINT64_C(0x7fffffffffffffff)
@@ -366,6 +381,353 @@ static uint32_t erbium_x64_to_f32(uint64_t val, unsigned int rm,
(uint32_t)F32_FRAC(sig);
}
+/****************************************************************************
+ * Name: erbium_f32_round_pack
+ *
+ * Description:
+ * Round and pack a single-precision result. 'sig' holds the significand
+ * with its leading one at bit 30 and seven guard bits, and the value is
+ * sig * 2^(exp - 156). A result that is tiny after rounding is flushed to
+ * zero, as ET-Minion does.
+ *
+ * Input Parameters:
+ * sign - True if the result is negative.
+ * exp - Biased exponent minus one.
+ * sig - Significand, below 2^31, with a sticky bit in bit 0.
+ * rm - Rounding mode, FRM_RNE to FRM_RMM.
+ * fflags - Accumulated exception flags.
+ *
+ * Returned Value:
+ * The single-precision result.
+ *
+ ****************************************************************************/
+
+static uint32_t erbium_f32_round_pack(bool sign, int exp, uint32_t sig,
+ unsigned int rm, uint32_t *fflags)
+{
+ uint32_t increment = 0x40;
+ uint32_t bits;
+ bool tiny;
+
+ if (rm == FRM_RTZ || (rm == FRM_RDN && !sign) || (rm == FRM_RUP && sign))
+ {
+ increment = 0;
+ }
+ else if (rm == FRM_RDN || rm == FRM_RUP)
+ {
+ increment = 0x7f;
+ }
+
+ if (exp < 0)
+ {
+ tiny = exp < -1 || sig + increment < 0x80000000u;
+ if (tiny)
+ {
+ *fflags |= FFLAG_UF | FFLAG_NX;
+ return sign ? F32_SIGN_BIT : 0;
+ }
+
+ sig = (sig >> 1) | (sig & 1); /* exp is -1: shift right, jam */
+ exp = 0;
+ }
+ else if (exp > 0xfd || (exp == 0xfd && sig + increment >= 0x80000000u))
+ {
+ *fflags |= FFLAG_OF | FFLAG_NX;
+ return (sign ? F32_SIGN_BIT : 0) | (increment != 0 ? F32_INF :
+ F32_INF - 1);
+ }
+
+ bits = sig & 0x7f;
+ if (bits != 0)
+ {
+ *fflags |= FFLAG_NX;
+ }
+
+ sig = (sig + increment) >> 7;
+ if (rm == FRM_RNE && bits == 0x40)
+ {
+ sig &= ~UINT32_C(1); /* Tie: round to even */
+ }
+
+ if (sig == 0)
+ {
+ exp = 0;
+ }
+
+ return (sign ? F32_SIGN_BIT : 0) + ((uint32_t)exp << F32_FRAC_BITS) + sig;
+}
+
+/****************************************************************************
+ * Name: erbium_f32_nan
+ *
+ * Description:
+ * Return the canonical NaN, and raise the invalid-operation flag if an
+ * operand is a signaling NaN.
+ *
+ * Input Parameters:
+ * a, b - Operands; pass a quiet value for an unused one.
+ * fflags - Accumulated exception flags.
+ *
+ * Returned Value:
+ * The canonical NaN.
+ *
+ ****************************************************************************/
+
+static uint32_t erbium_f32_nan(uint32_t a, uint32_t b, uint32_t *fflags)
+{
+ if (F32_IS_SNAN(a) || F32_IS_SNAN(b))
+ {
+ *fflags |= FFLAG_NV;
+ }
+
+ return F32_QNAN;
+}
+
+/****************************************************************************
+ * Name: erbium_f32_daz
+ *
+ * Description:
+ * Read a subnormal input as a zero of the same sign and flag it.
+ *
+ * Input Parameters:
+ * x - Operand.
+ * fflags - Accumulated exception flags.
+ *
+ * Returned Value:
+ * The operand, or a signed zero for a subnormal.
+ *
+ ****************************************************************************/
+
+static uint32_t erbium_f32_daz(uint32_t x, uint32_t *fflags)
+{
+ if (F32_EXP(x) == 0 && F32_FRAC(x) != 0)
+ {
+ *fflags |= FFLAG_INPUTDENORM;
+ return x & F32_SIGN_BIT;
+ }
+
+ return x;
+}
+
+/****************************************************************************
+ * Name: erbium_f32_div
+ *
+ * Description:
+ * Perform FDIV.S.
+ *
+ * Input Parameters:
+ * a - Dividend.
+ * b - Divisor.
+ * rm - Rounding mode, FRM_RNE to FRM_RMM.
+ * fflags - Accumulated exception flags.
+ *
+ * Returned Value:
+ * The single-precision quotient.
+ *
+ ****************************************************************************/
+
+static uint32_t erbium_f32_div(uint32_t a, uint32_t b, unsigned int rm,
+ uint32_t *fflags)
+{
+ bool sign = F32_SIGN(a) != F32_SIGN(b);
+ uint32_t sign_bit = sign ? F32_SIGN_BIT : 0;
+ unsigned int exp_a;
+ unsigned int exp_b;
+ uint64_t dividend;
+ uint64_t sig_a;
+ uint64_t sig_b;
+ uint64_t sig;
+ int exp;
+
+ a = erbium_f32_daz(a, fflags);
+ b = erbium_f32_daz(b, fflags);
+ exp_a = F32_EXP(a);
+ exp_b = F32_EXP(b);
+
+ if (exp_a == F32_EXP_INFNAN)
+ {
+ if (F32_FRAC(a) != 0 ||
+ (exp_b == F32_EXP_INFNAN && F32_FRAC(b) != 0))
+ {
+ return erbium_f32_nan(a, b, fflags);
+ }
+
+ if (exp_b == F32_EXP_INFNAN)
+ {
+ *fflags |= FFLAG_NV; /* Inf / Inf */
+ return F32_QNAN;
+ }
+
+ return sign_bit | F32_INF;
+ }
+
+ if (exp_b == F32_EXP_INFNAN)
+ {
+ if (F32_FRAC(b) != 0)
+ {
+ return erbium_f32_nan(a, b, fflags);
+ }
+
+ return sign_bit; /* x / Inf */
+ }
+
+ if (exp_b == 0)
+ {
+ if (exp_a == 0)
+ {
+ *fflags |= FFLAG_NV; /* 0 / 0 */
+ return F32_QNAN;
+ }
+
+ *fflags |= FFLAG_DZ;
+ return sign_bit | F32_INF;
+ }
+
+ if (exp_a == 0)
+ {
+ return sign_bit; /* 0 / x */
+ }
+
+ /* The quotient of two significands in [1, 2) lies in (1/2, 2); scale it
+ * so that its leading one lands on bit 30.
+ */
+
+ exp = (int)exp_a - (int)exp_b + 0x7e;
+ sig_a = F32_FRAC(a) | F32_HIDDEN;
+ sig_b = F32_FRAC(b) | F32_HIDDEN;
+ if (sig_a < sig_b)
+ {
+ exp--;
+ dividend = sig_a << 31;
+ }
+ else
+ {
+ dividend = sig_a << 30;
+ }
+
+ sig = dividend / sig_b;
+ if (sig * sig_b != dividend)
+ {
+ sig |= 1; /* Sticky */
+ }
+
+ return erbium_f32_round_pack(sign, exp, (uint32_t)sig, rm, fflags);
+}
+
+/****************************************************************************
+ * Name: erbium_isqrt64
+ *
+ * Description:
+ * Integer square root, bit by bit.
+ *
+ * Input Parameters:
+ * val - Radicand.
+ * rem - Receives val - root^2.
+ *
+ * Returned Value:
+ * floor(sqrt(val)).
+ *
+ ****************************************************************************/
+
+static uint64_t erbium_isqrt64(uint64_t val, uint64_t *rem)
+{
+ uint64_t root = 0;
+ uint64_t bit = UINT64_C(1) << 62;
+
+ while (bit > val)
+ {
+ bit >>= 2;
+ }
+
+ while (bit != 0)
+ {
+ if (val >= root + bit)
+ {
+ val -= root + bit;
+ root = (root >> 1) + bit;
+ }
+ else
+ {
+ root >>= 1;
+ }
+
+ bit >>= 2;
+ }
+
+ *rem = val;
+ return root;
+}
+
+/****************************************************************************
+ * Name: erbium_f32_sqrt
+ *
+ * Description:
+ * Perform FSQRT.S.
+ *
+ * Input Parameters:
+ * a - Operand.
+ * rm - Rounding mode, FRM_RNE to FRM_RMM.
+ * fflags - Accumulated exception flags.
+ *
+ * Returned Value:
+ * The single-precision square root.
+ *
+ ****************************************************************************/
+
+static uint32_t erbium_f32_sqrt(uint32_t a, unsigned int rm,
+ uint32_t *fflags)
+{
+ unsigned int exp_a = F32_EXP(a);
+ unsigned int shift;
+ uint64_t radicand;
+ uint64_t rem;
+ uint64_t sig;
+
+ if (exp_a == F32_EXP_INFNAN)
+ {
+ if (F32_FRAC(a) != 0)
+ {
+ return erbium_f32_nan(a, 0, fflags);
+ }
+
+ if (!F32_SIGN(a))
+ {
+ return a; /* sqrt(+Inf) */
+ }
+
+ *fflags |= FFLAG_NV;
+ return F32_QNAN;
+ }
+
+ if (exp_a == 0)
+ {
+ return erbium_f32_daz(a, fflags); /* sqrt(+-0) is +-0 */
+ }
+
+ if (F32_SIGN(a))
+ {
+ *fflags |= FFLAG_NV;
+ return F32_QNAN;
+ }
+
+ /* |a| = sig_a * 2^(exp_a - 150). Shift sig_a into [2^60, 2^62) by an
+ * amount that leaves an even power of two, so that the root has its
+ * leading one on bit 30 and the exponent halves exactly.
+ */
+
+ shift = (exp_a & 1) != 0 ? 37 : 38;
+ radicand = (uint64_t)(F32_FRAC(a) | F32_HIDDEN) << shift;
+ sig = erbium_isqrt64(radicand, &rem);
+ if (rem != 0)
+ {
+ sig |= 1; /* Sticky */
+ }
+
+ return erbium_f32_round_pack(false,
+ ((int)exp_a - 150 - (int)shift) / 2 + 156,
+ (uint32_t)sig, rm, fflags);
+}
+
/****************************************************************************
* Public Functions
****************************************************************************/
@@ -375,8 +737,8 @@ static uint32_t erbium_x64_to_f32(uint64_t val, unsigned int rm,
*
* Description:
* Perform the instruction that raised exception 30 (M-code emulation)
- * on the saved context and advance its EPC. Only FCVT.L.S, FCVT.LU.S,
- * FCVT.S.L and FCVT.S.LU are emulated.
+ * on the saved context and advance its EPC. FCVT.L.S, FCVT.LU.S,
+ * FCVT.S.L, FCVT.S.LU, FDIV.S and FSQRT.S are emulated.
*
* Input Parameters:
* regs - Saved registers of the trapped context.
@@ -418,10 +780,16 @@ int erbium_mcode_emulate(uintreg_t *regs)
case FCVT_LU_S:
case FCVT_S_L:
case FCVT_S_LU:
+ case FSQRT_S:
break;
default:
- return -ENOSYS; /* FDIV.S, FSQRT.S, FENCE.I, packed ops, ... */
+ if ((insn & FDIV_MASK) != FDIV_S)
+ {
+ return -ENOSYS; /* FENCE.I, SFENCE.VMA, packed ops, ... */
+ }
+
+ break;
}
rd = INSN_RD(insn);
@@ -449,7 +817,22 @@ int erbium_mcode_emulate(uintreg_t *regs)
return -EINVAL;
}
- if ((insn & FCVT_MASK) == FCVT_L_S || (insn & FCVT_MASK) == FCVT_LU_S)
+ if ((insn & FDIV_MASK) == FDIV_S)
+ {
+ erbium_mcode_setfpr(rd, erbium_f32_div(erbium_mcode_getfpr(rs1),
+ erbium_mcode_getfpr(
+ INSN_RS2(insn)),
+ rm, &fflags));
+ fp_written = true;
+ }
+ else if ((insn & FCVT_MASK) == FSQRT_S)
+ {
+ erbium_mcode_setfpr(rd, erbium_f32_sqrt(erbium_mcode_getfpr(rs1), rm,
+ &fflags));
+ fp_written = true;
+ }
+ else if ((insn & FCVT_MASK) == FCVT_L_S ||
+ (insn & FCVT_MASK) == FCVT_LU_S)
{
uint64_t res;
@@ -497,6 +880,6 @@ int erbium_mcode_emulate(uintreg_t *regs)
regs[REG_INT_CTX] |= MSTATUS_FS_DIRTY;
}
- regs[REG_EPC] = epc + 4; /* None of the four has a compressed form */
+ regs[REG_EPC] = epc + 4; /* None of these has a compressed form */
return OK;
}