#include #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 void 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 u16 amode_immediate(l6502_emu *e) { return e->pc++; } static u16 amode_absolute(l6502_emu *e) { u16 addr = l6502_read_addr(e, e->pc); e->pc += 2; return addr; } static u16 amode_absolute_x(l6502_emu *e) { u16 addr = l6502_read_addr(e, e->pc) + (u16) e->regs[L6502_REG_X]; e->pc += 2; return addr; } static u16 amode_absolute_y(l6502_emu *e) { u16 addr = l6502_read_addr(e, e->pc) + (u16) e->regs[L6502_REG_Y]; e->pc += 2; return addr; } static u16 amode_zeropage(l6502_emu *e) { return (u16) l6502_read(e, e->pc++); } static u16 amode_zeropage_x(l6502_emu *e) { return (u16) (u8) (l6502_read(e, e->pc++) + e->regs[L6502_REG_X]); } static u16 amode_zeropage_y(l6502_emu *e) { return (u16) (u8) (l6502_read(e, e->pc++) + e->regs[L6502_REG_Y]); } static u16 amode_indirect_zeropage_x(l6502_emu *e) { return l6502_read_addr_wrap_page(e, (u8) (l6502_read(e, e->pc++) + e->regs[L6502_REG_X])); } static u16 amode_indirect_zeropage_y(l6502_emu *e) { return l6502_read_addr_wrap_page(e, l6502_read(e, e->pc++)) + (u16) e->regs[L6502_REG_Y]; } const char *l6502_emulate(l6502_emu *e) { u8 ins = l6502_read(e, e->pc++); u8 val = 0; u16 res = 0; bool set_flags = true; bool set_carry = false; bool set_result = true; l6502_reg set_reg = L6502_REG__MARKER; u16 addr = 0; const char *nm = NULL; switch (ins) { /* identify some special-case instructions */ case BRK: nm = "brk"; l6502_push_addr(e, e->pc + 1); l6502_push(e, e->regs[L6502_REG_FLAGS] | L6502_FLAG_BREAK_COMMAND); e->pc = l6502_read_addr(e, 0xfffe); l6502_flag_set(e, L6502_FLAG_INTERRUPT_DISABLE, true); return nm; case PHP: nm = "php"; l6502_push(e, e->regs[L6502_REG_FLAGS] | L6502_FLAG_BREAK_COMMAND | L6502_FLAG_UNUSED); return nm; case CLC: nm = "clc"; l6502_flag_set(e, L6502_FLAG_CARRY, false); return nm; case JSR: { nm = "jsr"; u16 alo = l6502_read(e, e->pc); l6502_push_addr(e, e->pc + 1); u16 ahi = l6502_read(e, e->pc + 1); e->pc = ahi << 8 | alo; return nm; } case PLP: nm = "plp"; e->regs[L6502_REG_FLAGS] = l6502_pop(e) & (u8) ~L6502_FLAG_BREAK_COMMAND; return nm; case SEC: nm = "sec"; l6502_flag_set(e, L6502_FLAG_CARRY, true); return nm; case RTI: nm = "rti"; e->regs[L6502_REG_FLAGS] = l6502_pop(e) & (u8) ~L6502_FLAG_BREAK_COMMAND; e->pc = l6502_pop_addr(e); return nm; case PHA: nm = "pha"; l6502_push(e, e->regs[L6502_REG_A]); return nm; case JMP_ABSOLUTE: nm = "jmp"; addr = l6502_read_addr(e, e->pc); e->pc = addr; return nm; case CLI: nm = "cli"; l6502_flag_set(e, L6502_FLAG_INTERRUPT_DISABLE, false); return nm; case RTS: nm = "rts"; e->pc = l6502_pop_addr(e) + 1; return nm; case PLA: nm = "pla"; e->regs[L6502_REG_A] = l6502_pop(e); flags_check_zn(e, e->regs[L6502_REG_A]); return nm; case JMP_INDIRECT: nm = "jmp"; addr = l6502_read_addr_wrap_page(e, l6502_read_addr(e, e->pc)); e->pc = addr; return nm; case SEI: nm = "sei"; l6502_flag_set(e, L6502_FLAG_INTERRUPT_DISABLE, true); return nm; case DEY: nm = "dey"; e->regs[L6502_REG_Y] -= 1; flags_check_zn(e, e->regs[L6502_REG_Y]); return nm; case TXA: nm = "txa"; e->regs[L6502_REG_A] = e->regs[L6502_REG_X]; flags_check_zn(e, e->regs[L6502_REG_A]); return nm; case TYA: nm = "tya"; e->regs[L6502_REG_A] = e->regs[L6502_REG_Y]; flags_check_zn(e, e->regs[L6502_REG_A]); return nm; case TXS: nm = "txs"; e->regs[L6502_REG_SP] = e->regs[L6502_REG_X]; return nm; /* note TXS does not set flags */ case TAY: nm = "tay"; e->regs[L6502_REG_Y] = e->regs[L6502_REG_A]; flags_check_zn(e, e->regs[L6502_REG_Y]); return nm; case TAX: nm = "tax"; e->regs[L6502_REG_X] = e->regs[L6502_REG_A]; flags_check_zn(e, e->regs[L6502_REG_X]); return nm; case CLV: nm = "clv"; l6502_flag_set(e, L6502_FLAG_OVERFLOW, false); return nm; case TSX: nm = "tsx"; e->regs[L6502_REG_X] = e->regs[L6502_REG_SP]; flags_check_zn(e, e->regs[L6502_REG_X]); return nm; case INY: nm = "iny"; e->regs[L6502_REG_Y] += 1; flags_check_zn(e, e->regs[L6502_REG_Y]); return nm; case DEX: nm = "dex"; e->regs[L6502_REG_X] -= 1; flags_check_zn(e, e->regs[L6502_REG_X]); return nm; case CLD: nm = "cld"; l6502_flag_set(e, L6502_FLAG_DECIMAL, false); return nm; case INX: nm = "inx"; e->regs[L6502_REG_X] += 1; flags_check_zn(e, e->regs[L6502_REG_X]); return nm; case NOP: nm = "nop"; return nm; case SED: nm = "sed"; l6502_flag_set(e, L6502_FLAG_DECIMAL, true); return nm; } 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->pc++); if (l6502_flag_get(e, flags[(ins >> 6) & 0b11]) == ((ins >> 5) & 0b1)) { e->pc = (u16) ((i16) e->pc + (i16) (i8) off); } return "jcc"; } switch (ins & 0b11) { /* identify instructions in common forms */ case 0b01: { /* group one */ set_reg = L6502_REG_A; switch ((ins >> 2) & 0b111) { case G1_ADDRESSING_MODE_INDIRECT_ZEROPAGE_X: addr = amode_indirect_zeropage_x(e); break; case G1_ADDRESSING_MODE_ZEROPAGE: addr = amode_zeropage(e); break; case G1_ADDRESSING_MODE_IMMEDIATE: addr = amode_immediate(e); break; case G1_ADDRESSING_MODE_ABSOLUTE: addr = amode_absolute(e); break; case G1_ADDRESSING_MODE_INDIRECT_ZEROPAGE_Y: addr = amode_indirect_zeropage_y(e); break; case G1_ADDRESSING_MODE_ZEROPAGE_X: addr = amode_zeropage_x(e); break; case G1_ADDRESSING_MODE_ABSOLUTE_Y: addr = amode_absolute_y(e); break; case G1_ADDRESSING_MODE_ABSOLUTE_X: addr = amode_absolute_x(e); break; } val = l6502_read(e, addr); switch ((ins >> 5) & 0b111) { case G1_OPCODE_ORA: nm = "ora"; res = (u16) e->regs[L6502_REG_A] | (u16) val; break; case G1_OPCODE_AND: nm = "and"; res = (u16) e->regs[L6502_REG_A] & (u16) val; break; case G1_OPCODE_EOR: nm = "eor"; res = (u16) e->regs[L6502_REG_A] ^ (u16) val; break; case G1_OPCODE_ADC: { nm = "adc"; set_carry = true; i8 a0 = (i8) e->regs[L6502_REG_A]; i8 a1 = (i8) val; i8 a2 = (i8) l6502_flag_get(e, L6502_FLAG_CARRY); res = (u16) (u8) a0 + (u16) (u8) a1; res += (u16) (u8) a2; l6502_flag_set(e, L6502_FLAG_OVERFLOW, ((a0 >= 0) == (a1 >= 0)) && ((a0 >= 0) != ((i8) res >= 0))); break; } case G1_OPCODE_STA: nm = "sta"; l6502_write(e, addr, e->regs[L6502_REG_A]); set_flags = set_result = false; break; case G1_OPCODE_LDA: nm = "lda"; res = (u16) l6502_read(e, addr); break; case G1_OPCODE_CMP: nm = "cmp"; set_result = false; set_carry = true; res = (u16) (u8) (e->regs[L6502_REG_A] - val); if (e->regs[L6502_REG_A] >= val) res |= 1 << 8; break; case G1_OPCODE_SBC: nm = "sbc"; set_carry = true; i8 a0 = (i8) e->regs[L6502_REG_A]; i8 a1 = (i8) val; i8 a2 = (i8) (1 - l6502_flag_get(e, L6502_FLAG_CARRY)); res = (u16) (u8) a0 - (u16) (u8) a1; res -= (u16) (u8) a2; res &= 0xff; if ((u8) a0 >= (u8) a1 + (u8) a2) res |= 1 << 8; l6502_flag_set(e, L6502_FLAG_OVERFLOW, !((a0 >= 0) == (a1 >= 0)) && ((a0 >= 0) != ((i8) res >= 0))); break; } break; } case 0b10: { /* group two */ switch ((ins >> 2) & 0b111) { case G2_ADDRESSING_MODE_IMMEDIATE: addr = amode_immediate(e); break; case G2_ADDRESSING_MODE_ZEROPAGE: addr = amode_zeropage(e); break; case G2_ADDRESSING_MODE_ACCUMULATOR: addr = (u16) e->regs[L6502_REG_A]; set_reg = L6502_REG_A; break; case G2_ADDRESSING_MODE_ABSOLUTE: addr = amode_absolute(e); break; case G2_ADDRESSING_MODE_ZEROPAGE_X: switch ((ins >> 5) & 0b111) { case G2_OPCODE_LDX: case G2_OPCODE_STX: addr = amode_zeropage_y(e); break; default: addr = amode_zeropage_x(e); break; } break; case G2_ADDRESSING_MODE_ABSOLUTE_X: switch ((ins >> 5) & 0b111) { case G2_OPCODE_LDX: case G2_OPCODE_STX: addr = amode_absolute_y(e); break; default: addr = amode_absolute_x(e); break; } break; } val = set_reg != L6502_REG__MARKER ? (u8) addr : l6502_read(e, addr); switch ((ins >> 5) & 0b111) { case G2_OPCODE_ASL: nm = "asl"; set_carry = true; res = ((u16) val) << 1; break; case G2_OPCODE_ROL: nm = "rol"; set_carry = true; res = ((u16) val) << 1 | (u16) l6502_flag_get(e, L6502_FLAG_CARRY); break; case G2_OPCODE_LSR: nm = "lsr"; set_carry = true; res = ((u16) val) >> 1 | (u16) (val & 0b1) << 8; /* place the previous low bit in carry */ break; case G2_OPCODE_ROR: nm = "ror"; set_carry = true; 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: nm = "stx"; l6502_write(e, addr, e->regs[L6502_REG_X]); set_flags = set_result = false; break; case G2_OPCODE_LDX: nm = "ldx"; res = (u16) l6502_read(e, addr); set_reg = L6502_REG_X; break; case G2_OPCODE_DEC: nm = "dec"; res = (u16) val - 1; break; case G2_OPCODE_INC: nm = "inc"; res = (u16) val + 1; break; } break; } case 0b00: { /* group three */ switch ((ins >> 2) & 0b111) { case G3_ADDRESSING_MODE_IMMEDIATE: addr = amode_immediate(e); break; case G3_ADDRESSING_MODE_ZEROPAGE: addr = amode_zeropage(e); break; case G3_ADDRESSING_MODE_ABSOLUTE: addr = amode_absolute(e); break; case G3_ADDRESSING_MODE_ZEROPAGE_X: addr = amode_zeropage_x(e); break; case G3_ADDRESSING_MODE_ABSOLUTE_X: addr = amode_absolute_x(e); break; } val = l6502_read(e, addr); switch ((ins >> 5) & 0b111) { case G3_OPCODE_BIT: nm = "bit"; set_flags = set_result = false; l6502_flag_set(e, L6502_FLAG_ZERO, (e->regs[L6502_REG_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: nm = "sty"; l6502_write(e, addr, e->regs[L6502_REG_Y]); set_flags = set_result = false; break; case G3_OPCODE_LDY: nm = "ldy"; res = (u16) l6502_read(e, addr); set_reg = L6502_REG_Y; break; case G3_OPCODE_CPY: nm = "cpy"; set_result = false; set_carry = true; res = (u16) (u8) (e->regs[L6502_REG_Y] - val); if (e->regs[L6502_REG_Y] >= val) res |= 1 << 8; break; case G3_OPCODE_CPX: nm = "cpx"; set_result = false; set_carry = true; res = (u16) (u8) (e->regs[L6502_REG_X] - val); if (e->regs[L6502_REG_X] >= val) res |= 1 << 8; break; } } } if (set_flags) { l6502_flag_set(e, L6502_FLAG_ZERO, (res & 0xff) == 0); l6502_flag_set(e, L6502_FLAG_NEGATIVE, (res >> 7) & 0b1); if (set_carry) l6502_flag_set(e, L6502_FLAG_CARRY, (res >> 8) != 0); } if (set_result) { if (set_reg != L6502_REG__MARKER) e->regs[set_reg] = (u8) res; else l6502_write(e, addr, (u8) res); } return nm; }