mirror of
https://github.com/libretro/virtualjaguar-libretro.git
synced 2026-11-07 04:36:00 -10:00
NORMI gives the right shift that normalises an unsigned integer "as an IEEE 32-bit floating point value" (JTRM v8 p.52): the top set bit moves to bit 23, the hidden-bit position, so the result is msb - 23. JERRY's netlist (DSP_A-5Q.NET, normalisation integer generator) encodes exactly that and gives -32 for a zero source. It is line-for-line identical to TOM's ARITH.NET, the logic the GPU fix for #611 was decoded from. The old code normalised to bit 22: every result one too high, and 0 for a zero source. test_dsp_ops now checks six values (8, 0, -1, -23, -32, 1); all six fail on the old code. Refs #611 Co-Authored-By: Claude Opus 5.5 <[email protected]>
991 lines
29 KiB
C
991 lines
29 KiB
C
/* test_dsp_ops.c -- DSP instruction set verification.
|
|
* Each test writes a short DSP program, executes it, and checks results
|
|
* via register banks and DSP RAM.
|
|
*
|
|
* Build: cc -o test/test_dsp_ops test/test_dsp_ops.c -ldl
|
|
* Usage: ./test/test_dsp_ops
|
|
*
|
|
* Opcodes tested:
|
|
* 0 add 1 addc 2 addq 3 addqt
|
|
* 4 sub 5 subc 6 subq 7 subqt
|
|
* 8 neg 9 and 10 or 11 xor
|
|
* 12 not 13 btst 14 bset 15 bclr
|
|
* 16 mult 17 imult 18 imultn 19 resmac
|
|
* 20 imacn 21 div 22 abs 23 sh
|
|
* 24 shlq 25 shrq 26 sha 27 sharq
|
|
* 28 ror 29 rorq 30 cmp 31 cmpq
|
|
* 32 subqmod 33 sat16s 34 move 35 moveq
|
|
* 36 moveta 37 movefa 38 movei 39 loadb
|
|
* 40 loadw 41 load 42 sat32s 43 load_r14i
|
|
* 44 load_r15i 45 storeb 46 storew 47 store
|
|
* 48 mirror 49 stor14i 50 stor15i 51 move_pc
|
|
* 52 jump 53 jr 54 mmult 55 mtoi
|
|
* 56 normi 57 nop 58-61 load/store_r14/r15_ri
|
|
* 62 illegal 63 addqmod
|
|
*/
|
|
|
|
#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
|
|
|
|
/* DSP memory-mapped addresses */
|
|
#define DSP_FLAGS_ADDR 0xF1A100
|
|
#define DSP_CTRL_ADDR 0xF1A114
|
|
#define DSP_PC_ADDR 0xF1A110
|
|
#define DSP_RAM_BASE 0xF1B000
|
|
|
|
#define DSPGO 0x001
|
|
|
|
/* DSP opcode encoding: [opcode(6)][reg1(5)][reg2(5)] */
|
|
#define OP(opc, r1, r2) ((uint16_t)(((opc)<<10)|((r1)<<5)|(r2)))
|
|
|
|
/* Common opcodes */
|
|
#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_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_STORE(ra, rv) OP(47, ra, rv)
|
|
#define OP_MIRROR(rs, rd) OP(48, rs, rd)
|
|
#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_SAT16S(rs, rd) OP(33, rs, rd)
|
|
#define OP_SAT32S(rs, rd) OP(42, rs, rd)
|
|
#define OP_MTOI(rs, rd) OP(55, rs, rd)
|
|
#define OP_NORMI(rs, rd) OP(56, rs, 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_dsp_control;
|
|
static uint32_t *p_dsp_pc;
|
|
static uint32_t *p_dsp_reg_bank_0;
|
|
static uint32_t *p_dsp_reg_bank_1;
|
|
static uint8_t *(*p_DSPGetRAM)(void);
|
|
static void (*p_DSPWriteLong)(uint32_t, uint32_t, uint32_t);
|
|
static uint32_t (*p_DSPReadLong)(uint32_t, uint32_t);
|
|
static void (*p_DSPReset)(void);
|
|
static void (*p_DSPExec)(int32_t);
|
|
|
|
/* 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)
|
|
|
|
/* --- DSP RAM helpers --- */
|
|
static void w16(uint16_t off, uint16_t v) {
|
|
uint8_t *r = p_DSPGetRAM(); r[off]=(v>>8)&0xFF; r[off+1]=v&0xFF;
|
|
}
|
|
static void w32(uint16_t off, uint32_t v) {
|
|
uint8_t *r = p_DSPGetRAM();
|
|
r[off]=(v>>24)&0xFF; r[off+1]=(v>>16)&0xFF;
|
|
r[off+2]=(v>>8)&0xFF; r[off+3]=v&0xFF;
|
|
}
|
|
static uint32_t r32(uint16_t off) {
|
|
uint8_t *r = p_DSPGetRAM();
|
|
return ((uint32_t)r[off]<<24)|((uint32_t)r[off+1]<<16)|
|
|
((uint32_t)r[off+2]<<8)|(uint32_t)r[off+3];
|
|
}
|
|
static void wmovei(uint16_t off, uint32_t imm, uint8_t rd) {
|
|
w16(off, OP(38, 0, rd));
|
|
w16(off+2, imm & 0xFFFF);
|
|
w16(off+4, (imm>>16) & 0xFFFF);
|
|
}
|
|
|
|
/* Reset DSP, fill RAM with NOPs, clear all registers */
|
|
static void prep(void) {
|
|
uint32_t i;
|
|
p_DSPReset();
|
|
for (i = 0; i < 0x2000; i += 2)
|
|
w16(i, OP_NOP);
|
|
for (i = 0; i < 32; i++)
|
|
p_dsp_reg_bank_0[i] = p_dsp_reg_bank_1[i] = 0;
|
|
}
|
|
|
|
/* Run a program at $F1B100 for given cycles */
|
|
static void run(int32_t cycles) {
|
|
p_DSPWriteLong(DSP_PC_ADDR, 0xF1B100, 6);
|
|
p_DSPWriteLong(DSP_CTRL_ADDR, DSPGO, 6);
|
|
p_DSPExec(cycles);
|
|
p_DSPWriteLong(DSP_CTRL_ADDR, 0, 6);
|
|
}
|
|
|
|
/* Get register value from bank 0 (default after reset) */
|
|
#define REG(n) p_dsp_reg_bank_0[(n)]
|
|
|
|
/* ============================================================ */
|
|
|
|
static void test_add(void)
|
|
{
|
|
printf("\n--- ADD ---\n");
|
|
prep();
|
|
/* R0=10, R1=20, add R0,R1 -> R1=30 */
|
|
w16(0x100, OP_MOVEQ(10, 0));
|
|
w16(0x102, OP_MOVEQ(20, 1));
|
|
w16(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();
|
|
/* Set carry by overflowing: R0=0xFFFFFFFF, R1=1, add R1,R0 -> carry set */
|
|
wmovei(0x100, 0xFFFFFFFF, 0);
|
|
w16(0x106, OP_MOVEQ(1, 1));
|
|
w16(0x108, OP_ADD(1, 0)); /* R0 = 0, carry = 1 */
|
|
/* Now addc: R2=5, R3=10, addc R2,R3 -> R3=5+10+carry=16 */
|
|
w16(0x10A, OP_MOVEQ(5, 2));
|
|
w16(0x10C, OP_MOVEQ(10, 3));
|
|
w16(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();
|
|
/* addq #5, R0 (R0 starts at 0) -> R0=5 */
|
|
w16(0x100, OP_ADDQ(5, 0));
|
|
run(10);
|
|
if (REG(0) == 5) PASS("addq #5, R0 -> 5");
|
|
else FAIL("addq #5, R0 -> %u (expected 5)", REG(0));
|
|
|
|
/* addq #0 means add 32 (dsp_convert_zero) */
|
|
prep();
|
|
w16(0x100, OP_MOVEQ(10, 0));
|
|
w16(0x102, OP_ADDQ(0, 0));
|
|
run(10);
|
|
if (REG(0) == 42) PASS("addq #0(=32), R0(10) -> 42");
|
|
else FAIL("addq #0(=32) -> %u (expected 42)", REG(0));
|
|
}
|
|
|
|
static void test_addqt(void)
|
|
{
|
|
printf("\n--- ADDQT ---\n");
|
|
prep();
|
|
/* addqt doesn't affect flags -- just verify the add works */
|
|
w16(0x100, OP_MOVEQ(7, 0));
|
|
w16(0x102, OP_ADDQT(3, 0));
|
|
run(10);
|
|
if (REG(0) == 10) PASS("addqt #3, R0(7) -> 10");
|
|
else FAIL("addqt -> %u (expected 10)", REG(0));
|
|
}
|
|
|
|
static void test_sub(void)
|
|
{
|
|
printf("\n--- SUB ---\n");
|
|
prep();
|
|
w16(0x100, OP_MOVEQ(5, 0));
|
|
w16(0x102, OP_MOVEQ(20, 1));
|
|
w16(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_subc(void)
|
|
{
|
|
printf("\n--- SUBC ---\n");
|
|
prep();
|
|
/* First set carry by: 0-1 underflows */
|
|
w16(0x100, OP_MOVEQ(0, 0));
|
|
w16(0x102, OP_MOVEQ(1, 1));
|
|
w16(0x104, OP_SUB(1, 0)); /* R0=0-1=0xFFFFFFFF, carry set */
|
|
w16(0x106, OP_MOVEQ(10, 2));
|
|
w16(0x108, OP_MOVEQ(3, 3));
|
|
w16(0x10A, OP_SUBC(3, 2)); /* R2=10-3-carry=6 */
|
|
run(40);
|
|
if (REG(2) == 6) PASS("10-3-carry=6");
|
|
else FAIL("10-3-carry=%u (expected 6)", REG(2));
|
|
}
|
|
|
|
static void test_subq(void)
|
|
{
|
|
printf("\n--- SUBQ ---\n");
|
|
prep();
|
|
w16(0x100, OP_MOVEQ(20, 0));
|
|
w16(0x102, OP_SUBQ(5, 0));
|
|
run(10);
|
|
if (REG(0) == 15) PASS("subq #5, R0(20) -> 15");
|
|
else FAIL("subq -> %u (expected 15)", REG(0));
|
|
}
|
|
|
|
static void test_subqt(void)
|
|
{
|
|
printf("\n--- SUBQT ---\n");
|
|
prep();
|
|
w16(0x100, OP_MOVEQ(20, 0));
|
|
w16(0x102, OP_SUBQT(5, 0));
|
|
run(10);
|
|
if (REG(0) == 15) PASS("subqt #5, R0(20) -> 15");
|
|
else FAIL("subqt -> %u (expected 15)", REG(0));
|
|
}
|
|
|
|
static void test_neg(void)
|
|
{
|
|
printf("\n--- NEG ---\n");
|
|
prep();
|
|
w16(0x100, OP_MOVEQ(5, 0));
|
|
w16(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));
|
|
}
|
|
|
|
static void test_and(void)
|
|
{
|
|
printf("\n--- AND ---\n");
|
|
prep();
|
|
wmovei(0x100, 0xFF00FF00, 0);
|
|
wmovei(0x106, 0x0F0F0F0F, 1);
|
|
w16(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();
|
|
wmovei(0x100, 0xF0F00000, 0);
|
|
wmovei(0x106, 0x0F0F0000, 1);
|
|
w16(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();
|
|
wmovei(0x100, 0xFFFF0000, 0);
|
|
wmovei(0x106, 0xFF00FF00, 1);
|
|
w16(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();
|
|
/* NOT operates on RN only. Load value into R0 and NOT R0 */
|
|
wmovei(0x100, 0xAAAA5555, 0);
|
|
w16(0x106, OP_NOT(0, 0));
|
|
run(20);
|
|
if (REG(0) == 0x5555AAAA) PASS("NOT 0xAAAA5555 = 0x5555AAAA");
|
|
else FAIL("NOT = %08X (expected 5555AAAA)", REG(0));
|
|
}
|
|
|
|
static void test_btst(void)
|
|
{
|
|
printf("\n--- BTST ---\n");
|
|
prep();
|
|
/* BTST doesn't modify RN, it sets Z flag. We test by: btst then use jr z */
|
|
/* bit 3 of 0x08 = set -> Z clear */
|
|
w16(0x100, OP_MOVEQ(8, 0)); /* R0=8 (bit 3 set) */
|
|
w16(0x102, OP_BTST(3, 0)); /* test bit 3 -> Z=0 (bit is set) */
|
|
/* After BTST, if zero flag is clear (bit was set), add 1 to R5 */
|
|
w16(0x104, OP_MOVEQ(0, 5));
|
|
w16(0x106, OP_ADDQ(1, 5)); /* always executes, R5=1 */
|
|
run(20);
|
|
if (REG(5) == 1) PASS("BTST bit 3 of 0x08 runs");
|
|
else FAIL("BTST: R5=%u", REG(5));
|
|
}
|
|
|
|
static void test_bset(void)
|
|
{
|
|
printf("\n--- BSET ---\n");
|
|
prep();
|
|
w16(0x100, OP_MOVEQ(0, 0));
|
|
w16(0x102, OP_BSET(5, 0));
|
|
run(10);
|
|
if (REG(0) == 32) PASS("bset #5, R0(0) -> 32");
|
|
else FAIL("bset -> %u (expected 32)", REG(0));
|
|
}
|
|
|
|
static void test_bclr(void)
|
|
{
|
|
printf("\n--- BCLR ---\n");
|
|
prep();
|
|
wmovei(0x100, 0xFF, 0);
|
|
w16(0x106, OP_BCLR(3, 0));
|
|
run(20);
|
|
if (REG(0) == 0xF7) PASS("bclr #3, 0xFF -> 0xF7");
|
|
else FAIL("bclr -> %02X (expected F7)", REG(0));
|
|
}
|
|
|
|
static void test_mult(void)
|
|
{
|
|
printf("\n--- MULT ---\n");
|
|
prep();
|
|
/* unsigned 16-bit multiply: mult R0,R1 -> low 32 bits of (R0[15:0] * R1[15:0]) */
|
|
w16(0x100, OP_MOVEQ(7, 0));
|
|
w16(0x102, OP_MOVEQ(6, 1));
|
|
w16(0x104, OP_MULT(0, 1));
|
|
run(20);
|
|
if (REG(1) == 42) PASS("7*6=42 (unsigned)");
|
|
else FAIL("7*6=%u (expected 42)", REG(1));
|
|
}
|
|
|
|
static void test_imult(void)
|
|
{
|
|
printf("\n--- IMULT ---\n");
|
|
prep();
|
|
/* signed 16-bit multiply */
|
|
w16(0x100, OP_MOVEQ(7, 0));
|
|
w16(0x102, OP_MOVEQ(6, 1));
|
|
w16(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_imultn_resmac(void)
|
|
{
|
|
printf("\n--- IMULTN/RESMAC ---\n");
|
|
prep();
|
|
/* imultn sets accumulator = R0*R1, resmac reads low 32 bits */
|
|
w16(0x100, OP_MOVEQ(10, 0));
|
|
w16(0x102, OP_MOVEQ(20, 1));
|
|
w16(0x104, OP_IMULTN(0, 1));
|
|
w16(0x106, OP_RESMAC(2));
|
|
run(20);
|
|
if (REG(2) == 200) PASS("imultn+resmac: 10*20=200");
|
|
else FAIL("imultn+resmac=%d (expected 200)", (int32_t)REG(2));
|
|
}
|
|
|
|
static void test_imacn_resmac(void)
|
|
{
|
|
printf("\n--- IMACN/RESMAC ---\n");
|
|
prep();
|
|
/* imultn sets acc=3*4=12, then imacn adds 5*6=30, total=42 */
|
|
w16(0x100, OP_MOVEQ(3, 0));
|
|
w16(0x102, OP_MOVEQ(4, 1));
|
|
w16(0x104, OP_IMULTN(0, 1));
|
|
w16(0x106, OP_MOVEQ(5, 2));
|
|
w16(0x108, OP_MOVEQ(6, 3));
|
|
w16(0x10A, OP_IMACN(2, 3));
|
|
w16(0x10C, OP_RESMAC(4));
|
|
run(40);
|
|
if (REG(4) == 42) PASS("imultn(3*4)+imacn(5*6)=42");
|
|
else FAIL("imacn result=%d (expected 42)", (int32_t)REG(4));
|
|
}
|
|
|
|
static void test_div(void)
|
|
{
|
|
printf("\n--- DIV ---\n");
|
|
prep();
|
|
/* div R0,R1: R1 = R1 / R0, remainder in dsp_remain */
|
|
wmovei(0x100, 100, 1);
|
|
w16(0x106, OP_MOVEQ(7, 0));
|
|
w16(0x108, OP_DIV(0, 1));
|
|
run(40);
|
|
if (REG(1) == 14) PASS("100/7=14");
|
|
else FAIL("100/7=%u (expected 14)", REG(1));
|
|
}
|
|
|
|
static void test_abs(void)
|
|
{
|
|
printf("\n--- ABS ---\n");
|
|
prep();
|
|
wmovei(0x100, (uint32_t)-42, 0);
|
|
w16(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));
|
|
|
|
/* Positive stays positive */
|
|
prep();
|
|
w16(0x100, OP_MOVEQ(17, 0));
|
|
w16(0x102, OP_ABS(0));
|
|
run(10);
|
|
if (REG(0) == 17) PASS("abs(17)=17");
|
|
else FAIL("abs(17)=%u (expected 17)", REG(0));
|
|
}
|
|
|
|
static void test_sh(void)
|
|
{
|
|
printf("\n--- SH ---\n");
|
|
prep();
|
|
/* sh R0,R1: positive R0 = shift RIGHT, negative R0 = shift LEFT */
|
|
wmovei(0x100, (uint32_t)-2, 0); /* shift LEFT by 2 (negative = left) */
|
|
w16(0x106, OP_MOVEQ(5, 1)); /* value = 5 */
|
|
w16(0x108, OP_SH(0, 1)); /* R1 = 5 << 2 = 20 */
|
|
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();
|
|
w16(0x100, OP_MOVEQ(1, 0));
|
|
w16(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();
|
|
wmovei(0x100, 0x80, 0);
|
|
w16(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();
|
|
/* sha: positive R0 = shift RIGHT (arithmetic), negative = shift LEFT */
|
|
w16(0x100, OP_MOVEQ(2, 0)); /* shift RIGHT by 2 (positive = right) */
|
|
wmovei(0x102, (uint32_t)-16, 1); /* value = -16 */
|
|
w16(0x108, OP_SHA(0, 1)); /* R1 = -16 >> 2 = -4 (arithmetic) */
|
|
run(30);
|
|
if (REG(1) == (uint32_t)-4) PASS("sha right -16>>2=-4 (arithmetic)");
|
|
else FAIL("sha -> %08X (expected FFFFFFFC)", REG(1));
|
|
}
|
|
|
|
static void test_sharq(void)
|
|
{
|
|
printf("\n--- SHARQ ---\n");
|
|
prep();
|
|
wmovei(0x100, (uint32_t)-16, 0);
|
|
w16(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();
|
|
w16(0x100, OP_MOVEQ(4, 0)); /* rotate by 4 */
|
|
wmovei(0x102, 0x12345678, 1);
|
|
w16(0x108, OP_ROR(0, 1)); /* rotate right 4 -> 0x81234567 */
|
|
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();
|
|
wmovei(0x100, 0x12345678, 0);
|
|
w16(0x106, OP_RORQ(8, 0));
|
|
run(20);
|
|
if (REG(0) == 0x78123456) PASS("rorq #8, 0x12345678 -> 0x78123456");
|
|
else FAIL("rorq -> %08X (expected 78123456)", REG(0));
|
|
}
|
|
|
|
static void test_cmp(void)
|
|
{
|
|
printf("\n--- CMP ---\n");
|
|
prep();
|
|
/* cmp doesn't modify RN, just sets flags. Test equal case. */
|
|
w16(0x100, OP_MOVEQ(10, 0));
|
|
w16(0x102, OP_MOVEQ(10, 1));
|
|
w16(0x104, OP_CMP(0, 1));
|
|
/* If equal, Z flag set. Use jr z to skip over addq */
|
|
w16(0x106, OP_JR(2, 1)); /* jr z, +2 (skip next) */
|
|
w16(0x108, OP_MOVEQ(99, 5)); /* R5=99 if NOT equal */
|
|
w16(0x10A, OP_MOVEQ(1, 5)); /* R5=1 if equal (lands here after jr) */
|
|
run(30);
|
|
/* Note: JR has delay slot issues, so just check if R5 is not 0 */
|
|
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));
|
|
}
|
|
|
|
static void test_cmpq(void)
|
|
{
|
|
printf("\n--- CMPQ ---\n");
|
|
prep();
|
|
/* cmpq #5, R0 */
|
|
w16(0x100, OP_MOVEQ(10, 0));
|
|
w16(0x102, OP_CMPQ(5, 0));
|
|
run(10);
|
|
if (REG(0) == 10) PASS("cmpq doesn't modify R0");
|
|
else FAIL("cmpq modified R0=%u", REG(0));
|
|
}
|
|
|
|
static void test_move(void)
|
|
{
|
|
printf("\n--- MOVE ---\n");
|
|
prep();
|
|
wmovei(0x100, 42, 0);
|
|
w16(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();
|
|
w16(0x100, OP_MOVEQ(31, 0));
|
|
run(10);
|
|
if (REG(0) == 31) PASS("moveq #31 -> R0=31");
|
|
else FAIL("moveq -> %u (expected 31)", REG(0));
|
|
}
|
|
|
|
static void test_movei(void)
|
|
{
|
|
printf("\n--- MOVEI ---\n");
|
|
prep();
|
|
wmovei(0x100, 0xDEADBEEF, 0);
|
|
run(20);
|
|
if (REG(0) == 0xDEADBEEF) PASS("movei #$DEADBEEF -> R0");
|
|
else FAIL("movei -> %08X (expected DEADBEEF)", REG(0));
|
|
}
|
|
|
|
static void test_moveta_movefa(void)
|
|
{
|
|
printf("\n--- MOVETA/MOVEFA ---\n");
|
|
prep();
|
|
/* moveta: move R0 to alternate R0 */
|
|
wmovei(0x100, 77, 0);
|
|
w16(0x106, OP_MOVETA(0, 0));
|
|
/* movefa: move alternate R1 to R2 */
|
|
p_dsp_reg_bank_1[1] = 88;
|
|
w16(0x108, OP_MOVEFA(1, 2));
|
|
run(30);
|
|
if (p_dsp_reg_bank_1[0] == 77) PASS("moveta: alt_R0 = 77");
|
|
else FAIL("moveta: alt_R0 = %u (expected 77)", p_dsp_reg_bank_1[0]);
|
|
if (REG(2) == 88) PASS("movefa: R2 = alt_R1 = 88");
|
|
else FAIL("movefa: R2 = %u (expected 88)", REG(2));
|
|
}
|
|
|
|
static void test_load_store(void)
|
|
{
|
|
printf("\n--- LOAD/STORE ---\n");
|
|
prep();
|
|
/* Store 0xCAFEBABE to $F1B900, then load it back */
|
|
wmovei(0x100, 0xF1B900, 10);
|
|
wmovei(0x106, 0xCAFEBABE, 0);
|
|
w16(0x10C, OP_STORE(10, 0)); /* store R0 to (R10) */
|
|
w16(0x10E, OP_MOVEQ(0, 0)); /* clear R0 */
|
|
w16(0x110, OP_LOAD(10, 0)); /* load (R10) to R0 */
|
|
run(40);
|
|
if (REG(0) == 0xCAFEBABE) PASS("store+load round-trip: 0xCAFEBABE");
|
|
else FAIL("store+load: %08X (expected CAFEBABE)", REG(0));
|
|
}
|
|
|
|
static void test_mirror(void)
|
|
{
|
|
printf("\n--- MIRROR ---\n");
|
|
prep();
|
|
/* mirror reverses all 32 bits. Operates on RN only. */
|
|
wmovei(0x100, 0x00000001, 0);
|
|
w16(0x106, OP_MIRROR(0, 0)); /* mirror R0 in-place */
|
|
run(20);
|
|
/* mirror of 0x00000001: bit 0 -> bit 31 = 0x80000000 */
|
|
if (REG(0) == 0x80000000) PASS("mirror 0x00000001 -> 0x80000000");
|
|
else FAIL("mirror -> %08X (expected 80000000)", REG(0));
|
|
}
|
|
|
|
static void test_move_pc(void)
|
|
{
|
|
printf("\n--- MOVE_PC ---\n");
|
|
prep();
|
|
/* move_pc stores current PC - 2 into Rn */
|
|
w16(0x100, OP_MOVE_PC(0)); /* PC at execution = $F1B102, stores $F1B100 */
|
|
run(10);
|
|
if (REG(0) == 0xF1B100) PASS("move_pc -> $F1B100");
|
|
else FAIL("move_pc -> %08X (expected F1B100)", REG(0));
|
|
}
|
|
|
|
static void test_jr(void)
|
|
{
|
|
printf("\n--- JR ---\n");
|
|
prep();
|
|
/* jr always (+2) skips one instruction.
|
|
* MOVEQ only supports 5-bit immediates (0-31), so use small values. */
|
|
/* $F1B100: jr T, +2 (always true, cc=0, jump to $100+2+2*2=$106) */
|
|
/* $F1B102: nop (delay slot) */
|
|
/* $F1B104: moveq 7,R0 (should be skipped) */
|
|
/* $F1B106: moveq 25,R0 (target) */
|
|
w16(0x100, OP_JR(0, 2)); /* cc=0 means always */
|
|
w16(0x102, OP_NOP); /* delay slot */
|
|
w16(0x104, OP_MOVEQ(7, 0)); /* skipped */
|
|
w16(0x106, OP_MOVEQ(25, 0)); /* target */
|
|
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();
|
|
/* jump always to R10 (containing $F1B200).
|
|
* MOVEQ only supports 5-bit immediates (0-31). */
|
|
wmovei(0x100, 0xF1B200, 10);
|
|
w16(0x106, OP_JUMP(0, 10)); /* jump always to (R10) */
|
|
w16(0x108, OP_NOP); /* delay slot */
|
|
w16(0x10A, OP_MOVEQ(3, 0)); /* skipped */
|
|
w16(0x200, OP_MOVEQ(19, 0)); /* target */
|
|
run(30);
|
|
if (REG(0) == 19) PASS("jump to $F1B200: R0=19");
|
|
else FAIL("jump: R0=%u (expected 19)", REG(0));
|
|
}
|
|
|
|
static void test_nop(void)
|
|
{
|
|
printf("\n--- NOP ---\n");
|
|
prep();
|
|
w16(0x100, OP_MOVEQ(5, 0));
|
|
w16(0x102, OP_NOP);
|
|
w16(0x104, OP_NOP);
|
|
w16(0x106, OP_NOP);
|
|
run(20);
|
|
if (REG(0) == 5) PASS("NOPs don't modify registers");
|
|
else FAIL("NOP corrupted R0=%u", REG(0));
|
|
}
|
|
|
|
static void test_sat16s(void)
|
|
{
|
|
printf("\n--- SAT16S ---\n");
|
|
prep();
|
|
/* sat16s reads and writes RN. Load value into R1 (RN). */
|
|
wmovei(0x100, 50000, 1);
|
|
w16(0x106, OP_SAT16S(0, 1));
|
|
run(20);
|
|
if (REG(1) == 32767) PASS("sat16s(50000)=32767");
|
|
else FAIL("sat16s(50000)=%d (expected 32767)", (int32_t)REG(1));
|
|
}
|
|
|
|
static void test_sat32s(void)
|
|
{
|
|
printf("\n--- SAT32S ---\n");
|
|
prep();
|
|
/* sat32s reads RN, clamps based on accumulator high bits.
|
|
* After reset, dsp_acc=0, so acc>>32=0 → passthrough RN. */
|
|
w16(0x100, OP_MOVEQ(10, 1));
|
|
w16(0x102, OP_SAT32S(0, 1));
|
|
run(10);
|
|
if (REG(1) == 10) PASS("sat32s(10)=10 (acc=0, passthrough)");
|
|
else FAIL("sat32s(10)=%d", (int32_t)REG(1));
|
|
|
|
/* Regression: negative accumulator must NOT saturate to 0x7FFFFFFF.
|
|
* IMULTN(-100, 100) sets acc = -10000 → acc>>32 = -1 → passthrough. */
|
|
prep();
|
|
wmovei(0x100, (uint32_t)-100, 0);
|
|
w16(0x106, OP_MOVEQ(10, 1));
|
|
w16(0x108, OP_IMULTN(0, 1));
|
|
w16(0x10A, OP_MOVEQ(7, 2));
|
|
w16(0x10C, OP_SAT32S(0, 2));
|
|
run(30);
|
|
if (REG(2) == 7) PASS("sat32s(7) w/ neg acc=-10000: passthrough");
|
|
else FAIL("sat32s w/ neg acc=%d (expected 7)", (int32_t)REG(2));
|
|
}
|
|
|
|
static void test_normi(void)
|
|
{
|
|
/* NORMI = (index of the top set bit) - 23: the right shift that puts the
|
|
* top bit at the IEEE hidden-bit position (JTRM v8 p.52; JERRY netlist
|
|
* DSP_A-5Q.NET normalisation integer generator). Zero gives -32. */
|
|
static const uint32_t src[] = { 0x80000000u, 0x00800000u, 0x00400000u,
|
|
0x00000001u, 0x00000000u, 0x01000000u };
|
|
static const int32_t want[] = { 8, 0, -1, -23, -32, 1 };
|
|
unsigned i;
|
|
|
|
printf("\n--- NORMI ---\n");
|
|
for (i = 0; i < sizeof(src) / sizeof(src[0]); i++)
|
|
{
|
|
prep();
|
|
wmovei(0x100, src[i], 0);
|
|
w16(0x106, OP_NORMI(0, 1));
|
|
run(20);
|
|
if ((int32_t)REG(1) == want[i]) PASS("normi($%08X)=%d", (unsigned)src[i], (int)want[i]);
|
|
else FAIL("normi($%08X)=%d (expected %d)", (unsigned)src[i], (int32_t)REG(1), (int)want[i]);
|
|
}
|
|
}
|
|
|
|
static void test_mtoi(void)
|
|
{
|
|
printf("\n--- MTOI ---\n");
|
|
prep();
|
|
/* mtoi: mantissa to integer -- extracts exponent field from IEEE float */
|
|
/* For IEEE 754: 1.0 = 0x3F800000, exponent = 0x7F = 127 */
|
|
wmovei(0x100, 0x3F800000, 0);
|
|
w16(0x106, OP_MTOI(0, 1));
|
|
run(20);
|
|
/* mtoi extracts mantissa and shifts by exponent, implementation-dependent */
|
|
/* Just verify it doesn't crash and produces some output */
|
|
PASS("mtoi executed (R1=%08X)", REG(1));
|
|
}
|
|
|
|
/* ============================================================ */
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
void *handle;
|
|
uint8_t *dummy_rom;
|
|
struct retro_game_info game;
|
|
(void)argc; (void)argv;
|
|
|
|
printf("=== DSP 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(DSPReset);
|
|
LOAD(DSPExec);
|
|
LOAD(DSPGetRAM);
|
|
LOAD(DSPWriteLong);
|
|
LOAD(DSPReadLong);
|
|
|
|
p_dsp_control = dlsym(handle, "dsp_control");
|
|
p_dsp_pc = dlsym(handle, "dsp_pc");
|
|
p_dsp_reg_bank_0 = dlsym(handle, "dsp_reg_bank_0");
|
|
p_dsp_reg_bank_1 = dlsym(handle, "dsp_reg_bank_1");
|
|
|
|
if (!p_dsp_control || !p_dsp_pc || !p_dsp_reg_bank_0 || !p_dsp_reg_bank_1) {
|
|
fprintf(stderr, "Missing DSP 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();
|
|
|
|
/* 128K dummy ROM */
|
|
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_addqt();
|
|
test_sub();
|
|
test_subc();
|
|
test_subq();
|
|
test_subqt();
|
|
test_neg();
|
|
|
|
/* Logic */
|
|
test_and();
|
|
test_or();
|
|
test_xor();
|
|
test_not();
|
|
|
|
/* Bit operations */
|
|
test_btst();
|
|
test_bset();
|
|
test_bclr();
|
|
|
|
/* Multiply / Divide / MAC */
|
|
test_mult();
|
|
test_imult();
|
|
test_imultn_resmac();
|
|
test_imacn_resmac();
|
|
test_div();
|
|
|
|
/* Misc arithmetic */
|
|
test_abs();
|
|
|
|
/* Shifts */
|
|
test_sh();
|
|
test_shlq();
|
|
test_shrq();
|
|
test_sha();
|
|
test_sharq();
|
|
test_ror();
|
|
test_rorq();
|
|
|
|
/* Compare */
|
|
test_cmp();
|
|
test_cmpq();
|
|
|
|
/* Data movement */
|
|
test_move();
|
|
test_moveq();
|
|
test_movei();
|
|
test_moveta_movefa();
|
|
|
|
/* Memory */
|
|
test_load_store();
|
|
|
|
/* Misc */
|
|
test_mirror();
|
|
test_move_pc();
|
|
test_nop();
|
|
test_sat16s();
|
|
test_sat32s();
|
|
test_normi();
|
|
test_mtoi();
|
|
|
|
/* Control flow */
|
|
test_jr();
|
|
test_jump();
|
|
|
|
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;
|
|
}
|