Add disassembler
diff --git a/cpu.c b/cpu.c
new file mode 100644
index 0000000..f0d7d68
--- /dev/null
+++ b/cpu.c
@@ -0,0 +1,204 @@
+#include "cpu.h"
+#include "instructions.h"
+
+#include <endian.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define die(m, ...) \
+ printf("\033[31mError: " m "\033[0m\n", ##__VA_ARGS__); \
+ exit(1);
+
+cpu_t new_cpu()
+{
+ cpu_t cpu = { 0 };
+ cpu.regs[SR] = UNUSED; // unused flag always set
+ cpu.regs[SP] = 0xFD; // stack at is 0x100 + SP
+ cpu.pc = 0; // arbitrary program counter start
+ cpu.mem = malloc(0xFFFF);
+ memset(cpu.mem, 0, 0xFFFF);
+
+ if (!cpu.mem)
+ {
+ die("Could not allocate memory for CPU");
+ }
+
+ return cpu;
+}
+
+void free_cpu(cpu_t *cpu)
+{
+ free(cpu->mem);
+}
+
+void execute(cpu_t *cpu, const char *mnemonic, uint8_t op, uint16_t addr)
+{
+
+}
+
+uint16_t le_to_native(uint8_t a, uint8_t b)
+{
+#ifdef LITTLE_ENDIAN
+ return a << 8 | b;
+#else
+ return b << 8 | a;
+#endif
+}
+
+uint16_t fetch_le(cpu_t *cpu)
+{
+ uint8_t a = cpu->mem[cpu->pc++];
+ uint8_t b = cpu->mem[cpu->pc++];
+ return le_to_native(a, b);
+}
+
+uint16_t fetch_addr(cpu_t *cpu, uint8_t am)
+{
+ switch (am)
+ {
+ case AM_ACC:
+ case AM_IMP:
+ return 0;
+
+ // In both cases return immediate 8 bit value
+ case AM_IMM:
+ case AM_ZP:
+ return cpu->mem[cpu->pc++];
+
+ case AM_ABS:
+ return fetch_le(cpu);
+
+ case AM_REL:
+ {
+ // PC should point to the opcode
+ // braces needed to avoid c stupidity
+ uint16_t pc = cpu->pc - 1;
+ return cpu->mem[cpu->pc++] + pc;
+ }
+
+ case AM_IND:
+ {
+ uint16_t addr = fetch_le(cpu);
+ uint8_t low = cpu->mem[addr],
+ high = cpu->mem[addr + 1];
+
+ return le_to_native(low, high);
+ }
+
+ case AM_AX:
+ return fetch_le(cpu) + cpu->regs[X];
+
+ case AM_AY:
+ return fetch_le(cpu) + cpu->regs[Y];
+
+ case AM_ZPX:
+ return cpu->mem[cpu->pc++] + cpu->regs[X];
+
+ case AM_ZPY:
+ return cpu->mem[cpu->pc++] + cpu->regs[Y];
+
+ case AM_ZIX:
+ {
+ uint8_t zp = cpu->mem[cpu->pc++];
+ return le_to_native(cpu->mem[zp + cpu->regs[X]], cpu->mem[zp + cpu->regs[X] + 1]);
+ }
+
+ case AM_ZIY:
+ {
+ uint8_t zp = cpu->mem[cpu->pc++];
+ uint16_t base = le_to_native(cpu->mem[zp], cpu->mem[zp + 1]);
+ return base + cpu->regs[Y];
+ }
+
+ default:
+ die("Unknown address mode %x", am);
+ return -1; // unreachable
+ }
+}
+
+void step(cpu_t *cpu)
+{
+ switch (cpu->mem[cpu->pc++])
+ {
+#define INST(mn, am, op) \
+ case op: \
+ execute(cpu, #mn, mn, fetch_addr(cpu, am)); \
+ break;
+
+ INSTRUCTIONS
+
+#undef INST
+
+ default:
+ die("Undefined opcode");
+ }
+}
+
+void dump_inst(cpu_t *cpu, const char *mn, uint16_t addr, uint8_t am)
+{
+ printf("\t%s\t", mn);
+
+ switch (am)
+ {
+ case AM_IMM:
+ printf("#");
+ case AM_REL:
+ case AM_ABS:
+ case AM_ZP:
+ printf("$%x", addr);
+ break;
+
+ case AM_IND:
+ printf("($%x)", addr);
+ break;
+
+ case AM_AX:
+ case AM_ZPX:
+ printf("$%x, X", addr);
+ break;
+
+ case AM_AY:
+ case AM_ZPY:
+ printf("$%x, Y", addr);
+ break;
+
+ case AM_ZIX:
+ printf("($%x, X)", addr);
+ break;
+
+ case AM_ZIY:
+ printf("($%x), Y", addr);
+ break;
+ }
+
+ printf("\n");
+}
+
+void disas_step(cpu_t *cpu)
+{
+ printf("%x", cpu->pc);
+ uint8_t op = cpu->mem[cpu->pc++];
+ switch (op)
+ {
+#define INST(mn, am, op) \
+ case op: \
+ dump_inst(cpu, #mn, fetch_addr(cpu, am), am); \
+ break;
+
+ INSTRUCTIONS
+
+#undef INST
+
+ default:
+ die("Undefined opcode %x", op);
+ }
+}
+
+void disas(cpu_t *cpu)
+{
+ while (cpu->pc < 0xFFFF)
+ {
+ disas_step(cpu);
+ }
+}