Initial commit: NES emulator with GTK4 desktop frontend
Some checks failed
CI / rust (push) Has been cancelled
Some checks failed
CI / rust (push) Has been cancelled
Full NES emulation: CPU, PPU, APU, 47 mappers, iNES/NES 2.0 parsing. GTK4 desktop client with HeaderBar, pixel-perfect Cairo rendering, drag-and-drop ROM loading, and keyboard shortcuts. 187 tests covering core emulation, mappers, and runtime.
This commit is contained in:
245
src/native_core/cpu/helpers.rs
Normal file
245
src/native_core/cpu/helpers.rs
Normal file
@@ -0,0 +1,245 @@
|
||||
use super::*;
|
||||
|
||||
impl Cpu6502 {
|
||||
pub(super) fn fetch<B: CpuBus>(&mut self, bus: &mut B) -> u8 {
|
||||
let value = bus.read(self.pc);
|
||||
self.pc = self.pc.wrapping_add(1);
|
||||
value
|
||||
}
|
||||
|
||||
pub(super) fn fetch_u16<B: CpuBus>(&mut self, bus: &mut B) -> u16 {
|
||||
let lo = self.fetch(bus) as u16;
|
||||
let hi = self.fetch(bus) as u16;
|
||||
(hi << 8) | lo
|
||||
}
|
||||
|
||||
pub(super) fn read_u16<B: CpuBus>(&mut self, bus: &mut B, addr: u16) -> u16 {
|
||||
let lo = bus.read(addr) as u16;
|
||||
let hi = bus.read(addr.wrapping_add(1)) as u16;
|
||||
(hi << 8) | lo
|
||||
}
|
||||
|
||||
pub(super) fn read_u16_bug<B: CpuBus>(&mut self, bus: &mut B, addr: u16) -> u16 {
|
||||
let lo = bus.read(addr) as u16;
|
||||
let hi_addr = (addr & 0xFF00) | (addr.wrapping_add(1) & 0x00FF);
|
||||
let hi = bus.read(hi_addr) as u16;
|
||||
(hi << 8) | lo
|
||||
}
|
||||
|
||||
pub(super) fn addr_zp<B: CpuBus>(&mut self, bus: &mut B) -> u16 {
|
||||
self.fetch(bus) as u16
|
||||
}
|
||||
|
||||
pub(super) fn addr_zpx<B: CpuBus>(&mut self, bus: &mut B) -> u16 {
|
||||
self.fetch(bus).wrapping_add(self.x) as u16
|
||||
}
|
||||
|
||||
pub(super) fn addr_zpy<B: CpuBus>(&mut self, bus: &mut B) -> u16 {
|
||||
self.fetch(bus).wrapping_add(self.y) as u16
|
||||
}
|
||||
|
||||
pub(super) fn addr_abs<B: CpuBus>(&mut self, bus: &mut B) -> u16 {
|
||||
self.fetch_u16(bus)
|
||||
}
|
||||
|
||||
pub(super) fn addr_abx<B: CpuBus>(&mut self, bus: &mut B) -> u16 {
|
||||
self.addr_abx_cross(bus).0
|
||||
}
|
||||
|
||||
pub(super) fn addr_aby<B: CpuBus>(&mut self, bus: &mut B) -> u16 {
|
||||
self.addr_aby_cross(bus).0
|
||||
}
|
||||
|
||||
pub(super) fn addr_indx<B: CpuBus>(&mut self, bus: &mut B) -> u16 {
|
||||
let ptr = self.fetch(bus).wrapping_add(self.x);
|
||||
let lo = bus.read(ptr as u16) as u16;
|
||||
let hi = bus.read(ptr.wrapping_add(1) as u16) as u16;
|
||||
(hi << 8) | lo
|
||||
}
|
||||
|
||||
pub(super) fn addr_indy<B: CpuBus>(&mut self, bus: &mut B) -> u16 {
|
||||
self.addr_indy_cross(bus).0
|
||||
}
|
||||
|
||||
pub(super) fn addr_abx_cross<B: CpuBus>(&mut self, bus: &mut B) -> (u16, bool) {
|
||||
let base = self.fetch_u16(bus);
|
||||
let addr = base.wrapping_add(self.x as u16);
|
||||
(addr, (base & 0xFF00) != (addr & 0xFF00))
|
||||
}
|
||||
|
||||
pub(super) fn addr_aby_cross<B: CpuBus>(&mut self, bus: &mut B) -> (u16, bool) {
|
||||
let base = self.fetch_u16(bus);
|
||||
let addr = base.wrapping_add(self.y as u16);
|
||||
(addr, (base & 0xFF00) != (addr & 0xFF00))
|
||||
}
|
||||
|
||||
pub(super) fn addr_indy_cross<B: CpuBus>(&mut self, bus: &mut B) -> (u16, bool) {
|
||||
let ptr = self.fetch(bus);
|
||||
let lo = bus.read(ptr as u16) as u16;
|
||||
let hi = bus.read(ptr.wrapping_add(1) as u16) as u16;
|
||||
let base = (hi << 8) | lo;
|
||||
let addr = base.wrapping_add(self.y as u16);
|
||||
(addr, (base & 0xFF00) != (addr & 0xFF00))
|
||||
}
|
||||
|
||||
pub(super) fn read_zp<B: CpuBus>(&mut self, bus: &mut B) -> u8 {
|
||||
let addr = self.addr_zp(bus);
|
||||
bus.read(addr)
|
||||
}
|
||||
|
||||
pub(super) fn read_zpx<B: CpuBus>(&mut self, bus: &mut B) -> u8 {
|
||||
let addr = self.addr_zpx(bus);
|
||||
bus.read(addr)
|
||||
}
|
||||
|
||||
pub(super) fn read_zpy<B: CpuBus>(&mut self, bus: &mut B) -> u8 {
|
||||
let addr = self.addr_zpy(bus);
|
||||
bus.read(addr)
|
||||
}
|
||||
|
||||
pub(super) fn read_abs<B: CpuBus>(&mut self, bus: &mut B) -> u8 {
|
||||
let addr = self.addr_abs(bus);
|
||||
bus.read(addr)
|
||||
}
|
||||
|
||||
pub(super) fn read_abx_cross<B: CpuBus>(&mut self, bus: &mut B) -> (u8, bool) {
|
||||
let (addr, crossed) = self.addr_abx_cross(bus);
|
||||
(bus.read(addr), crossed)
|
||||
}
|
||||
|
||||
pub(super) fn read_aby_cross<B: CpuBus>(&mut self, bus: &mut B) -> (u8, bool) {
|
||||
let (addr, crossed) = self.addr_aby_cross(bus);
|
||||
(bus.read(addr), crossed)
|
||||
}
|
||||
|
||||
pub(super) fn read_indx<B: CpuBus>(&mut self, bus: &mut B) -> u8 {
|
||||
let addr = self.addr_indx(bus);
|
||||
bus.read(addr)
|
||||
}
|
||||
|
||||
pub(super) fn read_indy_cross<B: CpuBus>(&mut self, bus: &mut B) -> (u8, bool) {
|
||||
let (addr, crossed) = self.addr_indy_cross(bus);
|
||||
(bus.read(addr), crossed)
|
||||
}
|
||||
|
||||
pub(super) fn push<B: CpuBus>(&mut self, bus: &mut B, value: u8) {
|
||||
let addr = 0x0100 | self.sp as u16;
|
||||
bus.write(addr, value);
|
||||
self.sp = self.sp.wrapping_sub(1);
|
||||
}
|
||||
|
||||
pub(super) fn pop<B: CpuBus>(&mut self, bus: &mut B) -> u8 {
|
||||
self.sp = self.sp.wrapping_add(1);
|
||||
let addr = 0x0100 | self.sp as u16;
|
||||
bus.read(addr)
|
||||
}
|
||||
|
||||
pub(super) fn set_zn(&mut self, value: u8) {
|
||||
if value == 0 {
|
||||
self.p |= FLAG_ZERO;
|
||||
} else {
|
||||
self.p &= !FLAG_ZERO;
|
||||
}
|
||||
if (value & 0x80) != 0 {
|
||||
self.p |= FLAG_NEGATIVE;
|
||||
} else {
|
||||
self.p &= !FLAG_NEGATIVE;
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn set_flag(&mut self, mask: u8, enabled: bool) {
|
||||
if enabled {
|
||||
self.p |= mask;
|
||||
} else {
|
||||
self.p &= !mask;
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn compare(&mut self, lhs: u8, rhs: u8) {
|
||||
let result = lhs.wrapping_sub(rhs);
|
||||
self.set_flag(FLAG_CARRY, lhs >= rhs);
|
||||
self.set_zn(result);
|
||||
}
|
||||
|
||||
pub(super) fn bit(&mut self, value: u8) {
|
||||
self.set_flag(FLAG_ZERO, (self.a & value) == 0);
|
||||
self.set_flag(FLAG_OVERFLOW, (value & 0x40) != 0);
|
||||
self.set_flag(FLAG_NEGATIVE, (value & 0x80) != 0);
|
||||
}
|
||||
|
||||
pub(super) fn adc(&mut self, value: u8) {
|
||||
let carry = u16::from((self.p & FLAG_CARRY) != 0);
|
||||
let a = self.a as u16;
|
||||
let b = value as u16;
|
||||
let sum = a + b + carry;
|
||||
let result = sum as u8;
|
||||
|
||||
self.set_flag(FLAG_CARRY, sum > 0xFF);
|
||||
let overflow = ((self.a ^ result) & (value ^ result) & 0x80) != 0;
|
||||
self.set_flag(FLAG_OVERFLOW, overflow);
|
||||
|
||||
self.a = result;
|
||||
self.set_zn(self.a);
|
||||
}
|
||||
|
||||
pub(super) fn asl(&mut self, value: u8) -> u8 {
|
||||
self.set_flag(FLAG_CARRY, (value & 0x80) != 0);
|
||||
let out = value << 1;
|
||||
self.set_zn(out);
|
||||
out
|
||||
}
|
||||
|
||||
pub(super) fn lsr(&mut self, value: u8) -> u8 {
|
||||
self.set_flag(FLAG_CARRY, (value & 0x01) != 0);
|
||||
let out = value >> 1;
|
||||
self.set_zn(out);
|
||||
out
|
||||
}
|
||||
|
||||
pub(super) fn rol(&mut self, value: u8) -> u8 {
|
||||
let carry_in = u8::from((self.p & FLAG_CARRY) != 0);
|
||||
self.set_flag(FLAG_CARRY, (value & 0x80) != 0);
|
||||
let out = (value << 1) | carry_in;
|
||||
self.set_zn(out);
|
||||
out
|
||||
}
|
||||
|
||||
pub(super) fn ror(&mut self, value: u8) -> u8 {
|
||||
let carry_in = if (self.p & FLAG_CARRY) != 0 { 0x80 } else { 0 };
|
||||
self.set_flag(FLAG_CARRY, (value & 0x01) != 0);
|
||||
let out = (value >> 1) | carry_in;
|
||||
self.set_zn(out);
|
||||
out
|
||||
}
|
||||
|
||||
pub(super) fn branch<B: CpuBus>(&mut self, bus: &mut B, condition: bool) -> u8 {
|
||||
let offset = self.fetch(bus) as i8;
|
||||
if !condition {
|
||||
return 2;
|
||||
}
|
||||
let old_pc = self.pc;
|
||||
self.pc = self.pc.wrapping_add_signed(offset as i16);
|
||||
if (old_pc & 0xFF00) != (self.pc & 0xFF00) {
|
||||
4
|
||||
} else {
|
||||
3
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn service_interrupt<B: CpuBus>(
|
||||
&mut self,
|
||||
bus: &mut B,
|
||||
vector_addr: u16,
|
||||
break_flag: bool,
|
||||
) {
|
||||
self.push(bus, (self.pc >> 8) as u8);
|
||||
self.push(bus, self.pc as u8);
|
||||
let mut status = (self.p | FLAG_UNUSED) & !FLAG_BREAK;
|
||||
if break_flag {
|
||||
status |= FLAG_BREAK;
|
||||
}
|
||||
self.push(bus, status);
|
||||
self.p = (self.p | FLAG_IRQ_DISABLE | FLAG_UNUSED) & !FLAG_BREAK;
|
||||
self.pc = self.read_u16(bus, vector_addr);
|
||||
}
|
||||
}
|
||||
29
src/native_core/cpu/opcodes/mod.rs
Normal file
29
src/native_core/cpu/opcodes/mod.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use super::*;
|
||||
|
||||
mod official;
|
||||
mod ops;
|
||||
mod undocumented;
|
||||
|
||||
impl Cpu6502 {
|
||||
pub(super) fn execute_opcode<B: CpuBus>(
|
||||
&mut self,
|
||||
bus: &mut B,
|
||||
opcode: u8,
|
||||
pc_before: u16,
|
||||
) -> Result<u8, CpuError> {
|
||||
if let Some(cycles) = self.execute_undocumented(bus, opcode, pc_before) {
|
||||
self.p |= FLAG_UNUSED;
|
||||
return Ok(cycles);
|
||||
}
|
||||
|
||||
if let Some(cycles) = self.execute_official(bus, opcode) {
|
||||
self.p |= FLAG_UNUSED;
|
||||
return Ok(cycles);
|
||||
}
|
||||
|
||||
Err(CpuError::UnsupportedOpcode {
|
||||
opcode,
|
||||
pc: pc_before,
|
||||
})
|
||||
}
|
||||
}
|
||||
375
src/native_core/cpu/opcodes/official/alu.rs
Normal file
375
src/native_core/cpu/opcodes/official/alu.rs
Normal file
@@ -0,0 +1,375 @@
|
||||
use super::ops::{OperandReadMode, OperandWriteMode};
|
||||
use super::*;
|
||||
|
||||
impl Cpu6502 {
|
||||
pub(super) fn execute_official_alu<B: CpuBus>(
|
||||
&mut self,
|
||||
bus: &mut B,
|
||||
opcode: u8,
|
||||
) -> Option<u8> {
|
||||
let cycles = match opcode {
|
||||
// ADC
|
||||
0x69 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Imm, 2);
|
||||
self.adc(value);
|
||||
cycles
|
||||
}
|
||||
0x65 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Zp, 3);
|
||||
self.adc(value);
|
||||
cycles
|
||||
}
|
||||
0x75 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Zpx, 4);
|
||||
self.adc(value);
|
||||
cycles
|
||||
}
|
||||
0x6D => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Abs, 4);
|
||||
self.adc(value);
|
||||
cycles
|
||||
}
|
||||
0x7D => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::AbxCross, 4);
|
||||
self.adc(value);
|
||||
cycles
|
||||
}
|
||||
0x79 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::AbyCross, 4);
|
||||
self.adc(value);
|
||||
cycles
|
||||
}
|
||||
0x61 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Indx, 6);
|
||||
self.adc(value);
|
||||
cycles
|
||||
}
|
||||
0x71 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::IndyCross, 5);
|
||||
self.adc(value);
|
||||
cycles
|
||||
}
|
||||
|
||||
// SBC
|
||||
0xE9 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Imm, 2);
|
||||
self.adc(!value);
|
||||
cycles
|
||||
}
|
||||
0xE5 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Zp, 3);
|
||||
self.adc(!value);
|
||||
cycles
|
||||
}
|
||||
0xF5 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Zpx, 4);
|
||||
self.adc(!value);
|
||||
cycles
|
||||
}
|
||||
0xED => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Abs, 4);
|
||||
self.adc(!value);
|
||||
cycles
|
||||
}
|
||||
0xFD => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::AbxCross, 4);
|
||||
self.adc(!value);
|
||||
cycles
|
||||
}
|
||||
0xF9 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::AbyCross, 4);
|
||||
self.adc(!value);
|
||||
cycles
|
||||
}
|
||||
0xE1 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Indx, 6);
|
||||
self.adc(!value);
|
||||
cycles
|
||||
}
|
||||
0xF1 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::IndyCross, 5);
|
||||
self.adc(!value);
|
||||
cycles
|
||||
}
|
||||
|
||||
// AND
|
||||
0x29 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Imm, 2);
|
||||
self.a &= value;
|
||||
self.set_zn(self.a);
|
||||
cycles
|
||||
}
|
||||
0x25 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Zp, 3);
|
||||
self.a &= value;
|
||||
self.set_zn(self.a);
|
||||
cycles
|
||||
}
|
||||
0x35 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Zpx, 4);
|
||||
self.a &= value;
|
||||
self.set_zn(self.a);
|
||||
cycles
|
||||
}
|
||||
0x2D => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Abs, 4);
|
||||
self.a &= value;
|
||||
self.set_zn(self.a);
|
||||
cycles
|
||||
}
|
||||
0x3D => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::AbxCross, 4);
|
||||
self.a &= value;
|
||||
self.set_zn(self.a);
|
||||
cycles
|
||||
}
|
||||
0x39 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::AbyCross, 4);
|
||||
self.a &= value;
|
||||
self.set_zn(self.a);
|
||||
cycles
|
||||
}
|
||||
0x21 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Indx, 6);
|
||||
self.a &= value;
|
||||
self.set_zn(self.a);
|
||||
cycles
|
||||
}
|
||||
0x31 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::IndyCross, 5);
|
||||
self.a &= value;
|
||||
self.set_zn(self.a);
|
||||
cycles
|
||||
}
|
||||
|
||||
// ORA
|
||||
0x09 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Imm, 2);
|
||||
self.a |= value;
|
||||
self.set_zn(self.a);
|
||||
cycles
|
||||
}
|
||||
0x05 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Zp, 3);
|
||||
self.a |= value;
|
||||
self.set_zn(self.a);
|
||||
cycles
|
||||
}
|
||||
0x15 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Zpx, 4);
|
||||
self.a |= value;
|
||||
self.set_zn(self.a);
|
||||
cycles
|
||||
}
|
||||
0x0D => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Abs, 4);
|
||||
self.a |= value;
|
||||
self.set_zn(self.a);
|
||||
cycles
|
||||
}
|
||||
0x19 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::AbyCross, 4);
|
||||
self.a |= value;
|
||||
self.set_zn(self.a);
|
||||
cycles
|
||||
}
|
||||
0x1D => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::AbxCross, 4);
|
||||
self.a |= value;
|
||||
self.set_zn(self.a);
|
||||
cycles
|
||||
}
|
||||
0x01 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Indx, 6);
|
||||
self.a |= value;
|
||||
self.set_zn(self.a);
|
||||
cycles
|
||||
}
|
||||
0x11 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::IndyCross, 5);
|
||||
self.a |= value;
|
||||
self.set_zn(self.a);
|
||||
cycles
|
||||
}
|
||||
|
||||
// EOR
|
||||
0x49 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Imm, 2);
|
||||
self.a ^= value;
|
||||
self.set_zn(self.a);
|
||||
cycles
|
||||
}
|
||||
0x45 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Zp, 3);
|
||||
self.a ^= value;
|
||||
self.set_zn(self.a);
|
||||
cycles
|
||||
}
|
||||
0x55 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Zpx, 4);
|
||||
self.a ^= value;
|
||||
self.set_zn(self.a);
|
||||
cycles
|
||||
}
|
||||
0x4D => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Abs, 4);
|
||||
self.a ^= value;
|
||||
self.set_zn(self.a);
|
||||
cycles
|
||||
}
|
||||
0x5D => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::AbxCross, 4);
|
||||
self.a ^= value;
|
||||
self.set_zn(self.a);
|
||||
cycles
|
||||
}
|
||||
0x59 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::AbyCross, 4);
|
||||
self.a ^= value;
|
||||
self.set_zn(self.a);
|
||||
cycles
|
||||
}
|
||||
0x41 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Indx, 6);
|
||||
self.a ^= value;
|
||||
self.set_zn(self.a);
|
||||
cycles
|
||||
}
|
||||
0x51 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::IndyCross, 5);
|
||||
self.a ^= value;
|
||||
self.set_zn(self.a);
|
||||
cycles
|
||||
}
|
||||
|
||||
// BIT
|
||||
0x24 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Zp, 3);
|
||||
self.bit(value);
|
||||
cycles
|
||||
}
|
||||
0x2C => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Abs, 4);
|
||||
self.bit(value);
|
||||
cycles
|
||||
}
|
||||
|
||||
// CMP
|
||||
0xC9 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Imm, 2);
|
||||
self.compare(self.a, value);
|
||||
cycles
|
||||
}
|
||||
0xC5 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Zp, 3);
|
||||
self.compare(self.a, value);
|
||||
cycles
|
||||
}
|
||||
0xD5 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Zpx, 4);
|
||||
self.compare(self.a, value);
|
||||
cycles
|
||||
}
|
||||
0xCD => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Abs, 4);
|
||||
self.compare(self.a, value);
|
||||
cycles
|
||||
}
|
||||
0xDD => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::AbxCross, 4);
|
||||
self.compare(self.a, value);
|
||||
cycles
|
||||
}
|
||||
0xD9 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::AbyCross, 4);
|
||||
self.compare(self.a, value);
|
||||
cycles
|
||||
}
|
||||
0xC1 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Indx, 6);
|
||||
self.compare(self.a, value);
|
||||
cycles
|
||||
}
|
||||
0xD1 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::IndyCross, 5);
|
||||
self.compare(self.a, value);
|
||||
cycles
|
||||
}
|
||||
|
||||
// CPX / CPY
|
||||
0xE0 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Imm, 2);
|
||||
self.compare(self.x, value);
|
||||
cycles
|
||||
}
|
||||
0xE4 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Zp, 3);
|
||||
self.compare(self.x, value);
|
||||
cycles
|
||||
}
|
||||
0xEC => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Abs, 4);
|
||||
self.compare(self.x, value);
|
||||
cycles
|
||||
}
|
||||
0xC0 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Imm, 2);
|
||||
self.compare(self.y, value);
|
||||
cycles
|
||||
}
|
||||
0xC4 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Zp, 3);
|
||||
self.compare(self.y, value);
|
||||
cycles
|
||||
}
|
||||
0xCC => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Abs, 4);
|
||||
self.compare(self.y, value);
|
||||
cycles
|
||||
}
|
||||
|
||||
// ASL
|
||||
0x0A => {
|
||||
self.a = self.asl(self.a);
|
||||
2
|
||||
}
|
||||
0x06 => self.op_rmw(bus, OperandWriteMode::Zp, 5, |cpu, value| cpu.asl(value)),
|
||||
0x16 => self.op_rmw(bus, OperandWriteMode::Zpx, 6, |cpu, value| cpu.asl(value)),
|
||||
0x0E => self.op_rmw(bus, OperandWriteMode::Abs, 6, |cpu, value| cpu.asl(value)),
|
||||
0x1E => self.op_rmw(bus, OperandWriteMode::Abx, 7, |cpu, value| cpu.asl(value)),
|
||||
|
||||
// LSR
|
||||
0x4A => {
|
||||
self.a = self.lsr(self.a);
|
||||
2
|
||||
}
|
||||
0x46 => self.op_rmw(bus, OperandWriteMode::Zp, 5, |cpu, value| cpu.lsr(value)),
|
||||
0x56 => self.op_rmw(bus, OperandWriteMode::Zpx, 6, |cpu, value| cpu.lsr(value)),
|
||||
0x4E => self.op_rmw(bus, OperandWriteMode::Abs, 6, |cpu, value| cpu.lsr(value)),
|
||||
0x5E => self.op_rmw(bus, OperandWriteMode::Abx, 7, |cpu, value| cpu.lsr(value)),
|
||||
|
||||
// ROL
|
||||
0x2A => {
|
||||
self.a = self.rol(self.a);
|
||||
2
|
||||
}
|
||||
0x26 => self.op_rmw(bus, OperandWriteMode::Zp, 5, |cpu, value| cpu.rol(value)),
|
||||
0x36 => self.op_rmw(bus, OperandWriteMode::Zpx, 6, |cpu, value| cpu.rol(value)),
|
||||
0x2E => self.op_rmw(bus, OperandWriteMode::Abs, 6, |cpu, value| cpu.rol(value)),
|
||||
0x3E => self.op_rmw(bus, OperandWriteMode::Abx, 7, |cpu, value| cpu.rol(value)),
|
||||
|
||||
// ROR
|
||||
0x6A => {
|
||||
self.a = self.ror(self.a);
|
||||
2
|
||||
}
|
||||
0x66 => self.op_rmw(bus, OperandWriteMode::Zp, 5, |cpu, value| cpu.ror(value)),
|
||||
0x76 => self.op_rmw(bus, OperandWriteMode::Zpx, 6, |cpu, value| cpu.ror(value)),
|
||||
0x6E => self.op_rmw(bus, OperandWriteMode::Abs, 6, |cpu, value| cpu.ror(value)),
|
||||
0x7E => self.op_rmw(bus, OperandWriteMode::Abx, 7, |cpu, value| cpu.ror(value)),
|
||||
|
||||
_ => return None,
|
||||
};
|
||||
Some(cycles)
|
||||
}
|
||||
}
|
||||
133
src/native_core/cpu/opcodes/official/control.rs
Normal file
133
src/native_core/cpu/opcodes/official/control.rs
Normal file
@@ -0,0 +1,133 @@
|
||||
use super::*;
|
||||
|
||||
impl Cpu6502 {
|
||||
pub(super) fn execute_official_control<B: CpuBus>(
|
||||
&mut self,
|
||||
bus: &mut B,
|
||||
opcode: u8,
|
||||
) -> Option<u8> {
|
||||
let cycles = match opcode {
|
||||
0xEA => 2,
|
||||
|
||||
// Jumps / calls
|
||||
0x4C => {
|
||||
self.pc = self.addr_abs(bus);
|
||||
3
|
||||
}
|
||||
0x6C => {
|
||||
let ptr = self.addr_abs(bus);
|
||||
self.pc = self.read_u16_bug(bus, ptr);
|
||||
5
|
||||
}
|
||||
0x20 => {
|
||||
let addr = self.addr_abs(bus);
|
||||
let ret = self.pc.wrapping_sub(1);
|
||||
self.push(bus, (ret >> 8) as u8);
|
||||
self.push(bus, ret as u8);
|
||||
self.pc = addr;
|
||||
6
|
||||
}
|
||||
0x60 => {
|
||||
let lo = self.pop(bus) as u16;
|
||||
let hi = self.pop(bus) as u16;
|
||||
self.pc = ((hi << 8) | lo).wrapping_add(1);
|
||||
6
|
||||
}
|
||||
|
||||
// Stack
|
||||
0x48 => {
|
||||
self.push(bus, self.a);
|
||||
3
|
||||
}
|
||||
0x68 => {
|
||||
self.a = self.pop(bus);
|
||||
self.set_zn(self.a);
|
||||
4
|
||||
}
|
||||
0x08 => {
|
||||
self.push(bus, self.p | FLAG_BREAK | FLAG_UNUSED);
|
||||
3
|
||||
}
|
||||
0x28 => {
|
||||
let old_i = (self.p & FLAG_IRQ_DISABLE) != 0;
|
||||
self.p = (self.pop(bus) | FLAG_UNUSED) & !FLAG_BREAK;
|
||||
let new_i = (self.p & FLAG_IRQ_DISABLE) != 0;
|
||||
if old_i && !new_i {
|
||||
self.irq_delay = true;
|
||||
}
|
||||
4
|
||||
}
|
||||
|
||||
// Flags
|
||||
0x18 => {
|
||||
self.p &= !FLAG_CARRY;
|
||||
2
|
||||
}
|
||||
0x38 => {
|
||||
self.p |= FLAG_CARRY;
|
||||
2
|
||||
}
|
||||
0x58 => {
|
||||
self.p &= !FLAG_IRQ_DISABLE;
|
||||
self.irq_delay = true;
|
||||
2
|
||||
}
|
||||
0x78 => {
|
||||
self.p |= FLAG_IRQ_DISABLE;
|
||||
2
|
||||
}
|
||||
0xD8 => {
|
||||
self.p &= !FLAG_DECIMAL;
|
||||
2
|
||||
}
|
||||
0xF8 => {
|
||||
self.p |= FLAG_DECIMAL;
|
||||
2
|
||||
}
|
||||
0xB8 => {
|
||||
self.p &= !FLAG_OVERFLOW;
|
||||
2
|
||||
}
|
||||
|
||||
// BRK / RTI
|
||||
0x00 => {
|
||||
self.pc = self.pc.wrapping_add(1);
|
||||
self.push(bus, (self.pc >> 8) as u8);
|
||||
self.push(bus, self.pc as u8);
|
||||
self.push(bus, self.p | FLAG_BREAK | FLAG_UNUSED);
|
||||
self.p |= FLAG_IRQ_DISABLE;
|
||||
// NMI can hijack BRK and force vector $FFFA while preserving B=1 on stack.
|
||||
let vector = if bus.poll_nmi() { 0xFFFA } else { 0xFFFE };
|
||||
self.pending_nmi = false;
|
||||
self.pending_irq = false;
|
||||
self.pc = self.read_u16(bus, vector);
|
||||
7
|
||||
}
|
||||
0x40 => {
|
||||
let old_i = (self.p & FLAG_IRQ_DISABLE) != 0;
|
||||
self.p = (self.pop(bus) | FLAG_UNUSED) & !FLAG_BREAK;
|
||||
let new_i = (self.p & FLAG_IRQ_DISABLE) != 0;
|
||||
if old_i && !new_i {
|
||||
self.irq_delay = true;
|
||||
}
|
||||
let lo = self.pop(bus) as u16;
|
||||
let hi = self.pop(bus) as u16;
|
||||
self.pc = (hi << 8) | lo;
|
||||
6
|
||||
}
|
||||
|
||||
// Branches
|
||||
0xD0 => self.branch(bus, (self.p & FLAG_ZERO) == 0),
|
||||
0xF0 => self.branch(bus, (self.p & FLAG_ZERO) != 0),
|
||||
0x10 => self.branch(bus, (self.p & FLAG_NEGATIVE) == 0),
|
||||
0x30 => self.branch(bus, (self.p & FLAG_NEGATIVE) != 0),
|
||||
0x90 => self.branch(bus, (self.p & FLAG_CARRY) == 0),
|
||||
0xB0 => self.branch(bus, (self.p & FLAG_CARRY) != 0),
|
||||
0x50 => self.branch(bus, (self.p & FLAG_OVERFLOW) == 0),
|
||||
0x70 => self.branch(bus, (self.p & FLAG_OVERFLOW) != 0),
|
||||
|
||||
_ => return None,
|
||||
};
|
||||
Some(cycles)
|
||||
}
|
||||
}
|
||||
239
src/native_core/cpu/opcodes/official/load_store.rs
Normal file
239
src/native_core/cpu/opcodes/official/load_store.rs
Normal file
@@ -0,0 +1,239 @@
|
||||
use super::ops::{OperandReadMode, OperandWriteMode};
|
||||
use super::*;
|
||||
|
||||
impl Cpu6502 {
|
||||
pub(super) fn execute_official_load_store<B: CpuBus>(
|
||||
&mut self,
|
||||
bus: &mut B,
|
||||
opcode: u8,
|
||||
) -> Option<u8> {
|
||||
let cycles = match opcode {
|
||||
// LDA
|
||||
0xA9 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Imm, 2);
|
||||
self.a = value;
|
||||
self.set_zn(self.a);
|
||||
cycles
|
||||
}
|
||||
0xA5 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Zp, 3);
|
||||
self.a = value;
|
||||
self.set_zn(self.a);
|
||||
cycles
|
||||
}
|
||||
0xB5 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Zpx, 4);
|
||||
self.a = value;
|
||||
self.set_zn(self.a);
|
||||
cycles
|
||||
}
|
||||
0xAD => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Abs, 4);
|
||||
self.a = value;
|
||||
self.set_zn(self.a);
|
||||
cycles
|
||||
}
|
||||
0xBD => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::AbxCross, 4);
|
||||
self.a = value;
|
||||
self.set_zn(self.a);
|
||||
cycles
|
||||
}
|
||||
0xB9 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::AbyCross, 4);
|
||||
self.a = value;
|
||||
self.set_zn(self.a);
|
||||
cycles
|
||||
}
|
||||
0xA1 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Indx, 6);
|
||||
self.a = value;
|
||||
self.set_zn(self.a);
|
||||
cycles
|
||||
}
|
||||
0xB1 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::IndyCross, 5);
|
||||
self.a = value;
|
||||
self.set_zn(self.a);
|
||||
cycles
|
||||
}
|
||||
|
||||
// LDX
|
||||
0xA2 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Imm, 2);
|
||||
self.x = value;
|
||||
self.set_zn(self.x);
|
||||
cycles
|
||||
}
|
||||
0xA6 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Zp, 3);
|
||||
self.x = value;
|
||||
self.set_zn(self.x);
|
||||
cycles
|
||||
}
|
||||
0xB6 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Zpy, 4);
|
||||
self.x = value;
|
||||
self.set_zn(self.x);
|
||||
cycles
|
||||
}
|
||||
0xAE => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Abs, 4);
|
||||
self.x = value;
|
||||
self.set_zn(self.x);
|
||||
cycles
|
||||
}
|
||||
0xBE => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::AbyCross, 4);
|
||||
self.x = value;
|
||||
self.set_zn(self.x);
|
||||
cycles
|
||||
}
|
||||
|
||||
// LDY
|
||||
0xA0 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Imm, 2);
|
||||
self.y = value;
|
||||
self.set_zn(self.y);
|
||||
cycles
|
||||
}
|
||||
0xA4 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Zp, 3);
|
||||
self.y = value;
|
||||
self.set_zn(self.y);
|
||||
cycles
|
||||
}
|
||||
0xB4 => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Zpx, 4);
|
||||
self.y = value;
|
||||
self.set_zn(self.y);
|
||||
cycles
|
||||
}
|
||||
0xAC => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::Abs, 4);
|
||||
self.y = value;
|
||||
self.set_zn(self.y);
|
||||
cycles
|
||||
}
|
||||
0xBC => {
|
||||
let (value, cycles) = self.op_read(bus, OperandReadMode::AbxCross, 4);
|
||||
self.y = value;
|
||||
self.set_zn(self.y);
|
||||
cycles
|
||||
}
|
||||
|
||||
// STA/STX/STY
|
||||
0x85 => self.op_store(bus, OperandWriteMode::Zp, self.a, 3),
|
||||
0x95 => self.op_store(bus, OperandWriteMode::Zpx, self.a, 4),
|
||||
0x8D => self.op_store(bus, OperandWriteMode::Abs, self.a, 4),
|
||||
0x9D => self.op_store(bus, OperandWriteMode::Abx, self.a, 5),
|
||||
0x99 => self.op_store(bus, OperandWriteMode::Aby, self.a, 5),
|
||||
0x81 => self.op_store(bus, OperandWriteMode::Indx, self.a, 6),
|
||||
0x91 => self.op_store(bus, OperandWriteMode::Indy, self.a, 6),
|
||||
0x86 => self.op_store(bus, OperandWriteMode::Zp, self.x, 3),
|
||||
0x96 => self.op_store(bus, OperandWriteMode::Zpy, self.x, 4),
|
||||
0x8E => self.op_store(bus, OperandWriteMode::Abs, self.x, 4),
|
||||
0x84 => self.op_store(bus, OperandWriteMode::Zp, self.y, 3),
|
||||
0x94 => self.op_store(bus, OperandWriteMode::Zpx, self.y, 4),
|
||||
0x8C => self.op_store(bus, OperandWriteMode::Abs, self.y, 4),
|
||||
|
||||
// Transfers
|
||||
0xAA => {
|
||||
self.x = self.a;
|
||||
self.set_zn(self.x);
|
||||
2
|
||||
}
|
||||
0x8A => {
|
||||
self.a = self.x;
|
||||
self.set_zn(self.a);
|
||||
2
|
||||
}
|
||||
0xA8 => {
|
||||
self.y = self.a;
|
||||
self.set_zn(self.y);
|
||||
2
|
||||
}
|
||||
0x98 => {
|
||||
self.a = self.y;
|
||||
self.set_zn(self.a);
|
||||
2
|
||||
}
|
||||
0xBA => {
|
||||
self.x = self.sp;
|
||||
self.set_zn(self.x);
|
||||
2
|
||||
}
|
||||
0x9A => {
|
||||
self.sp = self.x;
|
||||
2
|
||||
}
|
||||
|
||||
// Increments / decrements regs
|
||||
0xE8 => {
|
||||
self.x = self.x.wrapping_add(1);
|
||||
self.set_zn(self.x);
|
||||
2
|
||||
}
|
||||
0xC8 => {
|
||||
self.y = self.y.wrapping_add(1);
|
||||
self.set_zn(self.y);
|
||||
2
|
||||
}
|
||||
0xCA => {
|
||||
self.x = self.x.wrapping_sub(1);
|
||||
self.set_zn(self.x);
|
||||
2
|
||||
}
|
||||
0x88 => {
|
||||
self.y = self.y.wrapping_sub(1);
|
||||
self.set_zn(self.y);
|
||||
2
|
||||
}
|
||||
|
||||
// INC/DEC memory
|
||||
0xE6 => self.op_rmw(bus, OperandWriteMode::Zp, 5, |cpu, value| {
|
||||
let value = value.wrapping_add(1);
|
||||
cpu.set_zn(value);
|
||||
value
|
||||
}),
|
||||
0xF6 => self.op_rmw(bus, OperandWriteMode::Zpx, 6, |cpu, value| {
|
||||
let value = value.wrapping_add(1);
|
||||
cpu.set_zn(value);
|
||||
value
|
||||
}),
|
||||
0xEE => self.op_rmw(bus, OperandWriteMode::Abs, 6, |cpu, value| {
|
||||
let value = value.wrapping_add(1);
|
||||
cpu.set_zn(value);
|
||||
value
|
||||
}),
|
||||
0xFE => self.op_rmw(bus, OperandWriteMode::Abx, 7, |cpu, value| {
|
||||
let value = value.wrapping_add(1);
|
||||
cpu.set_zn(value);
|
||||
value
|
||||
}),
|
||||
0xC6 => self.op_rmw(bus, OperandWriteMode::Zp, 5, |cpu, value| {
|
||||
let value = value.wrapping_sub(1);
|
||||
cpu.set_zn(value);
|
||||
value
|
||||
}),
|
||||
0xD6 => self.op_rmw(bus, OperandWriteMode::Zpx, 6, |cpu, value| {
|
||||
let value = value.wrapping_sub(1);
|
||||
cpu.set_zn(value);
|
||||
value
|
||||
}),
|
||||
0xCE => self.op_rmw(bus, OperandWriteMode::Abs, 6, |cpu, value| {
|
||||
let value = value.wrapping_sub(1);
|
||||
cpu.set_zn(value);
|
||||
value
|
||||
}),
|
||||
0xDE => self.op_rmw(bus, OperandWriteMode::Abx, 7, |cpu, value| {
|
||||
let value = value.wrapping_sub(1);
|
||||
cpu.set_zn(value);
|
||||
value
|
||||
}),
|
||||
|
||||
_ => return None,
|
||||
};
|
||||
Some(cycles)
|
||||
}
|
||||
}
|
||||
13
src/native_core/cpu/opcodes/official/mod.rs
Normal file
13
src/native_core/cpu/opcodes/official/mod.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
use super::*;
|
||||
|
||||
mod alu;
|
||||
mod control;
|
||||
mod load_store;
|
||||
|
||||
impl Cpu6502 {
|
||||
pub(super) fn execute_official<B: CpuBus>(&mut self, bus: &mut B, opcode: u8) -> Option<u8> {
|
||||
self.execute_official_load_store(bus, opcode)
|
||||
.or_else(|| self.execute_official_alu(bus, opcode))
|
||||
.or_else(|| self.execute_official_control(bus, opcode))
|
||||
}
|
||||
}
|
||||
100
src/native_core/cpu/opcodes/ops.rs
Normal file
100
src/native_core/cpu/opcodes/ops.rs
Normal file
@@ -0,0 +1,100 @@
|
||||
use super::*;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(super) enum OperandReadMode {
|
||||
Imm,
|
||||
Zp,
|
||||
Zpx,
|
||||
Zpy,
|
||||
Abs,
|
||||
AbxCross,
|
||||
AbyCross,
|
||||
Indx,
|
||||
IndyCross,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(super) enum OperandWriteMode {
|
||||
Zp,
|
||||
Zpx,
|
||||
Zpy,
|
||||
Abs,
|
||||
Abx,
|
||||
Aby,
|
||||
Indx,
|
||||
Indy,
|
||||
}
|
||||
|
||||
impl Cpu6502 {
|
||||
pub(super) fn op_read<B: CpuBus>(
|
||||
&mut self,
|
||||
bus: &mut B,
|
||||
mode: OperandReadMode,
|
||||
base_cycles: u8,
|
||||
) -> (u8, u8) {
|
||||
match mode {
|
||||
OperandReadMode::Imm => (self.fetch(bus), base_cycles),
|
||||
OperandReadMode::Zp => (self.read_zp(bus), base_cycles),
|
||||
OperandReadMode::Zpx => (self.read_zpx(bus), base_cycles),
|
||||
OperandReadMode::Zpy => (self.read_zpy(bus), base_cycles),
|
||||
OperandReadMode::Abs => (self.read_abs(bus), base_cycles),
|
||||
OperandReadMode::AbxCross => {
|
||||
let (value, crossed) = self.read_abx_cross(bus);
|
||||
(value, base_cycles + u8::from(crossed))
|
||||
}
|
||||
OperandReadMode::AbyCross => {
|
||||
let (value, crossed) = self.read_aby_cross(bus);
|
||||
(value, base_cycles + u8::from(crossed))
|
||||
}
|
||||
OperandReadMode::Indx => (self.read_indx(bus), base_cycles),
|
||||
OperandReadMode::IndyCross => {
|
||||
let (value, crossed) = self.read_indy_cross(bus);
|
||||
(value, base_cycles + u8::from(crossed))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn op_store<B: CpuBus>(
|
||||
&mut self,
|
||||
bus: &mut B,
|
||||
mode: OperandWriteMode,
|
||||
value: u8,
|
||||
cycles: u8,
|
||||
) -> u8 {
|
||||
let addr = self.addr_for_write(bus, mode);
|
||||
bus.write(addr, value);
|
||||
cycles
|
||||
}
|
||||
|
||||
pub(super) fn op_rmw<B: CpuBus, F>(
|
||||
&mut self,
|
||||
bus: &mut B,
|
||||
mode: OperandWriteMode,
|
||||
cycles: u8,
|
||||
op: F,
|
||||
) -> u8
|
||||
where
|
||||
F: FnOnce(&mut Self, u8) -> u8,
|
||||
{
|
||||
let addr = self.addr_for_write(bus, mode);
|
||||
let old = bus.read(addr);
|
||||
// 6502 RMW does a dummy write of the unmodified value before the final value.
|
||||
bus.write(addr, old);
|
||||
let value = op(self, old);
|
||||
bus.write(addr, value);
|
||||
cycles
|
||||
}
|
||||
|
||||
fn addr_for_write<B: CpuBus>(&mut self, bus: &mut B, mode: OperandWriteMode) -> u16 {
|
||||
match mode {
|
||||
OperandWriteMode::Zp => self.addr_zp(bus),
|
||||
OperandWriteMode::Zpx => self.addr_zpx(bus),
|
||||
OperandWriteMode::Zpy => self.addr_zpy(bus),
|
||||
OperandWriteMode::Abs => self.addr_abs(bus),
|
||||
OperandWriteMode::Abx => self.addr_abx(bus),
|
||||
OperandWriteMode::Aby => self.addr_aby(bus),
|
||||
OperandWriteMode::Indx => self.addr_indx(bus),
|
||||
OperandWriteMode::Indy => self.addr_indy(bus),
|
||||
}
|
||||
}
|
||||
}
|
||||
94
src/native_core/cpu/opcodes/undocumented/combos.rs
Normal file
94
src/native_core/cpu/opcodes/undocumented/combos.rs
Normal file
@@ -0,0 +1,94 @@
|
||||
use super::*;
|
||||
|
||||
impl Cpu6502 {
|
||||
pub(super) fn execute_undocumented_combos<B: CpuBus>(
|
||||
&mut self,
|
||||
bus: &mut B,
|
||||
opcode: u8,
|
||||
) -> Option<u8> {
|
||||
let cycles = match opcode {
|
||||
// Undocumented immediate combos
|
||||
0x0B | 0x2B => {
|
||||
self.a &= self.fetch(bus);
|
||||
self.set_zn(self.a);
|
||||
self.set_flag(FLAG_CARRY, (self.a & 0x80) != 0);
|
||||
2
|
||||
}
|
||||
0x4B => {
|
||||
self.a &= self.fetch(bus);
|
||||
self.a = self.lsr(self.a);
|
||||
2
|
||||
}
|
||||
0x6B => {
|
||||
let imm = self.fetch(bus);
|
||||
self.a &= imm;
|
||||
self.a = self.ror(self.a);
|
||||
let bit5 = (self.a & 0x20) != 0;
|
||||
let bit6 = (self.a & 0x40) != 0;
|
||||
self.set_flag(FLAG_CARRY, bit6);
|
||||
self.set_flag(FLAG_OVERFLOW, bit5 ^ bit6);
|
||||
2
|
||||
}
|
||||
0xCB => {
|
||||
let imm = self.fetch(bus);
|
||||
let ax = self.a & self.x;
|
||||
let v = ax.wrapping_sub(imm);
|
||||
self.x = v;
|
||||
self.set_flag(FLAG_CARRY, ax >= imm);
|
||||
self.set_zn(self.x);
|
||||
2
|
||||
}
|
||||
0xEB => {
|
||||
let value = self.fetch(bus);
|
||||
self.adc(!value);
|
||||
2
|
||||
}
|
||||
0xBB => {
|
||||
let (addr, crossed) = self.addr_aby_cross(bus);
|
||||
let v = bus.read(addr) & self.sp;
|
||||
self.a = v;
|
||||
self.x = v;
|
||||
self.sp = v;
|
||||
self.set_zn(v);
|
||||
4 + u8::from(crossed)
|
||||
}
|
||||
0x93 => {
|
||||
let zp = self.fetch(bus);
|
||||
let lo = bus.read(zp as u16) as u16;
|
||||
let hi = bus.read(zp.wrapping_add(1) as u16) as u16;
|
||||
let addr = ((hi << 8) | lo).wrapping_add(self.y as u16);
|
||||
let hi_mask = ((addr >> 8) as u8).wrapping_add(1);
|
||||
bus.write(addr, self.a & self.x & hi_mask);
|
||||
6
|
||||
}
|
||||
0x9F => {
|
||||
let addr = self.addr_aby(bus);
|
||||
let hi_mask = ((addr >> 8) as u8).wrapping_add(1);
|
||||
bus.write(addr, self.a & self.x & hi_mask);
|
||||
5
|
||||
}
|
||||
0x9B => {
|
||||
let addr = self.addr_aby(bus);
|
||||
self.sp = self.a & self.x;
|
||||
let hi_mask = ((addr >> 8) as u8).wrapping_add(1);
|
||||
bus.write(addr, self.sp & hi_mask);
|
||||
5
|
||||
}
|
||||
0x9C => {
|
||||
let addr = self.addr_abx(bus);
|
||||
let hi_mask = ((addr >> 8) as u8).wrapping_add(1);
|
||||
bus.write(addr, self.y & hi_mask);
|
||||
5
|
||||
}
|
||||
0x9E => {
|
||||
let addr = self.addr_aby(bus);
|
||||
let hi_mask = ((addr >> 8) as u8).wrapping_add(1);
|
||||
bus.write(addr, self.x & hi_mask);
|
||||
5
|
||||
}
|
||||
|
||||
_ => return None,
|
||||
};
|
||||
Some(cycles)
|
||||
}
|
||||
}
|
||||
18
src/native_core/cpu/opcodes/undocumented/mod.rs
Normal file
18
src/native_core/cpu/opcodes/undocumented/mod.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
use super::*;
|
||||
|
||||
mod combos;
|
||||
mod rmw;
|
||||
mod system;
|
||||
|
||||
impl Cpu6502 {
|
||||
pub(super) fn execute_undocumented<B: CpuBus>(
|
||||
&mut self,
|
||||
bus: &mut B,
|
||||
opcode: u8,
|
||||
pc_before: u16,
|
||||
) -> Option<u8> {
|
||||
self.execute_undocumented_system(bus, opcode, pc_before)
|
||||
.or_else(|| self.execute_undocumented_rmw(bus, opcode))
|
||||
.or_else(|| self.execute_undocumented_combos(bus, opcode))
|
||||
}
|
||||
}
|
||||
258
src/native_core/cpu/opcodes/undocumented/rmw.rs
Normal file
258
src/native_core/cpu/opcodes/undocumented/rmw.rs
Normal file
@@ -0,0 +1,258 @@
|
||||
use super::ops::OperandWriteMode;
|
||||
use super::*;
|
||||
|
||||
impl Cpu6502 {
|
||||
pub(super) fn execute_undocumented_rmw<B: CpuBus>(
|
||||
&mut self,
|
||||
bus: &mut B,
|
||||
opcode: u8,
|
||||
) -> Option<u8> {
|
||||
let cycles = match opcode {
|
||||
// Undocumented SLO: ASL M then ORA M
|
||||
0x03 => self.op_rmw(bus, OperandWriteMode::Indx, 8, |cpu, value| {
|
||||
let v = cpu.asl(value);
|
||||
cpu.a |= v;
|
||||
cpu.set_zn(cpu.a);
|
||||
v
|
||||
}),
|
||||
0x07 => self.op_rmw(bus, OperandWriteMode::Zp, 5, |cpu, value| {
|
||||
let v = cpu.asl(value);
|
||||
cpu.a |= v;
|
||||
cpu.set_zn(cpu.a);
|
||||
v
|
||||
}),
|
||||
0x0F => self.op_rmw(bus, OperandWriteMode::Abs, 6, |cpu, value| {
|
||||
let v = cpu.asl(value);
|
||||
cpu.a |= v;
|
||||
cpu.set_zn(cpu.a);
|
||||
v
|
||||
}),
|
||||
0x13 => self.op_rmw(bus, OperandWriteMode::Indy, 8, |cpu, value| {
|
||||
let v = cpu.asl(value);
|
||||
cpu.a |= v;
|
||||
cpu.set_zn(cpu.a);
|
||||
v
|
||||
}),
|
||||
0x17 => self.op_rmw(bus, OperandWriteMode::Zpx, 6, |cpu, value| {
|
||||
let v = cpu.asl(value);
|
||||
cpu.a |= v;
|
||||
cpu.set_zn(cpu.a);
|
||||
v
|
||||
}),
|
||||
0x1B => self.op_rmw(bus, OperandWriteMode::Aby, 7, |cpu, value| {
|
||||
let v = cpu.asl(value);
|
||||
cpu.a |= v;
|
||||
cpu.set_zn(cpu.a);
|
||||
v
|
||||
}),
|
||||
0x1F => self.op_rmw(bus, OperandWriteMode::Abx, 7, |cpu, value| {
|
||||
let v = cpu.asl(value);
|
||||
cpu.a |= v;
|
||||
cpu.set_zn(cpu.a);
|
||||
v
|
||||
}),
|
||||
|
||||
// Undocumented RLA: ROL M then AND M
|
||||
0x23 => self.op_rmw(bus, OperandWriteMode::Indx, 8, |cpu, value| {
|
||||
let v = cpu.rol(value);
|
||||
cpu.a &= v;
|
||||
cpu.set_zn(cpu.a);
|
||||
v
|
||||
}),
|
||||
0x27 => self.op_rmw(bus, OperandWriteMode::Zp, 5, |cpu, value| {
|
||||
let v = cpu.rol(value);
|
||||
cpu.a &= v;
|
||||
cpu.set_zn(cpu.a);
|
||||
v
|
||||
}),
|
||||
0x2F => self.op_rmw(bus, OperandWriteMode::Abs, 6, |cpu, value| {
|
||||
let v = cpu.rol(value);
|
||||
cpu.a &= v;
|
||||
cpu.set_zn(cpu.a);
|
||||
v
|
||||
}),
|
||||
0x33 => self.op_rmw(bus, OperandWriteMode::Indy, 8, |cpu, value| {
|
||||
let v = cpu.rol(value);
|
||||
cpu.a &= v;
|
||||
cpu.set_zn(cpu.a);
|
||||
v
|
||||
}),
|
||||
0x37 => self.op_rmw(bus, OperandWriteMode::Zpx, 6, |cpu, value| {
|
||||
let v = cpu.rol(value);
|
||||
cpu.a &= v;
|
||||
cpu.set_zn(cpu.a);
|
||||
v
|
||||
}),
|
||||
0x3B => self.op_rmw(bus, OperandWriteMode::Aby, 7, |cpu, value| {
|
||||
let v = cpu.rol(value);
|
||||
cpu.a &= v;
|
||||
cpu.set_zn(cpu.a);
|
||||
v
|
||||
}),
|
||||
0x3F => self.op_rmw(bus, OperandWriteMode::Abx, 7, |cpu, value| {
|
||||
let v = cpu.rol(value);
|
||||
cpu.a &= v;
|
||||
cpu.set_zn(cpu.a);
|
||||
v
|
||||
}),
|
||||
|
||||
// Undocumented SRE: LSR M then EOR M
|
||||
0x43 => self.op_rmw(bus, OperandWriteMode::Indx, 8, |cpu, value| {
|
||||
let v = cpu.lsr(value);
|
||||
cpu.a ^= v;
|
||||
cpu.set_zn(cpu.a);
|
||||
v
|
||||
}),
|
||||
0x47 => self.op_rmw(bus, OperandWriteMode::Zp, 5, |cpu, value| {
|
||||
let v = cpu.lsr(value);
|
||||
cpu.a ^= v;
|
||||
cpu.set_zn(cpu.a);
|
||||
v
|
||||
}),
|
||||
0x4F => self.op_rmw(bus, OperandWriteMode::Abs, 6, |cpu, value| {
|
||||
let v = cpu.lsr(value);
|
||||
cpu.a ^= v;
|
||||
cpu.set_zn(cpu.a);
|
||||
v
|
||||
}),
|
||||
0x53 => self.op_rmw(bus, OperandWriteMode::Indy, 8, |cpu, value| {
|
||||
let v = cpu.lsr(value);
|
||||
cpu.a ^= v;
|
||||
cpu.set_zn(cpu.a);
|
||||
v
|
||||
}),
|
||||
0x57 => self.op_rmw(bus, OperandWriteMode::Zpx, 6, |cpu, value| {
|
||||
let v = cpu.lsr(value);
|
||||
cpu.a ^= v;
|
||||
cpu.set_zn(cpu.a);
|
||||
v
|
||||
}),
|
||||
0x5B => self.op_rmw(bus, OperandWriteMode::Aby, 7, |cpu, value| {
|
||||
let v = cpu.lsr(value);
|
||||
cpu.a ^= v;
|
||||
cpu.set_zn(cpu.a);
|
||||
v
|
||||
}),
|
||||
0x5F => self.op_rmw(bus, OperandWriteMode::Abx, 7, |cpu, value| {
|
||||
let v = cpu.lsr(value);
|
||||
cpu.a ^= v;
|
||||
cpu.set_zn(cpu.a);
|
||||
v
|
||||
}),
|
||||
|
||||
// Undocumented RRA: ROR M then ADC M
|
||||
0x63 => self.op_rmw(bus, OperandWriteMode::Indx, 8, |cpu, value| {
|
||||
let v = cpu.ror(value);
|
||||
cpu.adc(v);
|
||||
v
|
||||
}),
|
||||
0x67 => self.op_rmw(bus, OperandWriteMode::Zp, 5, |cpu, value| {
|
||||
let v = cpu.ror(value);
|
||||
cpu.adc(v);
|
||||
v
|
||||
}),
|
||||
0x6F => self.op_rmw(bus, OperandWriteMode::Abs, 6, |cpu, value| {
|
||||
let v = cpu.ror(value);
|
||||
cpu.adc(v);
|
||||
v
|
||||
}),
|
||||
0x73 => self.op_rmw(bus, OperandWriteMode::Indy, 8, |cpu, value| {
|
||||
let v = cpu.ror(value);
|
||||
cpu.adc(v);
|
||||
v
|
||||
}),
|
||||
0x77 => self.op_rmw(bus, OperandWriteMode::Zpx, 6, |cpu, value| {
|
||||
let v = cpu.ror(value);
|
||||
cpu.adc(v);
|
||||
v
|
||||
}),
|
||||
0x7B => self.op_rmw(bus, OperandWriteMode::Aby, 7, |cpu, value| {
|
||||
let v = cpu.ror(value);
|
||||
cpu.adc(v);
|
||||
v
|
||||
}),
|
||||
0x7F => self.op_rmw(bus, OperandWriteMode::Abx, 7, |cpu, value| {
|
||||
let v = cpu.ror(value);
|
||||
cpu.adc(v);
|
||||
v
|
||||
}),
|
||||
|
||||
// Undocumented DCP: DEC M then CMP M
|
||||
0xC3 => self.op_rmw(bus, OperandWriteMode::Indx, 8, |cpu, value| {
|
||||
let v = value.wrapping_sub(1);
|
||||
cpu.compare(cpu.a, v);
|
||||
v
|
||||
}),
|
||||
0xC7 => self.op_rmw(bus, OperandWriteMode::Zp, 5, |cpu, value| {
|
||||
let v = value.wrapping_sub(1);
|
||||
cpu.compare(cpu.a, v);
|
||||
v
|
||||
}),
|
||||
0xCF => self.op_rmw(bus, OperandWriteMode::Abs, 6, |cpu, value| {
|
||||
let v = value.wrapping_sub(1);
|
||||
cpu.compare(cpu.a, v);
|
||||
v
|
||||
}),
|
||||
0xD3 => self.op_rmw(bus, OperandWriteMode::Indy, 8, |cpu, value| {
|
||||
let v = value.wrapping_sub(1);
|
||||
cpu.compare(cpu.a, v);
|
||||
v
|
||||
}),
|
||||
0xD7 => self.op_rmw(bus, OperandWriteMode::Zpx, 6, |cpu, value| {
|
||||
let v = value.wrapping_sub(1);
|
||||
cpu.compare(cpu.a, v);
|
||||
v
|
||||
}),
|
||||
0xDB => self.op_rmw(bus, OperandWriteMode::Aby, 7, |cpu, value| {
|
||||
let v = value.wrapping_sub(1);
|
||||
cpu.compare(cpu.a, v);
|
||||
v
|
||||
}),
|
||||
0xDF => self.op_rmw(bus, OperandWriteMode::Abx, 7, |cpu, value| {
|
||||
let v = value.wrapping_sub(1);
|
||||
cpu.compare(cpu.a, v);
|
||||
v
|
||||
}),
|
||||
|
||||
// Undocumented ISC/ISB: INC M then SBC M
|
||||
0xE3 => self.op_rmw(bus, OperandWriteMode::Indx, 8, |cpu, value| {
|
||||
let v = value.wrapping_add(1);
|
||||
cpu.adc(!v);
|
||||
v
|
||||
}),
|
||||
0xE7 => self.op_rmw(bus, OperandWriteMode::Zp, 5, |cpu, value| {
|
||||
let v = value.wrapping_add(1);
|
||||
cpu.adc(!v);
|
||||
v
|
||||
}),
|
||||
0xEF => self.op_rmw(bus, OperandWriteMode::Abs, 6, |cpu, value| {
|
||||
let v = value.wrapping_add(1);
|
||||
cpu.adc(!v);
|
||||
v
|
||||
}),
|
||||
0xF3 => self.op_rmw(bus, OperandWriteMode::Indy, 8, |cpu, value| {
|
||||
let v = value.wrapping_add(1);
|
||||
cpu.adc(!v);
|
||||
v
|
||||
}),
|
||||
0xF7 => self.op_rmw(bus, OperandWriteMode::Zpx, 6, |cpu, value| {
|
||||
let v = value.wrapping_add(1);
|
||||
cpu.adc(!v);
|
||||
v
|
||||
}),
|
||||
0xFB => self.op_rmw(bus, OperandWriteMode::Aby, 7, |cpu, value| {
|
||||
let v = value.wrapping_add(1);
|
||||
cpu.adc(!v);
|
||||
v
|
||||
}),
|
||||
0xFF => self.op_rmw(bus, OperandWriteMode::Abx, 7, |cpu, value| {
|
||||
let v = value.wrapping_add(1);
|
||||
cpu.adc(!v);
|
||||
v
|
||||
}),
|
||||
|
||||
_ => return None,
|
||||
};
|
||||
Some(cycles)
|
||||
}
|
||||
}
|
||||
97
src/native_core/cpu/opcodes/undocumented/system.rs
Normal file
97
src/native_core/cpu/opcodes/undocumented/system.rs
Normal file
@@ -0,0 +1,97 @@
|
||||
use super::ops::{OperandReadMode, OperandWriteMode};
|
||||
use super::*;
|
||||
|
||||
impl Cpu6502 {
|
||||
pub(super) fn execute_undocumented_system<B: CpuBus>(
|
||||
&mut self,
|
||||
bus: &mut B,
|
||||
opcode: u8,
|
||||
pc_before: u16,
|
||||
) -> Option<u8> {
|
||||
let cycles = match opcode {
|
||||
// Undocumented NOPs used by commercial ROMs.
|
||||
0x1A | 0x3A | 0x5A | 0x7A | 0xDA | 0xFA => 2,
|
||||
0x80 | 0x82 | 0x89 | 0xC2 | 0xE2 => self.op_read(bus, OperandReadMode::Imm, 2).1,
|
||||
0x04 | 0x44 | 0x64 => self.op_read(bus, OperandReadMode::Zp, 3).1,
|
||||
0x14 | 0x34 | 0x54 | 0x74 | 0xD4 | 0xF4 => self.op_read(bus, OperandReadMode::Zpx, 4).1,
|
||||
0x0C => self.op_read(bus, OperandReadMode::Abs, 4).1,
|
||||
0x1C | 0x3C | 0x5C | 0x7C | 0xDC | 0xFC => {
|
||||
self.op_read(bus, OperandReadMode::AbxCross, 4).1
|
||||
}
|
||||
|
||||
// JAM/KIL opcodes lock the real CPU until reset.
|
||||
0x02 | 0x12 | 0x22 | 0x32 | 0x42 | 0x52 | 0x62 | 0x72 | 0x92 | 0xB2 | 0xD2 | 0xF2 => {
|
||||
self.halted = true;
|
||||
self.pc = pc_before;
|
||||
2
|
||||
}
|
||||
|
||||
// Undocumented LAX: A <- M, X <- A
|
||||
0xAB => {
|
||||
let (v, cycles) = self.op_read(bus, OperandReadMode::Imm, 2);
|
||||
self.a = v;
|
||||
self.x = v;
|
||||
self.set_zn(v);
|
||||
cycles
|
||||
}
|
||||
0xA3 => {
|
||||
let (v, cycles) = self.op_read(bus, OperandReadMode::Indx, 6);
|
||||
self.a = v;
|
||||
self.x = v;
|
||||
self.set_zn(v);
|
||||
cycles
|
||||
}
|
||||
0xA7 => {
|
||||
let (v, cycles) = self.op_read(bus, OperandReadMode::Zp, 3);
|
||||
self.a = v;
|
||||
self.x = v;
|
||||
self.set_zn(v);
|
||||
cycles
|
||||
}
|
||||
0xAF => {
|
||||
let (v, cycles) = self.op_read(bus, OperandReadMode::Abs, 4);
|
||||
self.a = v;
|
||||
self.x = v;
|
||||
self.set_zn(v);
|
||||
cycles
|
||||
}
|
||||
0xB3 => {
|
||||
let (v, cycles) = self.op_read(bus, OperandReadMode::IndyCross, 5);
|
||||
self.a = v;
|
||||
self.x = v;
|
||||
self.set_zn(v);
|
||||
cycles
|
||||
}
|
||||
0xB7 => {
|
||||
let (v, cycles) = self.op_read(bus, OperandReadMode::Zpy, 4);
|
||||
self.a = v;
|
||||
self.x = v;
|
||||
self.set_zn(v);
|
||||
cycles
|
||||
}
|
||||
0xBF => {
|
||||
let (v, cycles) = self.op_read(bus, OperandReadMode::AbyCross, 4);
|
||||
self.a = v;
|
||||
self.x = v;
|
||||
self.set_zn(v);
|
||||
cycles
|
||||
}
|
||||
|
||||
// Undocumented SAX: M <- A & X
|
||||
0x83 => self.op_store(bus, OperandWriteMode::Indx, self.a & self.x, 6),
|
||||
0x87 => self.op_store(bus, OperandWriteMode::Zp, self.a & self.x, 3),
|
||||
0x8F => self.op_store(bus, OperandWriteMode::Abs, self.a & self.x, 4),
|
||||
0x97 => self.op_store(bus, OperandWriteMode::Zpy, self.a & self.x, 4),
|
||||
0x8B => {
|
||||
// XAA/ANE (unstable on hardware), practical emulation form.
|
||||
let (v, cycles) = self.op_read(bus, OperandReadMode::Imm, 2);
|
||||
self.a = self.x & v;
|
||||
self.set_zn(self.a);
|
||||
cycles
|
||||
}
|
||||
|
||||
_ => return None,
|
||||
};
|
||||
Some(cycles)
|
||||
}
|
||||
}
|
||||
10
src/native_core/cpu/tests.rs
Normal file
10
src/native_core/cpu/tests.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
use super::{Cpu6502, CpuBus, FLAG_CARRY, FLAG_NEGATIVE, FLAG_ZERO};
|
||||
use crate::native_core::test_support::{TestRamBus, cpu_setup_with_reset};
|
||||
|
||||
fn setup_cpu_with_reset(prog: &[u8]) -> (Cpu6502, TestRamBus) {
|
||||
cpu_setup_with_reset(prog)
|
||||
}
|
||||
|
||||
mod core;
|
||||
mod interrupts;
|
||||
mod property;
|
||||
272
src/native_core/cpu/tests/core.rs
Normal file
272
src/native_core/cpu/tests/core.rs
Normal file
@@ -0,0 +1,272 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn lda_sta_and_branch_work() {
|
||||
let (mut cpu, mut bus) = setup_cpu_with_reset(&[
|
||||
0xA9, 0x42, // LDA #$42
|
||||
0x8D, 0x34, 0x12, // STA $1234
|
||||
0xC9, 0x42, // CMP #$42
|
||||
0xF0, 0x02, // BEQ +2
|
||||
0xA9, 0x00, // skipped
|
||||
0xA9, 0x99, // LDA #$99
|
||||
]);
|
||||
|
||||
for _ in 0..5 {
|
||||
cpu.step(&mut bus).expect("opcode must be supported");
|
||||
}
|
||||
|
||||
assert_eq!(bus.0[0x1234], 0x42);
|
||||
assert_eq!(cpu.a, 0x99);
|
||||
assert_eq!(cpu.p & FLAG_ZERO, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn branch_not_taken_uses_two_cycles() {
|
||||
let (mut cpu, mut bus) = setup_cpu_with_reset(&[
|
||||
0x38, // SEC
|
||||
0x90, 0x7F, // BCC +$7F (not taken)
|
||||
]);
|
||||
assert_eq!(cpu.step(&mut bus).expect("SEC"), 2);
|
||||
let cycles = cpu.step(&mut bus).expect("BCC");
|
||||
assert_eq!(cycles, 2);
|
||||
assert_eq!(cpu.pc, 0x8003);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn branch_taken_same_page_uses_three_cycles() {
|
||||
let (mut cpu, mut bus) = setup_cpu_with_reset(&[
|
||||
0x18, // CLC
|
||||
0x90, 0x02, // BCC +2
|
||||
0xEA, // skipped
|
||||
0xEA, // target
|
||||
]);
|
||||
assert_eq!(cpu.step(&mut bus).expect("CLC"), 2);
|
||||
let cycles = cpu.step(&mut bus).expect("BCC");
|
||||
assert_eq!(cycles, 3);
|
||||
assert_eq!(cpu.pc, 0x8005);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn branch_taken_cross_page_uses_four_cycles() {
|
||||
let (_cpu, mut bus) = setup_cpu_with_reset(&[]);
|
||||
bus.0[0x80FD] = 0x90; // BCC
|
||||
bus.0[0x80FE] = 0x02; // target $8101 (page-crossing from $80FF)
|
||||
|
||||
let mut cpu = Cpu6502 {
|
||||
pc: 0x80FD,
|
||||
..Cpu6502::default()
|
||||
};
|
||||
cpu.p &= !FLAG_CARRY;
|
||||
let cycles = cpu.step(&mut bus).expect("BCC cross-page");
|
||||
assert_eq!(cycles, 4);
|
||||
assert_eq!(cpu.pc, 0x8101);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jsr_rts_roundtrip() {
|
||||
let (mut cpu, mut bus) = setup_cpu_with_reset(&[
|
||||
0x20, 0x06, 0x80, // JSR $8006
|
||||
0xA9, 0x11, // LDA #$11
|
||||
0xEA, // NOP
|
||||
0xA9, 0x77, // LDA #$77
|
||||
0x60, // RTS
|
||||
]);
|
||||
|
||||
for _ in 0..4 {
|
||||
cpu.step(&mut bus).expect("opcode must be supported");
|
||||
}
|
||||
|
||||
assert_eq!(cpu.a, 0x11);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn adc_and_sbc_update_flags() {
|
||||
let (mut cpu, mut bus) = setup_cpu_with_reset(&[
|
||||
0xA9, 0x01, // LDA #$01
|
||||
0x18, // CLC
|
||||
0x69, 0x01, // ADC #$01
|
||||
0x38, // SEC
|
||||
0xE9, 0x02, // SBC #$02
|
||||
]);
|
||||
|
||||
for _ in 0..5 {
|
||||
cpu.step(&mut bus).expect("opcode must be supported");
|
||||
}
|
||||
|
||||
assert_eq!(cpu.a, 0x00);
|
||||
assert_ne!(cpu.p & FLAG_CARRY, 0);
|
||||
assert_ne!(cpu.p & FLAG_ZERO, 0);
|
||||
assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn indirect_x_and_indirect_y_addressing() {
|
||||
let (mut cpu, mut bus) = setup_cpu_with_reset(&[
|
||||
0xA2, 0x04, // LDX #$04
|
||||
0xA1, 0x10, // LDA ($10,X) -> ptr at $14/$15
|
||||
0xA0, 0x01, // LDY #$01
|
||||
0xB1, 0x20, // LDA ($20),Y
|
||||
]);
|
||||
bus.0[0x0014] = 0x00;
|
||||
bus.0[0x0015] = 0x90;
|
||||
bus.0[0x9000] = 0x55;
|
||||
bus.0[0x0020] = 0x10;
|
||||
bus.0[0x0021] = 0x90;
|
||||
bus.0[0x9011] = 0xAA;
|
||||
|
||||
cpu.step(&mut bus).expect("ldx");
|
||||
cpu.step(&mut bus).expect("lda (ind,x)");
|
||||
assert_eq!(cpu.a, 0x55);
|
||||
cpu.step(&mut bus).expect("ldy");
|
||||
cpu.step(&mut bus).expect("lda (ind),y");
|
||||
assert_eq!(cpu.a, 0xAA);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bit_and_shift_ops_work() {
|
||||
let (mut cpu, mut bus) = setup_cpu_with_reset(&[
|
||||
0xA9, 0x40, // LDA #$40
|
||||
0x24, 0x10, // BIT $10
|
||||
0x0A, // ASL A (0x80)
|
||||
0x4A, // LSR A (0x40)
|
||||
0x38, // SEC
|
||||
0x6A, // ROR A (0xA0)
|
||||
]);
|
||||
bus.0[0x0010] = 0xC0;
|
||||
|
||||
for _ in 0..6 {
|
||||
cpu.step(&mut bus).expect("opcode must be supported");
|
||||
}
|
||||
|
||||
assert_eq!(cpu.a, 0xA0);
|
||||
assert_ne!(cpu.p & FLAG_NEGATIVE, 0);
|
||||
assert_eq!(cpu.p & FLAG_ZERO, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rmw_instructions_perform_dummy_write_before_final_write() {
|
||||
struct TraceBus {
|
||||
ram: [u8; 0x10000],
|
||||
writes: Vec<(u16, u8)>,
|
||||
}
|
||||
|
||||
impl CpuBus for TraceBus {
|
||||
fn read(&mut self, addr: u16) -> u8 {
|
||||
self.ram[addr as usize]
|
||||
}
|
||||
fn write(&mut self, addr: u16, value: u8) {
|
||||
self.ram[addr as usize] = value;
|
||||
self.writes.push((addr, value));
|
||||
}
|
||||
}
|
||||
|
||||
let mut bus = TraceBus {
|
||||
ram: [0; 0x10000],
|
||||
writes: Vec::new(),
|
||||
};
|
||||
bus.ram[0xFFFC] = 0x00;
|
||||
bus.ram[0xFFFD] = 0x80;
|
||||
bus.ram[0x8000] = 0xEE; // INC $2000
|
||||
bus.ram[0x8001] = 0x00;
|
||||
bus.ram[0x8002] = 0x20;
|
||||
bus.ram[0x2000] = 0x7F;
|
||||
|
||||
let mut cpu = Cpu6502::default();
|
||||
cpu.reset(&mut bus);
|
||||
cpu.step(&mut bus).expect("INC should execute");
|
||||
|
||||
let writes_to_target: Vec<_> = bus
|
||||
.writes
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|(addr, _)| *addr == 0x2000)
|
||||
.collect();
|
||||
assert_eq!(writes_to_target, vec![(0x2000, 0x7F), (0x2000, 0x80)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nmi_interrupt_jumps_to_vector() {
|
||||
struct NmiBus {
|
||||
ram: [u8; 0x10000],
|
||||
nmi_pending: bool,
|
||||
}
|
||||
|
||||
impl CpuBus for NmiBus {
|
||||
fn read(&mut self, addr: u16) -> u8 {
|
||||
self.ram[addr as usize]
|
||||
}
|
||||
fn write(&mut self, addr: u16, value: u8) {
|
||||
self.ram[addr as usize] = value;
|
||||
}
|
||||
fn poll_nmi(&mut self) -> bool {
|
||||
let out = self.nmi_pending;
|
||||
self.nmi_pending = false;
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
let mut bus = NmiBus {
|
||||
ram: [0; 0x10000],
|
||||
nmi_pending: true,
|
||||
};
|
||||
bus.ram[0xFFFC] = 0x00;
|
||||
bus.ram[0xFFFD] = 0x80;
|
||||
bus.ram[0xFFFA] = 0x34;
|
||||
bus.ram[0xFFFB] = 0x12;
|
||||
|
||||
let mut cpu = Cpu6502::default();
|
||||
cpu.reset(&mut bus);
|
||||
let cycles = cpu.step(&mut bus).expect("nmi should be serviced");
|
||||
assert_eq!(cycles, 7);
|
||||
assert_eq!(cpu.pc, 0x1234);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn undocumented_nop_da_is_accepted() {
|
||||
let (mut cpu, mut bus) = setup_cpu_with_reset(&[
|
||||
0xDA, // undocumented NOP
|
||||
0xA9, 0x42, // LDA #$42
|
||||
]);
|
||||
cpu.step(&mut bus).expect("0xDA should be supported");
|
||||
cpu.step(&mut bus).expect("LDA should execute after 0xDA");
|
||||
assert_eq!(cpu.a, 0x42);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn undocumented_slo_03_is_accepted() {
|
||||
let (mut cpu, mut bus) = setup_cpu_with_reset(&[
|
||||
0xA2, 0x00, // LDX #$00
|
||||
0x03, 0x10, // SLO ($10,X)
|
||||
]);
|
||||
bus.0[0x0010] = 0x00;
|
||||
bus.0[0x0011] = 0x90;
|
||||
bus.0[0x9000] = 0x40;
|
||||
|
||||
cpu.step(&mut bus).expect("LDX");
|
||||
cpu.step(&mut bus).expect("0x03 should be supported");
|
||||
|
||||
assert_eq!(bus.0[0x9000], 0x80);
|
||||
assert_eq!(cpu.a, 0x80);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jam_opcode_halts_cpu_until_reset() {
|
||||
let (mut cpu, mut bus) = setup_cpu_with_reset(&[
|
||||
0x02, // JAM/KIL
|
||||
0xA9, 0x77, // LDA #$77 (must not execute while halted)
|
||||
]);
|
||||
|
||||
let cycles = cpu.step(&mut bus).expect("jam opcode should decode");
|
||||
assert_eq!(cycles, 2);
|
||||
assert!(cpu.halted);
|
||||
assert_eq!(cpu.pc, 0x8000);
|
||||
|
||||
let cycles2 = cpu.step(&mut bus).expect("halted cpu step should be valid");
|
||||
assert_eq!(cycles2, 2);
|
||||
assert_eq!(cpu.pc, 0x8000);
|
||||
assert_eq!(cpu.a, 0x00);
|
||||
|
||||
cpu.reset(&mut bus);
|
||||
assert!(!cpu.halted);
|
||||
assert_eq!(cpu.pc, 0x8000);
|
||||
}
|
||||
242
src/native_core/cpu/tests/interrupts.rs
Normal file
242
src/native_core/cpu/tests/interrupts.rs
Normal file
@@ -0,0 +1,242 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn cli_delays_irq_acceptance_by_one_instruction() {
|
||||
struct IrqBus {
|
||||
ram: [u8; 0x10000],
|
||||
irq_level: bool,
|
||||
}
|
||||
|
||||
impl CpuBus for IrqBus {
|
||||
fn read(&mut self, addr: u16) -> u8 {
|
||||
self.ram[addr as usize]
|
||||
}
|
||||
fn write(&mut self, addr: u16, value: u8) {
|
||||
self.ram[addr as usize] = value;
|
||||
}
|
||||
fn poll_irq(&mut self) -> bool {
|
||||
self.irq_level
|
||||
}
|
||||
}
|
||||
|
||||
let mut bus = IrqBus {
|
||||
ram: [0; 0x10000],
|
||||
irq_level: true,
|
||||
};
|
||||
bus.ram[0xFFFC] = 0x00;
|
||||
bus.ram[0xFFFD] = 0x80;
|
||||
bus.ram[0xFFFE] = 0x00;
|
||||
bus.ram[0xFFFF] = 0x90; // IRQ vector -> $9000
|
||||
bus.ram[0x8000] = 0x58; // CLI
|
||||
bus.ram[0x8001] = 0xEA; // NOP
|
||||
|
||||
let mut cpu = Cpu6502::default();
|
||||
cpu.reset(&mut bus);
|
||||
|
||||
let c1 = cpu.step(&mut bus).expect("CLI");
|
||||
assert_eq!(c1, 2);
|
||||
assert_eq!(cpu.pc, 0x8001);
|
||||
|
||||
let c2 = cpu.step(&mut bus).expect("NOP executes before IRQ");
|
||||
assert_eq!(c2, 2);
|
||||
assert_eq!(cpu.pc, 0x8002);
|
||||
|
||||
let c3 = cpu.step(&mut bus).expect("IRQ taken now");
|
||||
assert_eq!(c3, 7);
|
||||
assert_eq!(cpu.pc, 0x9000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plp_delays_irq_acceptance_by_one_instruction_when_clearing_i() {
|
||||
struct IrqBus {
|
||||
ram: [u8; 0x10000],
|
||||
irq_level: bool,
|
||||
}
|
||||
|
||||
impl CpuBus for IrqBus {
|
||||
fn read(&mut self, addr: u16) -> u8 {
|
||||
self.ram[addr as usize]
|
||||
}
|
||||
fn write(&mut self, addr: u16, value: u8) {
|
||||
self.ram[addr as usize] = value;
|
||||
}
|
||||
fn poll_irq(&mut self) -> bool {
|
||||
self.irq_level
|
||||
}
|
||||
}
|
||||
|
||||
let mut bus = IrqBus {
|
||||
ram: [0; 0x10000],
|
||||
irq_level: true,
|
||||
};
|
||||
bus.ram[0xFFFC] = 0x00;
|
||||
bus.ram[0xFFFD] = 0x80;
|
||||
bus.ram[0xFFFE] = 0x00;
|
||||
bus.ram[0xFFFF] = 0x90;
|
||||
bus.ram[0x8000] = 0xA9; // LDA #$20
|
||||
bus.ram[0x8001] = 0x20;
|
||||
bus.ram[0x8002] = 0x48; // PHA
|
||||
bus.ram[0x8003] = 0x28; // PLP -> I clears
|
||||
bus.ram[0x8004] = 0xEA; // NOP
|
||||
|
||||
let mut cpu = Cpu6502::default();
|
||||
cpu.reset(&mut bus);
|
||||
|
||||
cpu.step(&mut bus).expect("LDA");
|
||||
cpu.step(&mut bus).expect("PHA");
|
||||
let c3 = cpu.step(&mut bus).expect("PLP");
|
||||
assert_eq!(c3, 4);
|
||||
assert_eq!(cpu.pc, 0x8004);
|
||||
|
||||
let c4 = cpu.step(&mut bus).expect("NOP executes before IRQ");
|
||||
assert_eq!(c4, 2);
|
||||
assert_eq!(cpu.pc, 0x8005);
|
||||
|
||||
let c5 = cpu.step(&mut bus).expect("IRQ taken");
|
||||
assert_eq!(c5, 7);
|
||||
assert_eq!(cpu.pc, 0x9000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jam_halt_ignores_nmi_and_irq() {
|
||||
struct InterruptBus {
|
||||
ram: [u8; 0x10000],
|
||||
nmi: bool,
|
||||
irq: bool,
|
||||
}
|
||||
|
||||
impl CpuBus for InterruptBus {
|
||||
fn read(&mut self, addr: u16) -> u8 {
|
||||
self.ram[addr as usize]
|
||||
}
|
||||
fn write(&mut self, addr: u16, value: u8) {
|
||||
self.ram[addr as usize] = value;
|
||||
}
|
||||
fn poll_nmi(&mut self) -> bool {
|
||||
let out = self.nmi;
|
||||
self.nmi = false;
|
||||
out
|
||||
}
|
||||
fn poll_irq(&mut self) -> bool {
|
||||
self.irq
|
||||
}
|
||||
}
|
||||
|
||||
let mut bus = InterruptBus {
|
||||
ram: [0; 0x10000],
|
||||
nmi: false,
|
||||
irq: false,
|
||||
};
|
||||
bus.ram[0xFFFC] = 0x00;
|
||||
bus.ram[0xFFFD] = 0x80;
|
||||
bus.ram[0xFFFA] = 0x00;
|
||||
bus.ram[0xFFFB] = 0x90;
|
||||
bus.ram[0xFFFE] = 0x00;
|
||||
bus.ram[0xFFFF] = 0xA0;
|
||||
bus.ram[0x8000] = 0x02; // JAM
|
||||
|
||||
let mut cpu = Cpu6502::default();
|
||||
cpu.reset(&mut bus);
|
||||
cpu.step(&mut bus).expect("jam");
|
||||
assert!(cpu.halted);
|
||||
|
||||
bus.nmi = true;
|
||||
bus.irq = true;
|
||||
let c = cpu.step(&mut bus).expect("halted step");
|
||||
assert_eq!(c, 2);
|
||||
assert_eq!(cpu.pc, 0x8000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rti_delays_irq_acceptance_by_one_instruction_when_clearing_i() {
|
||||
struct IrqBus {
|
||||
ram: [u8; 0x10000],
|
||||
irq_level: bool,
|
||||
}
|
||||
|
||||
impl CpuBus for IrqBus {
|
||||
fn read(&mut self, addr: u16) -> u8 {
|
||||
self.ram[addr as usize]
|
||||
}
|
||||
fn write(&mut self, addr: u16, value: u8) {
|
||||
self.ram[addr as usize] = value;
|
||||
}
|
||||
fn poll_irq(&mut self) -> bool {
|
||||
self.irq_level
|
||||
}
|
||||
}
|
||||
|
||||
let mut bus = IrqBus {
|
||||
ram: [0; 0x10000],
|
||||
irq_level: true,
|
||||
};
|
||||
bus.ram[0xFFFC] = 0x00;
|
||||
bus.ram[0xFFFD] = 0x80;
|
||||
bus.ram[0xFFFE] = 0x00;
|
||||
bus.ram[0xFFFF] = 0x90; // IRQ vector
|
||||
|
||||
bus.ram[0x8000] = 0x40; // RTI
|
||||
bus.ram[0x8001] = 0xEA; // NOP
|
||||
|
||||
// After reset SP is $FA, so RTI pops from $01FB/$01FC/$01FD.
|
||||
bus.ram[0x01FB] = 0x20; // P (I clear)
|
||||
bus.ram[0x01FC] = 0x01; // PC low
|
||||
bus.ram[0x01FD] = 0x80; // PC high
|
||||
|
||||
let mut cpu = Cpu6502::default();
|
||||
cpu.reset(&mut bus);
|
||||
|
||||
let c1 = cpu.step(&mut bus).expect("RTI");
|
||||
assert_eq!(c1, 6);
|
||||
assert_eq!(cpu.pc, 0x8001);
|
||||
|
||||
let c2 = cpu.step(&mut bus).expect("NOP before IRQ");
|
||||
assert_eq!(c2, 2);
|
||||
assert_eq!(cpu.pc, 0x8002);
|
||||
|
||||
let c3 = cpu.step(&mut bus).expect("IRQ now taken");
|
||||
assert_eq!(c3, 7);
|
||||
assert_eq!(cpu.pc, 0x9000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn brk_is_hijacked_by_nmi_vector_when_nmi_arrives_during_brk() {
|
||||
struct NmiBus {
|
||||
ram: [u8; 0x10000],
|
||||
nmi_now: bool,
|
||||
}
|
||||
|
||||
impl CpuBus for NmiBus {
|
||||
fn read(&mut self, addr: u16) -> u8 {
|
||||
self.ram[addr as usize]
|
||||
}
|
||||
fn write(&mut self, addr: u16, value: u8) {
|
||||
self.ram[addr as usize] = value;
|
||||
}
|
||||
fn poll_nmi(&mut self) -> bool {
|
||||
let out = self.nmi_now;
|
||||
self.nmi_now = false;
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
let mut bus = NmiBus {
|
||||
ram: [0; 0x10000],
|
||||
nmi_now: false,
|
||||
};
|
||||
bus.ram[0xFFFC] = 0x00;
|
||||
bus.ram[0xFFFD] = 0x80;
|
||||
bus.ram[0xFFFA] = 0x34; // NMI vector
|
||||
bus.ram[0xFFFB] = 0x12;
|
||||
bus.ram[0xFFFE] = 0x78; // IRQ/BRK vector
|
||||
bus.ram[0xFFFF] = 0x56;
|
||||
bus.ram[0x8000] = 0x00; // BRK
|
||||
|
||||
let mut cpu = Cpu6502::default();
|
||||
cpu.reset(&mut bus);
|
||||
|
||||
bus.nmi_now = true;
|
||||
let cycles = cpu.step(&mut bus).expect("BRK");
|
||||
assert_eq!(cycles, 7);
|
||||
assert_eq!(cpu.pc, 0x1234, "NMI vector should hijack BRK");
|
||||
}
|
||||
56
src/native_core/cpu/tests/property.rs
Normal file
56
src/native_core/cpu/tests/property.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn property_all_256_opcodes_decode_without_error() {
|
||||
for opcode in 0u16..=255 {
|
||||
let mut bus = TestRamBus::new();
|
||||
bus.0[0xFFFC] = 0x00;
|
||||
bus.0[0xFFFD] = 0x80;
|
||||
bus.0[0xFFFE] = 0x00;
|
||||
bus.0[0xFFFF] = 0x80;
|
||||
bus.0[0xFFFA] = 0x00;
|
||||
bus.0[0xFFFB] = 0x80;
|
||||
bus.0[0x8000] = opcode as u8;
|
||||
bus.0[0x8001] = 0x00;
|
||||
bus.0[0x8002] = 0x00;
|
||||
|
||||
let mut cpu = Cpu6502::default();
|
||||
cpu.reset(&mut bus);
|
||||
let res = cpu.step(&mut bus);
|
||||
assert!(res.is_ok(), "opcode {:02X} must be supported", opcode);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn property_lda_abs_x_adds_cycle_on_page_cross() {
|
||||
let (mut cpu, mut bus) = setup_cpu_with_reset(&[
|
||||
0xA2, 0x01, // LDX #$01
|
||||
0xBD, 0xFF, 0x12, // LDA $12FF,X -> crosses to $1300
|
||||
]);
|
||||
bus.0[0x1300] = 0x77;
|
||||
|
||||
let c1 = cpu.step(&mut bus).expect("LDX");
|
||||
let c2 = cpu.step(&mut bus).expect("LDA abs,X");
|
||||
|
||||
assert_eq!(c1, 2);
|
||||
assert_eq!(c2, 5);
|
||||
assert_eq!(cpu.a, 0x77);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn property_lda_ind_y_adds_cycle_on_page_cross() {
|
||||
let (mut cpu, mut bus) = setup_cpu_with_reset(&[
|
||||
0xA0, 0x01, // LDY #$01
|
||||
0xB1, 0x10, // LDA ($10),Y
|
||||
]);
|
||||
bus.0[0x0010] = 0xFF;
|
||||
bus.0[0x0011] = 0x12;
|
||||
bus.0[0x1300] = 0x55;
|
||||
|
||||
let c1 = cpu.step(&mut bus).expect("LDY");
|
||||
let c2 = cpu.step(&mut bus).expect("LDA ind,Y");
|
||||
|
||||
assert_eq!(c1, 2);
|
||||
assert_eq!(c2, 6);
|
||||
assert_eq!(cpu.a, 0x55);
|
||||
}
|
||||
Reference in New Issue
Block a user