testpico: 32x vint tests master github/master
authornotaz <notasas@gmail.com>
Tue, 29 Apr 2025 01:13:54 +0000 (04:13 +0300)
committernotaz <notasas@gmail.com>
Tue, 29 Apr 2025 01:17:12 +0000 (04:17 +0300)
testpico/Makefile
testpico/common.h
testpico/main.c
testpico/sh2_main.c
testpico/sh2_test.sh2

index b8c7988..dca29b2 100644 (file)
@@ -16,11 +16,17 @@ CFLAGS += -Wall -g -O2 -m68000 -fomit-frame-pointer
 LDLIBS_LIBGCC := $(shell $(CC) -print-file-name=libgcc.a)
 LDLIBS += $(LDLIBS_LIBGCC)
 
 LDLIBS_LIBGCC := $(shell $(CC) -print-file-name=libgcc.a)
 LDLIBS += $(LDLIBS_LIBGCC)
 
+ifeq ($(shell m68k-elf-gcc -dD -E -c - --param=min-pagesize=0 < /dev/null > /dev/null 2>&1 && echo ok),ok)
+CFLAGS += --param=min-pagesize=0
+endif
+
 SUFFIX := $(shell git describe --always --dirty)
 ifdef PICO
 CFLAGS += -DPICO
 SUFFIX := $(SUFFIX)-for-pd
 endif
 SUFFIX := $(shell git describe --always --dirty)
 ifdef PICO
 CFLAGS += -DPICO
 SUFFIX := $(SUFFIX)-for-pd
 endif
+CFLAGS += -DVERSION=\"$(SUFFIX)\"
+
 TARGET_BASE = testpico
 TARGET = $(TARGET_BASE)-$(SUFFIX)
 OBJS = sega_gcc.o main.o asmtools.o
 TARGET_BASE = testpico
 TARGET = $(TARGET_BASE)-$(SUFFIX)
 OBJS = sega_gcc.o main.o asmtools.o
index ce02a51..665b295 100644 (file)
@@ -37,6 +37,8 @@ enum x32x_cmd {
     CMD_WRITE32 = 8,
     CMD_GETGBR = 9,
     CMD_GETVBR = 10,
     CMD_WRITE32 = 8,
     CMD_GETGBR = 9,
     CMD_GETVBR = 10,
+    CMD_GETSR = 11,
+    CMD_SETSR = 12,
 };
 
 // vim:ts=4:sw=4:expandtab
 };
 
 // vim:ts=4:sw=4:expandtab
index b4a65c8..44fe5b9 100644 (file)
@@ -8,6 +8,10 @@
 #include "asmtools.h"
 //#pragma GCC diagnostic ignored "-Wunused-function"
 
 #include "asmtools.h"
 //#pragma GCC diagnostic ignored "-Wunused-function"
 
+#ifndef VERSION
+#define VERSION "unknown build"
+#endif
+
 #define VDP_DATA_PORT    0xC00000
 #define VDP_CTRL_PORT    0xC00004
 #define VDP_HV_COUNTER   0xC00008
 #define VDP_DATA_PORT    0xC00000
 #define VDP_CTRL_PORT    0xC00004
 #define VDP_HV_COUNTER   0xC00008
@@ -385,12 +389,16 @@ static void vdp_wait_for_fifo_empty(void)
 {
     while (!(read16(VDP_CTRL_PORT) & 0x200))
         /* fifo not empty */;
 {
     while (!(read16(VDP_CTRL_PORT) & 0x200))
         /* fifo not empty */;
+
+    mem_barrier();
 }
 
 static void vdp_wait_for_dma_idle(void)
 {
     while (read16(VDP_CTRL_PORT) & 2)
         /* dma busy */;
 }
 
 static void vdp_wait_for_dma_idle(void)
 {
     while (read16(VDP_CTRL_PORT) & 2)
         /* dma busy */;
+
+    mem_barrier();
 }
 
 static void vdp_wait_for_line_0(void)
 }
 
 static void vdp_wait_for_line_0(void)
@@ -403,6 +411,8 @@ static void vdp_wait_for_line_0(void)
         /* blanking */;
     while (read8(VDP_HV_COUNTER) != 0)
         ;
         /* blanking */;
     while (read8(VDP_HV_COUNTER) != 0)
         ;
