#include #include #include static u8 basic_read_handler(void *data, u16 addr) { return ((u8 *) data)[addr]; } static void basic_write_handler(void *data, u16 addr, u8 val) { ((u8 *) data)[addr] = val; } void l6502_mem_basic_new(l6502_mem *m) { m->rh = basic_read_handler; m->wh = basic_write_handler; m->data = calloc(1 << 16, sizeof(u8)); } static u8 l6502_flag_get(l6502_emu *e, l6502_flag f) { return (e->regs.flags & (u8) f) != 0; } static void l6502_flag_set(l6502_emu *e, l6502_flag f, u8 v) { if (v) e->regs.flags |= (u8) f; else e->regs.flags &= ~((u8) f); } static void l6502_flags_check_zn(l6502_emu *e, u8 v) { l6502_flag_set(e, L6502_FLAG_ZERO, v == 0); l6502_flag_set(e, L6502_FLAG_NEGATIVE, (v >> 7) & 0b1); } static u8 l6502_read(l6502_emu *e, u16 addr) { return e->mem.rh(e->mem.data, addr); } static void l6502_write(l6502_emu *e, u16 addr, u8 val) { e->mem.wh(e->mem.data, addr, val); } static u16 l6502_read_addr(l6502_emu *e, u16 addr) { u8 alo = l6502_read(e, addr); u8 ahi = l6502_read(e, addr + 1); return ahi << 8 | alo; } static u8 l6502_pop(l6502_emu *e) { return l6502_read(e, 0x0100 + (u16) ++e->regs.sp); } static void l6502_push(l6502_emu *e, u8 val) { l6502_write(e, 0x0100 + (u16) e->regs.sp--, val); } static u16 l6502_pop_addr(l6502_emu *e) { u8 ahi = l6502_pop(e); u8 alo = l6502_pop(e); return ahi << 8 | alo; } static void l6502_push_addr(l6502_emu *e, u16 addr) { l6502_push(e, (u8) (addr & 0xff)); l6502_push(e, (u8) (addr >> 8)); } static u16 l6502_addressing_mode_immediate(l6502_emu *e) { return e->regs.pc++; } static u16 l6502_addressing_mode_absolute(l6502_emu *e) { u16 addr = l6502_read_addr(e, e->regs.pc); e->regs.pc += 2; return addr; } static u16 l6502_addressing_mode_absolute_x(l6502_emu *e) { u16 addr = l6502_read_addr(e, e->regs.pc) + (u16) e->regs.x; e->regs.pc += 2; return addr; } static u16 l6502_addressing_mode_absolute_y(l6502_emu *e) { u16 addr = l6502_read_addr(e, e->regs.pc) + (u16) e->regs.y; e->regs.pc += 2; return addr; } static u16 l6502_addressing_mode_zeropage(l6502_emu *e) { return (u16) l6502_read(e, e->regs.pc++); } static u16 l6502_addressing_mode_zeropage_x(l6502_emu *e) { return (u16) (l6502_read(e, e->regs.pc++) + e->regs.x); } static u16 l6502_addressing_mode_indirect_zeropage_x(l6502_emu *e) { return l6502_read_addr(e, l6502_read(e, e->regs.pc++) + e->regs.x); } static u16 l6502_addressing_mode_indirect_zeropage_y(l6502_emu *e) { return l6502_read_addr(e, l6502_read(e, e->regs.pc++)) + (u16) e->regs.y; } void l6502_emulate(l6502_emu *e) { u8 ins = l6502_read(e, e->regs.pc++); u8 val = 0; u16 res = 0; bool set_flags = true; bool set_result = true; u8 *set_reg = NULL; u16 addr = 0; switch (ins) { /* identify some special-case instructions */ case BRK: l6502_push_addr(e, e->regs.pc); l6502_push(e, e->regs.flags); e->regs.pc = l6502_read_addr(e, 0xfffe); l6502_flag_set(e, L6502_FLAG_BREAK_COMMAND, true); break; case PHP: l6502_push(e, e->regs.flags); break; case CLC: l6502_flag_set(e, L6502_FLAG_CARRY, false); break; case JSR: addr = l6502_read_addr(e, e->regs.pc); l6502_push_addr(e, e->regs.pc + 1); e->regs.pc = addr; break; case PLP: e->regs.flags = l6502_pop(e); break; case SEC: l6502_flag_set(e, L6502_FLAG_CARRY, true); break; case RTI: e->regs.flags = l6502_pop(e); e->regs.pc = l6502_pop_addr(e); break; case PHA: l6502_push(e, e->regs.a); break; case JMP_ABSOLUTE: addr = l6502_read_addr(e, e->regs.pc); e->regs.pc = addr; break; case CLI: l6502_flag_set(e, L6502_FLAG_INTERRUPT_DISABLE, false); break; case RTS: e->regs.pc = l6502_pop_addr(e) + 1; break; case PLA: e->regs.a = l6502_pop(e); break; case JMP_INDIRECT: addr = l6502_read_addr(e, l6502_read_addr(e, e->regs.pc)); e->regs.pc = addr; break; case SEI: l6502_flag_set(e, L6502_FLAG_INTERRUPT_DISABLE, true); break; case DEY: e->regs.y -= 1; l6502_flags_check_zn(e, e->regs.y); break; case TXA: e->regs.a = e->regs.x; l6502_flags_check_zn(e, e->regs.a); break; case TYA: e->regs.a = e->regs.y; l6502_flags_check_zn(e, e->regs.a); break; case TXS: e->regs.sp = e->regs.x; break; /* note TXS does not set flags */ case TAY: e->regs.y = e->regs.a; l6502_flags_check_zn(e, e->regs.y); break; case TAX: e->regs.x = e->regs.a; l6502_flags_check_zn(e, e->regs.x); break; case CLV: l6502_flag_set(e, L6502_FLAG_OVERFLOW, false); break; case TSX: e->regs.x = e->regs.sp; l6502_flags_check_zn(e, e->regs.x); break; case INY: e->regs.y += 1; l6502_flags_check_zn(e, e->regs.y); break; case DEX: e->regs.x -= 1; l6502_flags_check_zn(e, e->regs.x); break; case CLD: l6502_flag_set(e, L6502_FLAG_DECIMAL, false); break; case INX: e->regs.x += 1; l6502_flags_check_zn(e, e->regs.x); break; case NOP: break; case SED: l6502_flag_set(e, L6502_FLAG_DECIMAL, true); break; } if ((ins & 0b11111) == 0b10000) { /* identify conditional branches */ l6502_flag flags[4] = { L6502_FLAG_NEGATIVE, L6502_FLAG_OVERFLOW, L6502_FLAG_CARRY, L6502_FLAG_ZERO }; u8 off = l6502_read(e, e->regs.pc++); if (l6502_flag_get(e, flags[(ins >> 6) & 0b11]) == ((ins >> 5) & 0b1)) { e->regs.pc = (u16) ((i16) e->regs.pc + (i16) (i8) off); } return; } switch (ins & 0b11) { /* identify instructions in common forms */ case 0b01: { /* group one */ set_reg = &e->regs.a; switch ((ins >> 2) & 0b111) { case G1_ADDRESSING_MODE_INDIRECT_ZEROPAGE_X: addr = l6502_addressing_mode_indirect_zeropage_x(e); break; case G1_ADDRESSING_MODE_ZEROPAGE: addr = l6502_addressing_mode_zeropage(e); break; case G1_ADDRESSING_MODE_IMMEDIATE: addr = l6502_addressing_mode_immediate(e); break; case G1_ADDRESSING_MODE_ABSOLUTE: addr = l6502_addressing_mode_absolute(e); break; case G1_ADDRESSING_MODE_INDIRECT_ZEROPAGE_Y: addr = l6502_addressing_mode_indirect_zeropage_y(e); break; case G1_ADDRESSING_MODE_ZEROPAGE_X: addr = l6502_addressing_mode_zeropage_x(e); break; case G1_ADDRESSING_MODE_ABSOLUTE_Y: addr = l6502_addressing_mode_absolute_y(e); break; case G1_ADDRESSING_MODE_ABSOLUTE_X: addr = l6502_addressing_mode_absolute_x(e); break; } val = l6502_read(e, addr); switch ((ins >> 5) & 0b111) { case G1_OPCODE_ORA: res = (u16) e->regs.a | (u16) val; break; case G1_OPCODE_AND: res = (u16) e->regs.a & (u16) val; break; case G1_OPCODE_EOR: res = (u16) e->regs.a ^ (u16) val; break; case G1_OPCODE_ADC: res = (u16) e->regs.a + (u16) val; res += (u16) l6502_flag_get(e, L6502_FLAG_CARRY); l6502_flag_set(e, L6502_FLAG_OVERFLOW, ((res >> 8) != 0) != ((res >> 7) & 0b1)); break; case G1_OPCODE_STA: l6502_write(e, addr, e->regs.a); set_flags = set_result = false; break; case G1_OPCODE_LDA: res = (u16) l6502_read(e, addr); break; case G1_OPCODE_CMP: set_result = false; res = (u16) e->regs.a - (u16) val; break; case G1_OPCODE_SBC: res = (u16) e->regs.a - (u16) val; res -= (u16) (1 - l6502_flag_get(e, L6502_FLAG_CARRY)); l6502_flag_set(e, L6502_FLAG_OVERFLOW, ((res >> 8) != 0) != ((res >> 7) & 0b1)); break; } break; } case 0b10: { /* group two */ switch ((ins >> 2) & 0b111) { case G2_ADDRESSING_MODE_IMMEDIATE: addr = l6502_addressing_mode_immediate(e); break; case G2_ADDRESSING_MODE_ZEROPAGE: addr = l6502_addressing_mode_zeropage(e); break; case G2_ADDRESSING_MODE_ACCUMULATOR: addr = (u16) e->regs.a; set_reg = &e->regs.a; break; case G2_ADDRESSING_MODE_ABSOLUTE: addr = l6502_addressing_mode_absolute(e); break; case G2_ADDRESSING_MODE_ZEROPAGE_X: addr = l6502_addressing_mode_zeropage_x(e); break; case G2_ADDRESSING_MODE_ABSOLUTE_X: addr = l6502_addressing_mode_absolute_x(e); break; } val = set_reg ? (u8) addr : l6502_read(e, addr); switch ((ins >> 5) & 0b111) { case G2_OPCODE_ASL: res = ((u16) val) << 1; break; case G2_OPCODE_ROL: res = ((u16) val) << 1 | (u16) l6502_flag_get(e, L6502_FLAG_CARRY); break; case G2_OPCODE_LSR: res = ((u16) val) >> 1 | (u16) (val & 0b1) << 8; /* place the previous low bit in carry */ break; case G2_OPCODE_ROR: res = ((u16) val) >> 1 | (u16) l6502_flag_get(e, L6502_FLAG_CARRY) << 7 | (u16) (val & 0b1) << 8; /* same as above */ break; case G2_OPCODE_STX: l6502_write(e, addr, e->regs.x); set_flags = set_result = false; break; case G2_OPCODE_LDX: res = (u16) l6502_read(e, addr); set_reg = &e->regs.x; break; case G2_OPCODE_DEC: res = (u16) val - 1; break; case G2_OPCODE_INC: res = (u16) val + 1; break; } break; } case 0b00: { /* group three */ switch ((ins >> 2) & 0b111) { case G3_ADDRESSING_MODE_IMMEDIATE: addr = l6502_addressing_mode_immediate(e); break; case G3_ADDRESSING_MODE_ZEROPAGE: addr = l6502_addressing_mode_zeropage(e); break; case G3_ADDRESSING_MODE_ABSOLUTE: addr = l6502_addressing_mode_absolute(e); break; case G3_ADDRESSING_MODE_ZEROPAGE_X: addr = l6502_addressing_mode_zeropage_x(e); break; case G3_ADDRESSING_MODE_ABSOLUTE_X: addr = l6502_addressing_mode_absolute_x(e); break; } val = l6502_read(e, addr); switch ((ins >> 5) & 0b111) { case G3_OPCODE_BIT: set_flags = set_result = false; l6502_flag_set(e, L6502_FLAG_ZERO, (e->regs.a & val) == 0); l6502_flag_set(e, L6502_FLAG_OVERFLOW, (val >> 6) & 0b1); l6502_flag_set(e, L6502_FLAG_NEGATIVE, (val >> 7) & 0b1); break; case G3_OPCODE_STY: l6502_write(e, addr, e->regs.y); set_flags = set_result = false; break; case G3_OPCODE_LDY: res = (u16) l6502_read(e, addr); set_reg = &e->regs.y; break; case G3_OPCODE_CPY: set_result = false; res = (u16) e->regs.y - (u16) val; break; case G3_OPCODE_CPX: set_result = false; res = (u16) e->regs.x - (u16) val; break; } } } if (set_flags) { l6502_flag_set(e, L6502_FLAG_ZERO, res == 0); l6502_flag_set(e, L6502_FLAG_NEGATIVE, (res >> 7) & 0b1); l6502_flag_set(e, L6502_FLAG_CARRY, (res >> 8) != 0); } if (set_result) { if (set_reg) *set_reg = (u8) res; else l6502_write(e, addr, (u8) res); } }