From: Nick Downing Date: Tue, 19 Jul 2022 14:04:33 +0000 (+1000) Subject: Can now pass Klaus Dormann's 6502 tests (not 65C02 yet) X-Git-Url: https://git.ndcode.org/public/gitweb.cgi?a=commitdiff_plain;h=cd5b35df02d573bdfb3d28d7e80620ac326420b9;p=multi_emu.git Can now pass Klaus Dormann's 6502 tests (not 65C02 yet) --- diff --git a/.gitignore b/.gitignore index 7575aa1..6e6fa2b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ +*.ihx *.o /emu_65c02 +/emu_65c02_alt diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..c444d04 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "6502_65C02_functional_tests"] + path = 6502_65C02_functional_tests + url = https://github.com/Klaus2m5/6502_65C02_functional_tests.git +[submodule "vrEmu6502"] + path = vrEmu6502 + url = https://github.com/nickd4/vrEmu6502.git diff --git a/6502_65C02_functional_tests b/6502_65C02_functional_tests new file mode 160000 index 0000000..7954e2d --- /dev/null +++ b/6502_65C02_functional_tests @@ -0,0 +1 @@ +Subproject commit 7954e2dbb49c469ea286070bf46cdd71aeb29e4b diff --git a/Makefile b/Makefile index b8b739a..6c03c95 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,13 @@ +BIN2HEX=bin2hex.py + CFLAGS=-g -Wall -Wno-attributes .PHONY: all -all: emu_65c02 +all: \ +emu_65c02 \ +emu_65c02_alt \ +6502_functional_test.ihx \ +65C02_extended_opcodes_test.ihx emu_65c02: emu_65c02.o cpu_65c02.o ${CC} ${CFLAGS} -o $@ $^ @@ -10,6 +16,27 @@ emu_65c02.o: cpu_65c02.h cpu_65c02.o: cpu_65c02.h +emu_65c02_alt: emu_65c02_alt.o vrEmu6502.o + ${CC} ${CFLAGS} -o $@ $^ + +emu_65c02_alt.o: emu_65c02.c vrEmu6502/src/vrEmu6502.h + ${CC} ${CFLAGS} -DVREMU6502=1 -DVR_6502_EMU_STATIC=1 -o $@ -c $< + +vrEmu6502.o: vrEmu6502/src/vrEmu6502.c vrEmu6502/src/vrEmu6502.h + ${CC} ${CFLAGS} -DVR_6502_EMU_STATIC=1 -c $< + +6502_functional_test.ihx: \ +6502_65C02_functional_tests/bin_files/6502_functional_test.bin + ${BIN2HEX} $< __temp__.ihx + ./entry_point.py 0x400 __temp__.ihx $@ + rm __temp__.ihx + +65C02_extended_opcodes_test.ihx: \ +6502_65C02_functional_tests/bin_files/65C02_extended_opcodes_test.bin + ${BIN2HEX} $< __temp__.ihx + ./entry_point.py 0x400 __temp__.ihx $@ + rm __temp__.ihx + .PHONY: clean clean: - rm -f *.o emu_65c02 + rm -f *.ihx *.o emu_65c02 diff --git a/cpu_65c02.c b/cpu_65c02.c index f1ea10b..8dabd59 100644 --- a/cpu_65c02.c +++ b/cpu_65c02.c @@ -1,6 +1,10 @@ #include #include "cpu_65c02.h" +#define NMI_VECTOR 0xfffa +#define RESET_VECTOR 0xfffc +#define IRQ_VECTOR 0xfffe + // gcc specific #define INLINE __attribute__((always_inline)) @@ -105,9 +109,9 @@ #define INY() cpu_65c02_inc(self, EA_Y) #define JMP(lvalue) cpu_65c02_jmp(self, (lvalue)) #define JSR(lvalue) cpu_65c02_jsr(self, (lvalue)) -#define LDA(rvalue) cpu_65c02_t(self, (rvalue), EA_A) -#define LDX(rvalue) cpu_65c02_t(self, (rvalue), EA_X) -#define LDY(rvalue) cpu_65c02_t(self, (rvalue), EA_Y) +#define LDA(rvalue) cpu_65c02_ld(self, EA_A, (rvalue)) +#define LDX(rvalue) cpu_65c02_ld(self, EA_X, (rvalue)) +#define LDY(rvalue) cpu_65c02_ld(self, EA_Y, (rvalue)) #define LSR(lvalue) cpu_65c02_lsr(self, (lvalue)) #define NOP() cpu_65c02_nop(self) #define ORA(rvalue) cpu_65c02_ora(self, (rvalue)) @@ -116,7 +120,9 @@ #define PHX() cpu_65c02_ph(self, X) #define PHY() cpu_65c02_ph(self, Y) #define PLA() cpu_65c02_pl(self, EA_A) -#define PLP() cpu_65c02_pl(self, EA_P) +// note: cpu_65c02_plp() is like cpu_65c02_pl(), but does not set Z/N flags, +// and forces unimplemented flags to 1 (need to ensure they stay 1 for php) +#define PLP() cpu_65c02_plp(self) #define PLX() cpu_65c02_pl(self, EA_X) #define PLY() cpu_65c02_pl(self, EA_Y) #define RMB0(lvalue) cpu_65c02_rmb(self, 1, (lvalue)) @@ -131,7 +137,7 @@ #define ROR(lvalue) cpu_65c02_ror(self, (lvalue)) #define RTI() cpu_65c02_rti(self) #define RTS() cpu_65c02_rts(self) -#define SBC(rvalue) cpu_65c02_adc(self, (rvalue) ^ 0xff) +#define SBC(rvalue) cpu_65c02_sbc(self, (rvalue)) #define SEC() cpu_65c02_se(self, FLAG_C) #define SED() cpu_65c02_se(self, FLAG_D) #define SEI() cpu_65c02_se(self, FLAG_I) @@ -143,19 +149,19 @@ #define SMB5(lvalue) cpu_65c02_smb(self, 0x20, (lvalue)) #define SMB6(lvalue) cpu_65c02_smb(self, 0x40, (lvalue)) #define SMB7(lvalue) cpu_65c02_smb(self, 0x80, (lvalue)) -// note: cpu_65c02_st() is like cpu_65c02_t() but no flags #define STA(lvalue) cpu_65c02_st(self, A, (lvalue)) #define STX(lvalue) cpu_65c02_st(self, X, (lvalue)) #define STY(lvalue) cpu_65c02_st(self, Y, (lvalue)) #define STZ(lvalue) cpu_65c02_st(self, 0, (lvalue)) -#define TAX() cpu_65c02_t(self, A, EA_X) -#define TAY() cpu_65c02_t(self, A, EA_Y) +#define TAX() cpu_65c02_ld(self, EA_X, A) +#define TAY() cpu_65c02_ld(self, EA_Y, A) #define TRB(lvalue) cpu_65c02_trb(self, (lvalue)) #define TSB(lvalue) cpu_65c02_tsb(self, (lvalue)) -#define TSX() cpu_65c02_t(self, S, EA_X) -#define TXA() cpu_65c02_t(self, X, EA_A) -#define TXS() cpu_65c02_t(self, X, EA_S) -#define TYA() cpu_65c02_t(self, Y, EA_A) +#define TSX() cpu_65c02_ld(self, EA_X, S) +#define TXA() cpu_65c02_ld(self, EA_A, X) +// TXS is the only transfer instruction that does not set flags +#define TXS() cpu_65c02_st(self, X, EA_S) +#define TYA() cpu_65c02_ld(self, EA_A, Y) #define WAI() cpu_65c02_wai(self) // memory (or internal register memory) access @@ -240,11 +246,11 @@ INLINE int cpu_65c02_ea_abs_ind(struct cpu_65c02 *self) { } INLINE int cpu_65c02_ea_rel(struct cpu_65c02 *self) { + int offset = FB(); + offset -= (offset << 1) & 0x100; // sign extend int addr = PC; - int rvalue = FB(); - rvalue -= (rvalue << 1) & 0x100; // sign extend - self->cycles += ((addr & 0xff) + (rvalue & 0xff)) >> 8; - return addr + rvalue; + self->cycles += ((addr & 0xff) + (offset & 0xff)) >> 8; + return addr + offset; } INLINE int cpu_65c02_ea_zpg(struct cpu_65c02 *self) { @@ -271,17 +277,29 @@ INLINE int cpu_65c02_ea_zpg_ind_idx(struct cpu_65c02 *self, int rvalue) { // instruction execute INLINE void cpu_65c02_adc(struct cpu_65c02 *self, int rvalue) { - if (P & FLAG_D) - abort(); - int partial = (A & 0x7f) + (rvalue & 0x7f) + (P & FLAG_C); - int result = A + rvalue + (P & FLAG_C); - A = result & 0xff; + int result0, result1, result2; + if (P & FLAG_D) { + result0 = (A & 0xf) + (rvalue & 0xf) + (P & FLAG_C); + if (result0 >= 0xa) + result0 += 6; + result0 += (A & 0x70) + (rvalue & 0x70); + result1 = (A & 0x80) + (rvalue & 0x80) + result0; + result2 = result1; + if (result2 >= 0xa0) + result2 += 0x60; + } + else { + result0 = (A & 0x7f) + (rvalue & 0x7f) + (P & FLAG_C); + result1 = (A & 0x80) + (rvalue & 0x80) + result0; + result2 = result1; + } + A = result2 & 0xff; P = (P & ~(FLAG_C | FLAG_Z | FLAG_V | FLAG_N)) | - (result >> 8) | // C - (((result & 0xff) == 0) << 1) | // Z - (((partial >> 1) ^ (result >> 2)) & FLAG_V) | - (result & FLAG_N); + ((result2 >> 8) & FLAG_C) | // C + (((result2 & 0xff) == 0) << 1) | // Z + (((result0 >> 1) ^ (result1 >> 2)) & FLAG_V) | + (result2 & FLAG_N); } INLINE void cpu_65c02_and(struct cpu_65c02 *self, int rvalue) { @@ -323,7 +341,11 @@ INLINE void cpu_65c02_bit(struct cpu_65c02 *self, int rvalue) { } INLINE void cpu_65c02_brk(struct cpu_65c02 *self) { - abort(); + ++self->cycles; // operand is ignored + PHW((PC + 1) & 0xffff); + PHB(P); // already has break flag set + PC = RW(IRQ_VECTOR); + P = (P & ~FLAG_D) | FLAG_I; } INLINE void cpu_65c02_cl(struct cpu_65c02 *self, int flag) { @@ -332,13 +354,11 @@ INLINE void cpu_65c02_cl(struct cpu_65c02 *self, int flag) { INLINE void cpu_65c02_cmp(struct cpu_65c02 *self, int rvalue0, int rvalue1) { rvalue1 ^= 0xff; - int partial = (rvalue0 & 0x7f) + (rvalue1 & 0x7f); - int result = rvalue0 + rvalue1; + int result = rvalue0 + rvalue1 + 1; P = - (P & ~(FLAG_C | FLAG_Z | FLAG_V | FLAG_N)) | + (P & ~(FLAG_C | FLAG_Z | FLAG_N)) | (result >> 8) | // C (((result & 0xff) == 0) << 1) | // Z - (((partial >> 1) ^ (result >> 2)) & FLAG_V) | (result & FLAG_N); } @@ -384,6 +404,14 @@ INLINE void cpu_65c02_jsr(struct cpu_65c02 *self, int lvalue) { PC = lvalue; } +INLINE void cpu_65c02_ld(struct cpu_65c02 *self, int lvalue, int rvalue) { + WB(lvalue, rvalue); + P = + (P & ~(FLAG_Z | FLAG_N)) | + ((rvalue == 0) << 1) | // Z + (rvalue & FLAG_N); +} + INLINE void cpu_65c02_lsr(struct cpu_65c02 *self, int lvalue) { int result = RB(lvalue); ++self->cycles; @@ -411,7 +439,16 @@ INLINE void cpu_65c02_ph(struct cpu_65c02 *self, int rvalue) { } INLINE void cpu_65c02_pl(struct cpu_65c02 *self, int lvalue) { - WB(lvalue, PLB()); + int result = PLB(); + WB(lvalue, result); + P = + (P & ~(FLAG_Z | FLAG_N)) | + ((result == 0) << 1) | // Z + (result & FLAG_N); +} + +INLINE void cpu_65c02_plp(struct cpu_65c02 *self) { + P = PLB() | 0x30; } INLINE void cpu_65c02_rmb(struct cpu_65c02 *self, int n, int lvalue) { @@ -441,13 +478,41 @@ INLINE void cpu_65c02_ror(struct cpu_65c02 *self, int lvalue) { } INLINE void cpu_65c02_rti(struct cpu_65c02 *self) { - abort(); + P = PLB() | 0x30; + PC = PLW(); } INLINE void cpu_65c02_rts(struct cpu_65c02 *self) { PC = (PLW() + 1) & 0xffff; } +INLINE void cpu_65c02_sbc(struct cpu_65c02 *self, int rvalue) { + rvalue ^= 0xff; + int result0, result1, result2; + if (P & FLAG_D) { + result0 = (A & 0xf) + (rvalue & 0xf) + (P & FLAG_C); + if (result0 < 0x10) + result0 -= 6; + result0 += (A & 0x70) + (rvalue & 0x70); + result1 = (A & 0x80) + (rvalue & 0x80) + result0; + result2 = result1; + if (result2 < 0x100) + result2 -= 0x60; + } + else { + result0 = (A & 0x7f) + (rvalue & 0x7f) + (P & FLAG_C); + result1 = (A & 0x80) + (rvalue & 0x80) + result0; + result2 = result1; + } + A = result2 & 0xff; + P = + (P & ~(FLAG_C | FLAG_Z | FLAG_V | FLAG_N)) | + ((result2 >> 8) & FLAG_C) | // C + (((result2 & 0xff) == 0) << 1) | // Z + (((result0 >> 1) ^ (result1 >> 2)) & FLAG_V) | + (result2 & FLAG_N); +} + INLINE void cpu_65c02_se(struct cpu_65c02 *self, int flag) { P |= flag; } @@ -460,14 +525,6 @@ INLINE void cpu_65c02_st(struct cpu_65c02 *self, int rvalue, int lvalue) { WB(lvalue, rvalue); } -INLINE void cpu_65c02_t(struct cpu_65c02 *self, int rvalue, int lvalue) { - WB(lvalue, rvalue); - P = - (P & ~(FLAG_Z | FLAG_N)) | - ((rvalue == 0) << 1) | // Z - (rvalue & FLAG_N); -} - INLINE void cpu_65c02_trb(struct cpu_65c02 *self, int lvalue) { abort(); } diff --git a/emu_65c02.c b/emu_65c02.c index 5cb27f8..f188ac1 100644 --- a/emu_65c02.c +++ b/emu_65c02.c @@ -3,7 +3,14 @@ #include #include #include +#if VREMU6502 +#include "vrEmu6502/src/vrEmu6502.h" +#else #include "cpu_65c02.h" +#endif + +#define REG_TRACE 1 +#define MEM_TRACE 1 #define MEM_SIZE 0x10000 uint8_t mem[MEM_SIZE]; @@ -125,27 +132,112 @@ int load_ihx(char *name) { } int read_byte(int addr) { +#if MEM_TRACE + int data = mem[addr]; + printf("addr=%04x rd=%02x\n", addr, data); + return data; +#else return mem[addr]; +#endif } void write_byte(int addr, int data) { +#if MEM_TRACE + printf("addr=%04x wr=%02x\n", addr, data); +#endif mem[addr] = data; } +#if VREMU6502 +uint8_t mem_read(uint16_t addr, bool isDbg) { + return read_byte(addr); +} + +void mem_write(uint16_t addr, uint8_t val) { + write_byte(addr, val); +} +#endif + int main(int argc, char **argv) { if (argc < 2) { fprintf(stderr, "usage: %s image.ihx\n", argv[0]); exit(EXIT_FAILURE); } - char *image_ihx = argv[1]; + int entry_point = load_ihx(argv[1]); + +#if VREMU6502 + mem[0xfffc] = (uint8_t)(entry_point & 0xff); + mem[0xfffd] = (uint8_t)(entry_point >> 8); + + VrEmu6502 *cpu = vrEmu6502New(CPU_65C02, mem_read, mem_write); + if (cpu == NULL) { + perror("malloc()"); + exit(EXIT_FAILURE); + } + while (true) { +#if REG_TRACE + printf( + "pc=%04x a=%02x x=%02x y=%02x s=%02x p=%02x c=%d z=%d i=%d d=%d v=%d n=%d\n", + vrEmu6502GetPC(cpu), + vrEmu6502GetAcc(cpu), + vrEmu6502GetX(cpu), + vrEmu6502GetY(cpu), + vrEmu6502GetStackPointer(cpu), + vrEmu6502GetStatus(cpu) | 0x30, + vrEmu6502GetStatus(cpu) & 1, + (vrEmu6502GetStatus(cpu) >> 1) & 1, + (vrEmu6502GetStatus(cpu) >> 2) & 1, + (vrEmu6502GetStatus(cpu) >> 3) & 1, + (vrEmu6502GetStatus(cpu) >> 6) & 1, + (vrEmu6502GetStatus(cpu) >> 7) & 1 + ); +#endif + + int pc = vrEmu6502GetPC(cpu); + int i; + vrEmu6502RunInstrs(cpu, 1, &i); + if (pc == vrEmu6502GetPC(cpu)) { + printf("hung at %04x\n", pc); + break; + } + } +#else struct cpu_65c02 cpu; memset(&cpu, 0, sizeof(struct cpu_65c02)); + cpu.pc = entry_point; cpu.regs[CPU_65C02_REG_S] = 0xff; - cpu.pc = load_ihx(image_ihx); + cpu.regs[CPU_65C02_REG_P] = 0x30; cpu.read_byte = read_byte; cpu.write_byte = write_byte; - while (true) + while (true) { +#if REG_TRACE + printf( + "pc=%04x a=%02x x=%02x y=%02x s=%02x p=%02x c=%d z=%d i=%d d=%d v=%d n=%d\n", + cpu.pc, + cpu.regs[CPU_65C02_REG_A], + cpu.regs[CPU_65C02_REG_X], + cpu.regs[CPU_65C02_REG_Y], + cpu.regs[CPU_65C02_REG_S], + cpu.regs[CPU_65C02_REG_P], + (cpu.regs[CPU_65C02_REG_P] >> CPU_65C02_BIT_C) & 1, + (cpu.regs[CPU_65C02_REG_P] >> CPU_65C02_BIT_Z) & 1, + (cpu.regs[CPU_65C02_REG_P] >> CPU_65C02_BIT_I) & 1, + (cpu.regs[CPU_65C02_REG_P] >> CPU_65C02_BIT_D) & 1, + (cpu.regs[CPU_65C02_REG_P] >> CPU_65C02_BIT_V) & 1, + (cpu.regs[CPU_65C02_REG_P] >> CPU_65C02_BIT_N) & 1 + ); +#endif + + int pc = cpu.pc; cpu_65c02_execute(&cpu); + if (pc == cpu.pc) { + printf("hung at %04x\n", pc); + break; + } + } +#endif + + return 0; } diff --git a/entry_point.py b/entry_point.py new file mode 100755 index 0000000..39f878e --- /dev/null +++ b/entry_point.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 + +import sys +from intelhex import IntelHex + +EXIT_SUCCESS = 0 +EXIT_FAILURE = 1 + +if len(sys.argv) < 4: + print(f'usage: {sys.argv[0]:s} entry_point in.ihx out.ihx') + sys.exit(EXIT_FAILURE) +entry_point = int(sys.argv[1], 0) +in_ihx = sys.argv[2] +out_ihx = sys.argv[3] + +intelhex = IntelHex(in_ihx) +segments = [j for i in intelhex.segments() for j in i] +for i in range(0, len(segments), 2): + print(f'[{segments[i]:04x}, {segments[i + 1]:04x})') +intelhex.start_addr = {'EIP': entry_point} +intelhex.write_hex_file(out_ihx) diff --git a/vrEmu6502 b/vrEmu6502 new file mode 160000 index 0000000..ba3ea52 --- /dev/null +++ b/vrEmu6502 @@ -0,0 +1 @@ +Subproject commit ba3ea522e088a000bfa830bb72f662ade804260d