+
+    mem_barrier();
 }
 
 static void wait_next_vsync(void)
 }
 
 static void wait_next_vsync(void)
@@ -411,6 +421,8 @@ static void wait_next_vsync(void)
         /* blanking */;
     while (!(read16(VDP_CTRL_PORT) & SR_VB))
         /* not blanking */;
         /* blanking */;
     while (!(read16(VDP_CTRL_PORT) & SR_VB))
         /* not blanking */;
+
+    mem_barrier();
 }
 
 static void t_dma_zero_wrap_early(void)
 }
 
 static void t_dma_zero_wrap_early(void)
@@ -454,13 +466,13 @@ static void t_dma_zero_fill_early(void)
 
 #define expect(ok_, v0_, v1_) \
 do { if ((v0_) != (v1_)) { \
 
 #define expect(ok_, v0_, v1_) \
 do { if ((v0_) != (v1_)) { \
-    printf("%s: %08x %08x\n", #v0_, v0_, v1_); \
+    printf("%d %s: %08x %08x\n", __LINE__, #v0_, v0_, v1_); \
     ok_ = 0; \
 }} while (0)
 
 #define expect_sh2(ok_, sh2_, v0_, v1_) \
 do { if ((v0_) != (v1_)) { \
     ok_ = 0; \
 }} while (0)
 
 #define expect_sh2(ok_, sh2_, v0_, v1_) \
 do { if ((v0_) != (v1_)) { \
-    printf("%csh2: %08x %08x\n", sh2_ ? 's' : 'm', v0_, v1_); \
+    printf("%d %csh2: %08x %08x\n", __LINE__, sh2_ ? 's' : 'm', v0_, v1_); \
     ok_ = 0; \
 }} while (0)
 
     ok_ = 0; \
 }} while (0)
 
@@ -810,6 +822,33 @@ static int t_dma_fill_src(void)
     return ok;
 }
 
     return ok;
 }
 
+// should not see the busy flag
+static int t_dma_busy_vram(void)
+{
+    const u32 *src = (const u32 *)0x3c0000;
+    u16 sr[3];
+    int ok = 1;
+
+    vdp_wait_for_line_0();
+
+    do_setup_dma(src, 1);
+    write32(VDP_CTRL_PORT, CTL_WRITE_VRAM(0x100) | CTL_WRITE_DMA);
+    sr[0] = read16(VDP_CTRL_PORT);
+
+    do_setup_dma(src, 4);
+    write32(VDP_CTRL_PORT, CTL_WRITE_VRAM(0x100) | CTL_WRITE_DMA);
+    sr[1] = read16(VDP_CTRL_PORT);
+
+    VDP_setReg(VDP_DMA_LEN0, 8);
+    write32(VDP_CTRL_PORT, CTL_WRITE_VRAM(0x100) | CTL_WRITE_DMA);
+    sr[2] = read16(VDP_CTRL_PORT);
+
+    expect_bits(ok, sr[0], 0, SR_DMA);
+    expect_bits(ok, sr[1], 0, SR_DMA);
+    expect_bits(ok, sr[2], 0, SR_DMA);
+    return ok;
+}
+
 // (((a & 2) >> 1) ^ 1) | ((a & $400) >> 9) | (a & $3FC) | ((a & $1F800) >> 1)
 static int t_dma_128k(void)
 {
 // (((a & 2) >> 1) ^ 1) | ((a & $400) >> 9) | (a & $3FC) | ((a & $1F800) >> 1)
 static int t_dma_128k(void)
 {
@@ -1789,11 +1828,12 @@ static void x32_cmd(enum x32x_cmd cmd, u32 a0, u32 a1, u16 is_slave)
         printf("exc m s: %02x %02x\n", r8[0x0e], r8[0x0f]);
         write16(r, 0);
     }
         printf("exc m s: %02x %02x\n", r8[0x0e], r8[0x0f]);
         write16(r, 0);
     }
-    v = read16(&r[1]);
+    v = read8(&r8[2]);
     if (v != 0) {
         printf("cmd err: %x\n", v);
         write16(&r[1], 0);
     }
     if (v != 0) {
         printf("cmd err: %x\n", v);
         write16(&r[1], 0);
     }
+    mem_barrier();
 }
 
 static int t_32x_reset_btn(void)
 }
 
 static int t_32x_reset_btn(void)
