mirror of
https://github.com/libretro/virtualjaguar-libretro.git
synced 2026-11-09 17:30:53 -10:00
jag_sim DIVIDE.NET (TOM and JERRY identical): an unsigned non-restoring divider whose working value is the 32-bit dividend_hi register plus the last_neg sign flop -- both adders carry a 33rd bit "to correctly determine the sign, as the arguments are 32-bit unsigned values". The core's loop kept a 32-bit working value and took the sign from bit 31, so every divisor above $80000000 gave garbage ($FCC00000/$FCC00000 = $F9A9342A instead of 1, found in Xenowings' joypad reader), as did 16.16 divides whose quotient overflows. For divisors up to $80000000 with an in-range quotient the result is bit-identical to before (checked over 20M random cases). - src/tom/risc_div.h: risc_divide(), the netlist algorithm, header-only like dsp_acc40.h. Used by gpu_opcode_div, dsp_opcode_div and the dead pipelined DSP_div (DSPExecP is never defined), so no two paths can disagree. - The DSP's divide-by-zero guard goes: the hardware has no special case, every step subtracts 0, giving $FFFFFFFF (unchanged) with the dividend in D_REMAIN (was 0). - Tests: test_dsp_unit DIV cases incl. remainders (an even quotient ends on a negative step: remainder - divisor, as DIVIDE.NET documents); test_gpu_ops $FCC00000/$FCC00000. Co-Authored-By: Claude Opus 5.5 <[email protected]>
1229 lines
36 KiB
C
1229 lines
36 KiB
C
/* test_gpu_ops.c -- GPU instruction set verification.
|
|
* Each test writes a short GPU program, executes it, and checks results
|
|
* via register banks and GPU RAM.
|
|
*
|
|
* Build: cc -o test/test_gpu_ops test/test_gpu_ops.c -ldl
|
|
* Usage: ./test/test_gpu_ops
|
|
*
|
|
* GPU-specific opcodes (different from DSP):
|
|
* 32 sat8 33 sat16 42 loadp 48 storep
|
|
* 62 sat24 63 pack/unpack
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <dlfcn.h>
|
|
#include <stdarg.h>
|
|
#include <stdbool.h>
|
|
#include "../libretro-common/include/libretro.h"
|
|
|
|
#ifdef __APPLE__
|
|
#define CORE_FILENAME "virtualjaguar_libretro.dylib"
|
|
#elif defined(_WIN32)
|
|
#define CORE_FILENAME "virtualjaguar_libretro.dll"
|
|
#else
|
|
#define CORE_FILENAME "virtualjaguar_libretro.so"
|
|
#endif
|
|
|
|
#define GPU_FLAGS_ADDR 0xF02100
|
|
#define GPU_CTRL_ADDR 0xF02114
|
|
#define GPU_PC_ADDR 0xF02110
|
|
#define GPU_RAM_BASE 0xF03000
|
|
#define GPU_RAM_SIZE 0x1000
|
|
|
|
#define GPUGO 0x001
|
|
|
|
#define OP(opc, r1, r2) ((uint16_t)(((opc)<<10)|((r1)<<5)|(r2)))
|
|
|
|
#define OP_ADD(rs, rd) OP(0, rs, rd)
|
|
#define OP_ADDC(rs, rd) OP(1, rs, rd)
|
|
#define OP_ADDQ(n, rd) OP(2, n, rd)
|
|
#define OP_ADDQT(n, rd) OP(3, n, rd)
|
|
#define OP_SUB(rs, rd) OP(4, rs, rd)
|
|
#define OP_SUBC(rs, rd) OP(5, rs, rd)
|
|
#define OP_SUBQ(n, rd) OP(6, n, rd)
|
|
#define OP_SUBQT(n, rd) OP(7, n, rd)
|
|
#define OP_NEG(rd) OP(8, 0, rd)
|
|
#define OP_AND(rs, rd) OP(9, rs, rd)
|
|
#define OP_OR(rs, rd) OP(10, rs, rd)
|
|
#define OP_XOR(rs, rd) OP(11, rs, rd)
|
|
#define OP_NOT(rs, rd) OP(12, rs, rd)
|
|
#define OP_BTST(n, rd) OP(13, n, rd)
|
|
#define OP_BSET(n, rd) OP(14, n, rd)
|
|
#define OP_BCLR(n, rd) OP(15, n, rd)
|
|
#define OP_MULT(rs, rd) OP(16, rs, rd)
|
|
#define OP_IMULT(rs, rd) OP(17, rs, rd)
|
|
#define OP_IMULTN(rs, rd) OP(18, rs, rd)
|
|
#define OP_RESMAC(rd) OP(19, 0, rd)
|
|
#define OP_IMACN(rs, rd) OP(20, rs, rd)
|
|
#define OP_DIV(rs, rd) OP(21, rs, rd)
|
|
#define OP_ABS(rd) OP(22, 0, rd)
|
|
#define OP_SH(rs, rd) OP(23, rs, rd)
|
|
#define OP_SHLQ(n, rd) OP(24, 32-(n), rd)
|
|
#define OP_SHRQ(n, rd) OP(25, (n), rd)
|
|
#define OP_SHA(rs, rd) OP(26, rs, rd)
|
|
#define OP_SHARQ(n, rd) OP(27, (n), rd)
|
|
#define OP_ROR(rs, rd) OP(28, rs, rd)
|
|
#define OP_RORQ(n, rd) OP(29, (n), rd)
|
|
#define OP_CMP(rs, rd) OP(30, rs, rd)
|
|
#define OP_CMPQ(n, rd) OP(31, n, rd)
|
|
#define OP_SAT8(rs, rd) OP(32, rs, rd)
|
|
#define OP_SAT16(rs, rd) OP(33, rs, rd)
|
|
#define OP_MOVE(rs, rd) OP(34, rs, rd)
|
|
#define OP_MOVEQ(n, rd) OP(35, n, rd)
|
|
#define OP_MOVETA(rs, rd) OP(36, rs, rd)
|
|
#define OP_MOVEFA(rs, rd) OP(37, rs, rd)
|
|
#define OP_LOAD(rs, rd) OP(41, rs, rd)
|
|
#define OP_LOADP(rs, rd) OP(42, rs, rd)
|
|
#define OP_STORE(ra, rv) OP(47, ra, rv)
|
|
#define OP_STOREP(ra, rv) OP(48, ra, rv)
|
|
#define OP_MOVE_PC(rd) OP(51, 0, rd)
|
|
#define OP_JUMP(cc, ra) OP(52, ra, cc)
|
|
#define OP_JR(cc, off) OP(53, (off)&0x1F, cc)
|
|
#define OP_NOP OP(57, 0, 0)
|
|
#define OP_SAT24(rs, rd) OP(62, rs, rd)
|
|
#define OP_PACK(n, rd) OP(63, n, rd)
|
|
|
|
/* libretro function pointers */
|
|
static void (*p_retro_init)(void);
|
|
static void (*p_retro_deinit)(void);
|
|
static void (*p_retro_set_environment)(retro_environment_t);
|
|
static void (*p_retro_set_video_refresh)(retro_video_refresh_t);
|
|
static void (*p_retro_set_audio_sample)(retro_audio_sample_t);
|
|
static void (*p_retro_set_audio_sample_batch)(retro_audio_sample_batch_t);
|
|
static void (*p_retro_set_input_poll)(retro_input_poll_t);
|
|
static void (*p_retro_set_input_state)(retro_input_state_t);
|
|
static bool (*p_retro_load_game)(const struct retro_game_info *);
|
|
static void (*p_retro_unload_game)(void);
|
|
|
|
static void *core_handle;
|
|
static uint32_t *p_gpu_pc;
|
|
static uint32_t *p_gpu_reg_bank_0;
|
|
static uint32_t *p_gpu_reg_bank_1;
|
|
static void (*p_GPUReset)(void);
|
|
static void (*p_GPUExec)(int32_t);
|
|
static void (*p_GPUWriteLong)(uint32_t, uint32_t, uint32_t);
|
|
static void (*p_GPUWriteWord)(uint32_t, uint16_t, uint32_t);
|
|
static uint32_t (*p_GPUReadLong)(uint32_t, uint32_t);
|
|
/* Optional (G_FLAGS retire window, #865): the tests that need them SKIP
|
|
* when a core does not export them. */
|
|
static size_t (*p_GPUStateSave)(uint8_t *);
|
|
static size_t (*p_GPUStateLoad)(const uint8_t *);
|
|
static void (*p_GPUSetFlags)(uint32_t);
|
|
static void (*p_GPUUpdateRegisterBanks)(void);
|
|
static void (*p_GPUSetIRQLine)(int, int);
|
|
|
|
/* Stubs */
|
|
static void video_refresh(const void *d, unsigned w, unsigned h, size_t p)
|
|
{ (void)d;(void)w;(void)h;(void)p; }
|
|
static void audio_sample(int16_t l, int16_t r) { (void)l;(void)r; }
|
|
static size_t audio_batch(const int16_t *d, size_t f) { (void)d; return f; }
|
|
static void input_poll(void) {}
|
|
static int16_t input_state(unsigned p, unsigned d, unsigned i, unsigned id)
|
|
{ (void)p;(void)d;(void)i;(void)id; return 0; }
|
|
|
|
static void log_printf(enum retro_log_level level, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
if (level < RETRO_LOG_WARN) return;
|
|
va_start(ap, fmt);
|
|
vfprintf(stderr, fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
static struct retro_log_callback log_cb = { log_printf };
|
|
|
|
static bool environment(unsigned cmd, void *data)
|
|
{
|
|
switch (cmd) {
|
|
case RETRO_ENVIRONMENT_GET_LOG_INTERFACE:
|
|
*(struct retro_log_callback *)data = log_cb;
|
|
return true;
|
|
case RETRO_ENVIRONMENT_SET_PIXEL_FORMAT:
|
|
case RETRO_ENVIRONMENT_SET_VARIABLES:
|
|
case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2:
|
|
case RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE:
|
|
case RETRO_ENVIRONMENT_SET_MEMORY_MAPS:
|
|
case RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS:
|
|
case RETRO_ENVIRONMENT_SET_SERIALIZATION_QUIRKS:
|
|
case RETRO_ENVIRONMENT_SET_GEOMETRY:
|
|
case RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION:
|
|
case RETRO_ENVIRONMENT_GET_PREFERRED_HW_RENDER:
|
|
return true;
|
|
case RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY:
|
|
case RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY:
|
|
*(const char **)data = "/tmp";
|
|
return true;
|
|
case RETRO_ENVIRONMENT_GET_VARIABLE: {
|
|
struct retro_variable *var = (struct retro_variable *)data;
|
|
if (var->key && strcmp(var->key, "virtualjaguar_bios") == 0)
|
|
{ var->value = "disabled"; return true; }
|
|
if (var->key && strcmp(var->key, "virtualjaguar_usefastblitter") == 0)
|
|
{ var->value = "enabled"; return true; }
|
|
var->value = NULL;
|
|
return false;
|
|
}
|
|
default: return false;
|
|
}
|
|
}
|
|
|
|
static int passes = 0, fails = 0;
|
|
#define PASS(msg, ...) do { printf(" PASS: " msg "\n", ##__VA_ARGS__); passes++; } while(0)
|
|
#define FAIL(msg, ...) do { printf(" FAIL: " msg "\n", ##__VA_ARGS__); fails++; } while(0)
|
|
|
|
/* --- GPU RAM helpers (via memory-mapped writes) --- */
|
|
static void gw16(uint16_t off, uint16_t v) {
|
|
p_GPUWriteWord(GPU_RAM_BASE + off, v, 1);
|
|
}
|
|
static void gw32(uint16_t off, uint32_t v) {
|
|
p_GPUWriteLong(GPU_RAM_BASE + off, v, 1);
|
|
}
|
|
static uint32_t gr32(uint16_t off) {
|
|
return p_GPUReadLong(GPU_RAM_BASE + off, 1);
|
|
}
|
|
static void gwmovei(uint16_t off, uint32_t imm, uint8_t rd) {
|
|
gw16(off, OP(38, 0, rd));
|
|
gw16(off+2, imm & 0xFFFF);
|
|
gw16(off+4, (imm>>16) & 0xFFFF);
|
|
}
|
|
|
|
static void prep(void) {
|
|
uint32_t i;
|
|
p_GPUReset();
|
|
for (i = 0; i < GPU_RAM_SIZE; i += 2)
|
|
gw16(i, OP_NOP);
|
|
for (i = 0; i < 32; i++)
|
|
p_gpu_reg_bank_0[i] = p_gpu_reg_bank_1[i] = 0;
|
|
}
|
|
|
|
static void run(int32_t cycles) {
|
|
p_GPUWriteLong(GPU_PC_ADDR, GPU_RAM_BASE + 0x100, 1);
|
|
p_GPUWriteLong(GPU_CTRL_ADDR, GPUGO, 1);
|
|
p_GPUExec(cycles);
|
|
p_GPUWriteLong(GPU_CTRL_ADDR, 0, 1);
|
|
}
|
|
|
|
#define REG(n) p_gpu_reg_bank_0[(n)]
|
|
|
|
/* ============================================================ */
|
|
/* Arithmetic (shared with DSP, verify GPU implementation) */
|
|
/* ============================================================ */
|
|
|
|
static void test_add(void)
|
|
{
|
|
printf("\n--- ADD ---\n");
|
|
prep();
|
|
gw16(0x100, OP_MOVEQ(10, 0));
|
|
gw16(0x102, OP_MOVEQ(20, 1));
|
|
gw16(0x104, OP_ADD(0, 1));
|
|
run(20);
|
|
if (REG(1) == 30) PASS("10+20=30");
|
|
else FAIL("10+20=%u (expected 30)", REG(1));
|
|
}
|
|
|
|
static void test_addc(void)
|
|
{
|
|
printf("\n--- ADDC ---\n");
|
|
prep();
|
|
gwmovei(0x100, 0xFFFFFFFF, 0);
|
|
gw16(0x106, OP_MOVEQ(1, 1));
|
|
gw16(0x108, OP_ADD(1, 0));
|
|
gw16(0x10A, OP_MOVEQ(5, 2));
|
|
gw16(0x10C, OP_MOVEQ(10, 3));
|
|
gw16(0x10E, OP_ADDC(2, 3));
|
|
run(40);
|
|
if (REG(3) == 16) PASS("5+10+carry=16");
|
|
else FAIL("5+10+carry=%u (expected 16)", REG(3));
|
|
}
|
|
|
|
static void test_addq(void)
|
|
{
|
|
printf("\n--- ADDQ ---\n");
|
|
prep();
|
|
gw16(0x100, OP_ADDQ(5, 0));
|
|
run(10);
|
|
if (REG(0) == 5) PASS("addq #5 -> 5");
|
|
else FAIL("addq -> %u (expected 5)", REG(0));
|
|
|
|
prep();
|
|
gw16(0x100, OP_MOVEQ(10, 0));
|
|
gw16(0x102, OP_ADDQ(0, 0));
|
|
run(10);
|
|
if (REG(0) == 42) PASS("addq #0(=32) -> 42");
|
|
else FAIL("addq #0(=32) -> %u (expected 42)", REG(0));
|
|
}
|
|
|
|
static void test_sub(void)
|
|
{
|
|
printf("\n--- SUB ---\n");
|
|
prep();
|
|
gw16(0x100, OP_MOVEQ(5, 0));
|
|
gw16(0x102, OP_MOVEQ(20, 1));
|
|
gw16(0x104, OP_SUB(0, 1));
|
|
run(20);
|
|
if (REG(1) == 15) PASS("20-5=15");
|
|
else FAIL("20-5=%u (expected 15)", REG(1));
|
|
}
|
|
|
|
static void test_neg(void)
|
|
{
|
|
printf("\n--- NEG ---\n");
|
|
prep();
|
|
gw16(0x100, OP_MOVEQ(5, 0));
|
|
gw16(0x102, OP_NEG(0));
|
|
run(10);
|
|
if (REG(0) == (uint32_t)-5) PASS("neg 5 = -5");
|
|
else FAIL("neg 5 = %08X (expected FFFFFFFB)", REG(0));
|
|
}
|
|
|
|
/* Logic */
|
|
|
|
static void test_and(void)
|
|
{
|
|
printf("\n--- AND ---\n");
|
|
prep();
|
|
gwmovei(0x100, 0xFF00FF00, 0);
|
|
gwmovei(0x106, 0x0F0F0F0F, 1);
|
|
gw16(0x10C, OP_AND(0, 1));
|
|
run(30);
|
|
if (REG(1) == 0x0F000F00) PASS("AND = 0x0F000F00");
|
|
else FAIL("AND = %08X (expected 0F000F00)", REG(1));
|
|
}
|
|
|
|
static void test_or(void)
|
|
{
|
|
printf("\n--- OR ---\n");
|
|
prep();
|
|
gwmovei(0x100, 0xF0F00000, 0);
|
|
gwmovei(0x106, 0x0F0F0000, 1);
|
|
gw16(0x10C, OP_OR(0, 1));
|
|
run(30);
|
|
if (REG(1) == 0xFFFF0000) PASS("OR = 0xFFFF0000");
|
|
else FAIL("OR = %08X (expected FFFF0000)", REG(1));
|
|
}
|
|
|
|
static void test_xor(void)
|
|
{
|
|
printf("\n--- XOR ---\n");
|
|
prep();
|
|
gwmovei(0x100, 0xFFFF0000, 0);
|
|
gwmovei(0x106, 0xFF00FF00, 1);
|
|
gw16(0x10C, OP_XOR(0, 1));
|
|
run(30);
|
|
if (REG(1) == 0x00FFFF00) PASS("XOR = 0x00FFFF00");
|
|
else FAIL("XOR = %08X (expected 00FFFF00)", REG(1));
|
|
}
|
|
|
|
static void test_not(void)
|
|
{
|
|
printf("\n--- NOT ---\n");
|
|
prep();
|
|
gwmovei(0x100, 0xAAAA5555, 0);
|
|
gw16(0x106, OP_NOT(0, 0));
|
|
run(20);
|
|
if (REG(0) == 0x5555AAAA) PASS("NOT 0xAAAA5555 = 0x5555AAAA");
|
|
else FAIL("NOT = %08X (expected 5555AAAA)", REG(0));
|
|
}
|
|
|
|
/* Bit ops */
|
|
|
|
static void test_bset(void)
|
|
{
|
|
printf("\n--- BSET ---\n");
|
|
prep();
|
|
gw16(0x100, OP_MOVEQ(0, 0));
|
|
gw16(0x102, OP_BSET(5, 0));
|
|
run(10);
|
|
if (REG(0) == 32) PASS("bset #5 -> 32");
|
|
else FAIL("bset -> %u (expected 32)", REG(0));
|
|
}
|
|
|
|
static void test_bclr(void)
|
|
{
|
|
printf("\n--- BCLR ---\n");
|
|
prep();
|
|
gwmovei(0x100, 0xFF, 0);
|
|
gw16(0x106, OP_BCLR(3, 0));
|
|
run(20);
|
|
if (REG(0) == 0xF7) PASS("bclr #3, 0xFF -> 0xF7");
|
|
else FAIL("bclr -> %02X (expected F7)", REG(0));
|
|
}
|
|
|
|
/* Multiply / Divide */
|
|
|
|
static void test_mult(void)
|
|
{
|
|
printf("\n--- MULT ---\n");
|
|
prep();
|
|
gw16(0x100, OP_MOVEQ(7, 0));
|
|
gw16(0x102, OP_MOVEQ(6, 1));
|
|
gw16(0x104, OP_MULT(0, 1));
|
|
run(20);
|
|
if (REG(1) == 42) PASS("7*6=42");
|
|
else FAIL("7*6=%u (expected 42)", REG(1));
|
|
}
|
|
|
|
static void test_imult(void)
|
|
{
|
|
printf("\n--- IMULT ---\n");
|
|
prep();
|
|
gw16(0x100, OP_MOVEQ(7, 0));
|
|
gw16(0x102, OP_MOVEQ(6, 1));
|
|
gw16(0x104, OP_IMULT(0, 1));
|
|
run(20);
|
|
if (REG(1) == 42) PASS("imult 7*6=42");
|
|
else FAIL("imult 7*6=%d (expected 42)", (int32_t)REG(1));
|
|
}
|
|
|
|
static void test_div(void)
|
|
{
|
|
printf("\n--- DIV ---\n");
|
|
prep();
|
|
gwmovei(0x100, 100, 1);
|
|
gw16(0x106, OP_MOVEQ(7, 0));
|
|
gw16(0x108, OP_DIV(0, 1));
|
|
run(40);
|
|
if (REG(1) == 14) PASS("100/7=14");
|
|
else FAIL("100/7=%u (expected 14)", REG(1));
|
|
|
|
/* Divisor above $80000000: the 33-bit working value of DIVIDE.NET
|
|
* (#895). The old 32-bit loop gave $F9A9342A here. */
|
|
prep();
|
|
gwmovei(0x100, 0xFCC00000u, 1);
|
|
gwmovei(0x106, 0xFCC00000u, 0);
|
|
gw16(0x10C, OP_DIV(0, 1));
|
|
run(40);
|
|
if (REG(1) == 1) PASS("$FCC00000/$FCC00000=1");
|
|
else FAIL("$FCC00000/$FCC00000=%08X (expected 1)", REG(1));
|
|
}
|
|
|
|
static void test_abs(void)
|
|
{
|
|
printf("\n--- ABS ---\n");
|
|
prep();
|
|
gwmovei(0x100, (uint32_t)-42, 0);
|
|
gw16(0x106, OP_ABS(0));
|
|
run(20);
|
|
if (REG(0) == 42) PASS("abs(-42)=42");
|
|
else FAIL("abs(-42)=%d (expected 42)", (int32_t)REG(0));
|
|
}
|
|
|
|
/* Shifts */
|
|
|
|
static void test_sh(void)
|
|
{
|
|
printf("\n--- SH ---\n");
|
|
prep();
|
|
gwmovei(0x100, (uint32_t)-2, 0);
|
|
gw16(0x106, OP_MOVEQ(5, 1));
|
|
gw16(0x108, OP_SH(0, 1));
|
|
run(30);
|
|
if (REG(1) == 20) PASS("sh left 5<<2=20");
|
|
else FAIL("sh left 5<<2=%u (expected 20)", REG(1));
|
|
}
|
|
|
|
static void test_shlq(void)
|
|
{
|
|
printf("\n--- SHLQ ---\n");
|
|
prep();
|
|
gw16(0x100, OP_MOVEQ(1, 0));
|
|
gw16(0x102, OP_SHLQ(4, 0));
|
|
run(10);
|
|
if (REG(0) == 16) PASS("shlq #4, 1 -> 16");
|
|
else FAIL("shlq -> %u (expected 16)", REG(0));
|
|
}
|
|
|
|
static void test_shrq(void)
|
|
{
|
|
printf("\n--- SHRQ ---\n");
|
|
prep();
|
|
gwmovei(0x100, 0x80, 0);
|
|
gw16(0x106, OP_SHRQ(3, 0));
|
|
run(20);
|
|
if (REG(0) == 0x10) PASS("shrq #3, 0x80 -> 0x10");
|
|
else FAIL("shrq -> %08X (expected 10)", REG(0));
|
|
}
|
|
|
|
static void test_sha(void)
|
|
{
|
|
printf("\n--- SHA ---\n");
|
|
prep();
|
|
gw16(0x100, OP_MOVEQ(2, 0));
|
|
gwmovei(0x102, (uint32_t)-16, 1);
|
|
gw16(0x108, OP_SHA(0, 1));
|
|
run(30);
|
|
if (REG(1) == (uint32_t)-4) PASS("sha right -16>>2=-4");
|
|
else FAIL("sha -> %08X (expected FFFFFFFC)", REG(1));
|
|
}
|
|
|
|
static void test_sharq(void)
|
|
{
|
|
printf("\n--- SHARQ ---\n");
|
|
prep();
|
|
gwmovei(0x100, (uint32_t)-16, 0);
|
|
gw16(0x106, OP_SHARQ(2, 0));
|
|
run(20);
|
|
if (REG(0) == (uint32_t)-4) PASS("sharq #2, -16 -> -4");
|
|
else FAIL("sharq -> %08X (expected FFFFFFFC)", REG(0));
|
|
}
|
|
|
|
static void test_ror(void)
|
|
{
|
|
printf("\n--- ROR ---\n");
|
|
prep();
|
|
gw16(0x100, OP_MOVEQ(4, 0));
|
|
gwmovei(0x102, 0x12345678, 1);
|
|
gw16(0x108, OP_ROR(0, 1));
|
|
run(30);
|
|
if (REG(1) == 0x81234567) PASS("ror 4, 0x12345678 -> 0x81234567");
|
|
else FAIL("ror -> %08X (expected 81234567)", REG(1));
|
|
}
|
|
|
|
static void test_rorq(void)
|
|
{
|
|
printf("\n--- RORQ ---\n");
|
|
prep();
|
|
gwmovei(0x100, 0x12345678, 0);
|
|
gw16(0x106, OP_RORQ(8, 0));
|
|
run(20);
|
|
if (REG(0) == 0x78123456) PASS("rorq #8 -> 0x78123456");
|
|
else FAIL("rorq -> %08X (expected 78123456)", REG(0));
|
|
}
|
|
|
|
/* Compare */
|
|
|
|
static void test_cmp(void)
|
|
{
|
|
printf("\n--- CMP ---\n");
|
|
prep();
|
|
gw16(0x100, OP_MOVEQ(10, 0));
|
|
gw16(0x102, OP_MOVEQ(10, 1));
|
|
gw16(0x104, OP_CMP(0, 1));
|
|
run(20);
|
|
if (REG(0) == 10 && REG(1) == 10) PASS("CMP leaves operands unchanged");
|
|
else FAIL("CMP modified operands: R0=%u R1=%u", REG(0), REG(1));
|
|
}
|
|
|
|
/* Data movement */
|
|
|
|
static void test_move(void)
|
|
{
|
|
printf("\n--- MOVE ---\n");
|
|
prep();
|
|
gwmovei(0x100, 42, 0);
|
|
gw16(0x106, OP_MOVE(0, 1));
|
|
run(20);
|
|
if (REG(1) == 42) PASS("move R0(42) -> R1=42");
|
|
else FAIL("move -> R1=%u (expected 42)", REG(1));
|
|
}
|
|
|
|
static void test_moveq(void)
|
|
{
|
|
printf("\n--- MOVEQ ---\n");
|
|
prep();
|
|
gw16(0x100, OP_MOVEQ(31, 0));
|
|
run(10);
|
|
if (REG(0) == 31) PASS("moveq #31 -> 31");
|
|
else FAIL("moveq -> %u (expected 31)", REG(0));
|
|
}
|
|
|
|
static void test_movei(void)
|
|
{
|
|
printf("\n--- MOVEI ---\n");
|
|
prep();
|
|
gwmovei(0x100, 0xDEADBEEF, 0);
|
|
run(20);
|
|
if (REG(0) == 0xDEADBEEF) PASS("movei #$DEADBEEF");
|
|
else FAIL("movei -> %08X (expected DEADBEEF)", REG(0));
|
|
}
|
|
|
|
static void test_moveta_movefa(void)
|
|
{
|
|
printf("\n--- MOVETA/MOVEFA ---\n");
|
|
prep();
|
|
gwmovei(0x100, 77, 0);
|
|
gw16(0x106, OP_MOVETA(0, 0));
|
|
p_gpu_reg_bank_1[1] = 88;
|
|
gw16(0x108, OP_MOVEFA(1, 2));
|
|
run(30);
|
|
if (p_gpu_reg_bank_1[0] == 77) PASS("moveta: alt_R0 = 77");
|
|
else FAIL("moveta: alt_R0 = %u (expected 77)", p_gpu_reg_bank_1[0]);
|
|
if (REG(2) == 88) PASS("movefa: R2 = 88");
|
|
else FAIL("movefa: R2 = %u (expected 88)", REG(2));
|
|
}
|
|
|
|
/* Memory */
|
|
|
|
static void test_load_store(void)
|
|
{
|
|
printf("\n--- LOAD/STORE ---\n");
|
|
prep();
|
|
gwmovei(0x100, GPU_RAM_BASE + 0x900, 10);
|
|
gwmovei(0x106, 0xCAFEBABE, 0);
|
|
gw16(0x10C, OP_STORE(10, 0));
|
|
gw16(0x10E, OP_MOVEQ(0, 0));
|
|
gw16(0x110, OP_LOAD(10, 0));
|
|
run(40);
|
|
if (REG(0) == 0xCAFEBABE) PASS("store+load: 0xCAFEBABE");
|
|
else FAIL("store+load: %08X (expected CAFEBABE)", REG(0));
|
|
}
|
|
|
|
/* Every long-store addressing form must force long alignment in GPU local
|
|
* RAM (issue #866). MiSTer's GPU gateware drives the local RAM row from
|
|
* dataddr[11:2] only (rtl/jaguar_common/gpu_mem.v: ram_addr[11:2]), so
|
|
* A1:A0 are dropped whatever the opcode. Ruiner Pinball patches movei
|
|
* immediates with a store (r15+rn) at a word-aligned address. */
|
|
#define OP_STORE_R14_IMM(n, rd) OP(49, n, rd)
|
|
#define OP_STORE_R15_IMM(n, rd) OP(50, n, rd)
|
|
#define OP_STORE_R14_REG(rm, rd) OP(60, rm, rd)
|
|
#define OP_STORE_R15_REG(rm, rd) OP(61, rm, rd)
|
|
|
|
static void store_align_case(const char *name, int form)
|
|
{
|
|
const uint32_t X = GPU_RAM_BASE + 0x900;
|
|
uint32_t r10 = X + 2, r14 = 0, r15 = 0, expect_off = 0;
|
|
uint16_t insn = 0;
|
|
|
|
switch (form) {
|
|
case 0: insn = OP_STORE(10, 0); expect_off = 0; break;
|
|
case 1: r14 = X + 2; insn = OP_STORE_R14_IMM(1, 0); expect_off = 4; break;
|
|
case 2: r15 = X + 2; insn = OP_STORE_R15_IMM(1, 0); expect_off = 4; break;
|
|
case 3: r14 = X; insn = OP_STORE_R14_REG(11, 0); expect_off = 0; break;
|
|
default: r15 = X; insn = OP_STORE_R15_REG(11, 0); expect_off = 0; break;
|
|
}
|
|
|
|
prep();
|
|
gw32(0x900, 0x11111111);
|
|
gw32(0x904, 0x22222222);
|
|
gw32(0x908, 0x33333333);
|
|
gwmovei(0x100, 0xCAFEBABE, 0);
|
|
gwmovei(0x106, r10, 10);
|
|
gwmovei(0x10C, 2, 11);
|
|
gwmovei(0x112, r14, 14);
|
|
gwmovei(0x118, r15, 15);
|
|
gw16(0x11E, insn);
|
|
run(100);
|
|
if (gr32(0x900 + expect_off) == 0xCAFEBABE
|
|
&& gr32(0x900 + (expect_off ? 0 : 4)) == (expect_off ? 0x11111111u : 0x22222222u)
|
|
&& gr32(0x908) == 0x33333333u)
|
|
PASS("%s to word-aligned address lands long-aligned", name);
|
|
else
|
|
FAIL("%s: [900]=%08X [904]=%08X [908]=%08X (CAFEBABE at +%u expected)",
|
|
name, gr32(0x900), gr32(0x904), gr32(0x908), expect_off);
|
|
}
|
|
|
|
static void test_store_alignment(void)
|
|
{
|
|
printf("\n--- STORE alignment in GPU local RAM (#866) ---\n");
|
|
store_align_case("store (rn)", 0);
|
|
store_align_case("store (r14+n)", 1);
|
|
store_align_case("store (r15+n)", 2);
|
|
store_align_case("store (r14+rn)", 3);
|
|
store_align_case("store (r15+rn)", 4);
|
|
}
|
|
|
|
static void test_move_pc(void)
|
|
{
|
|
printf("\n--- MOVE_PC ---\n");
|
|
prep();
|
|
gw16(0x100, OP_MOVE_PC(0));
|
|
run(10);
|
|
if (REG(0) == GPU_RAM_BASE + 0x100) PASS("move_pc -> $%06X", GPU_RAM_BASE + 0x100);
|
|
else FAIL("move_pc -> %08X (expected %08X)", REG(0), GPU_RAM_BASE + 0x100);
|
|
}
|
|
|
|
/* Control flow */
|
|
|
|
static void test_jr(void)
|
|
{
|
|
printf("\n--- JR ---\n");
|
|
prep();
|
|
gw16(0x100, OP_JR(0, 2));
|
|
gw16(0x102, OP_NOP);
|
|
gw16(0x104, OP_MOVEQ(7, 0));
|
|
gw16(0x106, OP_MOVEQ(25, 0));
|
|
run(20);
|
|
if (REG(0) == 25) PASS("jr skipped to moveq #25");
|
|
else FAIL("jr: R0=%u (expected 25)", REG(0));
|
|
}
|
|
|
|
static void test_jump(void)
|
|
{
|
|
printf("\n--- JUMP ---\n");
|
|
prep();
|
|
gwmovei(0x100, GPU_RAM_BASE + 0x200, 10);
|
|
gw16(0x106, OP_JUMP(0, 10));
|
|
gw16(0x108, OP_NOP);
|
|
gw16(0x10A, OP_MOVEQ(3, 0));
|
|
gw16(0x200, OP_MOVEQ(19, 0));
|
|
run(30);
|
|
if (REG(0) == 19) PASS("jump to $%06X: R0=19", GPU_RAM_BASE + 0x200);
|
|
else FAIL("jump: R0=%u (expected 19)", REG(0));
|
|
}
|
|
|
|
static void test_nop(void)
|
|
{
|
|
printf("\n--- NOP ---\n");
|
|
prep();
|
|
gw16(0x100, OP_MOVEQ(5, 0));
|
|
gw16(0x102, OP_NOP);
|
|
gw16(0x104, OP_NOP);
|
|
run(20);
|
|
if (REG(0) == 5) PASS("NOPs don't modify registers");
|
|
else FAIL("NOP corrupted R0=%u", REG(0));
|
|
}
|
|
|
|
/* ============================================================ */
|
|
/* GPU-specific opcodes */
|
|
/* ============================================================ */
|
|
|
|
static void test_sat8(void)
|
|
{
|
|
printf("\n--- SAT8 (GPU-specific) ---\n");
|
|
prep();
|
|
/* sat8: clamp to [0, 255]. Operates on RN. */
|
|
gwmovei(0x100, 500, 0);
|
|
gw16(0x106, OP_SAT8(0, 0));
|
|
run(20);
|
|
if (REG(0) == 255) PASS("sat8(500)=255");
|
|
else FAIL("sat8(500)=%u (expected 255)", REG(0));
|
|
|
|
prep();
|
|
gwmovei(0x100, (uint32_t)-5, 0);
|
|
gw16(0x106, OP_SAT8(0, 0));
|
|
run(20);
|
|
if (REG(0) == 0) PASS("sat8(-5)=0");
|
|
else FAIL("sat8(-5)=%u (expected 0)", REG(0));
|
|
|
|
prep();
|
|
gw16(0x100, OP_MOVEQ(17, 0));
|
|
gw16(0x102, OP_SAT8(0, 0));
|
|
run(10);
|
|
if (REG(0) == 17) PASS("sat8(17)=17 (passthrough)");
|
|
else FAIL("sat8(17)=%u (expected 17)", REG(0));
|
|
}
|
|
|
|
static void test_sat16(void)
|
|
{
|
|
printf("\n--- SAT16 (GPU-specific) ---\n");
|
|
prep();
|
|
/* sat16: clamp to [0, 65535]. Operates on RN. */
|
|
gwmovei(0x100, 100000, 0);
|
|
gw16(0x106, OP_SAT16(0, 0));
|
|
run(20);
|
|
if (REG(0) == 65535) PASS("sat16(100000)=65535");
|
|
else FAIL("sat16(100000)=%u (expected 65535)", REG(0));
|
|
|
|
prep();
|
|
gwmovei(0x100, (uint32_t)-1, 0);
|
|
gw16(0x106, OP_SAT16(0, 0));
|
|
run(20);
|
|
if (REG(0) == 0) PASS("sat16(-1)=0");
|
|
else FAIL("sat16(-1)=%u (expected 0)", REG(0));
|
|
|
|
prep();
|
|
gwmovei(0x100, 1000, 0);
|
|
gw16(0x106, OP_SAT16(0, 0));
|
|
run(20);
|
|
if (REG(0) == 1000) PASS("sat16(1000)=1000 (passthrough)");
|
|
else FAIL("sat16(1000)=%u (expected 1000)", REG(0));
|
|
}
|
|
|
|
static void test_sat24(void)
|
|
{
|
|
printf("\n--- SAT24 (GPU-specific) ---\n");
|
|
prep();
|
|
/* sat24: clamp to [0, 0xFFFFFF]. Operates on RN. */
|
|
gwmovei(0x100, 0x01000001, 0);
|
|
gw16(0x106, OP_SAT24(0, 0));
|
|
run(20);
|
|
if (REG(0) == 0xFFFFFF) PASS("sat24(0x01000001)=0xFFFFFF");
|
|
else FAIL("sat24(0x01000001)=%08X (expected FFFFFF)", REG(0));
|
|
|
|
prep();
|
|
gwmovei(0x100, (uint32_t)-1, 0);
|
|
gw16(0x106, OP_SAT24(0, 0));
|
|
run(20);
|
|
if (REG(0) == 0) PASS("sat24(-1)=0");
|
|
else FAIL("sat24(-1)=%08X (expected 0)", REG(0));
|
|
|
|
prep();
|
|
gwmovei(0x100, 0x123456, 0);
|
|
gw16(0x106, OP_SAT24(0, 0));
|
|
run(20);
|
|
if (REG(0) == 0x123456) PASS("sat24(0x123456) passthrough");
|
|
else FAIL("sat24=%08X (expected 123456)", REG(0));
|
|
}
|
|
|
|
static void test_pack(void)
|
|
{
|
|
printf("\n--- PACK/UNPACK (GPU-specific) ---\n");
|
|
prep();
|
|
/* pack (RM=0): extracts bits from 32-bit value.
|
|
* Result = ((val >> 10) & 0xF000) | ((val >> 5) & 0x0F00) | (val & 0xFF)
|
|
* For 0x7C00_3E00_1F = val with R=31,G=31,B=31 in 15-bit positions:
|
|
* Actually, test with a known value. */
|
|
gwmovei(0x100, 0x001F03E0, 0);
|
|
gw16(0x106, OP_PACK(0, 0));
|
|
run(20);
|
|
/* pack: ((val >> 10) & 0xF000) | ((val >> 5) & 0x0F00) | (val & 0xFF)
|
|
* val = 0x001F03E0
|
|
* (val >> 10) = 0x000007C0, & 0xF000 = 0x0000
|
|
* (val >> 5) = 0x0000F81F, & 0x0F00 = 0x0800
|
|
* val & 0xFF = 0xE0
|
|
* result = 0x0000 | 0x0800 | 0x00E0 = 0x08E0
|
|
*/
|
|
if (REG(0) == 0x000008E0) PASS("pack(0x001F03E0)=0x000008E0");
|
|
else FAIL("pack(0x001F03E0)=%08X (expected 000008E0)", REG(0));
|
|
|
|
/* unpack (RM=1): reverse of pack */
|
|
prep();
|
|
gwmovei(0x100, 0x00001234, 0);
|
|
gw16(0x106, OP_PACK(1, 0));
|
|
run(20);
|
|
/* unpack: ((val & 0xF000) << 10) | ((val & 0x0F00) << 5) | (val & 0xFF)
|
|
* val = 0x1234
|
|
* (val & 0xF000) = 0x1000, << 10 = 0x00400000
|
|
* (val & 0x0F00) = 0x0200, << 5 = 0x00004000
|
|
* val & 0xFF = 0x34
|
|
* result = 0x00400000 | 0x00004000 | 0x34 = 0x00404034
|
|
*/
|
|
if (REG(0) == 0x00404034) PASS("unpack(0x1234)=0x00404034");
|
|
else FAIL("unpack(0x1234)=%08X (expected 00404034)", REG(0));
|
|
}
|
|
|
|
/* NORMI = (index of the top set bit) - 23: the right shift that normalises
|
|
* the value as an IEEE single (JTRM v8 p.52), as TOM's ARITH.NET encodes
|
|
* it; a zero source gives -32. The old expectation (9 for $80000000)
|
|
* normalised to bit 22 and was one too high; Club Drive's clip-corner
|
|
* table lookup depends on the exact value (#611). */
|
|
static void normi_case(uint32_t in, int32_t want)
|
|
{
|
|
prep();
|
|
gwmovei(0x100, in, 0);
|
|
gw16(0x106, OP(56, 0, 1)); /* normi R0, R1 */
|
|
run(20);
|
|
if ((int32_t)REG(1) == want) PASS("normi(0x%08X)=%d", in, want);
|
|
else FAIL("normi(0x%08X)=%d (expected %d)", in, (int32_t)REG(1), want);
|
|
}
|
|
|
|
static void test_normi(void)
|
|
{
|
|
printf("\n--- NORMI ---\n");
|
|
normi_case(0x80000000u, 8);
|
|
normi_case(0x01000000u, 1);
|
|
normi_case(0x00800000u, 0);
|
|
normi_case(0x00FFFFFFu, 0);
|
|
normi_case(0x00400000u, -1);
|
|
normi_case(0x00000080u, -16);
|
|
normi_case(0x00000002u, -22);
|
|
normi_case(0x00000001u, -23);
|
|
normi_case(0x00000000u, -32);
|
|
}
|
|
|
|
/* ============================================================ */
|
|
/* G_FLAGS store retire window (#865) */
|
|
/* ============================================================ */
|
|
|
|
/* A GPU-issued store to G_FLAGS does not retire until the instruction
|
|
* behind it is already past Read Operands (JTRM v8 p.59: "writing a value
|
|
* to the flag bits and making use of those flag bits in the following
|
|
* instruction will not work properly due to pipe-lining effects"). When
|
|
* the store flips REGPAGE, the ONE instruction behind it still reads AND
|
|
* writes the old bank, and the one after that uses the new bank. The GPU
|
|
* port of test_dsp_unit Tests 17-21 (#853). */
|
|
|
|
#define G_IMASK 0x0008u
|
|
#define G_INT_ENA0 0x0010u
|
|
#define G_REGPAGE 0x4000u
|
|
#define REG1(n) p_gpu_reg_bank_1[(n)]
|
|
|
|
/* NOP-filled RAM, all registers zero, bank 0 live (host write), bank-0
|
|
* r1 = G_FLAGS and bank-0 r0 = REGPAGE. */
|
|
static void flags_bank_setup(void)
|
|
{
|
|
prep();
|
|
p_GPUWriteLong(GPU_FLAGS_ADDR, 0, 1);
|
|
REG(0) = G_REGPAGE;
|
|
REG(1) = GPU_FLAGS_ADDR;
|
|
}
|
|
|
|
static void gw_idle(uint16_t off)
|
|
{
|
|
gw16(off, OP_JR(0, -1)); /* jr T,self */
|
|
gw16(off + 2, OP_NOP);
|
|
}
|
|
|
|
static void go_from_100(void)
|
|
{
|
|
p_GPUWriteLong(GPU_PC_ADDR, GPU_RAM_BASE + 0x100, 1);
|
|
p_GPUWriteLong(GPU_CTRL_ADDR, GPUGO, 1);
|
|
}
|
|
|
|
static void test_flags_store_bank_movei(void)
|
|
{
|
|
printf("\n--- G_FLAGS store retire window: movei behind the store ---\n");
|
|
flags_bank_setup();
|
|
/* $F03100 store r0,(r1) ; G_FLAGS = REGPAGE (bank 0 -> 1)
|
|
* $F03102 movei #$11111111,r5 ; in flight: OLD bank (0)
|
|
* $F03108 movei #$22222222,r6 ; past the window: NEW bank (1) */
|
|
gw16(0x100, OP_STORE(1, 0));
|
|
gwmovei(0x102, 0x11111111, 5);
|
|
gwmovei(0x108, 0x22222222, 6);
|
|
gw_idle(0x10E);
|
|
run(12);
|
|
|
|
if (REG(5) == 0x11111111 && REG1(5) == 0)
|
|
PASS("movei behind the G_FLAGS store wrote the pre-store bank (bank-0 r5)");
|
|
else
|
|
FAIL("movei behind the store: bank0 r5=$%08X bank1 r5=$%08X (expected bank 0)",
|
|
REG(5), REG1(5));
|
|
if (REG1(6) == 0x22222222 && REG(6) == 0)
|
|
PASS("next instruction, past the window, wrote the new bank (bank-1 r6)");
|
|
else
|
|
FAIL("instruction after the window: bank0 r6=$%08X bank1 r6=$%08X (expected bank 1)",
|
|
REG(6), REG1(6));
|
|
}
|
|
|
|
static void test_flags_store_bank_move(void)
|
|
{
|
|
printf("\n--- G_FLAGS store retire window: source operand ---\n");
|
|
flags_bank_setup();
|
|
REG(2) = 0xAAAA0000;
|
|
REG1(2) = 0xBBBB0000;
|
|
/* store r0,(r1); move r2,r7 (old bank in and out); move r2,r8 (new) */
|
|
gw16(0x100, OP_STORE(1, 0));
|
|
gw16(0x102, OP_MOVE(2, 7));
|
|
gw16(0x104, OP_MOVE(2, 8));
|
|
gw_idle(0x106);
|
|
run(12);
|
|
|
|
if (REG(7) == 0xAAAA0000 && REG1(7) == 0)
|
|
PASS("move behind the store read and wrote the pre-store bank");
|
|
else
|
|
FAIL("move behind the store: bank0 r7=$%08X bank1 r7=$%08X (expected bank0 = $AAAA0000)",
|
|
REG(7), REG1(7));
|
|
if (REG1(8) == 0xBBBB0000 && REG(8) == 0)
|
|
PASS("move after the window read and wrote the new bank");
|
|
else
|
|
FAIL("move after the window: bank0 r8=$%08X bank1 r8=$%08X (expected bank1 = $BBBB0000)",
|
|
REG(8), REG1(8));
|
|
}
|
|
|
|
static void test_flags_store_then_jump(void)
|
|
{
|
|
printf("\n--- G_FLAGS store retire window: store; jump ---\n");
|
|
flags_bank_setup();
|
|
REG(3) = GPU_RAM_BASE + 0x200; /* the real target */
|
|
REG1(3) = GPU_RAM_BASE + 0x300; /* the wrong one */
|
|
/* store r0,(r1); jump T,(r3); moveq #9,r4 (delay slot) */
|
|
gw16(0x100, OP_STORE(1, 0));
|
|
gw16(0x102, OP_JUMP(0, 3));
|
|
gw16(0x104, OP_MOVEQ(9, 4));
|
|
gw16(0x200, OP_MOVEQ(7, 5));
|
|
gw_idle(0x202);
|
|
gw16(0x300, OP_MOVEQ(13, 5));
|
|
gw_idle(0x302);
|
|
run(12);
|
|
|
|
if (REG1(5) == 7)
|
|
PASS("jump behind the store took its target from the pre-store bank");
|
|
else
|
|
FAIL("jump target: bank0 r5=%u bank1 r5=%u (expected bank1 r5 = 7 from $F03200)",
|
|
REG(5), REG1(5));
|
|
if (REG1(4) == 9 && REG(4) == 0)
|
|
PASS("jump delay slot (second instruction behind the store) wrote the new bank");
|
|
else
|
|
FAIL("jump delay slot: bank0 r4=%u bank1 r4=%u (expected bank 1)",
|
|
REG(4), REG1(4));
|
|
}
|
|
|
|
static void test_jump_with_flags_store_in_slot(void)
|
|
{
|
|
printf("\n--- G_FLAGS store retire window: jump; store in the delay slot ---\n");
|
|
flags_bank_setup();
|
|
REG(15) = GPU_RAM_BASE + 0x200;
|
|
/* jump T,(r15); store r0,(r1) (delay slot); target: movei #$33333333,r6 */
|
|
gw16(0x100, OP_JUMP(0, 15));
|
|
gw16(0x102, OP_STORE(1, 0));
|
|
gwmovei(0x200, 0x33333333, 6);
|
|
gw_idle(0x206);
|
|
run(12);
|
|
|
|
if (REG1(6) == 0x33333333 && REG(6) == 0)
|
|
PASS("branch target after a delay-slot G_FLAGS store wrote the new bank");
|
|
else
|
|
FAIL("branch target: bank0 r6=$%08X bank1 r6=$%08X (expected bank 1)",
|
|
REG(6), REG1(6));
|
|
}
|
|
|
|
static void test_flags_window_savestate(void)
|
|
{
|
|
static uint8_t state[65536];
|
|
|
|
printf("\n--- G_FLAGS store retire window: savestate inside the window ---\n");
|
|
if (!p_GPUStateSave || !p_GPUStateLoad) {
|
|
printf(" SKIP: GPUStateSave/GPUStateLoad not exported\n");
|
|
return;
|
|
}
|
|
flags_bank_setup();
|
|
gw16(0x100, OP_STORE(1, 0));
|
|
gwmovei(0x102, 0x44444444, 5);
|
|
gwmovei(0x108, 0x55555555, 6);
|
|
gw_idle(0x10E);
|
|
|
|
go_from_100();
|
|
/* One cycle = the store alone: the slice ends inside the window. */
|
|
p_GPUExec(1);
|
|
p_GPUStateSave(state);
|
|
|
|
/* Scribble the live state (a host write commits bank 0 and drops any
|
|
* window), then restore the mid-window snapshot. */
|
|
p_GPUWriteLong(GPU_FLAGS_ADDR, 0, 1);
|
|
p_GPUStateLoad(state);
|
|
p_GPUExec(12);
|
|
p_GPUWriteLong(GPU_CTRL_ADDR, 0, 1);
|
|
|
|
if (REG(5) == 0x44444444 && REG1(5) == 0)
|
|
PASS("instruction behind the store still used the pre-store bank after a load");
|
|
else
|
|
FAIL("after load: bank0 r5=$%08X bank1 r5=$%08X (expected bank 0)",
|
|
REG(5), REG1(5));
|
|
/* A load that dropped the window would leave bank 0 live for good. */
|
|
if (REG1(6) == 0x55555555 && REG(6) == 0)
|
|
PASS("the switch the store owed still happened after a load (bank-1 r6)");
|
|
else
|
|
FAIL("after load, past the window: bank0 r6=$%08X bank1 r6=$%08X (expected bank 1)",
|
|
REG(6), REG1(6));
|
|
}
|
|
|
|
/* Interrupt-latched-while-masked setup for the next two tests: bank 0
|
|
* live with IMASK set (raw poke -- a register write cannot SET IMASK),
|
|
* r31 = $F03F00, the GPU running, interrupt 0 latched, and an idle loop
|
|
* at the interrupt-0 vector ($F03000). */
|
|
static int irq_masked_setup(void)
|
|
{
|
|
if (!p_GPUSetFlags || !p_GPUUpdateRegisterBanks || !p_GPUSetIRQLine) {
|
|
printf(" SKIP: GPUSetFlags/GPUUpdateRegisterBanks/GPUSetIRQLine not exported\n");
|
|
return 0;
|
|
}
|
|
prep();
|
|
gw_idle(0x000);
|
|
p_GPUSetFlags(G_IMASK | G_INT_ENA0);
|
|
p_GPUUpdateRegisterBanks();
|
|
REG(31) = GPU_RAM_BASE + 0xF00;
|
|
go_from_100();
|
|
p_GPUSetIRQLine(0, 1);
|
|
return 1;
|
|
}
|
|
|
|
static void test_flags_store_imask_holdoff(void)
|
|
{
|
|
uint32_t pushed;
|
|
|
|
printf("\n--- G_FLAGS store retire window: IMASK clear, interrupt held off one instruction ---\n");
|
|
if (!irq_masked_setup())
|
|
return;
|
|
REG(0) = G_INT_ENA0; /* clears IMASK, keeps INT0 enabled */
|
|
REG(1) = GPU_FLAGS_ADDR;
|
|
/* $F03100 store r0,(r1) ; IMASK cleared, interrupt 0 latched
|
|
* $F03102 moveq #5,r6 ; in flight: still masked, so it runs
|
|
* $F03104 jr T,self ; the interrupt lands before this */
|
|
gw16(0x100, OP_STORE(1, 0));
|
|
gw16(0x102, OP_MOVEQ(5, 6));
|
|
gw_idle(0x104);
|
|
p_GPUExec(12);
|
|
p_GPUWriteLong(GPU_CTRL_ADDR, 0, 1);
|
|
pushed = gr32(0xEFC);
|
|
|
|
if (*p_gpu_pc >= GPU_RAM_BASE && *p_gpu_pc < GPU_RAM_BASE + 4)
|
|
PASS("interrupt 0 was taken (PC in the vector, $%08X)", *p_gpu_pc);
|
|
else
|
|
FAIL("interrupt 0 not taken: PC=$%08X", *p_gpu_pc);
|
|
if (REG(6) == 5 && pushed == GPU_RAM_BASE + 0x102)
|
|
PASS("the instruction behind the store ran masked; pushed $%08X", pushed);
|
|
else
|
|
FAIL("pushed $%08X r6=%u (expected $F03102 and 5: one instruction after the store)",
|
|
pushed, REG(6));
|
|
}
|
|
|
|
static void test_isr_epilogue_jump_store(void)
|
|
{
|
|
uint32_t pushed;
|
|
|
|
printf("\n--- G_FLAGS store retire window: jump (r28); store r29,(r30) ISR epilogue ---\n");
|
|
if (!irq_masked_setup())
|
|
return;
|
|
REG(28) = GPU_RAM_BASE + 0x200; /* return address */
|
|
REG(29) = G_INT_ENA0; /* restored flags: IMASK clear */
|
|
REG(30) = GPU_FLAGS_ADDR;
|
|
/* JTRM v8 p.38-39 example ISR epilogue, with interrupt 0 already
|
|
* latched again: the interrupt is taken at the jump target, which is
|
|
* what gets pushed (target - 2); the target itself has not run. */
|
|
gw16(0x100, OP_JUMP(0, 28));
|
|
gw16(0x102, OP_STORE(30, 29));
|
|
gw16(0x200, OP_MOVEQ(7, 5));
|
|
gw_idle(0x202);
|
|
p_GPUExec(12);
|
|
p_GPUWriteLong(GPU_CTRL_ADDR, 0, 1);
|
|
pushed = gr32(0xEFC);
|
|
|
|
if (*p_gpu_pc >= GPU_RAM_BASE && *p_gpu_pc < GPU_RAM_BASE + 4
|
|
&& pushed == GPU_RAM_BASE + 0x1FE && REG(5) == 0)
|
|
PASS("interrupt taken at the branch target (pushed $%08X, PC $%08X)",
|
|
pushed, *p_gpu_pc);
|
|
else
|
|
FAIL("PC=$%08X pushed=$%08X r5=%u (expected vector, $F031FE, 0)",
|
|
*p_gpu_pc, pushed, REG(5));
|
|
}
|
|
|
|
/* ============================================================ */
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
void *handle;
|
|
uint8_t *dummy_rom;
|
|
struct retro_game_info game;
|
|
(void)argc; (void)argv;
|
|
|
|
printf("=== GPU Instruction Set Tests ===\n");
|
|
|
|
handle = dlopen("./" CORE_FILENAME, RTLD_NOW);
|
|
if (!handle) { fprintf(stderr, "dlopen: %s\n", dlerror()); return 1; }
|
|
core_handle = handle;
|
|
|
|
#define LOAD(sym) do { \
|
|
p_##sym = dlsym(handle, #sym); \
|
|
if (!p_##sym) { fprintf(stderr, "Missing: %s\n", #sym); return 1; } \
|
|
} while(0)
|
|
|
|
LOAD(retro_init);
|
|
LOAD(retro_deinit);
|
|
LOAD(retro_set_environment);
|
|
LOAD(retro_set_video_refresh);
|
|
LOAD(retro_set_audio_sample);
|
|
LOAD(retro_set_audio_sample_batch);
|
|
LOAD(retro_set_input_poll);
|
|
LOAD(retro_set_input_state);
|
|
LOAD(retro_load_game);
|
|
LOAD(retro_unload_game);
|
|
LOAD(GPUReset);
|
|
LOAD(GPUExec);
|
|
LOAD(GPUWriteLong);
|
|
LOAD(GPUWriteWord);
|
|
LOAD(GPUReadLong);
|
|
p_GPUStateSave = (size_t (*)(uint8_t *))dlsym(handle, "GPUStateSave");
|
|
p_GPUStateLoad = (size_t (*)(const uint8_t *))dlsym(handle, "GPUStateLoad");
|
|
p_GPUSetFlags = (void (*)(uint32_t))dlsym(handle, "GPUSetFlags");
|
|
p_GPUUpdateRegisterBanks = (void (*)(void))dlsym(handle, "GPUUpdateRegisterBanks");
|
|
p_GPUSetIRQLine = (void (*)(int, int))dlsym(handle, "GPUSetIRQLine");
|
|
|
|
p_gpu_pc = dlsym(handle, "gpu_pc");
|
|
p_gpu_reg_bank_0 = dlsym(handle, "gpu_reg_bank_0");
|
|
p_gpu_reg_bank_1 = dlsym(handle, "gpu_reg_bank_1");
|
|
|
|
if (!p_gpu_pc || !p_gpu_reg_bank_0 || !p_gpu_reg_bank_1) {
|
|
fprintf(stderr, "Missing GPU internal symbols\n");
|
|
return 1;
|
|
}
|
|
|
|
p_retro_set_environment(environment);
|
|
p_retro_set_video_refresh(video_refresh);
|
|
p_retro_set_audio_sample(audio_sample);
|
|
p_retro_set_audio_sample_batch(audio_batch);
|
|
p_retro_set_input_poll(input_poll);
|
|
p_retro_set_input_state(input_state);
|
|
p_retro_init();
|
|
|
|
dummy_rom = calloc(1, 131072);
|
|
dummy_rom[0x404] = 0x00; dummy_rom[0x405] = 0x80;
|
|
dummy_rom[0x406] = 0x20; dummy_rom[0x407] = 0x00;
|
|
dummy_rom[0x2000] = 0x60; dummy_rom[0x2001] = 0xFE;
|
|
|
|
memset(&game, 0, sizeof(game));
|
|
game.path = "dummy.jag";
|
|
game.data = dummy_rom;
|
|
game.size = 131072;
|
|
|
|
if (!p_retro_load_game(&game)) {
|
|
fprintf(stderr, "retro_load_game failed\n");
|
|
p_retro_deinit(); free(dummy_rom);
|
|
return 1;
|
|
}
|
|
|
|
/* Arithmetic */
|
|
test_add();
|
|
test_addc();
|
|
test_addq();
|
|
test_sub();
|
|
test_neg();
|
|
|
|
/* Logic */
|
|
test_and();
|
|
test_or();
|
|
test_xor();
|
|
test_not();
|
|
|
|
/* Bit ops */
|
|
test_bset();
|
|
test_bclr();
|
|
|
|
/* Multiply / Divide */
|
|
test_mult();
|
|
test_imult();
|
|
test_div();
|
|
test_abs();
|
|
|
|
/* Shifts */
|
|
test_sh();
|
|
test_shlq();
|
|
test_shrq();
|
|
test_sha();
|
|
test_sharq();
|
|
test_ror();
|
|
test_rorq();
|
|
|
|
/* Compare */
|
|
test_cmp();
|
|
|
|
/* Data movement */
|
|
test_move();
|
|
test_moveq();
|
|
test_movei();
|
|
test_moveta_movefa();
|
|
|
|
/* Memory */
|
|
test_load_store();
|
|
test_store_alignment();
|
|
test_move_pc();
|
|
test_nop();
|
|
|
|
/* Control flow */
|
|
test_jr();
|
|
test_jump();
|
|
|
|
/* GPU-specific */
|
|
test_sat8();
|
|
test_sat16();
|
|
test_sat24();
|
|
test_pack();
|
|
test_normi();
|
|
|
|
/* G_FLAGS store retire window (#865) */
|
|
test_flags_store_bank_movei();
|
|
test_flags_store_bank_move();
|
|
test_flags_store_then_jump();
|
|
test_jump_with_flags_store_in_slot();
|
|
test_flags_window_savestate();
|
|
test_flags_store_imask_holdoff();
|
|
test_isr_epilogue_jump_store();
|
|
|
|
printf("\n=== Results: %d passed, %d failed ===\n", passes, fails);
|
|
|
|
p_retro_unload_game();
|
|
p_retro_deinit();
|
|
dlclose(handle);
|
|
free(dummy_rom);
|
|
return fails > 0 ? 1 : 0;
|
|
}
|