6502

minimal 6502 emulator written in C
Index Commits Files Refs README LICENSE
commit 02cbabcf2599e7acd58c5a0d14a5c99743d6248f
parent c53c1a51abe04bcd6755a74e5e493d46af93578b
Author: mjkloeckner <martin.cachari@gmail.com>
Date:   Thu, 14 Jul 2022 01:59:05 -0300

All CPU adrressing modes and instruction fully implemented

CPU instructions and addressing modes are fully implemented.

The 6502_functional_test gets stuck at pc 0x05A4, so there is a bug in
there to solve for future commits.

The MEM_load_from_file function now loads the entire memory from the
provided file instead of only a part from address 0x8000 like before.

New function added: MEM_set_pc_start which sets the bytes that the pc
reads, when CPU_reset gets called, to the address passed as an argument.

Diffstat:
M6502.c | 205+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
M6502.h | 10+++++-----
Mmain.c | 18++++++++++++------
3 files changed, 191 insertions(+), 42 deletions(-)
diff --git a/6502.c b/6502.c
@@ -7,8 +7,8 @@ static MEM mem; /* 8 bit word */
 
 static uint8_t cyc;
 static uint8_t opc;
-static uint8_t tmp;
 
+static uint16_t   tmp;
 static uint16_t  addr_abs;
 static uint16_t  addr_rel;
 
