Can now pass Klaus Dormann's 6502 tests (not 65C02 yet)
authorNick Downing <nick@ndcode.org>
Tue, 19 Jul 2022 14:04:33 +0000 (00:04 +1000)
committerNick Downing <nick@ndcode.org>
Tue, 19 Jul 2022 14:12:36 +0000 (00:12 +1000)
.gitignore
.gitmodules [new file with mode: 0644]
6502_65C02_functional_tests [new submodule]
Makefile
cpu_65c02.c
emu_65c02.c
entry_point.py [new file with mode: 0755]
vrEmu6502 [new submodule]

index 7575aa1..6e6fa2b 100644 (file)
@@ -1,2 +1,4 @@
+*.ihx
 *.o
 /emu_65c02
+/emu_65c02_alt
diff --git a/.gitmodules b/.gitmodules
new file mode 100644 (file)
index 0000000..c444d04
--- /dev/null
@@ -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 (submodule)
index 0000000..7954e2d
--- /dev/null
@@ -0,0 +1 @@
+Subproject commit 7954e2dbb49c469ea286070bf46cdd71aeb29e4b
index b8b739a..6c03c95 100644 (file)
--- 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
index f1ea10b..8dabd59 100644 (file)
@@ -1,6 +1,10 @@
 #include <stdlib.h>
 #include "cpu_65c02.h"
 
+#define NMI_VECTOR 0xfffa
+#define RESET_VECTOR 0xfffc
+#define IRQ_VECTOR 0xfffe
+
 // gcc specific
 #define INLINE __attribute__((always_inline))
 
 #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))
 #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))
 #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)
 #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();
 }
index 5cb27f8..f188ac1 100644 (file)
@@ -3,7 +3,14 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#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 (executable)
index 0000000..39f878e
--- /dev/null
@@ -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 (submodule)
index 0000000..ba3ea52
--- /dev/null
+++ b/vrEmu6502
@@ -0,0 +1 @@
+Subproject commit ba3ea522e088a000bfa830bb72f662ade804260d