diff --git a/.gitignore b/.gitignore index f4073e6c..0b0cf7bc 100644 --- a/.gitignore +++ b/.gitignore @@ -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/ diff --git a/Makefile b/Makefile index db791999..ee6d20ef 100644 --- a/Makefile +++ b/Makefile @@ -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: diff --git a/mupen64plus-core/src/device/r4300/cached_interp.h b/mupen64plus-core/src/device/r4300/cached_interp.h index 86c62316..8260739b 100644 --- a/mupen64plus-core/src/device/r4300/cached_interp.h +++ b/mupen64plus-core/src/device/r4300/cached_interp.h @@ -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); diff --git a/mupen64plus-core/src/device/r4300/hacktarux_dynarec/hacktarux_dynarec.c b/mupen64plus-core/src/device/r4300/hacktarux_dynarec/hacktarux_dynarec.c index b3087d1c..4677d557 100644 --- a/mupen64plus-core/src/device/r4300/hacktarux_dynarec/hacktarux_dynarec.c +++ b/mupen64plus-core/src/device/r4300/hacktarux_dynarec/hacktarux_dynarec.c @@ -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) diff --git a/mupen64plus-core/src/device/r4300/recomp.c b/mupen64plus-core/src/device/r4300/recomp.c index f9814374..44ba7133 100644 --- a/mupen64plus-core/src/device/r4300/recomp.c +++ b/mupen64plus-core/src/device/r4300/recomp.c @@ -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 diff --git a/tools/cpu_fallback_test.c b/tools/cpu_fallback_test.c new file mode 100644 index 00000000..319ed606 --- /dev/null +++ b/tools/cpu_fallback_test.c @@ -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 +#include +#include + +#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; +} diff --git a/tools/savestate_latch_test.c b/tools/savestate_latch_test.c index 9ae8f961..8fb5bd69 100644 --- a/tools/savestate_latch_test.c +++ b/tools/savestate_latch_test.c @@ -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 #include