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 <[email protected]> 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 <[email protected]> --- 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; }
