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;
 }

Reply via email to