/* * mii_cpu_test.c * * Copyright (C) 2023 Michel Pollet * * SPDX-License-Identifier: MIT * * This was heavily inspired and converted from * https://github.com/ct6502/apple2ts/blob/main/src/emulator/instructions.test.ts * */ #include #include #include #include #include "mii_65c02.h" #include "mii_65c02_asm.h" #include "mii_65c02_disasm.h" #include "mii_65c02_ops.h" static void _run_one_dump_state( mii_cpu_t *cpu, mii_cpu_state_t s, void *p) { printf("PC:%04X A:%02X X:%02X Y:%02X S:%02x #%d %s AD:%04X D:%02x %s ", cpu->PC, cpu->A, cpu->X, cpu->Y, cpu->S, cpu->cycle, s.sync ? "I" : " ", s.addr, s.data, s.w ? "W" : "R"); // display the S flags static const char *s_flags = "CZIDBRVN"; for (int i = 0; i < 8; i++) printf("%c", cpu->P.P[i] ? s_flags[i] : tolower(s_flags[i])); if (s.sync) { mii_op_t d = mii_cpu_op[cpu->ram[cpu->PC]]; printf(" "); char dis[64]; mii_cpu_disasm_one(cpu->ram + cpu->PC, cpu->PC, dis, sizeof(dis), MII_DUMP_DIS_PC | MII_DUMP_DIS_DUMP_HEX); printf("%s", dis); if (d.desc.branch) { if (cpu->P.P[d.desc.s_bit] == d.desc.s_bit_value) printf(" ; taken"); } printf("\n"); } else printf("\n"); } static int _run_one_test( const char *prog, int verbose) { uint8_t *ram = calloc(1, 0x10000); mii_cpu_asm_program_t p = {}; { p.verbose = verbose; if (mii_cpu_asm(&p, prog) == 0) { // uint8_t *o = p.output; if (verbose || p.verbose) mii_cpu_disasm(p.output, p.org, p.output_len); memcpy(ram + p.org, p.output, p.output_len); } else { printf("error\n"); exit(1); } } mii_cpu_t cpu = {0}; mii_cpu_state_t s = {0}; verbose += p.verbose; cpu.ram = ram; if (verbose) { // cpu.debug = _run_one_dump_state; } cpu.P.P[5] = 1; cpu.S = 0xFF; cpu.PC = p.org; cpu.A = 0x01; int count = 1000; int error = 1; do { s = mii_cpu_run(&cpu, s); if (s.w) ram[s.addr] = s.data; else s.data = ram[s.addr]; if (verbose) _run_one_dump_state(&cpu, s, NULL); if (s.sync && !s.w && s.data == 0x00) { mii_cpu_asm_line_t *l = p.prog; while (l) { if (l->addr == s.addr) break; l = l->next; } if (l && !strcasecmp(l->label, "pass")) { printf("TEST %-40.40s: PASS\n", p.prog->line + 2); error = 0; } else if (l) { printf("TEST %-40.40s: FAIL\n", p.prog->line + 2); printf("\t%s\n", l->line); _run_one_dump_state(&cpu, s, NULL); } else { printf("TEST %-40.40s: FAIL (out of program!)\n", p.prog->line + 2); _run_one_dump_state(&cpu, s, NULL); } break; } } while (count--); if (!count) { printf("TEST %-40.40s: FAIL (out of _run_this cycles!)\n", p.prog->line + 2); _run_one_dump_state(&cpu, s, NULL); } free(ram); return error; } int _run_this_one( const char *desc, const char *prog, uint8_t expexted_A, uint8_t expected_flags, int verbose ) { uint8_t *ram = calloc(1, 0x10000); mii_cpu_asm_program_t p = {.org = 0x2000}; { if (mii_cpu_asm(&p, prog) == 0) { // uint8_t *o = p.output; if (verbose || p.verbose) mii_cpu_disasm(p.output, p.org, p.output_len); memcpy(ram + p.org, p.output, p.output_len); } else { printf("error\n"); exit(1); } } mii_cpu_t cpu = {0}; mii_cpu_state_t s = {0}; verbose += p.verbose; cpu.ram = ram; cpu.P.P[B_X] = 1; expected_flags |= (1 << B_X); cpu.S = 0xFF; cpu.PC = p.org; cpu.A = 0x01; int count = 1000; do { s = mii_cpu_run(&cpu, s); if (s.w) ram[s.addr] = s.data; else s.data = ram[s.addr]; if (verbose) _run_one_dump_state(&cpu, s, NULL); if (s.sync && !s.w && s.data == 0x00) { if (cpu.A == expexted_A) { int err = 0; static const char *s_flags = "CZIDBRVN"; for (int i = 0; i < 8; i++) if (cpu.P.P[i] != ((expected_flags >> i) & 1)) { printf("** S bit %c mismatch %d want %d\n", s_flags[i], cpu.P.P[i], (expected_flags >> i) & 1); err++; } if (err) goto fail; printf("TEST %-40.40s: PASS\n", desc); break; } else { printf("** A Mismatch %02x vs %02x\n", cpu.A, expexted_A); fail: printf("TEST %-40.40s: FAIL\n", desc); return -1; } } } while (count--); free(ram); return 0; } void _run_this( const char *desc, const char *prog, uint8_t expexted_A, uint8_t expected_flags, int verbose ) { int e = _run_this_one(desc, prog, expexted_A, expected_flags, verbose); if (e && !verbose) { if (_run_this_one(desc, prog, expexted_A, expected_flags, 1)) exit(1); } } static char * indirect(char *prefix, uint8_t A, uint8_t mem, char *suffix) { char *s = calloc(1, 256); sprintf(s, " %s\n" " LDA #$01\n" " STA $12\n" " LDA #$30\n" " STA $13\n" " LDA #$%02x\n" " STA $3001\n" " LDA #$%02x\n" " %s\n", prefix, mem, A, suffix); return s; } static char * doSED_ADC(uint8_t a, uint8_t b) { char *s = calloc(1, 256); sprintf(s, " SED\n" " LDA #$%02x\n" " ADC #$%02x\n", a, b); return s; } #include int main() { glob_t globbuf; glob("test/asm/0*.asm", 0, NULL, &globbuf); for (int i = 0; i < (int)globbuf.gl_pathc; i++) { printf("Loading %s\n", globbuf.gl_pathv[i]); FILE *f = fopen(globbuf.gl_pathv[i], "r"); if (f) { char *prog = calloc(1, 65536); fread(prog, 1, 65536, f); fclose(f); _run_one_test(prog, 0); free(prog); } } globfree(&globbuf); enum { C = (1 << B_C), Z = (1 << B_Z), I = (1 << B_I), D = (1 << B_D), B = (1 << B_B), X = (1 << B_X), V = (1 << B_V), N = (1 << B_N), }; _run_this("RMB $12", " lda #$FF\n" " sta $12\n" " rmb1 $12\n" " lda $12\n", 0xfd, N, 0); _run_this("RMB1 $ea", " ldx $EA\n" " lda #$FF\n" " sta $EA\n" " rmb1 $EA\n" " cmp $EA\n" " stx $EA\n", 0xff, C, 0); _run_this("ASL Carry", " clc\n lda #$80\n asl\n", 0x00, Z|C, 0); _run_this("ROL Base", " sec\n lda #$80\n rol\n", 0x01, C, 0); _run_this("SBC Over+", " sec\n lda #$d0\n sbc #$70\n", 0x60, V|C, 0); _run_this("SBC Neg3", " sec\n lda #$d0\n sbc #$30\n", 0xA0, N|C, 0); _run_this("LDA ($aa,X)", " LDX #$E9\n" " LDY #$81\n" " STY $3104\n" " LDY #$04\n" " STY $003A\n" " LDY #$31\n" " STY $003B\n" " LDA ($51,X)\n", 0x81, N, 0); _run_this("LDA ($aa),Y", " LDY #$E9\n" " LDX #$BB\n" " STX $403A\n" " LDX #$51\n" " STX $00A4\n" " LDX #$3F\n" " STX $00A5\n" " LDA ($A4),Y\n", 0xBB, N, 0); _run_this("JMP ($3000,X)", " JMP skip\n" " LDA #$99 ; $2003\n" " JMP done\n" "skip LDA #$03\n" " STA $3002\n" " LDA #$20\n" " STA $3003\n" " LDX #$02\n" " JMP ($3000,X) ; JMP -> $2003\n" " LDA #$01 ; should not reach here\n" "done NOP ;\n", 0x99, N, 0); _run_this("DEC", " LDA #99\n" " DEC\n", 0x98, N, 0); _run_this("PLX", " LDX #$97\n" " PHX\n" " PLX\n" " TXA\n", 0x97, N, 0); _run_this("ASL", " LDA #$80\n ASL\n", 0x00, Z | C, 0); _run_this("ROR", " LDA #$81\n ROR\n", 0x40, C, 0); _run_this("ROL Z", " LDA #$80\n ROL\n", 0x00, Z | C, 0); _run_this("ROL", " LDA #$81\n ROL\n", 0x02, C, 0); _run_this("LDA ($12)", indirect("", 0x99, 0xFC, "LDA ($12)"), 0xFC, N, 0); _run_this("ADC ($12)", indirect("", 0x7E, 0xAF, "ADC ($12)"), 0x2D, C, 0); _run_this("ADC ($12) SED", indirect("SED", 0x75, 0x25, "ADC ($12)"), 0x00, V | C | D, 0); _run_this("AND ($12)", indirect("", 0x3F, 0xFC, "AND ($12)"), 0x3C, 0, 0); _run_this("CMP ($12)", indirect("", 0xFC, 0x3F, "CMP ($12)"), 0xFC, C | N, 0); _run_this("EOR ($12)", indirect("", 0x3F, 0xFC, "EOR ($12)"), 0xC3, N, 0); _run_this("ORA ($12)", indirect("", 0x0E, 0xFC, "ORA ($12)"), 0xFE, N, 0); _run_this("SBC ($12)", indirect("SEC", 0xFF, 0xC0, "SBC ($12)"), 0x3F, C, 0); _run_this("SBC ($12) SED", indirect("SEC\n SED", 0x75, 0x25, "SBC ($12)"), 0x50, C | D, 0); _run_this("STA ($12)", indirect("", 0xF1, 0xC0, "STA ($12)\n LDA $3001"), 0xF1, N, 0); _run_this("BIT #$F0", " LDA #$0F\n BIT #$F0\n", 0x0F, Z , 0); _run_this("BIT #$80", " LDA #$0F\n BIT #$80", 0x0F, Z, 0); _run_this("BIT #$70", " LDA #$0F\n BIT #$70", 0x0F, Z, 0); _run_this("BIT #$FF", " LDA #$0F\n BIT #$FF", 0x0F, 0, 0); _run_this("BIT $12,X", " LDY #$F0\n" " STY $14\n" " LDA #$0F\n" " LDX #$02\n" " BIT $12,X\n", 0x0F, Z | V | N, 0); _run_this("BIT $1234,X", " LDY #$F0\n" " STY $1236\n" " LDA #$0F\n" " LDX #$02\n" " BIT $1234,X\n", 0x0F, Z | V | N, 0); _run_this("DEC $xx", " LDA #$FF\n" " STA $12\n" " DEC $12\n" " LDA $12\n", 0xFE, N, 0); _run_this("INC $xxxx", " LDA #$FF\n" " STA $3000\n" " INC $3000\n" " LDA $3000\n", 0x00, Z, 0); _run_this("LDA ($9d),Y", " LDX #$90\n" " STX $90\n" " LDY #$D\n" " STZ $91\n" " LDX #$de\n" " STX $9D\n" " LDA ($90),y\n", 0xde, N, 0); _run_this("LDA ($aa,X)", " LDX #$E9\n" " LDY #$81\n" " STY $3104\n" " LDY #$04\n" " STY $003A\n" " LDY #$31\n" " STY $003B\n" " LDA ($51,X)\n", 0x81, N, 0); _run_this("LDA ($aa),Y", " LDY #$E9\n" " LDX #$BB\n" " STX $403A\n" " LDX #$51\n" " STX $00A4\n" " LDX #$3F\n" " STX $00A5\n" " LDA ($A4),Y\n", 0xBB, N, 0); _run_this("LDA ($aa)", " LDX #$AA\n" " STX $3F51\n" " LDX #$51\n" " STX $00A4\n" " LDX #$3F\n" " STX $00A5\n" " LDA ($A4)\n", 0xAA, N, 0); _run_this("SED ADC 0", doSED_ADC(0, 0), 0x0, Z | D, 0); _run_this("SED ADC 1", doSED_ADC(1, 0), 0x01, D, 0); _run_this("SED ADC 9", doSED_ADC(9, 0), 0x09, D, 0); _run_this("SED ADC 10", doSED_ADC(0x10, 0), 0x10, D, 0); _run_this("SED ADC 1D", doSED_ADC(0x1D, 0), 0x23, D, 0); _run_this("SED ADC 99", doSED_ADC(0x99, 0), 0x99, N | D, 0); _run_this("SED ADC 99", doSED_ADC(0x99, 1), 0x00, C | D, 0); _run_this("SED ADC BD", doSED_ADC(0xBD, 0), 0x23, C | D, 0); #if 0 // _run_this("SED ADC FF", doSED_ADC(0xFF, 0), 0x65, C | D | N, 0); _run_this("SED ADC 0,1", doSED_ADC(0, 1), 0x01, D, 0); _run_this("SED ADC 0,9", doSED_ADC(0, 9), 0x09, D, 0); _run_this("SED ADC 0,10", doSED_ADC(0, 0x10), 0x10, D, 0); _run_this("SED ADC 0,1D", doSED_ADC(0, 0x1D), 0x23, D, 0); _run_this("SED ADC 0,99", doSED_ADC(0, 0x99), 0x99, N | D, 0); _run_this("SED ADC 0,BD", doSED_ADC(0, 0xBD), 0x23, C | D, 0); // _run_this("SED ADC 0,FF", doSED_ADC(0, 0xFF), 0x65, C | D, 0); _run_this("SED ADC 99,1", doSED_ADC(0x99, 1), 0x0, Z | C | D, 0); _run_this("SED ADC 35,35", doSED_ADC(0x35, 0x35), 0x70, D, 0); _run_this("SED ADC 45,45", doSED_ADC(0x45, 0x45), 0x90, N | V | D, 0); _run_this("SED ADC 50,50", doSED_ADC(0x50, 0x50), 0x0, V | Z | C | D, 0); _run_this("SED ADC 99,99", doSED_ADC(0x99, 0x99), 0x98, N | V | C | D, 0); // _run_this("SED ADC B1,C1", doSED_ADC(0xB1, 0xC1), 0xD2, N | V | C | D, 0); #endif // create an emulator, load the binary file 6502_functional_test.bin at $0000 // and run it until we hit a BRK const char *bigtest[] = { // "test/asm/6502_functional_test.bin", // "test/asm/65C02_extended_opcodes_test.bin", NULL, }; for (int ti = 0; bigtest[ti]; ti++) { FILE *f = fopen(bigtest[ti], "r"); if (!f) { fprintf(stderr, "Cannot open %s\n", bigtest[ti]); continue; } printf("TEST %s\n", bigtest[ti]); uint8_t *ram = calloc(1, 0x10000); fread(ram + 0x000, 1, 0x10000 - 0x400, f); fclose(f); mii_cpu_t cpu = {0}; mii_cpu_state_t s = {0}; cpu.ram = ram; // cpu.debug = _run_one_dump_state; cpu.P.P[B_X] = 1; cpu.P.P[B_I] = 1; cpu.S = 0xFF; cpu.PC = 0x400; cpu.A = 0x00; int count = 100000000; int same_pc_count = 0; uint16_t prev_pc = 0; do { s = mii_cpu_run(&cpu, s); if (s.w) ram[s.addr] = s.data; else s.data = ram[s.addr]; if (s.sync && !s.w) { if (cpu.PC == prev_pc) { same_pc_count++; if (same_pc_count > 3) { printf("TEST %s: FAIL (stuck at %04X)\n", bigtest[ti], cpu.PC); _run_one_dump_state(&cpu, s, NULL); printf(" Failed instruction is %c%c%c\n", cpu.A, cpu.X, cpu.Y); break; } } else { same_pc_count = 0; prev_pc = cpu.PC; } } // if (s.sync) _run_one_dump_state(&cpu, s, NULL); } while (count--); printf("TEST run with %d spare\n", count); _run_one_dump_state(&cpu, s, NULL); if (!count) { printf("TEST %s: FAIL (out of _run_this cycles!)\n", bigtest[ti]); _run_one_dump_state(&cpu, s, NULL); } free(ram); } }