@@ -1828,7 +1868,7 @@ static int t_32x_reset_btn(void)
     expect(ok, r32[0x20/4], 0x00005a20);
     expect(ok, r32[0x24/4], 0x5a5a5a24);
     expect(ok, r32[0x28/4], 0x5a5a5a28);
     expect(ok, r32[0x20/4], 0x00005a20);
     expect(ok, r32[0x24/4], 0x5a5a5a24);
     expect(ok, r32[0x28/4], 0x5a5a5a28);
-    expect(ok, r32[0x2c/4], 0x07075a2c); // 7 - last_irq_vec
+    expect(ok, r32[0x2c/4], 0x075a5a2c); // 7 - last_irq_vec
     if (!(r16[0x00/2] & 0x8000)) {
         expect(ok, r8 [0x81], 1);
         expect(ok, r16[0x82/2], 1);
     if (!(r16[0x00/2] & 0x8000)) {
         expect(ok, r8 [0x81], 1);
         expect(ok, r16[0x82/2], 1);
@@ -1858,7 +1898,18 @@ static int t_32x_reset_btn(void)
         expect(ok, s_icnt[i], 0x100);
     }
     expect(ok, m_icnt[7], 0x101); // VRES happened
         expect(ok, s_icnt[i], 0x100);
     }
     expect(ok, m_icnt[7], 0x101); // VRES happened
+    expect(ok, s_icnt[7], 0x100); // masked on slave
+
+    x32_cmd(CMD_GETSR, 0, 0, 1);
+    expect_sh2(ok, 1, r32[0x24/4] & ~1, 0xf0); // still masked
+    x32_cmd(CMD_SETSR, 0x10, 0, 1);
+    expect(ok, r16[0x00/2], 0x8083);
+    write8(r8, 0x00); // FM=0
+    mem_barrier();
+    expect(ok, m_icnt[7], 0x101);
     expect(ok, s_icnt[7], 0x101);
     expect(ok, s_icnt[7], 0x101);
+    expect(ok, r32[0x2c/4], 0x00070000); // 7 - last_irq_vec
+    r32[0x2c/4] = 0;
 
     memcpy_(do_32x_disable, x32x_disable,
             x32x_disable_end - x32x_disable);
 
     memcpy_(do_32x_disable, x32x_disable,
             x32x_disable_end - x32x_disable);
@@ -2109,7 +2160,7 @@ static int t_32x_sh_fb(void)
     return ok;
 }
 
     return ok;
 }
 
-static int t_32x_irq(void)
+static int t_32x_irq_cmd(void)
 {
     u32 *fbl_icnt = (u32 *)(0x840000 + IRQ_CNT_FB_BASE);
     u16 *m_icnt = (u16 *)fbl_icnt;
 {
     u32 *fbl_icnt = (u32 *)(0x840000 + IRQ_CNT_FB_BASE);
     u16 *m_icnt = (u16 *)fbl_icnt;
@@ -2142,7 +2193,9 @@ static int t_32x_irq(void)
     write16(&r16[0x02/2], 0xaaaa); // INTS+unused_bits
     mem_barrier();
     expect(ok, r16[0x02/2], 2);
     write16(&r16[0x02/2], 0xaaaa); // INTS+unused_bits
     mem_barrier();
     expect(ok, r16[0x02/2], 2);
-    burn10(10);
+    //burn10(10);
+    x32_cmd(CMD_WRITE8, 0x20004001, 0, 0); // mask again
+    x32_cmd(CMD_WRITE8, 0x20004001, 0, 1);
     mem_barrier();
     expect(ok, r16[0x02/2], 0);
     expect(ok, r8 [0x2c], 4);
     mem_barrier();
     expect(ok, r16[0x02/2], 0);
     expect(ok, r8 [0x2c], 4);
@@ -2161,6 +2214,102 @@ static int t_32x_irq(void)
     return ok;
 }
 
     return ok;
 }
 
