mirror of
https://github.com/libretro/virtualjaguar-libretro.git
synced 2026-11-09 03:39:38 -10:00
vj_core_init() calls GPUInit() directly with no game loaded, so retro_deinit's no-game guard never reaches GPUDone and LeakSanitizer flags the 256-byte branch-condition table in test_butch_cd (the only framework test that tears down without a game). Free it in vj_core_unload via the exported GPUDone (idempotent; the game-loaded tests already reach it through JaguarDone -> TOMDone). Co-Authored-By: Claude Fable 5 <[email protected]>
608 lines
20 KiB
C
608 lines
20 KiB
C
/*
|
|
* test_framework.h — Minimal unit test framework for Virtual Jaguar.
|
|
*
|
|
* Usage:
|
|
* #include "test_framework.h"
|
|
*
|
|
* TEST(my_test) {
|
|
* ASSERT_EQ(1 + 1, 2);
|
|
* ASSERT_TRUE(some_condition);
|
|
* }
|
|
*
|
|
* int main(int argc, char *argv[]) {
|
|
* TEST_INIT("My Test Suite");
|
|
* RUN_TEST(my_test);
|
|
* return TEST_REPORT();
|
|
* }
|
|
*/
|
|
|
|
#ifndef TEST_FRAMEWORK_H
|
|
#define TEST_FRAMEWORK_H
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
#include <dlfcn.h>
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Test runner state */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
static int tf_pass = 0;
|
|
static int tf_fail = 0;
|
|
static int tf_skip = 0;
|
|
static const char *tf_suite_name = "";
|
|
static const char *tf_current_test = "";
|
|
static bool tf_current_failed = false;
|
|
static bool tf_current_skipped = false;
|
|
|
|
#define TEST_INIT(name) \
|
|
do { tf_suite_name = (name); tf_pass = tf_fail = tf_skip = 0; \
|
|
fprintf(stderr, "\n=== %s ===\n", tf_suite_name); } while(0)
|
|
|
|
#define TEST(name) static void test_##name(void)
|
|
|
|
/* A test that SKIPs must NOT also be printed/counted as PASS -- a body
|
|
* that bailed out (missing disc, missing exports) proved nothing. This
|
|
* exact bug produced a false PASS against a stale core binary that
|
|
* lacked the exports a test needed (2026-07-15). */
|
|
#define RUN_TEST(name) \
|
|
do { \
|
|
tf_current_test = #name; \
|
|
tf_current_failed = false; \
|
|
tf_current_skipped = false; \
|
|
test_##name(); \
|
|
if (tf_current_failed) { tf_fail++; } \
|
|
else if (!tf_current_skipped) \
|
|
{ tf_pass++; fprintf(stderr, " PASS %s\n", #name); } \
|
|
} while(0)
|
|
|
|
#define SKIP_TEST(name, reason) \
|
|
do { tf_skip++; tf_current_skipped = true; \
|
|
fprintf(stderr, " SKIP %s (%s)\n", #name, reason); } while(0)
|
|
|
|
#define TEST_REPORT() \
|
|
(fprintf(stderr, "\n--- %s: %d passed, %d failed, %d skipped ---\n\n", \
|
|
tf_suite_name, tf_pass, tf_fail, tf_skip), tf_fail)
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Assertions */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
#define FAIL(fmt, ...) \
|
|
do { \
|
|
fprintf(stderr, " FAIL %s:%d: " fmt "\n", \
|
|
tf_current_test, __LINE__, ##__VA_ARGS__); \
|
|
tf_current_failed = true; \
|
|
return; \
|
|
} while(0)
|
|
|
|
#define ASSERT_TRUE(cond) \
|
|
do { if (!(cond)) FAIL("expected true: %s", #cond); } while(0)
|
|
|
|
#define ASSERT_FALSE(cond) \
|
|
do { if (cond) FAIL("expected false: %s", #cond); } while(0)
|
|
|
|
#define ASSERT_EQ(a, b) \
|
|
do { \
|
|
long long _a = (long long)(a), _b = (long long)(b); \
|
|
if (_a != _b) FAIL("%s == %s: got %lld (0x%llX), expected %lld (0x%llX)", \
|
|
#a, #b, _a, _a, _b, _b); \
|
|
} while(0)
|
|
|
|
#define ASSERT_NEQ(a, b) \
|
|
do { \
|
|
long long _a = (long long)(a), _b = (long long)(b); \
|
|
if (_a == _b) FAIL("%s != %s: both are %lld (0x%llX)", #a, #b, _a, _a); \
|
|
} while(0)
|
|
|
|
#define ASSERT_EQ_U32(a, b) \
|
|
do { \
|
|
uint32_t _a = (uint32_t)(a), _b = (uint32_t)(b); \
|
|
if (_a != _b) FAIL("%s == %s: got 0x%08X, expected 0x%08X", #a, #b, _a, _b); \
|
|
} while(0)
|
|
|
|
#define ASSERT_EQ_U16(a, b) \
|
|
do { \
|
|
uint16_t _a = (uint16_t)(a), _b = (uint16_t)(b); \
|
|
if (_a != _b) FAIL("%s == %s: got 0x%04X, expected 0x%04X", #a, #b, _a, _b); \
|
|
} while(0)
|
|
|
|
#define ASSERT_EQ_U8(a, b) \
|
|
do { \
|
|
uint8_t _a = (uint8_t)(a), _b = (uint8_t)(b); \
|
|
if (_a != _b) FAIL("%s == %s: got 0x%02X, expected 0x%02X", #a, #b, _a, _b); \
|
|
} while(0)
|
|
|
|
#define ASSERT_MEM_EQ(ptr, expected, len) \
|
|
do { \
|
|
if (memcmp((ptr), (expected), (len)) != 0) \
|
|
FAIL("memory mismatch at %s (length %u)", #ptr, (unsigned)(len)); \
|
|
} while(0)
|
|
|
|
/* Non-fatal check — logs failure but continues */
|
|
#define CHECK_EQ(a, b) \
|
|
do { \
|
|
long long _a = (long long)(a), _b = (long long)(b); \
|
|
if (_a != _b) { \
|
|
fprintf(stderr, " CHECK %s:%d: %s == %s: got %lld (0x%llX), expected %lld (0x%llX)\n", \
|
|
tf_current_test, __LINE__, #a, #b, _a, _a, _b, _b); \
|
|
tf_current_failed = true; \
|
|
} \
|
|
} while(0)
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Core loader (dlsym-based, loads virtualjaguar_libretro.dylib) */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
#include "../libretro-common/include/libretro.h"
|
|
|
|
struct vj_core {
|
|
void *handle;
|
|
|
|
/* libretro API */
|
|
void (*retro_init)(void);
|
|
void (*retro_deinit)(void);
|
|
void (*retro_set_environment)(retro_environment_t);
|
|
void (*retro_set_video_refresh)(retro_video_refresh_t);
|
|
void (*retro_set_audio_sample)(retro_audio_sample_t);
|
|
void (*retro_set_audio_sample_batch)(retro_audio_sample_batch_t);
|
|
void (*retro_set_input_poll)(retro_input_poll_t);
|
|
void (*retro_set_input_state)(retro_input_state_t);
|
|
|
|
/* Hardware subsystem functions */
|
|
void (*GPUInit)(void);
|
|
void (*GPUDone)(void);
|
|
void (*GPUReset)(void);
|
|
void (*GPUExec)(int32_t);
|
|
void (*GPUHandleIRQs)(void);
|
|
void (*GPUSetIRQLine)(int, int);
|
|
uint8_t (*GPUReadByte)(uint32_t, uint32_t);
|
|
uint16_t (*GPUReadWord)(uint32_t, uint32_t);
|
|
uint32_t (*GPUReadLong)(uint32_t, uint32_t);
|
|
void (*GPUWriteByte)(uint32_t, uint8_t, uint32_t);
|
|
void (*GPUWriteWord)(uint32_t, uint16_t, uint32_t);
|
|
void (*GPUWriteLong)(uint32_t, uint32_t, uint32_t);
|
|
uint32_t (*GPUGetPC)(void);
|
|
int (*GPUIsRunning)(void);
|
|
|
|
void (*DSPInit)(void);
|
|
void (*DSPReset)(void);
|
|
void (*DSPExec)(int32_t);
|
|
void (*DSPSetIRQLine)(int, int);
|
|
uint8_t (*DSPReadByte)(uint32_t, uint32_t);
|
|
uint16_t (*DSPReadWord)(uint32_t, uint32_t);
|
|
uint32_t (*DSPReadLong)(uint32_t, uint32_t);
|
|
void (*DSPWriteByte)(uint32_t, uint8_t, uint32_t);
|
|
void (*DSPWriteWord)(uint32_t, uint16_t, uint32_t);
|
|
void (*DSPWriteLong)(uint32_t, uint32_t, uint32_t);
|
|
|
|
void (*TOMInit)(void);
|
|
void (*TOMReset)(void);
|
|
uint16_t (*TOMReadWord)(uint32_t, uint32_t);
|
|
void (*TOMWriteWord)(uint32_t, uint16_t, uint32_t);
|
|
uint32_t (*TOMGetVideoModeWidth)(void);
|
|
uint32_t (*TOMGetVideoModeHeight)(void);
|
|
int (*TOMIRQEnabled)(int);
|
|
uint16_t (*TOMIRQControlReg)(void);
|
|
void (*TOMSetIRQLatch)(int, int);
|
|
void (*TOMSetPendingVideoInt)(void);
|
|
void (*TOMSetPendingGPUInt)(void);
|
|
void (*TOMSetPendingTimerInt)(void);
|
|
void (*TOMSetPendingObjectInt)(void);
|
|
void (*TOMSetPendingJERRYInt)(void);
|
|
|
|
void (*JERRYInit)(void);
|
|
void (*JERRYReset)(void);
|
|
uint16_t (*JERRYReadWord)(uint32_t, uint32_t);
|
|
void (*JERRYWriteWord)(uint32_t, uint16_t, uint32_t);
|
|
bool (*JERRYIRQEnabled)(int);
|
|
void (*JERRYSetPendingIRQ)(int);
|
|
|
|
void (*CDROMInit)(void);
|
|
void (*CDROMReset)(void);
|
|
uint16_t (*CDROMReadWord)(uint32_t, uint32_t);
|
|
void (*CDROMWriteWord)(uint32_t, uint16_t, uint32_t);
|
|
|
|
uint8_t (*JaguarReadByte)(uint32_t, uint32_t);
|
|
uint16_t (*JaguarReadWord)(uint32_t, uint32_t);
|
|
void (*JaguarWriteByte)(uint32_t, uint8_t, uint32_t);
|
|
void (*JaguarWriteWord)(uint32_t, uint16_t, uint32_t);
|
|
void (*JaguarWriteLong)(uint32_t, uint32_t, uint32_t);
|
|
|
|
void (*JaguarInit)(void);
|
|
void (*JaguarReset)(void);
|
|
|
|
/* m68k access */
|
|
unsigned int (*m68k_get_reg)(void *, int);
|
|
void (*m68k_set_reg)(int, unsigned int);
|
|
int (*m68k_execute)(int);
|
|
void (*m68k_pulse_reset)(void);
|
|
|
|
/* Raw memory pointer */
|
|
uint8_t * (*GetRamPtr)(void);
|
|
|
|
/* GPU register banks (exported arrays) */
|
|
uint32_t *gpu_reg_bank_0;
|
|
uint32_t *dsp_reg_bank_0;
|
|
uint32_t *gpu_reg_bank_1;
|
|
|
|
/* Settings */
|
|
void *vjs;
|
|
};
|
|
|
|
#define LOAD_SYM(coreptr, sym) \
|
|
do { \
|
|
(coreptr)->sym = dlsym((coreptr)->handle, #sym); \
|
|
if (!(coreptr)->sym) { \
|
|
fprintf(stderr, " WARN: dlsym(%s) failed: %s\n", #sym, dlerror()); \
|
|
} \
|
|
} while(0)
|
|
|
|
#define LOAD_SYM_REQUIRED(coreptr, sym) \
|
|
do { \
|
|
(coreptr)->sym = dlsym((coreptr)->handle, #sym); \
|
|
if (!(coreptr)->sym) { \
|
|
fprintf(stderr, " FATAL: dlsym(%s) failed: %s\n", #sym, dlerror()); \
|
|
return false; \
|
|
} \
|
|
} while(0)
|
|
|
|
/* Stub callbacks for libretro */
|
|
static void tf_video_refresh(const void *d, unsigned w, unsigned h, size_t p) { (void)d; (void)w; (void)h; (void)p; }
|
|
static void tf_audio_sample(int16_t l, int16_t r) { (void)l; (void)r; }
|
|
static size_t tf_audio_sample_batch(const int16_t *d, size_t f) { (void)d; return f; }
|
|
static void tf_input_poll(void) {}
|
|
static int16_t tf_input_state(unsigned p, unsigned d, unsigned i, unsigned id) { (void)p; (void)d; (void)i; (void)id; return 0; }
|
|
|
|
static bool tf_environment(unsigned cmd, void *data)
|
|
{
|
|
switch (cmd & 0xFF)
|
|
{
|
|
case RETRO_ENVIRONMENT_GET_LOG_INTERFACE:
|
|
return false;
|
|
case RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY:
|
|
case RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY:
|
|
case RETRO_ENVIRONMENT_GET_CORE_ASSETS_DIRECTORY:
|
|
*(const char **)data = ".";
|
|
return true;
|
|
case RETRO_ENVIRONMENT_SET_VARIABLES:
|
|
case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2:
|
|
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; }
|
|
if (var->key && strcmp(var->key, "virtualjaguar_cd_boot_mode") == 0)
|
|
{ var->value = "hle"; return true; }
|
|
var->value = NULL;
|
|
return false;
|
|
}
|
|
case RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE:
|
|
*(bool *)data = false;
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static bool vj_core_load(struct vj_core *core)
|
|
{
|
|
#ifdef __APPLE__
|
|
const char *lib = "./virtualjaguar_libretro.dylib";
|
|
#elif defined(_WIN32)
|
|
const char *lib = "./virtualjaguar_libretro.dll";
|
|
#else
|
|
const char *lib = "./virtualjaguar_libretro.so";
|
|
#endif
|
|
|
|
memset(core, 0, sizeof(*core));
|
|
|
|
core->handle = dlopen(lib, RTLD_LAZY);
|
|
if (!core->handle)
|
|
{
|
|
fprintf(stderr, "FATAL: dlopen(%s): %s\n", lib, dlerror());
|
|
return false;
|
|
}
|
|
|
|
/* Build-identity guard: print exactly which binary we are testing, and
|
|
* if VJ_EXPECT_BUILD is set (e.g. by `make test` / cd_boot_matrix.sh to
|
|
* `scripts/build-id.sh` output), refuse a core whose embedded version
|
|
* string does not contain it. Prevents silently testing a stale build
|
|
* -- which has produced a false PASS before (2026-07-15). */
|
|
{
|
|
void (*p_sysinfo)(struct retro_system_info *) =
|
|
(void (*)(struct retro_system_info *))dlsym(core->handle, "retro_get_system_info");
|
|
const char *expect = getenv("VJ_EXPECT_BUILD");
|
|
struct retro_system_info si;
|
|
memset(&si, 0, sizeof(si));
|
|
if (p_sysinfo)
|
|
{
|
|
p_sysinfo(&si);
|
|
fprintf(stderr, "core: %s %s (%s)\n",
|
|
si.library_name ? si.library_name : "?",
|
|
si.library_version ? si.library_version : "?", lib);
|
|
}
|
|
if (expect && expect[0])
|
|
{
|
|
/* Token-boundary match: "91f0804" must not accept a stale
|
|
* "91f0804-dirty" build (version format: "vX.Y.Z rev[-dirty]"). */
|
|
const char *hit = si.library_version ? strstr(si.library_version, expect) : NULL;
|
|
char tail = hit ? hit[strlen(expect)] : '-';
|
|
if (!hit || (tail != '\0' && tail != ' '))
|
|
{
|
|
fprintf(stderr,
|
|
"FATAL: build mismatch -- core reports \"%s\" but "
|
|
"VJ_EXPECT_BUILD=\"%s\". The binary under test is "
|
|
"stale or from another branch; rebuild with "
|
|
"`make TEST_EXPORTS=1` and re-run.\n",
|
|
si.library_version ? si.library_version : "(none)", expect);
|
|
dlclose(core->handle);
|
|
core->handle = NULL;
|
|
return false;
|
|
}
|
|
fprintf(stderr, "core build id verified: %s\n", expect);
|
|
}
|
|
}
|
|
|
|
/* libretro API */
|
|
LOAD_SYM_REQUIRED(core, retro_init);
|
|
LOAD_SYM_REQUIRED(core, retro_deinit);
|
|
LOAD_SYM_REQUIRED(core, retro_set_environment);
|
|
LOAD_SYM_REQUIRED(core, retro_set_video_refresh);
|
|
LOAD_SYM_REQUIRED(core, retro_set_audio_sample);
|
|
LOAD_SYM_REQUIRED(core, retro_set_audio_sample_batch);
|
|
LOAD_SYM_REQUIRED(core, retro_set_input_poll);
|
|
LOAD_SYM_REQUIRED(core, retro_set_input_state);
|
|
|
|
/* GPU */
|
|
LOAD_SYM(core, GPUInit);
|
|
LOAD_SYM(core, GPUDone);
|
|
LOAD_SYM(core, GPUReset);
|
|
LOAD_SYM(core, GPUExec);
|
|
LOAD_SYM(core, GPUHandleIRQs);
|
|
LOAD_SYM(core, GPUSetIRQLine);
|
|
LOAD_SYM(core, GPUReadByte);
|
|
LOAD_SYM(core, GPUReadWord);
|
|
LOAD_SYM(core, GPUReadLong);
|
|
LOAD_SYM(core, GPUWriteByte);
|
|
LOAD_SYM(core, GPUWriteWord);
|
|
LOAD_SYM(core, GPUWriteLong);
|
|
LOAD_SYM(core, GPUGetPC);
|
|
LOAD_SYM(core, GPUIsRunning);
|
|
|
|
/* DSP */
|
|
LOAD_SYM(core, DSPInit);
|
|
LOAD_SYM(core, DSPReset);
|
|
LOAD_SYM(core, DSPExec);
|
|
LOAD_SYM(core, DSPSetIRQLine);
|
|
LOAD_SYM(core, DSPReadByte);
|
|
LOAD_SYM(core, DSPReadWord);
|
|
LOAD_SYM(core, DSPReadLong);
|
|
LOAD_SYM(core, DSPWriteByte);
|
|
LOAD_SYM(core, DSPWriteWord);
|
|
LOAD_SYM(core, DSPWriteLong);
|
|
|
|
/* TOM */
|
|
LOAD_SYM(core, TOMInit);
|
|
LOAD_SYM(core, TOMReset);
|
|
LOAD_SYM(core, TOMReadWord);
|
|
LOAD_SYM(core, TOMWriteWord);
|
|
LOAD_SYM(core, TOMGetVideoModeWidth);
|
|
LOAD_SYM(core, TOMGetVideoModeHeight);
|
|
LOAD_SYM(core, TOMIRQEnabled);
|
|
LOAD_SYM(core, TOMIRQControlReg);
|
|
LOAD_SYM(core, TOMSetIRQLatch);
|
|
LOAD_SYM(core, TOMSetPendingVideoInt);
|
|
LOAD_SYM(core, TOMSetPendingGPUInt);
|
|
LOAD_SYM(core, TOMSetPendingTimerInt);
|
|
LOAD_SYM(core, TOMSetPendingObjectInt);
|
|
LOAD_SYM(core, TOMSetPendingJERRYInt);
|
|
|
|
/* JERRY */
|
|
LOAD_SYM(core, JERRYInit);
|
|
LOAD_SYM(core, JERRYReset);
|
|
LOAD_SYM(core, JERRYReadWord);
|
|
LOAD_SYM(core, JERRYWriteWord);
|
|
LOAD_SYM(core, JERRYIRQEnabled);
|
|
LOAD_SYM(core, JERRYSetPendingIRQ);
|
|
|
|
/* CDROM */
|
|
LOAD_SYM(core, CDROMInit);
|
|
LOAD_SYM(core, CDROMReset);
|
|
LOAD_SYM(core, CDROMReadWord);
|
|
LOAD_SYM(core, CDROMWriteWord);
|
|
|
|
/* Jaguar core */
|
|
LOAD_SYM(core, JaguarReadByte);
|
|
LOAD_SYM(core, JaguarReadWord);
|
|
LOAD_SYM(core, JaguarWriteByte);
|
|
LOAD_SYM(core, JaguarWriteWord);
|
|
LOAD_SYM(core, JaguarWriteLong);
|
|
LOAD_SYM(core, JaguarInit);
|
|
LOAD_SYM(core, JaguarReset);
|
|
|
|
/* m68k */
|
|
LOAD_SYM(core, m68k_get_reg);
|
|
LOAD_SYM(core, m68k_set_reg);
|
|
LOAD_SYM(core, m68k_execute);
|
|
LOAD_SYM(core, m68k_pulse_reset);
|
|
|
|
/* Memory */
|
|
LOAD_SYM(core, GetRamPtr);
|
|
|
|
/* Exported data */
|
|
core->gpu_reg_bank_0 = dlsym(core->handle, "gpu_reg_bank_0");
|
|
core->dsp_reg_bank_0 = dlsym(core->handle, "dsp_reg_bank_0");
|
|
core->gpu_reg_bank_1 = dlsym(core->handle, "gpu_reg_bank_1");
|
|
core->vjs = dlsym(core->handle, "vjs");
|
|
|
|
return true;
|
|
}
|
|
|
|
static void vj_core_init(struct vj_core *core)
|
|
{
|
|
core->retro_set_environment(tf_environment);
|
|
core->retro_set_video_refresh(tf_video_refresh);
|
|
core->retro_set_audio_sample(tf_audio_sample);
|
|
core->retro_set_audio_sample_batch(tf_audio_sample_batch);
|
|
core->retro_set_input_poll(tf_input_poll);
|
|
core->retro_set_input_state(tf_input_state);
|
|
core->retro_init();
|
|
if (core->GPUInit) core->GPUInit();
|
|
if (core->DSPInit) core->DSPInit();
|
|
}
|
|
|
|
static void vj_core_unload(struct vj_core *core)
|
|
{
|
|
/* vj_core_init() calls GPUInit() directly (no game load), so
|
|
* retro_deinit's no-game guard never reaches GPUDone -- release the
|
|
* branch-condition LUT here or LeakSanitizer flags 256 bytes. */
|
|
if (core->retro_deinit) core->retro_deinit();
|
|
if (core->GPUDone) core->GPUDone();
|
|
if (core->handle) dlclose(core->handle);
|
|
memset(core, 0, sizeof(*core));
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* GPU/DSP instruction encoding helpers */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
/* Jaguar GPU/DSP instruction format: 6-bit opcode | 5-bit src | 5-bit dst
|
|
* Bits: [15:10] opcode [9:5] src_reg [4:0] dst_reg */
|
|
static inline uint16_t gpu_encode(uint8_t opcode, uint8_t src, uint8_t dst)
|
|
{
|
|
return (uint16_t)((opcode & 0x3F) << 10) | ((src & 0x1F) << 5) | (dst & 0x1F);
|
|
}
|
|
|
|
/* MOVEI: opcode 38, followed by 32-bit immediate (low word first) */
|
|
static inline void gpu_write_movei(struct vj_core *c, uint32_t addr,
|
|
uint8_t dst, uint32_t imm)
|
|
{
|
|
c->GPUWriteWord(addr, gpu_encode(38, 0, dst), 0);
|
|
c->GPUWriteWord(addr + 2, (uint16_t)(imm & 0xFFFF), 0);
|
|
c->GPUWriteWord(addr + 4, (uint16_t)(imm >> 16), 0);
|
|
}
|
|
|
|
/* NOP: opcode 57 */
|
|
#define GPU_NOP gpu_encode(57, 0, 0)
|
|
|
|
/* Common opcodes */
|
|
#define GPU_OP_ADD 0
|
|
#define GPU_OP_ADDC 1
|
|
#define GPU_OP_ADDQ 2
|
|
#define GPU_OP_ADDQT 3
|
|
#define GPU_OP_SUB 4
|
|
#define GPU_OP_SUBC 5
|
|
#define GPU_OP_SUBQ 6
|
|
#define GPU_OP_SUBQT 7
|
|
#define GPU_OP_NEG 8
|
|
#define GPU_OP_AND 9
|
|
#define GPU_OP_OR 10
|
|
#define GPU_OP_XOR 11
|
|
#define GPU_OP_NOT 12
|
|
#define GPU_OP_BTST 13
|
|
#define GPU_OP_BSET 14
|
|
#define GPU_OP_BCLR 15
|
|
#define GPU_OP_MULT 16
|
|
#define GPU_OP_IMULT 17
|
|
#define GPU_OP_IMULTN 18
|
|
#define GPU_OP_RESMAC 19
|
|
#define GPU_OP_IMACN 20
|
|
#define GPU_OP_DIV 21
|
|
#define GPU_OP_ABS 22
|
|
#define GPU_OP_SH 23
|
|
#define GPU_OP_SHLQ 24
|
|
#define GPU_OP_SHRQ 25
|
|
#define GPU_OP_SHA 26
|
|
#define GPU_OP_SHARQ 27
|
|
#define GPU_OP_ROR 28
|
|
#define GPU_OP_RORQ 29
|
|
#define GPU_OP_CMP 30
|
|
#define GPU_OP_CMPQ 31
|
|
#define GPU_OP_SAT8 32
|
|
#define GPU_OP_SAT16 33
|
|
#define GPU_OP_MOVE 34
|
|
#define GPU_OP_MOVEQ 35
|
|
#define GPU_OP_MOVETA 36
|
|
#define GPU_OP_MOVEFA 37
|
|
#define GPU_OP_MOVEI 38
|
|
#define GPU_OP_LOADB 39
|
|
#define GPU_OP_LOADW 40
|
|
#define GPU_OP_LOAD 41
|
|
#define GPU_OP_LOADP 42
|
|
#define GPU_OP_SAT24 42
|
|
#define GPU_OP_LOAD14I 43
|
|
#define GPU_OP_LOAD15I 44
|
|
#define GPU_OP_STOREB 45
|
|
#define GPU_OP_STOREW 46
|
|
#define GPU_OP_STORE 47
|
|
#define GPU_OP_STOREP 48
|
|
#define GPU_OP_STORE14I 49
|
|
#define GPU_OP_STORE15I 50
|
|
#define GPU_OP_MOVPC 51
|
|
#define GPU_OP_JUMP 52
|
|
#define GPU_OP_JR 53
|
|
#define GPU_OP_MMULT 54
|
|
#define GPU_OP_MTOI 55
|
|
#define GPU_OP_NORMI 56
|
|
#define GPU_OP_NOP 57
|
|
#define GPU_OP_LOAD14R 58
|
|
#define GPU_OP_LOAD15R 59
|
|
#define GPU_OP_STORE14R 60
|
|
#define GPU_OP_STORE15R 61
|
|
#define GPU_OP_SAT16S 62
|
|
#define GPU_OP_PACK 63
|
|
|
|
/* GPU register addresses for control regs */
|
|
#define GPU_FLAGS_REG 0xF02100
|
|
#define GPU_MTXC_REG 0xF02104
|
|
#define GPU_MTXA_REG 0xF02108
|
|
#define GPU_END_REG 0xF0210C
|
|
#define GPU_PC_REG 0xF02110
|
|
#define GPU_CTRL_REG 0xF02114
|
|
#define GPU_HIDATA_REG 0xF02118
|
|
|
|
/* GPU flag bits in G_FLAGS ($F02100) */
|
|
#define GPU_FLAG_ZERO 0x0001
|
|
#define GPU_FLAG_CARRY 0x0002
|
|
#define GPU_FLAG_NEGA 0x0004
|
|
#define GPU_FLAG_IMASK 0x0008
|
|
|
|
/* DSP register addresses */
|
|
#define DSP_FLAGS_REG 0xF1A100
|
|
#define DSP_CTRL_REG 0xF1A114
|
|
#define DSP_PC_REG 0xF1A110
|
|
#define DSP_RAM_BASE 0xF1B000
|
|
|
|
/* Pad remaining program space with NOPs up to a max address */
|
|
static void gpu_fill_nops(struct vj_core *c, uint32_t from, uint32_t to)
|
|
{
|
|
uint32_t a;
|
|
for (a = from; a < to; a += 2)
|
|
c->GPUWriteWord(a, GPU_NOP, 0);
|
|
}
|
|
|
|
/* Execute a GPU program: set PC, start GPU, run N cycles, then stop.
|
|
* The program should be short enough to complete within cycle_budget. */
|
|
static void gpu_run_program(struct vj_core *c, uint32_t pc_addr)
|
|
{
|
|
c->GPUWriteLong(GPU_PC_REG, pc_addr, 0);
|
|
c->GPUWriteLong(GPU_CTRL_REG, 1, 0); /* GPUGO */
|
|
c->GPUExec(200);
|
|
c->GPUWriteLong(GPU_CTRL_REG, 0, 0); /* stop */
|
|
}
|
|
|
|
/* Read GPU flags register */
|
|
static uint32_t gpu_read_flags(struct vj_core *c)
|
|
{
|
|
return c->GPUReadLong(GPU_FLAGS_REG, 0);
|
|
}
|
|
|
|
#endif /* TEST_FRAMEWORK_H */
|