6502

minimal 6502 emulator written in C
Index Commits Files Refs README LICENSE
commit c53c1a51abe04bcd6755a74e5e493d46af93578b
parent 8f82cdc161df05779893ea2414640c58ce1d08a2
Author: mjkloeckner <martin.cachari@gmail.com>
Date:   Wed, 13 Jul 2022 18:53:28 -0300

All addressing modes implemented

All the memory addressing modes are fully implemented, only left to
implement a few CPU instructions.

Also a new function was introduced, MEM_dump_last_six which as its name
suggest prints to console the last bytes of memory, those are important
since addresses that the cpu uses when an interrupt occurs or when it is
reset are stored there.

Diffstat:
M6502.c | 119++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
M6502.h | 10+++++++---
MREADME.md | 9+++++++++
Mmain.c | 6++----
4 files changed, 130 insertions(+), 14 deletions(-)
diff --git a/6502.c b/6502.c
@@ -1,7 +1,6 @@
 #include "6502.h"
 
 #include <stdio.h>
-#include <string.h>
 
 static CPU cpu;
 static MEM mem; /* 8 bit word */
@@ -77,10 +76,41 @@ void CPU_exec(INS ins) {
 
 /* interrupt request */
 void CPU_irq(void) {
+    if(!CPU_get_flag(I)) {
+        /* requires two writes since `sp` is 8bits */
+        MEM_write(0x100 + cpu.sp--, (cpu.pc >> 8) & 0x00FF);
+        MEM_write(0x100 + cpu.sp--, cpu.pc & 0x00FF);
+
+        CPU_set_flag(I, 1);
+        MEM_write(0x100 + cpu.sp--, cpu.st);
+
+        addr_abs = 0xFFFE;
+        uint16_t lo = MEM_read(addr_abs + 0);
+        uint16_t hi = MEM_read(addr_abs + 1);
+
+        cpu.pc = (hi << 8) | lo;
+
+        cyc = 7;
+    }
 }
 
 /* non maskeable interrupt request */
 void CPU_nm_irq(void) {
+    /* requires two writes since `sp` is 8bits */
+    MEM_write(0x100 + cpu.sp--, (cpu.pc >> 8) & 0x00FF);
+    MEM_write(0x100 + cpu.sp--, cpu.pc & 0x00FF);
+
+    CPU_set_flag(B, 0);
+    CPU_set_flag(I, 1);
+    MEM_write(0x100 + cpu.sp--, cpu.st);
+
+    addr_abs = 0xFFFA;
+    uint16_t lo = MEM_read(addr_abs + 0);
+    uint16_t hi = MEM_read(addr_abs + 1);
+
+    cpu.pc = (hi << 8) | lo;
+
+    cyc = 8;
 }
 
 void CPU_set_flag(ST_FLAG flag, uint8_t val) {
@@ -96,7 +126,6 @@ void print_reg(uint8_t reg) {
         putchar((reg & (0x01 << (i - 1))) ? '1' : '0');
         putchar(' ');
     }
-
     putchar('\n');
 }
 
@@ -108,7 +137,7 @@ void CPU_dump(void) {
     print_reg(cpu.st);
     printf("    y:   %02X (%3d)\n", cpu.y, cpu.y);
     printf("   sp:   %02X\n", cpu.sp);
-    printf("   pc: %04X -> %02X (%s)(%s)\n", cpu.pc, mem.ram[cpu.pc], aux.name, CPU_mode_name(aux.mode));
+    printf("   pc: %04X -> %02X (%s)(%s)\n\n", cpu.pc, mem.ram[cpu.pc], aux.name, CPU_mode_name(aux.mode));
 }
 
 /* Memory methods */
@@ -154,12 +183,20 @@ void MEM_dump_page(uint16_t page) {
     uint16_t j = 1;
 
     printf("Page %04X:\n", page);
-    for(uint16_t i = page; i < (page + 0x3C); i++, j++)
+    for(uint16_t i = page; i < (page + 0xFF); i++, j++)
         printf("%02X %s", mem.ram[i], (j % 0xF) ? "" : "\n");
 
     putchar('\n');
 }
 
+void MEM_dump_last_six(void) {
+    printf("Last six:\n");
+    for(size_t i = 0xFFFA; i <= 0xFFFF; i++)
+        printf("%02X ", mem.ram[i]);
+
+    putchar('\n');
+}
+
 char *CPU_mode_name(uint8_t (*mode)(void)) {
     if(mode == &IMM) return "IMM";
     else if(mode == &ABS) return "ABS";
@@ -187,11 +224,23 @@ uint8_t ABS(void) {
 }
 
 uint8_t ABX(void) {
-    return 0;
+    uint16_t lo = MEM_read(cpu.pc++);
+    uint16_t hi = MEM_read(cpu.pc++);
+
+    addr_abs = (hi << 8) | lo;
+    addr_abs += cpu.x;
+
+    return ((addr_abs & 0x00FF) != (hi << 8)) ? 1 : 0;
 }
 
 uint8_t ABY(void) {
-    return 0;
+    uint16_t lo = MEM_read(cpu.pc++);
+    uint16_t hi = MEM_read(cpu.pc++);
+
+    addr_abs = (hi << 8) | lo;
+    addr_abs += cpu.y;
+
+    return ((addr_abs & 0x00FF) != (hi << 8)) ? 1 : 0;
 }
 
 uint8_t IMM(void) {
@@ -205,15 +254,46 @@ uint8_t IMP(void) {
 }
 
 uint8_t IND(void) {
+    /* get a direction from memory */
+    uint16_t lo = MEM_read(cpu.pc++);
+    uint16_t hi = MEM_read(cpu.pc++);
+
+    /* assign the direction to ptr */
+    uint16_t ptr = (hi << 8) | lo;
+
+    /* get the address contained in the address stored in ptr
+     * and simulate page boundary hardware bug */
+    addr_abs = (MEM_read((ptr + (lo == 0x00FF) ? 1 : 0) & 0xFF00) << 8) | MEM_read(ptr);
+
     return 0;
 }
 
 uint8_t IZX(void) {
+    uint8_t addr = MEM_read(cpu.pc++);
+
+    /* get a direction from memory */
+    uint16_t lo = MEM_read((addr + cpu.x + 0) & 0xFF);
+    uint16_t hi = MEM_read((addr + cpu.x + 1) & 0xFF);
+
+    uint16_t ptr = (hi << 8) | lo;
+
+    /* get the direction contained on the memory that we read */
+    addr_abs = (MEM_read(ptr + 1) << 8) | MEM_read(ptr + 0);
+
     return 0;
 }
 
 uint8_t IZY(void) {
-    return 0;
+    uint8_t addr = MEM_read(cpu.pc++);
+
+    /* get a direction from memory */
+    uint16_t lo = MEM_read((addr + 0) & 0xFF);
+    uint16_t hi = MEM_read((addr + 1) & 0xFF);
+
+    addr_abs = (hi << 8) | lo;
+    addr_abs += cpu.y;
+
+    return ((addr_abs & 0xFF00) != (hi << 8)) ? 1 : 0;
 }
 
 uint8_t REL(void) {
@@ -225,15 +305,27 @@ uint8_t REL(void) {
     return 0;
 }
 
+/* read only the offset of the zero page */
 uint8_t ZP0(void) {
+    addr_abs = MEM_read(cpu.pc++);
+    addr_abs &= 0xFF;
+
     return 0;
 }
 
 uint8_t ZPX(void) {
+    addr_abs = MEM_read(cpu.pc + cpu.x);
+    addr_abs &= 0xFF;
+    cpu.pc++;
+
     return 0;
 }
 
 uint8_t ZPY(void) {
+    addr_abs = MEM_read(cpu.pc + cpu.y);
+    addr_abs &= 0xFF;
+    cpu.pc++;
+
     return 0;
 }
 
@@ -354,6 +446,10 @@ uint8_t BPL(void) {
 }
 
 uint8_t BRK(void) {
+    CPU_set_flag(B, 1);
+
+    printf("break arrived\n");
+    CPU_irq();
 
     return 0;
 }
@@ -563,6 +659,15 @@ uint8_t ROR(void) {
 }
 
 uint8_t RTI(void) {
+    /* restore cpu status register and increase stack pointer */
+    cpu.st = MEM_read(0x100 + ++cpu.sp);
+
+    /* cpu.st &= ~ST_FLAG_MASK[B]; */
+    CPU_set_flag(B, 0);
+
+    cpu.pc = MEM_read(0x100 + ++cpu.sp);
+    cpu.pc |= (MEM_read(0x100 + ++cpu.sp) << 8);
+
     return 0;
 }
 
diff --git a/6502.h b/6502.h
@@ -1,7 +1,6 @@
 #ifndef __6502_H__
 #define __6502_H__
 
-#include <stdio.h>
 #include <stdint.h>
 
 /* Instruction */
@@ -17,11 +16,15 @@ typedef struct {
     uint16_t pc;
 } CPU;
 
+
+/* zero-page: fast shorter instructions
+ * first-page: stack;
+ * last-six bytes: used to store special addresses */
 typedef struct {
-    uint8_t ram[0xFFFF]; /* 8 bit word */
+    uint8_t ram[0x10000]; /* 64kb memory */
 } MEM;
 
-/* 6502 status register */
+/* 6502 status register flags */
 typedef enum {
     C,    /* Carry */
     Z,    /* Zero  */
@@ -50,6 +53,7 @@ char *CPU_mode_name(uint8_t (*mode)(void));
 void MEM_init(void);
 void MEM_dump(void);
 void MEM_dump_page(uint16_t page);
+void MEM_dump_last_six(void);
 void MEM_load_from_file(char *fp);
 
 uint8_t MEM_read(uint16_t addr);
diff --git a/README.md b/README.md
@@ -3,6 +3,15 @@
 WARNING: not all the CPU instructions are implemented yet (too minimal lol)
 
 ## Resources used
+- W65C02S datasheet (newer 6502): <br>
+    https://eater.net/datasheets/w65c02s.pdf
+
+- 6502 Instruction Set: <br>
+    https://www.masswerk.at/6502/6502_instruction_set.html
+
+- 6502 Assembly: <br>
+    https://en.wikibooks.org/wiki/6502_Assembly
+
 - 27c3: Reverse Engineering the MOS 6502 CPU (en): <br>
     https://www.youtube.com/watch?v=fWqBmmPQP40&t=34s
 
diff --git a/main.c b/main.c
@@ -1,7 +1,6 @@
 #include "6502.h"
 
 #include <stdio.h>
-#include <stdbool.h>
 
 #define INPUT_FILE_PATH    "10times3.bin"
 
@@ -28,10 +27,9 @@ int main (void) {
 
         CPU_dump();
 
-        putchar('\n');
-
         MEM_dump_page(0x0000);
-        MEM_dump_page(0x8000);
+        MEM_dump_page(0x0100);
+        MEM_dump_last_six();
 
         /* Execute instruction */
         CPU_exec(ins);