Files
Joseph MattielloandClaude Opus 5.5 1495e50e4c fix(dsp): NORMI normalises to bit 23, not bit 22 (same off-by-one as the GPU, #611)
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]>
2026-10-07 21:10:34 -04:00

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