#include #include #include #define CHIPS_IMPL #include "m6502.h" #define lament(...) fprintf(stderr, __VA_ARGS__) #define error_when(cond, ...) do { \ if ((cond) || errno) { \ lament(__VA_ARGS__); \ lament(": %s\n", strerror(errno)); \ goto error; \ } \ } while (0) // setup 64 kBytes of memory #define MEMSIZE 65536 static uint8_t mem[MEMSIZE] = {0}; // NOTE: renamed KIL to JAM for consistency. static const char instrnames[] = "BRK\0ORA\0JAM\0SLO\0NOP\0ORA\0ASL\0SLO\0PHP\0ORA\0ASL\0ANC\0NOP\0ORA\0ASL\0SLO\0" "BPL\0ORA\0JAM\0SLO\0NOP\0ORA\0ASL\0SLO\0CLC\0ORA\0NOP\0SLO\0NOP\0ORA\0ASL\0SLO\0" "JSR\0AND\0JAM\0RLA\0BIT\0AND\0ROL\0RLA\0PLP\0AND\0ROL\0ANC\0BIT\0AND\0ROL\0RLA\0" "BMI\0AND\0JAM\0RLA\0NOP\0AND\0ROL\0RLA\0SEC\0AND\0NOP\0RLA\0NOP\0AND\0ROL\0RLA\0" "RTI\0EOR\0JAM\0SRE\0NOP\0EOR\0LSR\0SRE\0PHA\0EOR\0LSR\0ALR\0JMP\0EOR\0LSR\0SRE\0" "BVC\0EOR\0JAM\0SRE\0NOP\0EOR\0LSR\0SRE\0CLI\0EOR\0NOP\0SRE\0NOP\0EOR\0LSR\0SRE\0" "RTS\0ADC\0JAM\0RRA\0NOP\0ADC\0ROR\0RRA\0PLA\0ADC\0ROR\0ARR\0JMP\0ADC\0ROR\0RRA\0" "BVS\0ADC\0JAM\0RRA\0NOP\0ADC\0ROR\0RRA\0SEI\0ADC\0NOP\0RRA\0NOP\0ADC\0ROR\0RRA\0" "NOP\0STA\0NOP\0SAX\0STY\0STA\0STX\0SAX\0DEY\0NOP\0TXA\0XAA\0STY\0STA\0STX\0SAX\0" "BCC\0STA\0JAM\0AHX\0STY\0STA\0STX\0SAX\0TYA\0STA\0TXS\0TAS\0SHY\0STA\0SHX\0AHX\0" "LDY\0LDA\0LDX\0LAX\0LDY\0LDA\0LDX\0LAX\0TAY\0LDA\0TAX\0LAX\0LDY\0LDA\0LDX\0LAX\0" "BCS\0LDA\0JAM\0LAX\0LDY\0LDA\0LDX\0LAX\0CLV\0LDA\0TSX\0LAS\0LDY\0LDA\0LDX\0LAX\0" "CPY\0CMP\0NOP\0DCP\0CPY\0CMP\0DEC\0DCP\0INY\0CMP\0DEX\0AXS\0CPY\0CMP\0DEC\0DCP\0" "BNE\0CMP\0JAM\0DCP\0NOP\0CMP\0DEC\0DCP\0CLD\0CMP\0NOP\0DCP\0NOP\0CMP\0DEC\0DCP\0" "CPX\0SBC\0NOP\0ISC\0CPX\0SBC\0INC\0ISC\0INX\0SBC\0NOP\0SBC\0CPX\0SBC\0INC\0ISC\0" "BEQ\0SBC\0JAM\0ISC\0NOP\0SBC\0INC\0ISC\0SED\0SBC\0NOP\0ISC\0NOP\0SBC\0INC\0ISC"; static const char documented[] = { 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0 }; static int loadmem(const char *fp) { FILE *f = NULL; long size = MEMSIZE; errno = 0; f = fopen(fp, "rb"); error_when(f == NULL, "Error opening file: %s", fp); error_when(fread(mem, 1, size, f) != (size_t)size, "Error reading %li bytes from file: %s", size, fp); error_when(fclose(f) != 0, "Error closing file: %s", fp); return 0; error: //return 65; // EX_DATAERR return 66; // EX_NOINPUT } static void memdebug(const m6502_t cpu, long instrs) { uint64_t pins = cpu.PINS; uint16_t pc = cpu.PC; uint8_t instr = cpu.IR >> 3; int ic = cpu.IR & 7; uint16_t addr = M6502_GET_ADDR(pins); const char *mode = (pins & M6502_RW) ? "READ " : "WRITE "; uint8_t value = (pins & M6502_RW) ? mem[addr] : M6502_GET_DATA(pins); const char *instrname = instrnames + instr * 4; const char *ok = documented[instr] ? "..." : "!!!"; lament("[%4li.%i:$%04X:$%02X (%s %s)] %s mem[0x%04X]=0x%02X;\n", instrs, ic, pc, instr, instrname, ok, mode, addr, value); } static void xxd(const uint8_t *start, int length) { while (length >= 16) { printf("%08x: ", (unsigned int)(start - mem)); for (int i = 0; i < 16; i += 2) { printf("%02x%02x ", start[i], start[i + 1]); } printf(" "); for (int i = 0; i < 16; i++) { uint8_t value = start[i]; if (value < 0x20 || value >= 0x7F) { value = '.'; } printf("%c", value); } printf("\n"); start += 16; // FIXME: can technically invoke undefined behavior on boundary. length -= 16; } // TODO: handle the remainder. //for (int i = 0; i < length; i += 2) { //} } int main(int argc, char **argv) { char *name = NULL; long cycle = 0; long oldcycle = 0; long instrs = 0; long instr_limit = 1000; uint64_t pins; m6502_t cpu; m6502_desc_t desc = {0}; if (argc <= 0 || argv == NULL || argv[0] == NULL) { lament("You've met with a terrible fate.\n"); return 64; // EX_USAGE } name = argv[0]; if (argc != 2 && argc != 3) { lament("usage: %s {ram.bin} [instructions]\n", name); return 64; // EX_USAGE } if (argc == 3) { instr_limit = strtol(argv[2], NULL, 0); // can be negative, i guess. if (errno) { lament("%s: failed to parse integer: %s\n", name, argv[2]); return 64; // EX_USAGE } } { int res = 0; if ((res = loadmem(argv[1]))) return res; } // initialize the CPU desc.bcd_disabled = true; // TODO: do this from instructions? pins = m6502_init(&cpu, &desc); for (;; cycle++) { // run the CPU emulation for one tick pins = m6502_tick(&cpu, pins); // extract 16-bit address from pin mask const uint16_t addr = M6502_GET_ADDR(pins); // perform memory access if (pins & M6502_RW) { // a memory read uint8_t value = mem[addr]; memdebug(cpu, instrs); M6502_SET_DATA(pins, value); } else { // a memory write uint8_t value = M6502_GET_DATA(pins); memdebug(cpu, instrs); mem[addr] = value; } if (cycle >= oldcycle + 8) { lament("CPU is locked up!\n"); break; } if (pins & M6502_SYNC) { instrs++; oldcycle = cycle; if (instrs >= instr_limit) break; } } fflush(stdout); fflush(stderr); printf("cpu.PC=0x%04X, cpu.A=0x%02X, cpu.X=0x%02X, cpu.Y=0x%02X, cpu.S=0x%02X, cpu.P=0x%02X;\n", cpu.PC, cpu.A, cpu.X, cpu.Y, cpu.S, cpu.P); lament("exiting after %li instructions and %li cycles.\n", instrs, cycle + 1); xxd(mem, 0x100); return 0; }