Files
nesemu/src/native_core/cpu.rs
se.cherkasov bdf23de8db
Some checks failed
CI / rust (push) Has been cancelled
Initial commit: NES emulator with GTK4 desktop frontend
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.
2026-03-13 11:48:45 +03:00

113 lines
3.0 KiB
Rust

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CpuError {
UnsupportedOpcode { opcode: u8, pc: u16 },
}
pub trait CpuBus {
fn read(&mut self, addr: u16) -> u8;
fn write(&mut self, addr: u16, value: u8);
fn poll_nmi(&mut self) -> bool {
false
}
fn poll_irq(&mut self) -> bool {
false
}
}
const FLAG_CARRY: u8 = 0b0000_0001;
const FLAG_ZERO: u8 = 0b0000_0010;
const FLAG_IRQ_DISABLE: u8 = 0b0000_0100;
const FLAG_DECIMAL: u8 = 0b0000_1000;
const FLAG_BREAK: u8 = 0b0001_0000;
const FLAG_UNUSED: u8 = 0b0010_0000;
const FLAG_OVERFLOW: u8 = 0b0100_0000;
const FLAG_NEGATIVE: u8 = 0b1000_0000;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Cpu6502 {
pub a: u8,
pub x: u8,
pub y: u8,
pub sp: u8,
pub pc: u16,
pub p: u8,
pub halted: bool,
pub irq_delay: bool,
pub pending_nmi: bool,
pub pending_irq: bool,
}
impl Default for Cpu6502 {
fn default() -> Self {
Self {
a: 0,
x: 0,
y: 0,
sp: 0xFD,
pc: 0,
p: FLAG_IRQ_DISABLE | FLAG_UNUSED,
halted: false,
irq_delay: false,
pending_nmi: false,
pending_irq: false,
}
}
}
impl Cpu6502 {
pub fn reset<B: CpuBus>(&mut self, bus: &mut B) {
let lo = bus.read(0xFFFC) as u16;
let hi = bus.read(0xFFFD) as u16;
self.pc = (hi << 8) | lo;
self.sp = self.sp.wrapping_sub(3);
self.p = (self.p | FLAG_IRQ_DISABLE | FLAG_UNUSED) & !FLAG_BREAK;
self.halted = false;
self.irq_delay = false;
self.pending_nmi = false;
self.pending_irq = false;
}
pub fn step<B: CpuBus>(&mut self, bus: &mut B) -> Result<u8, CpuError> {
if self.halted {
return Ok(2);
}
if self.pending_nmi {
self.pending_nmi = false;
self.pending_irq = false;
self.service_interrupt(bus, 0xFFFA, false);
self.p |= FLAG_UNUSED;
return Ok(7);
}
let irq_delayed = self.irq_delay;
self.irq_delay = false;
if self.pending_irq && !irq_delayed && (self.p & FLAG_IRQ_DISABLE) == 0 {
self.pending_irq = false;
self.service_interrupt(bus, 0xFFFE, false);
self.p |= FLAG_UNUSED;
return Ok(7);
}
let pc_before = self.pc;
let opcode = self.fetch(bus);
let cycles = self.execute_opcode(bus, opcode, pc_before)?;
// Real 6502 polls IRQ/NMI near the end of the current instruction and
// services it before the next opcode fetch.
if bus.poll_nmi() {
self.pending_nmi = true;
self.pending_irq = false;
} else if !irq_delayed && (self.p & FLAG_IRQ_DISABLE) == 0 && bus.poll_irq() {
self.pending_irq = true;
}
Ok(cycles)
}
}
// Addressing modes, ALU helpers, stack and interrupt internals.
mod helpers;
// Opcode decoder/executor table split out from cpu.rs for readability.
mod opcodes;
#[cfg(test)]
mod tests;