@@ -40,6 +40,7 @@ void CPU_reset(void) {
             cpu.st = 0x00 | ST_FLAG_MASK[U];
 
             /* little endian */
+            printf("pc: 0x%4X%4X\n", MEM_read(0xFFFD), MEM_read(0xFFFC));
             uint16_t lo = MEM_read(0xFFFC);
             uint16_t hi = MEM_read(0xFFFD);
 
@@ -54,7 +55,7 @@ void CPU_reset(void) {
 }
 
 void CPU_fetch(INS *ins) {
-    *ins = decode[MEM_read(cpu.pc)];
+    *ins = decode[opc = MEM_read(cpu.pc)];
 }
 
 void CPU_exec(INS ins) {
@@ -78,11 +79,11 @@ void CPU_exec(INS ins) {
 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 & 0xFF00) >> 8);
         MEM_write(0x100 + cpu.sp--, cpu.pc & 0x00FF);
 
-        CPU_set_flag(I, 1);
         MEM_write(0x100 + cpu.sp--, cpu.st);
+        CPU_set_flag(I, 1);
 
         addr_abs = 0xFFFE;
         uint16_t lo = MEM_read(addr_abs + 0);
@@ -144,12 +145,15 @@ void CPU_dump(void) {
 void MEM_init(void) {
     for(uint16_t i = 0; i < 0xFFFF; i++)
         mem.ram[i] = 0;
+}
 
+void MEM_set_pc_start(uint16_t addr) {
     /* First address that pc reads */
-    mem.ram[0xFFFC] = 0x00;
-    mem.ram[0xFFFD] = 0x80;
+    mem.ram[0xFFFC] = (addr & 0x00FF);
+    mem.ram[0xFFFD] = ((addr & 0xFF00) >> 8);
 }
 
+
 void MEM_load_from_file(char *path) {
     FILE *fp;
 
@@ -158,7 +162,10 @@ void MEM_load_from_file(char *path) {
     fp = fopen(path, "rb");
     if(fp == NULL) return;
 
-    fread(mem.ram + 0x8000, 1, 0x7FFF - 0x6, fp);
+    printf("Loading %ld bytes from file `%s`\n", sizeof(mem.ram), path);
+    fread(mem.ram, 1, sizeof(mem.ram), fp);
+    printf("Loading done\n");
+    /* fread(mem.ram + 0x8000, 1, 0x7FFF - 0x6, fp); */
 
     fclose(fp);
 }
@@ -184,7 +191,7 @@ void MEM_dump_page(uint16_t page) {
 
     printf("Page %04X:\n", page);
     for(uint16_t i = page; i < (page + 0xFF); i++, j++)
-        printf("%02X %s", mem.ram[i], (j % 0xF) ? "" : "\n");
+        printf("%02X %s", mem.ram[i], (j % 0x1E) ? "" : "\n");
 
     putchar('\n');
 }
@@ -261,7 +268,7 @@ uint8_t IND(void) {
     /* assign the direction to ptr */
     uint16_t ptr = (hi << 8) | lo;
 
-    /* get the address contained in the address stored in ptr
+    /* get the address pointed by ptr
      * and simulate page boundary hardware bug */
     addr_abs = (MEM_read((ptr + (lo == 0x00FF) ? 1 : 0) & 0xFF00) << 8) | MEM_read(ptr);
 
@@ -332,13 +339,15 @@ uint8_t ZPY(void) {
 
 /* Instructions */
 uint8_t ADC(void) {
-    uint8_t data = MEM_read(addr_abs);
+    uint16_t data = MEM_read(addr_abs);
+    uint16_t res = (cpu.ac + data + (uint16_t)CPU_get_flag(C));
 
-    cpu.ac += (data + CPU_get_flag(C));
+    CPU_set_flag(N, res & (1 << 7));
+    CPU_set_flag(Z, (res & 0x00FF) == 0);
+    CPU_set_flag(C, ((res & 0xFF00) >> 7));
+    CPU_set_flag(V, ((cpu.ac ^ res) & ~(res ^ data)) & (1 << 7));
 
-    CPU_set_flag(Z, cpu.ac == 0);
-    CPU_set_flag(N, cpu.ac & (1 << 7));
-    CPU_set_flag(V, ((cpu.ac^data) & ~(cpu.ac^data)) & (1 << 7));
+    cpu.ac = (uint8_t)(res & 0x00FF);
 
     return 1;
 }
@@ -353,16 +362,27 @@ uint8_t AND(void) {
 }
 
 uint8_t ASL(void) {
+    tmp = (cpu.ac <<= 1);
+
+    CPU_set_flag(C, (tmp & (1 << 8)) != 0);
+    CPU_set_flag(Z, tmp == 0);
+    CPU_set_flag(N, tmp & (1 << 7));
+
+    if(decode[opc].mode == &IMP)
+        cpu.ac = (tmp & 0x00FF);
+    else
+        MEM_write(addr_abs, tmp & 0x00FF);
 
     return 0;
 }
 
 uint8_t BCC(void) {
     if(!CPU_get_flag(C)) {
+        cyc++;
         addr_abs = cpu.pc + addr_rel;
 
         /* if address changes page */
-        if((addr_abs & (1 << 7)) != (cpu.pc & (1 << 7)))
+        if((addr_abs & 0xFF00) != (cpu.pc & 0xFF00))
             cyc++;
 
         cpu.pc = addr_abs;
@@ -373,10 +393,11 @@ uint8_t BCC(void) {
 
 uint8_t BCS(void) {
     if(CPU_get_flag(C)) {
+        cyc++;
         addr_abs = cpu.pc + addr_rel;
 
         /* if address changes page */
-        if((addr_abs & (1 << 7)) != (cpu.pc & (1 << 7)))
+        if((addr_abs & 0xFF00) != (cpu.pc & 0xFF00))
             cyc++;
 
         cpu.pc = addr_abs;
@@ -387,10 +408,11 @@ uint8_t BCS(void) {
 
 uint8_t BEQ(void) {
     if(CPU_get_flag(Z)) {
+        cyc++;
         addr_abs = cpu.pc + addr_rel;
 
         /* if address changes page */
-        if((addr_abs & (1 << 7)) != (cpu.pc & (1 << 7)))
+        if((addr_abs & 0xFF00) != (cpu.pc & 0xFF00))
             cyc++;
 
         cpu.pc = addr_abs;
@@ -400,15 +422,22 @@ uint8_t BEQ(void) {
 }
 
 uint8_t BIT(void) {
+    tmp = cpu.ac & MEM_read(cpu.pc);
+
+    CPU_set_flag(Z, tmp == 0);
+    CPU_set_flag(N, tmp & (1 << 7));
+    CPU_set_flag(V, tmp & (1 << 6));
+
     return 0;
 }
 
 uint8_t BMI(void) {
     if(CPU_get_flag(N)) {
+        cyc++;
         addr_abs = cpu.pc + addr_rel;
 
         /* if address changes page */
-        if((addr_abs & (1 << 7)) != (cpu.pc & (1 << 7)))
+        if((addr_abs & 0xFF00) != (cpu.pc & 0xFF00))
             cyc++;
 
         cpu.pc = addr_abs;
@@ -419,10 +448,11 @@ uint8_t BMI(void) {
 
 uint8_t BNE(void) {
     if(!CPU_get_flag(Z)) {
+        cyc++;
         addr_abs = cpu.pc + addr_rel;
 
         /* if address changes page */
-        if((addr_abs & (1 << 7)) != (cpu.pc & (1 << 7)))
+        if((addr_abs & 0xFF00) != (cpu.pc & 0xFF00))
             cyc++;
 
         cpu.pc = addr_abs;
@@ -432,11 +462,12 @@ uint8_t BNE(void) {
 }
 
 uint8_t BPL(void) {
-    if(!CPU_get_flag(N)) {
+    if(CPU_get_flag(N) == 0) {
+        cyc++;
         addr_abs = cpu.pc + addr_rel;
 
         /* if address changes page */
-        if((addr_abs & (1 << 7)) != (cpu.pc & (1 << 7)))
+        if((addr_abs & 0xFF00) != (cpu.pc & 0xFF00))
             cyc++;
 
         cpu.pc = addr_abs;
@@ -446,20 +477,20 @@ uint8_t BPL(void) {
 }
 
 uint8_t BRK(void) {
-    CPU_set_flag(B, 1);
-
-    printf("break arrived\n");
     CPU_irq();
 
+    CPU_set_flag(B, 1);
+
     return 0;
 }
 
 uint8_t BVC(void) {
     if(!CPU_get_flag(V)) {
+        cyc++;
         addr_abs = cpu.pc + addr_rel;
 
         /* if address changes page */
-        if((addr_abs & (1 << 7)) != (cpu.pc & (1 << 7)))
+        if((addr_abs & 0xFF00) != (cpu.pc & 0xFF00))
             cyc++;
 
         cpu.pc = addr_abs;
@@ -470,10 +501,11 @@ uint8_t BVC(void) {
 
 uint8_t BVS(void) {
     if(CPU_get_flag(V)) {
+        cyc++;
         addr_abs = cpu.pc + addr_rel;
 
         /* if address changes page */
-        if((addr_abs & (1 << 7)) != (cpu.pc & (1 << 7)))
+        if((addr_abs & 0xFF00) != (cpu.pc & 0xFF00))
             cyc++;
 
         cpu.pc = addr_abs;
@@ -503,19 +535,37 @@ uint8_t CLV(void) {
 }
 
 uint8_t CMP(void) {
+    tmp = MEM_read(addr_abs);
+
+    CPU_set_flag(C, cpu.ac >= tmp);
+    CPU_set_flag(Z, cpu.ac == tmp);
+    CPU_set_flag(N, cpu.ac & (1<<7));
+
     return 0;
 }
 
 uint8_t CPX(void) {
+    tmp = MEM_read(addr_abs);
+
+    CPU_set_flag(C, cpu.x >= tmp);
+    CPU_set_flag(Z, cpu.x == tmp);
+    CPU_set_flag(N, cpu.x & (1<<7));
+
     return 0;
 }
 
 uint8_t CPY(void) {
+    tmp = MEM_read(addr_abs);
+
+    CPU_set_flag(C, cpu.y >= tmp);
+    CPU_set_flag(Z, cpu.y == tmp);
+    CPU_set_flag(N, cpu.y & (1<<7));
+
     return 0;
 }
 
 uint8_t DEC(void) {
-    tmp = (MEM_read(addr_abs) - 1) & 0x00FF;
+    tmp = (MEM_read(addr_abs) - 1) & 0xFF;
     MEM_write(addr_abs, tmp);
 
     CPU_set_flag(Z, tmp == 0);
@@ -583,10 +633,18 @@ uint8_t INY(void) {
 }
 
 uint8_t JMP(void) {
+    cpu.pc = addr_abs;
     return 0;
 }
 
 uint8_t JSR(void) {
+    cpu.pc--;
+
+    MEM_write(0x100 + cpu.sp--, (cpu.pc & 0xFF00) >> 8);
+    MEM_write(0x100 + cpu.sp--, (cpu.pc & 0x00FF));
+
+    cpu.pc = addr_abs;
+
     return 0;
 }
 
@@ -618,6 +676,17 @@ uint8_t LDY(void) {
 }
 
 uint8_t LSR(void) {
+    tmp = (cpu.ac >>= 1);
+
+    CPU_set_flag(C, (tmp & (1 << 8)) != 0);
+    CPU_set_flag(Z, tmp == 0);
+    CPU_set_flag(N, tmp & (1 << 7));
+
+    if(decode[opc].mode == &IMP)
+        cpu.ac = (tmp & 0x00FF);
+    else
+        MEM_write(addr_abs, tmp & 0x00FF);
+
     return 0;
 }
 
@@ -635,26 +704,61 @@ uint8_t ORA(void) {
 }
 
 uint8_t PHA(void) {
+    MEM_write(0x100 + cpu.sp--, cpu.ac);
+
     return 0;
 }
 
 uint8_t PHP(void) {
+    MEM_write(0x100 + cpu.sp--, cpu.st);
+
     return 0;
 }
 
 uint8_t PLA(void) {
+    cpu.ac = MEM_read(0x100 + ++cpu.sp);
+
+    CPU_set_flag(Z, (cpu.ac == 0));
+    CPU_set_flag(N, cpu.ac & (1 << 7));
+
     return 0;
 }
 
 uint8_t PLP(void) {
+    cpu.st = MEM_read(0x100 + ++cpu.sp);
+
     return 0;
 }
 
 uint8_t ROL(void) {
+    tmp = (cpu.ac <<= 1);
+    tmp = (tmp & 0xFF00) ? (tmp | 0x01) : (tmp & ~(0x01));
+
+    CPU_set_flag(Z, tmp == 0);
+    CPU_set_flag(N, tmp & (1 << 7));
+    CPU_set_flag(C, (tmp & (1 << 8)) != 0);
+
+    if(decode[opc].mode == &IMP)
+        cpu.ac = (tmp & 0x00FF);
+    else
+        MEM_write(addr_abs, tmp & 0x00FF);
+
     return 0;
 }
 
 uint8_t ROR(void) {
+    tmp = (cpu.ac <<= 1);
+    tmp = (tmp & 0xFF00) ? (tmp | 0x01) : (tmp & ~(0x01));
+
+    CPU_set_flag(Z, tmp == 0);
+    CPU_set_flag(N, tmp & (1 << 7));
+    CPU_set_flag(C, (tmp & (1 << 8)) != 0);
+
+    if(decode[opc].mode == &IMP)
+        cpu.ac = (tmp & 0x00FF);
+    else
+        MEM_write(addr_abs, tmp & 0x00FF);
+
     return 0;
 }
 
@@ -662,9 +766,6 @@ 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);
 
@@ -672,11 +773,26 @@ uint8_t RTI(void) {
 }
 
 uint8_t RTS(void) {
+    cpu.pc = MEM_read(0x100 + ++cpu.sp);
+    cpu.pc |= (MEM_read(0x100 + ++cpu.sp) << 8);
+
+    cpu.pc++;
+
     return 0;
 }
 
 uint8_t SBC(void) {
-    return 0;
+    uint8_t data = MEM_read(addr_abs) ^ 0xFF;
+    uint16_t res = (cpu.ac + data + (uint16_t)CPU_get_flag(C));
+
+    CPU_set_flag(Z, cpu.ac == 0);
+    CPU_set_flag(N, cpu.ac & (1 << 7));
+    CPU_set_flag(C, ((res & 0xFF00) >> 7));
+    CPU_set_flag(V, ((cpu.ac^data) & ~(cpu.ac^data)) & (1 << 7));
+
+    cpu.ac = (uint8_t)(res & 0x00FF);
+
+    return 1;
 }
 
 uint8_t SEC(void) {
@@ -710,26 +826,53 @@ uint8_t STY(void) {
 }
 
 uint8_t TAX(void) {
+    cpu.x = cpu.ac;
+
+    CPU_set_flag(Z, cpu.x == 0);
+    CPU_set_flag(N, cpu.x & (1 << 7));
+
     return 0;
 }
 
 uint8_t TAY(void) {
+    cpu.y = cpu.ac;
+
+    CPU_set_flag(Z, cpu.y == 0);
+    CPU_set_flag(N, cpu.y & (1 << 7));
+
     return 0;
 }
 
 uint8_t TSX(void) {
+    cpu.x = cpu.sp;
+
+    CPU_set_flag(Z, cpu.x == 0);
+    CPU_set_flag(N, cpu.x & (1 << 7));
+
     return 0;
 }
 
 uint8_t TXA(void) {
+    cpu.ac = cpu.x;
+
+    CPU_set_flag(Z, cpu.ac == 0);
+    CPU_set_flag(N, cpu.ac & (1 << 7));
+
     return 0;
 }
 
 uint8_t TXS(void) {
+    cpu.sp = cpu.x;
+
     return 0;
 }
 
 uint8_t TYA(void) {
+    cpu.ac = cpu.y;
+
+    CPU_set_flag(Z, cpu.y == 0);
+    CPU_set_flag(N, cpu.y & (1 << 7));
+
     return 0;
 }
 
diff --git a/6502.h b/6502.h
@@ -40,25 +40,25 @@ void CPU_init(void);
 void CPU_reset(void);
 void CPU_fetch(INS *ins);
 void CPU_exec(INS ins);
-
 void CPU_irq(void);
 void CPU_nm_irq(void);
-
 void CPU_set_flag(ST_FLAG flag, uint8_t val);
 uint8_t CPU_get_flag(ST_FLAG flag);
 
 void CPU_dump(void);
 char *CPU_mode_name(uint8_t (*mode)(void));
 
+
 void MEM_init(void);
+void MEM_set_pc_start(uint16_t addr);
+uint8_t MEM_read(uint16_t addr);
+uint8_t MEM_write(uint16_t addr, uint8_t val);
+
 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);
-uint8_t MEM_write(uint16_t addr, uint8_t val);
-
 
 /*  Addresing modes  */
 uint8_t ABS(void);
diff --git a/main.c b/main.c
@@ -1,8 +1,10 @@
 #include "6502.h"
 
 #include <stdio.h>
+#include <stdlib.h>
 
-#define INPUT_FILE_PATH    "10times3.bin"
+/* #define INPUT_FILE_PATH    "10times3.bin" */
+#define INPUT_FILE_PATH    "6502_functional_test.bin"
 
 /* TODO: Finish with CPU instructions implementation */
 /* TODO: add support for command line arguments */
@@ -14,27 +16,31 @@ int main (void) {
     /* Initialize memory to 0 */
     MEM_init();
 
+    /* set the first address that the pc should be set to */
+    MEM_set_pc_start(0x0400);
+
     /* Load program to memory */
     MEM_load_from_file(INPUT_FILE_PATH);
 
-    /* Initialize registers to 0 */
+    /* Initialize cpu registers to 0 */
     CPU_init();
+
     CPU_reset();
 
     do {
+        system("clear");
         /* Fetch an instruction and increment pc */
         CPU_fetch(&ins);
 
         CPU_dump();
-
+        MEM_dump_last_six();
         MEM_dump_page(0x0000);
         MEM_dump_page(0x0100);
-        MEM_dump_last_six();
 
         /* Execute instruction */
         CPU_exec(ins);
-
-    } while(getchar() != 'q');
+    } while(1);
+    /* } while(getchar() != 'q'); */
 
     return 0;
 }