hacktarux: run BREAK, the COP2 moves and DCFC1/DCTC1 through the interpreter

These were compiled as not-implemented instructions, which request an
emulator stop instead of raising the Breakpoint, Coprocessor Unusable or
Floating-Point exception and leave the moved register unwritten. Each now
calls its interpreter form with the jump flag set, as SYSCALL does.

make check-cpu-fallback logs the exceptions and moved values of a test
cartridge under the pure interpreter and requires the cached interpreter
and Hacktarux to log the same; check-savestate-latch now checks the COP2
latch under Hacktarux too.
This commit is contained in:
LibretroAdmin authored and libretroadmin committed 2026-10-29 01:53:29 +01:00
1 parent 73fff0a3ec
commit 25a6efb3c4
7 files changed
+451 -17

No files matched your search

+2
View File
@@ -45,3 +45,5 @@ new_dynarec_disasm.txt
/dynarec_block_check.tmp/
/savestate_latch_test
/savestate_latch_check.tmp/
/cpu_fallback_test
/cpu_fallback_check.tmp/
+39 -5
View File
@@ -1203,7 +1203,7 @@ clean: clean-tools
clean-tools:
rm -f $(ANGRYLION_TOOLS) lrhost$(EXE_EXT) lrhost_vk$(EXE_EXT) angrylion_replay$(EXE_EXT) $(ANGRYLION_CHECKS) dd_ipl_hle_test$(EXE_EXT) \
$(CORE_CHECKS) dynarec_long_block_test$(EXE_EXT) savestate_latch_test$(EXE_EXT) tools/test_prsp_dmem_wrap.o tools/test_command_ring_rthreads.o tools/test_command_ring_ec.o
$(CORE_CHECKS) dynarec_long_block_test$(EXE_EXT) savestate_latch_test$(EXE_EXT) cpu_fallback_test$(EXE_EXT) tools/test_prsp_dmem_wrap.o tools/test_command_ring_rthreads.o tools/test_command_ring_ec.o
# Angrylion benchmark and bit-exactness harnesses (tools/angrylion_*.c),
# linked against the core's own angrylion objects: `make tools` after a
@@ -1371,9 +1371,11 @@ check-dynarec-block: $(TARGET) lrhost$(EXE_EXT) dynarec_long_block_test$(EXE_EXT
# The COP0 and COP2 move latches across a savestate: a state saved between
# the moves and their read-back is loaded into a fresh boot, with each CPU
# core. The `saved` step checks the save frame lands in that window; the
# recompilers do not implement COP2 moves, so they check the COP0 latch.
# core. The `saved` step checks the save frame lands in that window; ari64
# does not implement COP2 moves, so its runs check the COP0 latch only.
# "dynamic_recompiler" is Hacktarux when x86_64 builds both recompilers.
SAVESTATE_LATCH_CHECK_DIR := savestate_latch_check.tmp
CHECK_HACKTARUX := $(if $(and $(filter x86_64,$(WITH_DYNAREC)),$(filter 1,$(WANT_HACKTARUX)),$(filter 1,$(HAVE_ARI64_X64))),1,0)
SAVESTATE_LATCH_SAVE_AT := 16
savestate_latch_test$(EXE_EXT): tools/savestate_latch_test.c
@@ -1384,7 +1386,9 @@ check-savestate-latch: $(TARGET) lrhost$(EXE_EXT) savestate_latch_test$(EXE_EXT)
./savestate_latch_test$(EXE_EXT) rom $(SAVESTATE_LATCH_CHECK_DIR)/latch.z64; \
for core in pure_interpreter cached_interpreter dynamic_recompiler dynamic_recompiler_ari64; do \
echo "== $$core"; \
case $$core in dynamic_*) check=check-cop0 ;; *) check=check ;; esac; \
case $$core in dynamic_recompiler_ari64) check=check-cop0 ;; \
dynamic_recompiler) check=check-cop0; [ "$(CHECK_HACKTARUX)" = 1 ] && check=check ;; \
*) check=check ;; esac; \
( cd $(SAVESTATE_LATCH_CHECK_DIR) && LRHOST_SAVE=state_$$core.bin LRHOST_SAVE_AT=$(SAVESTATE_LATCH_SAVE_AT) \
LRHOST_RAMDUMP=saved_$$core.bin ../lrhost$(EXE_EXT) ../$(TARGET) latch.z64 $$(( $(SAVESTATE_LATCH_SAVE_AT) + 1 )) \
parallel-n64-gfxplugin=angrylion parallel-n64-rspplugin=hle \
@@ -1397,6 +1401,36 @@ check-savestate-latch: $(TARGET) lrhost$(EXE_EXT) savestate_latch_test$(EXE_EXT)
./savestate_latch_test$(EXE_EXT) $$check $(SAVESTATE_LATCH_CHECK_DIR)/ram_$$core.bin; \
done; rm -rf $(SAVESTATE_LATCH_CHECK_DIR)
# Instructions a recompiler runs through the interpreter (BREAK, the COP2
# moves, DCFC1/DCTC1): the pure interpreter's exception and value log is the
# reference, and the cached interpreter and Hacktarux must log the same.
# ari64 does not implement these yet and is not compared.
CPU_FALLBACK_CHECK_DIR := cpu_fallback_check.tmp
CPU_FALLBACK_CORES := cached_interpreter
ifeq ($(CHECK_HACKTARUX), 1)
CPU_FALLBACK_CORES += dynamic_recompiler
endif
cpu_fallback_test$(EXE_EXT): tools/cpu_fallback_test.c
$(CC) -O1 -g $< -o $@
check-cpu-fallback: $(TARGET) lrhost$(EXE_EXT) cpu_fallback_test$(EXE_EXT)
@set -e; rm -rf $(CPU_FALLBACK_CHECK_DIR); mkdir -p $(CPU_FALLBACK_CHECK_DIR)/lrhost-system; \
./cpu_fallback_test$(EXE_EXT) rom $(CPU_FALLBACK_CHECK_DIR)/fallback.z64; \
for core in pure_interpreter $(CPU_FALLBACK_CORES); do \
echo "== $$core"; \
( cd $(CPU_FALLBACK_CHECK_DIR) && LRHOST_RAMDUMP=ram_$$core.bin \
../lrhost$(EXE_EXT) ../$(TARGET) fallback.z64 4 \
parallel-n64-gfxplugin=angrylion parallel-n64-rspplugin=hle \
parallel-n64-cpucore=$$core > /dev/null ); \
if [ $$core = pure_interpreter ]; then \
./cpu_fallback_test$(EXE_EXT) check $(CPU_FALLBACK_CHECK_DIR)/ram_$$core.bin; \
else \
./cpu_fallback_test$(EXE_EXT) compare $(CPU_FALLBACK_CHECK_DIR)/ram_pure_interpreter.bin \
$(CPU_FALLBACK_CHECK_DIR)/ram_$$core.bin; \
fi; \
done; rm -rf $(CPU_FALLBACK_CHECK_DIR)
# Core self-tests: MMIO register and DMA bounds, the frontend memory map,
# framebuffer write notifications, COP0 exceptions, PI status, the MusyX
# voice loaders, the BOSS audio arithmetic and, in a HAVE_PARALLEL_RSP=1
@@ -1577,7 +1611,7 @@ endif
check-core: $(CORE_CHECKS)
@set -e; for t in $(CORE_CHECKS); do echo "== $$t"; ./$$t; done
.PHONY: clean clean-tools tools check-angrylion check-core check-dynarec-block check-savestate-latch
.PHONY: clean clean-tools tools check-angrylion check-core check-dynarec-block check-savestate-latch check-cpu-fallback
-include $(OBJECTS:.o=.d)
# A header named by an existing .d file but since deleted is not an error:
@@ -226,6 +226,15 @@ void cached_interp_SCD(void);
void cached_interp_DMFC0(void);
void cached_interp_MFC0(void);
void cached_interp_MTC0(void);
void cached_interp_BREAK(void);
void cached_interp_CFC2(void);
void cached_interp_CTC2(void);
void cached_interp_DCFC1(void);
void cached_interp_DCTC1(void);
void cached_interp_DMFC2(void);
void cached_interp_DMTC2(void);
void cached_interp_MFC2(void);
void cached_interp_MTC2(void);
void cached_interp_LWC1(void);
void cached_interp_LDC1(void);
void cached_interp_SWC1(void);
@@ -4210,6 +4210,56 @@ void gen_SYSCALL(struct r4300_core* r4300)
gencallinterp(r4300, (unsigned long long)cached_interp_SYSCALL, 1);
}
/* BREAK, the COP2 moves and the doubleword COP1 control moves have no
* native form: run them through the interpreter. Each can raise an
* exception (Breakpoint, Coprocessor Unusable, Reserved Instruction), so
* the jump flag is set, as for SYSCALL. */
void gen_BREAK(struct r4300_core* r4300)
{
gencallinterp(r4300, (unsigned long long)cached_interp_BREAK, 1);
}
void gen_CFC2(struct r4300_core* r4300)
{
gencallinterp(r4300, (unsigned long long)cached_interp_CFC2, 1);
}
void gen_CTC2(struct r4300_core* r4300)
{
gencallinterp(r4300, (unsigned long long)cached_interp_CTC2, 1);
}
void gen_DCFC1(struct r4300_core* r4300)
{
gencallinterp(r4300, (unsigned long long)cached_interp_DCFC1, 1);
}
void gen_DCTC1(struct r4300_core* r4300)
{
gencallinterp(r4300, (unsigned long long)cached_interp_DCTC1, 1);
}
void gen_DMFC2(struct r4300_core* r4300)
{
gencallinterp(r4300, (unsigned long long)cached_interp_DMFC2, 1);
}
void gen_DMTC2(struct r4300_core* r4300)
{
gencallinterp(r4300, (unsigned long long)cached_interp_DMTC2, 1);
}
void gen_MFC2(struct r4300_core* r4300)
{
gencallinterp(r4300, (unsigned long long)cached_interp_MFC2, 1);
}
void gen_MTC2(struct r4300_core* r4300)
{
gencallinterp(r4300, (unsigned long long)cached_interp_MTC2, 1);
}
/* Trap instructions */
void gen_TGE(struct r4300_core* r4300)
+9 -9
View File
@@ -273,6 +273,15 @@ void gen_MFC1(struct r4300_core* r4300);
void gen_MFHI(struct r4300_core* r4300);
void gen_MFLO(struct r4300_core* r4300);
void gen_MTC0(struct r4300_core* r4300);
void gen_BREAK(struct r4300_core* r4300);
void gen_CFC2(struct r4300_core* r4300);
void gen_CTC2(struct r4300_core* r4300);
void gen_DCFC1(struct r4300_core* r4300);
void gen_DCTC1(struct r4300_core* r4300);
void gen_DMFC2(struct r4300_core* r4300);
void gen_DMTC2(struct r4300_core* r4300);
void gen_MFC2(struct r4300_core* r4300);
void gen_MTC2(struct r4300_core* r4300);
void gen_MTC1(struct r4300_core* r4300);
void gen_MTHI(struct r4300_core* r4300);
void gen_MTLO(struct r4300_core* r4300);
@@ -422,26 +431,17 @@ static void gen_CP1_CVT_S(struct r4300_core* r4300)
#define gen_BC2TL genni
#define gen_BC2TL_IDLE genni
#define gen_BC2TL_OUT genni
#define gen_BREAK genni
#define gen_CFC0 genni
#define gen_CFC2 genni
#define gen_CTC0 genni
#define gen_CTC2 genni
#define gen_DMFC2 genni
#define gen_DMTC2 genni
#define gen_JR_IDLE genni
#define gen_JR_OUT gen_JR
#define gen_JALR_IDLE genni
#define gen_JALR_OUT gen_JALR
#define gen_LDC2 genni
#define gen_LWC2 genni
#define gen_MFC2 genni
#define gen_MTC2 genni
#define gen_SDC2 genni
#define gen_SWC2 genni
#define gen_DCFC1 genni
#define gen_DCFC2 genni
#define gen_DCTC1 genni
#define gen_DCTC2 genni
#define X(op) gen_##op
+339
View File
@@ -0,0 +1,339 @@
/* cpu_fallback_test -- instructions a recompiler hands to the interpreter.
*
* cpu_fallback_test rom FILE write the test cartridge
* cpu_fallback_test check RAMDUMP check the reference run's log
* cpu_fallback_test compare REF RAMDUMP check a run logged what REF did
*
* The cartridge's boot code, run from DMEM by the HLE PIF boot, programs the
* VI's V_SYNC so frames are timed, copies a payload to RDRAM 0x400 and an
* exception handler to 0x180 by PI DMA, and jumps to the payload. The
* handler logs Cause.ExcCode, steps EPC past the instruction and returns.
* The payload runs BREAK, the COP2 moves with COP2 unusable and usable,
* DCFC1 and DCTC1, logging each exception and each value moved back, then
* stores how many words it logged and a marker.
*
* `make check-cpu-fallback` takes the pure interpreter's log as the
* reference, checks its exception codes, and requires the cached
* interpreter and Hacktarux to log the same words.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define ROM_SIZE 0x100000
#define PAYLOAD_ROM 0x1000
#define PAYLOAD_RDRAM 0x400
#define PAYLOAD_WORDS 96
#define HANDLER_ROM 0x2000
#define HANDLER_RDRAM 0x180
#define HANDLER_WORDS 10
#define LOG_ADDR 0x200
#define LOG_WORDS 16
#define COUNT_ADDR 0x1f8
#define MARKER_ADDR 0x1fc
#define MARKER 0x600dUL
#define EXC_BP 0x24UL /* Breakpoint << 2 */
#define EXC_CPU 0x2cUL /* Coprocessor Unusable << 2 */
#define EXC_FPE 0x3cUL /* Floating-Point << 2 */
#define VAL_MOVE 0x1357c2deUL
#define VAL_DMOVE 0x89abcdefUL /* sign-extends to 64 bits */
#define VAL_CTRL 0x2468c2c2UL
#define T0 8
#define T1 9
#define T2 10
#define T3 11
#define T4 12
#define T5 13
#define T6 14
#define S7 23
#define K0 26
#define R(op, rs, rt, rd, sa, fn) (((unsigned long)(op) << 26) | ((unsigned long)(rs) << 21) | ((unsigned long)(rt) << 16) \
| ((unsigned long)(rd) << 11) | ((unsigned long)(sa) << 6) | (unsigned long)(fn))
#define I(op, rs, rt, imm) (((unsigned long)(op) << 26) | ((unsigned long)(rs) << 21) | ((unsigned long)(rt) << 16) \
| ((unsigned long)(imm) & 0xffffUL))
#define LUI(rt, imm) I(0x0f, 0, rt, imm)
#define ORI(rt, rs, imm) I(0x0d, rs, rt, imm)
#define ANDI(rt, rs, imm) I(0x0c, rs, rt, imm)
#define ADDIU(rt, rs, imm) I(0x09, rs, rt, imm)
#define LW(rt, off, base) I(0x23, base, rt, off)
#define SW(rt, off, base) I(0x2b, base, rt, off)
#define BNE(rs, rt, off) I(0x05, rs, rt, off)
#define BEQ(rs, rt, off) I(0x04, rs, rt, off)
#define OR(rd, rs, rt) R(0, rs, rt, rd, 0, 0x25)
#define AND(rd, rs, rt) R(0, rs, rt, rd, 0, 0x24)
#define NOR(rd, rs, rt) R(0, rs, rt, rd, 0, 0x27)
#define DSRL32(rd, rt, sa) R(0, 0, rt, rd, sa, 0x3e)
#define JR(rs) R(0, rs, 0, 0, 0, 0x08)
#define BREAK R(0, 0, 0, 0, 0, 0x0d)
#define MFC0(rt, rd) R(0x10, 0x00, rt, rd, 0, 0)
#define MTC0(rt, rd) R(0x10, 0x04, rt, rd, 0, 0)
#define ERET R(0x10, 0x10, 0, 0, 0, 0x18)
#define DCFC1(rt, rd) R(0x11, 0x03, rt, rd, 0, 0)
#define DCTC1(rt, rd) R(0x11, 0x07, rt, rd, 0, 0)
#define MFC2(rt, rd) R(0x12, 0x00, rt, rd, 0, 0)
#define DMFC2(rt, rd) R(0x12, 0x01, rt, rd, 0, 0)
#define CFC2(rt, rd) R(0x12, 0x02, rt, rd, 0, 0)
#define MTC2(rt, rd) R(0x12, 0x04, rt, rd, 0, 0)
#define DMTC2(rt, rd) R(0x12, 0x05, rt, rd, 0, 0)
#define CTC2(rt, rd) R(0x12, 0x06, rt, rd, 0, 0)
#define NOP 0UL
#define CP0_STATUS 12
#define CP0_CAUSE 13
#define CP0_EPC 14
#define STATUS_CU2 0x4000
struct emitter
{
unsigned char* base;
unsigned n;
};
static void emit(struct emitter* e, unsigned long v)
{
unsigned char* p = e->base + 4 * e->n++;
p[0] = (unsigned char)(v >> 24);
p[1] = (unsigned char)(v >> 16);
p[2] = (unsigned char)(v >> 8);
p[3] = (unsigned char)v;
}
static void emit_li(struct emitter* e, unsigned rt, unsigned long v)
{
emit(e, LUI(rt, v >> 16));
emit(e, ORI(rt, rt, v & 0xffff));
}
/* PI DMA len bytes from cartridge offset cart to RDRAM dram; $t0 = PI */
static void emit_dma(struct emitter* e, unsigned long cart, unsigned long dram, unsigned long len)
{
unsigned wait;
emit(e, ORI(T1, 0, dram));
emit(e, SW(T1, 0x00, T0));
emit_li(e, T1, 0x10000000UL + cart);
emit(e, SW(T1, 0x04, T0));
emit(e, ORI(T1, 0, len - 1));
emit(e, SW(T1, 0x0c, T0));
wait = e->n;
emit(e, LW(T1, 0x10, T0));
emit(e, ANDI(T1, T1, 3));
emit(e, BNE(T1, 0, (int)wait - (int)(e->n + 1)));
emit(e, NOP);
}
/* log $rt at $s7 and advance it */
static void emit_log(struct emitter* e, unsigned rt)
{
emit(e, SW(rt, 0, S7));
emit(e, ADDIU(S7, S7, 4));
}
/* Status.CU2 on or off, through $t1/$t2 */
static void emit_cu2(struct emitter* e, int on)
{
emit(e, MFC0(T1, CP0_STATUS));
emit(e, LUI(T2, STATUS_CU2));
if (on)
emit(e, OR(T1, T1, T2));
else
{
emit(e, NOR(T2, T2, 0));
emit(e, AND(T1, T1, T2));
}
emit(e, MTC0(T1, CP0_STATUS));
}
static int write_rom(const char* path)
{
unsigned char* rom = (unsigned char*)calloc(1, ROM_SIZE);
struct emitter e;
FILE* f;
if (!rom)
return 1;
rom[0] = 0x80; rom[1] = 0x37; rom[2] = 0x12; rom[3] = 0x40;
rom[7] = 0x0f;
rom[8] = 0x80; rom[10] = 0x04;
memcpy(rom + 0x20, "CPU FALLBACK ", 20);
/* boot code at 0x40, run from DMEM */
e.base = rom + 0x40;
e.n = 0;
emit(&e, LUI(T2, 0xa440)); /* VI: frames need V_SYNC */
emit(&e, ORI(T1, 0, 2));
emit(&e, SW(T1, 0x0c, T2));
emit(&e, ORI(T1, 0, 0x20d));
emit(&e, SW(T1, 0x18, T2));
emit(&e, LUI(T0, 0xa460));
emit_dma(&e, HANDLER_ROM, HANDLER_RDRAM, HANDLER_WORDS * 4);
emit_dma(&e, PAYLOAD_ROM, PAYLOAD_RDRAM, PAYLOAD_WORDS * 4);
emit_li(&e, T2, 0x80000000UL + PAYLOAD_RDRAM);
emit(&e, JR(T2));
emit(&e, NOP);
/* exception handler: log ExcCode, return past the instruction */
e.base = rom + HANDLER_ROM;
e.n = 0;
emit(&e, MFC0(K0, CP0_CAUSE));
emit(&e, ANDI(K0, K0, 0x7c));
emit_log(&e, K0);
emit(&e, MFC0(K0, CP0_EPC));
emit(&e, ADDIU(K0, K0, 4));
emit(&e, MTC0(K0, CP0_EPC));
emit(&e, NOP);
emit(&e, ERET);
emit(&e, NOP);
if (e.n > HANDLER_WORDS)
goto overflow;
/* payload */
e.base = rom + PAYLOAD_ROM;
e.n = 0;
emit_li(&e, S7, 0x80000000UL + LOG_ADDR);
emit(&e, BREAK); /* Bp */
emit_cu2(&e, 0);
emit_li(&e, T3, VAL_MOVE);
emit(&e, MTC2(T3, 0)); /* CpU */
emit_cu2(&e, 1);
emit(&e, MTC2(T3, 0));
emit(&e, MFC2(T4, 0));
emit_log(&e, T4); /* VAL_MOVE */
emit_li(&e, T3, VAL_DMOVE);
emit(&e, DMTC2(T3, 0));
emit(&e, DMFC2(T5, 0));
emit(&e, DSRL32(T6, T5, 0));
emit_log(&e, T5); /* low word */
emit_log(&e, T6); /* sign extension */
emit_li(&e, T3, VAL_CTRL);
emit(&e, CTC2(T3, 0));
emit(&e, CFC2(T4, 0));
emit_log(&e, T4); /* VAL_CTRL */
emit(&e, DCFC1(T4, 0)); /* FPE */
emit(&e, DCTC1(T4, 0)); /* FPE */
emit_cu2(&e, 0);
emit(&e, MFC2(T4, 0)); /* CpU */
emit_li(&e, T1, 0x80000000UL);
emit_li(&e, T2, LOG_ADDR + 0x80000000UL);
emit(&e, R(0, S7, T2, T2, 0, 0x23)); /* SUBU: bytes logged */
emit(&e, SW(T2, COUNT_ADDR, T1));
emit(&e, ORI(T2, 0, MARKER));
emit(&e, SW(T2, MARKER_ADDR, T1));
emit(&e, BEQ(0, 0, 0xffff));
emit(&e, NOP);
if (e.n > PAYLOAD_WORDS)
goto overflow;
f = fopen(path, "wb");
if (!f || fwrite(rom, 1, ROM_SIZE, f) != ROM_SIZE)
{
perror(path);
if (f)
fclose(f);
free(rom);
return 1;
}
fclose(f);
free(rom);
return 0;
overflow:
fprintf(stderr, "code overflows its DMA\n");
free(rom);
return 1;
}
/* RETRO_MEMORY_SYSTEM_RAM holds RDRAM as host-order 32-bit words */
static unsigned long ram_word(const unsigned char* ram, unsigned long addr)
{
return (unsigned long)ram[addr] | ((unsigned long)ram[addr + 1] << 8)
| ((unsigned long)ram[addr + 2] << 16) | ((unsigned long)ram[addr + 3] << 24);
}
static int load_log(const char* path, unsigned long* log, unsigned* count)
{
unsigned char ram[LOG_ADDR + 4 * LOG_WORDS];
unsigned i;
FILE* f = fopen(path, "rb");
if (!f || fread(ram, 1, sizeof(ram), f) != sizeof(ram))
{
perror(path);
if (f)
fclose(f);
return 1;
}
fclose(f);
if (ram_word(ram, MARKER_ADDR) != MARKER)
{
fprintf(stderr, "FAIL: %s: the payload did not finish\n", path);
return 1;
}
*count = (unsigned)(ram_word(ram, COUNT_ADDR) / 4);
if (*count > LOG_WORDS)
{
fprintf(stderr, "FAIL: %s: %u log words\n", path, *count);
return 1;
}
for (i = 0; i < *count; ++i)
log[i] = ram_word(ram, LOG_ADDR + 4 * i);
return 0;
}
int main(int argc, char** argv)
{
static const unsigned long expect[] = {
EXC_BP, EXC_CPU, VAL_MOVE, VAL_DMOVE, 0xffffffffUL, VAL_CTRL, EXC_FPE, EXC_FPE, EXC_CPU
};
unsigned long ref[LOG_WORDS], got[LOG_WORDS];
unsigned nref, ngot, i;
if (argc == 3 && !strcmp(argv[1], "rom"))
return write_rom(argv[2]);
if (argc == 3 && !strcmp(argv[1], "check"))
{
if (load_log(argv[2], ref, &nref))
return 1;
if (nref != sizeof(expect) / sizeof(expect[0]))
{
fprintf(stderr, "FAIL: %s: %u log words, expected %u\n", argv[2], nref,
(unsigned)(sizeof(expect) / sizeof(expect[0])));
return 1;
}
for (i = 0; i < nref; ++i)
if (ref[i] != expect[i])
{
fprintf(stderr, "FAIL: %s: log word %u is %#lx, expected %#lx\n", argv[2], i, ref[i], expect[i]);
return 1;
}
printf("cpu_fallback_test: %s: reference log as expected\n", argv[2]);
return 0;
}
if (argc == 4 && !strcmp(argv[1], "compare"))
{
if (load_log(argv[2], ref, &nref) || load_log(argv[3], got, &ngot))
return 1;
for (i = 0; i < nref || i < ngot; ++i)
if (i >= nref || i >= ngot || ref[i] != got[i])
{
fprintf(stderr, "FAIL: %s: log word %u is %#lx, the reference logged %#lx\n", argv[3], i,
i < ngot ? got[i] : 0UL, i < nref ? ref[i] : 0UL);
return 1;
}
printf("cpu_fallback_test: %s: matches the reference\n", argv[3]);
return 0;
}
fprintf(stderr, "usage: %s rom FILE | check RAMDUMP | compare REF RAMDUMP\n", argv[0]);
return 2;
}
+3 -3
View File
@@ -14,9 +14,9 @@
*
* `make check-savestate-latch` saves a state during the second wait, loads
* it into a fresh boot that has not reached the latch writes, and checks the
* values read back are the ones moved in before the save. Hacktarux and
* ari64 run COP2 moves as unimplemented instructions that write nothing, so
* their runs check the COP0 latch only.
* values read back are the ones moved in before the save. ari64 runs COP2
* moves as unimplemented instructions that write nothing, so its runs
* check the COP0 latch only.
*/
#include <stdio.h>
#include <stdlib.h>