+static int t_32x_irq_vint(void)
+{
+    u32 *fbl_icnt = (u32 *)(0x840000 + IRQ_CNT_FB_BASE);
+    u16 *m_icnt = (u16 *)fbl_icnt;
+    u16 *s_icnt = m_icnt + 8;
+    u32 *r = (u32 *)0xa15100;
+    u16 *r16 = (u16 *)r;
+    u8 *r8 = (u8 *)r;
+    int ok = 1, i;
+
+    vdp_wait_for_line_0();
+    write8(r, 0x00); // FM=0
+    r[0x2c/4] = 0;
+    mem_barrier();
+    for (i = 0; i < 8; i++)
+        write32(&fbl_icnt[i], 0);
+    mem_barrier();
+    x32_cmd(CMD_SETSR, 0xf0, 0, 0); // master mask at sr
+    x32_cmd(CMD_WRITE8, 0x20004001, 8, 0); // unmask both 32x vint
+    x32_cmd(CMD_WRITE8, 0x20004001, 8, 1);
+    burn10(10);
+    mem_barrier();
+    expect(ok, r16[0x2c/2], 0); // no pending vints
+    expect(ok, r16[0x2e/2], 0); // no exception_index
+
+    write8(&r8[0x23], 0x5a);    // no-32x-source-autoclear flag
+    wait_next_vsync();
+    burn10(10);
+    expect(ok, r8 [0x2c], 0);
+    expect(ok, r8 [0x2d], 12/2);
+    write8(&r8[0x2d], 0);
+    burn10(10);
+    expect(ok, r8 [0x2c], 0);
+    expect(ok, r8 [0x2d], 12/2);
+
+    x32_cmd(CMD_WRITE16, 0x20004016, 0, 0); // clear on 32x (from master)
+    burn10(10);
+    write16(&r[0x2c/4], 0);
+    burn10(10);
+    expect(ok, r8 [0x2c], 0);
+    expect(ok, r8 [0x2d], 12/2);
+    // (here the slave can't accept commands as it keeps retaking the irq)
+    write8(&r8[0x23], 0);       // handler 32x clear on
+    burn10(10);
+    write16(&r[0x2c/4], 0);
+    burn10(10);
+    expect(ok, r16[0x2c/2], 0); // no pending vints
+    expect(ok, r16[0x2e/2], 0); // no exception_index
+    write8(r, 0x00); // FM=0
+    mem_barrier();
+    expect(ok, m_icnt[12/2], 0);
+    expect_range(ok, s_icnt[12/2], 0x10, 0x1000);
+
+    x32_cmd(CMD_SETSR, 0x10, 0, 0); // master unmask at sr
+    wait_next_vsync();
+    burn10(10);
+    expect(ok, r8 [0x2c], 12/2);
+    expect(ok, r8 [0x2d], 12/2);
+
+    x32_cmd(CMD_WRITE8, 0x20004001, 0, 0); // mask
+    x32_cmd(CMD_WRITE8, 0x20004001, 0, 1);
+    write16(&r[0x2c/4], 0);
+    wait_next_vsync();
+    burn10(10);
+    expect(ok, r16[0x2c/2], 0); // no pending vints
+    expect(ok, r16[0x2e/2], 0); // no exception_index
+
+    x32_cmd(CMD_WRITE8, 0x20004001, 8, 1); // slave unmask
+    burn10(10);
+    x32_cmd(CMD_WRITE8, 0x20004001, 0, 1); // mask
+    burn10(10);
+    expect(ok, r8 [0x2c], 0);
+    expect(ok, r8 [0x2d], 12/2);
+    write16(&r[0x2c/4], 0);
+
+    vdp_wait_for_line_0();
+    x32_cmd(CMD_WRITE8, 0x20004001, 8, 0); // master unmask
+    burn10(10);
+    x32_cmd(CMD_WRITE8, 0x20004001, 0, 0); // mask
+    burn10(10);
+    expect(ok, r8 [0x2c], 0);
+    expect(ok, r8 [0x2d], 0);
+
+    write8(&r8[0x23], 0);       // handler 32x clear on
+    write8(r, 0x00); // FM=0
+    mem_barrier();
+    expect(ok, m_icnt[12/2], 1);
+    for (i = 0; i < 8; i++) {
+        if (i == 12/2)
+            continue;
+        expect(ok, m_icnt[i], 0);
+        expect(ok, s_icnt[i], 0);
+    }
+    return ok;
+}
+
 static int t_32x_reg_w(void)
 {
     u32 *r32 = (u32 *)0xa15100;
 static int t_32x_reg_w(void)
 {
     u32 *r32 = (u32 *)0xa15100;
@@ -2202,6 +2351,7 @@ static int t_32x_reset_prep(void)
         write32(&fbl_icnt[i], 0x01000100);
     x32_cmd(CMD_WRITE8, 0x20004001, 0x02, 0); // unmask cmd
     x32_cmd(CMD_WRITE8, 0x20004001, 0x02, 1); // unmask slave
         write32(&fbl_icnt[i], 0x01000100);
     x32_cmd(CMD_WRITE8, 0x20004001, 0x02, 0); // unmask cmd
     x32_cmd(CMD_WRITE8, 0x20004001, 0x02, 1); // unmask slave
+    x32_cmd(CMD_SETSR, 0xf0, 0, 1);           // mask slave irqs (on the cpu)
     burn10(10);
     write8(r8, 0x00); // FM=0
     expect(ok, r32[0x2c/4], 0);
     burn10(10);
     write8(r8, 0x00); // FM=0
     expect(ok, r32[0x2c/4], 0);
@@ -2222,7 +2372,7 @@ static int t_32x_reset_prep(void)
         r16[0x8a/2] = 0x0001;
         mem_barrier();
         for (i = 0; i < 220/2; i++)
         r16[0x8a/2] = 0x0001;
         mem_barrier();
         for (i = 0; i < 220/2; i++)
-            fbl[i] = 0;
+            write32(&fbl[i], 0);
         r8 [0x81] = 1;
         r16[0x82/2] = 0xffff;
         r16[0x84/2] = 0xffff;
         r8 [0x81] = 1;
         r16[0x82/2] = 0xffff;
         r16[0x84/2] = 0xffff;
@@ -2261,6 +2411,7 @@ static const struct {
     { T_MD, t_dma_fill3_vsram,     "dma fill3 vsram" },
     { T_MD, t_dma_fill_dis,        "dma fill disabled" },
     { T_MD, t_dma_fill_src,        "dma fill src incr" },
     { T_MD, t_dma_fill3_vsram,     "dma fill3 vsram" },
     { T_MD, t_dma_fill_dis,        "dma fill disabled" },
     { T_MD, t_dma_fill_src,        "dma fill src incr" },
+    { T_MD, t_dma_busy_vram,       "dma no busy" },
     { T_MD, t_dma_128k,            "dma 128k mode" },
     { T_MD, t_vdp_128k_b16,        "vdp 128k addr bit16" },
     // { t_vdp_128k_b16_inc,    "vdp 128k bit16 inc" }, // mystery
     { T_MD, t_dma_128k,            "dma 128k mode" },
     { T_MD, t_vdp_128k_b16,        "vdp 128k addr bit16" },
     // { t_vdp_128k_b16_inc,    "vdp 128k bit16 inc" }, // mystery
@@ -2304,7 +2455,8 @@ static const struct {
     { T_32, t_32x_md_rom,          "32x md rom" },
     { T_32, t_32x_md_fb,           "32x md fb" },
     { T_32, t_32x_sh_fb,           "32x sh fb" },
     { T_32, t_32x_md_rom,          "32x md rom" },
     { T_32, t_32x_md_fb,           "32x md fb" },
     { T_32, t_32x_sh_fb,           "32x sh fb" },
-    { T_32, t_32x_irq,             "32x irq" },
+    { T_32, t_32x_irq_cmd,         "32x irq cmd" },
+    { T_32, t_32x_irq_vint,        "32x irq vint" },
     { T_32, t_32x_reg_w,           "32x reg w" },
     { T_32, t_32x_reset_prep,      "32x rstprep" }, // must be last 32x
 };
     { T_32, t_32x_reg_w,           "32x reg w" },
     { T_32, t_32x_reset_prep,      "32x rstprep" }, // must be last 32x
 };
@@ -2427,6 +2579,7 @@ int main()
 
     VDP_setReg(VDP_MODE2, VDP_MODE2_MD | VDP_MODE2_DMA | VDP_MODE2_DISP);
 
 
     VDP_setReg(VDP_MODE2, VDP_MODE2_MD | VDP_MODE2_DMA | VDP_MODE2_DISP);
 
+    printf("%s\n", VERSION);
     have_32x = read32(0xa130ec) == MKLONG('M','A','R','S');
     en_32x = have_32x && (read16(0xa15100) & 1);
     v8 = read8(0xa10001);
     have_32x = read32(0xa130ec) == MKLONG('M','A','R','S');
     en_32x = have_32x && (read16(0xa15100) & 1);
     v8 = read8(0xa10001);
@@ -2496,7 +2649,7 @@ int main()
         while (!(read16(VDP_CTRL_PORT) & SR_VB))
             write16(-4, read16(VDP_HV_COUNTER)); /* not blanking */;
     }
         while (!(read16(VDP_CTRL_PORT) & SR_VB))
             write16(-4, read16(VDP_HV_COUNTER)); /* not blanking */;
     }
