[PATCH 1/2] target/sh4: add tcg tests for fmov instructions

Randy Schifflin <[email protected]> Wed, 29 Jul 2026 13:55:07 -0700
Newsgroups org.nongnu.qemu-trivial,org.nongnu.qemu-devel
Message-ID <20260729-sh4-fix-fmov-instructions-b4-v1-1-65cc9679025a@gmail.com>
Add tests for the TCG generation of the sh4-specific `fmov`
  instructions. Covers register/register and register/memory
  in each direction, for single and double precision fp registers,
  and with direct and indirect memory addressing.

Signed-off-by: Randy Schifflin <[email protected]>
---
 tests/tcg/sh4/Makefile.target |   3 +
 tests/tcg/sh4/test-fpu-fmov.c | 518 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 521 insertions(+)

diff --git a/tests/tcg/sh4/Makefile.target b/tests/tcg/sh4/Makefile.target
index b7a8737be0..82f4898f90 100644
--- a/tests/tcg/sh4/Makefile.target
+++ b/tests/tcg/sh4/Makefile.target
@@ -16,3 +16,6 @@ TESTS += test-addv
 
 test-subv: CFLAGS += -O -g
 TESTS += test-subv
+
+test-fpu-fmov: CFLAGS += -O -g
+TESTS += test-fpu-fmov
diff --git a/tests/tcg/sh4/test-fpu-fmov.c b/tests/tcg/sh4/test-fpu-fmov.c
new file mode 100644
index 0000000000..90e51923ed
--- /dev/null
+++ b/tests/tcg/sh4/test-fpu-fmov.c
@@ -0,0 +1,518 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+
+#define ARR_SIZE 16
+static const float in_arr[ARR_SIZE] = {
+    1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0,
+    9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0
+};
+
+static float out_arr[ARR_SIZE] = {
+    0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
+    0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0
+};
+
+static const unsigned long FPSCR_PR_BIT = 1UL << 19;
+static void single_precision_mode(void)
+{
+    unsigned long fpscr;
+    asm volatile("sts fpscr, %0" : "=r" (fpscr));
+    fpscr &= ~FPSCR_PR_BIT;
+    asm volatile("lds %0, fpscr" : : "r" (fpscr));
+}
+
+static void double_precision_mode(void)
+{
+    unsigned long fpscr;
+    asm volatile("sts fpscr, %0" : "=r" (fpscr));
+    fpscr |= FPSCR_PR_BIT;
+    asm volatile("lds %0, fpscr" : : "r" (fpscr));
+}
+
+static void fill_arr(float *arr, float n)
+{
+    for (int i = 0; i < ARR_SIZE; i++) {
+        arr[i] = n;
+    }
+}
+
+/* uses fmov frm,frn instruction */
+static int test_fmov_freg_freg(void)
+{
+    int pass = true;
+
+    register float out asm ("fr0") = 0.0;
+    asm volatile(
+        "fldi1 fr1\n"
+        "fmov fr1,%0\n"
+        : "=f" (out)
+    );
+
+    if (out != 1.0) {
+        fprintf(
+            stderr,
+            "fmov freg,freg failed. Expected %f, got %f\n", 1.0, out
+        );
+        pass = false;
+    }
+
+    return pass;
+}
+
+/* uses fmov frm,@rn instruction */
+static int test_fmov_freg_addr(float *out)
+{
+    int pass = true;
+    fill_arr(out, 0);
+
+    register float *out_addr = out;
+    asm volatile(
+        "fldi1   fr0\n"
+        "fmov.s  fr0,@%0\n"
+        : : "r" (out_addr) : "memory"
+    );
+
+    if (out[0] != 1.0) {
+        fprintf(
+            stderr,
+            "fmov freg,addr failed. Expected %f, got %f\n", 1.0, out[0]
+        );
+        pass = false;
+    }
+
+    fill_arr(out, 0);
+    return pass;
+}
+
+/* uses fmov @rm,frn instruction */
+static int test_fmov_addr_freg(const float *in)
+{
+    int pass = true;
+    const register float *in_addr = in;
+    register float out = 0.0;
+    asm volatile(
+        "fmov.s    @%1,%0\n"
+        : "=f" (out) : "r" (in_addr)
+    );
+
+    if (out != 1.0) {
+        fprintf(
+            stderr,
+            "fmov addr,freg failed. Expected %f, got %f\n", 1.0, out
+        );
+        pass = false;
+    }
+
+    return pass;
+}
+
+/* uses fmov frm,@(r0,rn) instruction */
+static int test_fmov_freg_indirect_addr(float *out)
+{
+    int pass = true;
+    fill_arr(out, 0.0);
+
+    register float *out_addr = out;
+    asm volatile(
+        "mov      #4,r0\n"
+        "fldi1    fr0\n"
+        "fmov.s   fr0, @(r0, %0)\n"
+        : : "r" (out_addr) : "memory"
+    );
+
+    if (out[1] != 1.0) {
+        fprintf(
+            stderr,
+            "fmov freg,indirect_addr failed. Expected %f, got %f\n", 1.0, out[1]
+        );
+        pass = false;
+    }
+
+    fill_arr(out, 0.0);
+    return pass;
+}
+
+/* uses fmov @(r0,rm),frn instruction */
+static int test_fmov_indirect_addr_freg(const float *in)
+{
+    int pass = true;
+
+    const register float *in_addr asm ("r1") = in;
+    register float out asm ("fr0") = 0.0;
+    asm volatile(
+        "mov      #4,r0\n"
+        "fmov.s   @(r0, %1), %0\n"
+        : "=f" (out) : "r" (in_addr) : "memory"
+    );
+
+    if (out != in[1]) {
+        fprintf(
+            stderr,
+            "fmov indirect_addr,freg failed. Expected %f, got %f\n", in[1], out
+        );
+        pass = false;
+    }
+
+    return pass;
+}
+
+/* uses fmov drm,drn instruction */
+static int test_fmov_dreg_dreg(void)
+{
+    int pass = true;
+    double_precision_mode();
+
+    register double out asm ("dr0") = 0.0;
+    asm volatile(
+        "mov   #42,r0\n"
+        "lds   r0,fpul\n"
+        "float fpul,dr2\n"
+        "fmov  dr2,%0\n"
+        : "=f" (out)
+    );
+
+    if (out != 42.0) {
+        fprintf(
+            stderr,
+            "fmov dreg,dreg failed. Expected %f, got %f\n", 42.0, out
+        );
+        pass = false;
+    }
+
+    single_precision_mode();
+    return pass;
+}
+
+/* uses fmov drm,@rn instruction */
+static int test_fmov_dreg_addr(float *out)
+{
+    int pass = true;
+    fill_arr(out, -1.0);
+
+    register float *out_addr = out;
+    asm volatile(
+        "fldi0 fr0\n"
+        "fldi1 fr1\n"
+        "fschg\n" /* Set fast pairs on */
+        "fmov   dr0, @%0\n"
+        "fschg\n" /* Set fast pairs off */
+        : : "r" (out_addr) : "memory"
+    );
+
+    if (out[0] != 0.0) {
+        fprintf(
+            stderr,
+            "fmov dreg,addr failed. " \
+                "Expected element 0 to be %f, got %f\n", 0.0, out[0]
+        );
+        pass = false;
+    }
+    if (out[1] != 1.0) {
+        fprintf(
+            stderr,
+            "fmov dreg,addr failed. " \
+                "Expected element 1 to be %f, got %f\n", 1.0, out[1]
+        );
+        pass = false;
+    }
+
+    fill_arr(out, 0.0);
+    return pass;
+}
+
+/* uses fmov @rm,drn instruction */
+static int test_fmov_addr_dreg(const float *in)
+{
+    int pass = true;
+
+    const register float *in_addr asm ("r1") = in;
+    register float write_out_0 asm ("fr0") = 0.0;
+    register float write_out_1 asm ("fr1") = 0.0;
+
+    asm volatile(
+        "fschg\n" /* Set fast pairs on */
+        "fmov    @%2,dr0\n"
+        "fschg\n" /* Set fast pairs off */
+        : "=f" (write_out_0), "=f" (write_out_1) : "r" (in_addr) : "memory"
+    );
+
+    if (write_out_0 != in[0]) {
+        fprintf(
+            stderr,
+            "fmov addr,dreg failed. " \
+                "Expected element 0 to be %f, got %f\n", in[0], write_out_0
+        );
+        pass = false;
+    }
+    if (write_out_1 != in[1]) {
+        fprintf(
+            stderr,
+            "fmov addr,dreg failed. " \
+                "Expected element 1 to be %f, got %f\n", in[1], write_out_1
+        );
+        pass = false;
+    }
+
+    return pass;
+}
+
+/* uses fmov drm,@(r0,rn) instruction */
+static int test_fmov_dreg_indirect_addr(float *out)
+{
+    int pass = true;
+    fill_arr(out, -1.0);
+
+    register float *out_addr = out;
+    asm volatile(
+        "mov      #8,r0\n"
+        "fldi0    fr0\n"
+        "fldi1    fr1\n"
+        "fschg\n" /* Set fast pairs on */
+        "fmov   dr0, @(r0, %0)\n"
+        "fschg\n" /* Set fast pairs off */
+        : : "r" (out_addr) : "memory"
+    );
+
+    if (out[2] != 0.0) {
+        fprintf(
+            stderr,
+            "fmov dreg,indirect_addr failed. " \
+                "Expected element 0 to be %f, got %f\n", 0.0, out[2]
+        );
+        pass = false;
+    }
+
+    if (out[3] != 1.0) {
+        fprintf(
+            stderr,
+            "fmov dreg,indirect_addr failed. " \
+                "Expected element 1 to be %f, got %f\n", 1.0, out[3]
+        );
+        pass = false;
+    }
+
+    fill_arr(out, 0.0);
+    return pass;
+}
+
+/* uses fmov @(r0,rm),drn instruction */
+static int test_fmov_indirect_addr_dreg(const float *in)
+{
+    int pass = true;
+
+    const register float *in_addr asm ("r1") = in;
+    register float write_out_0 asm ("fr0") = 0.0;
+    register float write_out_1 asm ("fr1") = 0.0;
+    asm volatile(
+        "mov      #8,r0\n"
+        "fschg\n" /* Set fast pairs on */
+        "fmov     @(r0, %2), dr0\n"
+        "fschg\n" /* Set fast pairs off */
+        : "=f" (write_out_0), "=f" (write_out_1) : "r" (in_addr) : "memory"
+    );
+
+    if (write_out_0 != in[2]) {
+        fprintf(
+            stderr,
+            "fmov indirect_addr,dreg failed. " \
+                "Expected element 0 to be %f, got %f\n", in[2], write_out_0
+        );
+        pass = false;
+    }
+
+    if (write_out_1 != in[3]) {
+        fprintf(
+            stderr,
+            "fmov indirect_addr,dreg failed. " \
+                "Expected element 1 to be %f, got %f\n", in[3], write_out_1
+        );
+        pass = false;
+    }
+
+    return pass;
+}
+
+/*
+ * reads in data from `in` using fast pairs
+ * writes data to `out` using slow singles
+ * uses fmov @rm+,drn instruction
+ * uses fmov frm,@-rn instruction
+ */
+static int test_fmov_fast_in_slow_out(const float *in, float *out)
+{
+    int pass = true;
+    fill_arr(out, 0.0);
+
+    const register float *in_addr = in;
+    register float *out_addr = out;
+    asm volatile(
+        "fschg\n" /* Set fast pairs on */
+        "fmov @%0+,dr0\n"
+        "fmov @%0+,dr2\n"
+        "fmov @%0+,dr4\n"
+        "fmov @%0+,dr6\n"
+        "fmov @%0+,dr8\n"
+        "fmov @%0+,dr10\n"
+        "fmov @%0+,dr12\n"
+        "fmov @%0+,dr14\n"
+        "fschg\n" /* Set fast pairs off */
+
+        "add #64,%1\n" /* Start at the back and work to the front */
+        "fmov fr15,@-%1\n"
+        "fmov fr14,@-%1\n"
+        "fmov fr13,@-%1\n"
+        "fmov fr12,@-%1\n"
+        "fmov fr11,@-%1\n"
+        "fmov fr10,@-%1\n"
+        "fmov fr9,@-%1\n"
+        "fmov fr8,@-%1\n"
+        "fmov fr7,@-%1\n"
+        "fmov fr6,@-%1\n"
+        "fmov fr5,@-%1\n"
+        "fmov fr4,@-%1\n"
+        "fmov fr3,@-%1\n"
+        "fmov fr2,@-%1\n"
+        "fmov fr1,@-%1\n"
+        "fmov fr0,@-%1\n"
+        : "+r" (in_addr), "+r" (out_addr) :: "memory"
+      );
+
+    for (int i = 0; i < ARR_SIZE; i++) {
+        if (in[i] != out[i]) {
+            fprintf(
+                stderr,
+                "fmov fast->slow failed. " \
+                    "Expected element %i to be %f, got %f\n", i, in[i], out[i]
+            );
+            pass = false;
+        }
+    }
+
+    fill_arr(out, 0.0);
+    return pass;
+}
+
+/* reads in data from `in` using slow singles */
+/* writes data to `out` using fast pairs */
+/* uses fmov @rm+,frn instruction */
+/* uses fmov drm,@-rn instruction */
+static int test_fmov_slow_in_fast_out(const float in[16], float out[16])
+{
+    int pass = true;
+    fill_arr(out, 0.0);
+
+    const register float *in_addr = in;
+    register float *out_addr = out;
+    asm volatile(
+        "fmov @%0+,fr0\n"
+        "fmov @%0+,fr1\n"
+        "fmov @%0+,fr2\n"
+        "fmov @%0+,fr3\n"
+        "fmov @%0+,fr4\n"
+        "fmov @%0+,fr5\n"
+        "fmov @%0+,fr6\n"
+        "fmov @%0+,fr7\n"
+        "fmov @%0+,fr8\n"
+        "fmov @%0+,fr9\n"
+        "fmov @%0+,fr10\n"
+        "fmov @%0+,fr11\n"
+        "fmov @%0+,fr12\n"
+        "fmov @%0+,fr13\n"
+        "fmov @%0+,fr14\n"
+        "fmov @%0+,fr15\n"
+
+        "fschg\n" /* Set fast pairs on */
+        "add #64,%1\n" /* Start at the back and work to the front */
+        "fmov dr14,@-%1\n"
+        "fmov dr12,@-%1\n"
+        "fmov dr10,@-%1\n"
+        "fmov dr8,@-%1\n"
+        "fmov dr6,@-%1\n"
+        "fmov dr4,@-%1\n"
+        "fmov dr2,@-%1\n"
+        "fmov dr0,@-%1\n"
+        "fschg\n" /* Set fast pairs off */
+        : "+r" (in_addr), "+r" (out_addr) :: "memory"
+      );
+
+    for (int i = 0; i < ARR_SIZE; i++) {
+        if (in[i] != out[i]) {
+            fprintf(
+                stderr,
+                "fmov slow->fast failed. " \
+                    "Expected element %i to be %f, got %f\n", i, in[i], out[i]
+            );
+            pass = false;
+        }
+    }
+
+    fill_arr(out, 0.0);
+    return pass;
+}
+
+int main(void)
+{
+    single_precision_mode();
+    int pass = true;
+
+    if (!test_fmov_freg_freg()) {
+        pass = false;
+    };
+
+    if (!test_fmov_freg_addr(out_arr)) {
+        pass = false;
+    }
+
+    if (!test_fmov_addr_freg(in_arr)) {
+        pass = false;
+    }
+
+    if (!test_fmov_freg_indirect_addr(out_arr)) {
+        pass = false;
+    }
+
+    if (!test_fmov_indirect_addr_freg(in_arr)) {
+        pass = false;
+    };
+
+    if (!test_fmov_dreg_dreg()) {
+        pass = false;
+    }
+
+    if (!test_fmov_dreg_addr(out_arr)) {
+        pass = false;
+    }
+
+    if (!test_fmov_addr_dreg(in_arr)) {
+        pass = false;
+    }
+
+    if (!test_fmov_dreg_indirect_addr(out_arr)) {
+        pass = false;
+    }
+
+    if (!test_fmov_indirect_addr_dreg(in_arr)) {
+        pass = false;
+    };
+
+    if (!test_fmov_fast_in_slow_out(in_arr, out_arr)) {
+        pass = false;
+    }
+
+    if (!test_fmov_slow_in_fast_out(in_arr, out_arr)) {
+        pass = false;
+    }
+
+    if (!pass) {
+        fprintf(
+            stderr,
+            "Fmov tests failed\n"
+        );
+        abort();
+    }
+
+    return 0;
+}

-- 
2.43.0