-#ifndef PICO
+#if 0 //ndef PICO
     // blank due to my lame tv being burn-in prone
     VDP_setReg(VDP_MODE2, VDP_MODE2_MD);
 #endif
     // blank due to my lame tv being burn-in prone
     VDP_setReg(VDP_MODE2, VDP_MODE2_MD);
 #endif
index 1bd9b90..333842c 100644 (file)
@@ -3,9 +3,14 @@
 void spin(int loops);
 u16  read_frt(void);
 
 void spin(int loops);
 u16  read_frt(void);
 
+// vram map:
+// 00-0f: master irq counters (16bit)
+// 10-1f: slave ...
+
 // comm area map:
 // 00-01: cmd
 // comm area map:
 // 00-01: cmd
-// 02-03: error
+// 02:    error counter
+// 03:    handler input: no 32x irqsrc clear flag for both (if val == 0x5a)
 // 04-07: arg0/response
 // 08-0b: arg1
 // 0c: last_irq_vec_master
 // 04-07: arg0/response
 // 08-0b: arg1
 // 0c: last_irq_vec_master
@@ -15,6 +20,7 @@ u16  read_frt(void);
 static void do_cmd(u16 cmd, u16 r[6], u32 is_slave)
 {
     u32 *rl = (u32 *)r;
 static void do_cmd(u16 cmd, u16 r[6], u32 is_slave)
 {
     u32 *rl = (u32 *)r;
+    u8 *r8 = (u8 *)r;
     u32 a, d;
     u16 v;
 
     u32 a, d;
     u16 v;
 
@@ -59,15 +65,23 @@ static void do_cmd(u16 cmd, u16 r[6], u32 is_slave)
         write32(a, d);
         break;
     case CMD_GETGBR:
         write32(a, d);
         break;
     case CMD_GETGBR:
-        asm("stc gbr, %0" : "=r"(d));
+        asm volatile("stc gbr, %0" : "=r"(d));
         write32(&rl[4/4], d);
         break;
     case CMD_GETVBR:
         write32(&rl[4/4], d);
         break;
     case CMD_GETVBR:
-        asm("stc vbr, %0" : "=r"(d));
+        asm volatile("stc vbr, %0" : "=r"(d));
+        write32(&rl[4/4], d);
+        break;
+    case CMD_GETSR:
+        asm volatile("stc sr, %0" : "=r"(d));
         write32(&rl[4/4], d);
         break;
         write32(&rl[4/4], d);
         break;
+    case CMD_SETSR:
+        d = read32(&rl[4/4]);
+        asm volatile("ldc %0, sr" :: "r"(d));
+        break;
     default:
     default:
-        r[2/2]++; // error
+        r8[2]++; // error
         mem_barrier();
         break;
     }
         mem_barrier();
         break;
     }
index d72f083..8c36723 100644 (file)
@@ -130,10 +130,14 @@ do_irq_cmn:
         mov.w   @r0, r1
         add     #1, r1
         mov.w   r1, @r0
         mov.w   @r0, r1
         add     #1, r1
         mov.w   r1, @r0
+        mov.b   @(0x23, gbr), r0
+        cmp/eq  #0x5a, r0
+        bt      0f /* noack */
         mova    l_irq_ao, r0
         mov.w   @(r0, r2), r0
         stc     gbr, r1
         mova    l_irq_ao, r0
         mov.w   @(r0, r2), r0
         stc     gbr, r1
-        mov.w   r0, @(r0, r1) /* ack */
+        mov.w   r0, @(r0, r1) /* 32x irq clear */
+0:
         mov     #0x80, r0
         mov.b   r0, @(0, gbr) /* FM=1 and flush writebuf (alt: ~20 nops) */
         mov.l   @r15+, r2
         mov     #0x80, r0
         mov.b   r0, @(0, gbr) /* FM=1 and flush writebuf (alt: ~20 nops) */
         mov.l   @r15+, r2