442 lines
16 KiB
Rust
442 lines
16 KiB
Rust
use crate::bus::Bus;
|
|
|
|
// Flag bit constants
|
|
const FLAG_CARRY: u8 = 0b0000_0001;
|
|
const FLAG_ZERO: u8 = 0b0000_0010;
|
|
const FLAG_INTERRUPT: 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;
|
|
|
|
pub struct Cpu {
|
|
a: u8, // Accumulator Register
|
|
x: u8, // X Register
|
|
y: u8, // Y Register
|
|
sp: u8, // Stack Pointer
|
|
pc: u16, // Program Counter
|
|
p: u8, // Status Register
|
|
cycles: u64,
|
|
irq_pending: bool,
|
|
nmi_pending: bool,
|
|
}
|
|
|
|
impl Cpu {
|
|
// Testing related
|
|
pub fn set_test_start(&mut self, pc: u16, cycles: u64) {
|
|
self.pc = pc;
|
|
self.cycles = cycles;
|
|
}
|
|
|
|
// Helpers for changing flags on the CPU
|
|
fn set_flag(&mut self, bit: u8) { self.p |= bit; }
|
|
fn clear_flag(&mut self, bit: u8) { self.p &= !bit; }
|
|
fn flag(&self, bit: u8) -> bool { self.p & bit != 0 }
|
|
|
|
fn set_zp_flags(&mut self, value: u8) {
|
|
// Clear before setting
|
|
self.clear_flag(FLAG_ZERO);
|
|
self.clear_flag(FLAG_NEGATIVE);
|
|
|
|
if value == 0 { self.set_flag(FLAG_ZERO); } // Set Zero if Zero
|
|
if value & 0x80 != 0 { self.set_flag(FLAG_NEGATIVE) } // Set if Negative
|
|
}
|
|
|
|
// New and reset methods
|
|
pub fn new() -> Self {
|
|
Self { a: 0, x: 0, y: 0, sp: 0xFD, pc: 0, p: FLAG_UNUSED | FLAG_INTERRUPT, cycles: 0, irq_pending: false, nmi_pending: false } // kick stars a NES CPU
|
|
}
|
|
|
|
pub fn reset(&mut self, bus: &mut impl Bus) {
|
|
self.sp = 0xFD;
|
|
self.p = FLAG_UNUSED | FLAG_INTERRUPT;
|
|
self.pc = self.read16(bus, 0xFFFC);
|
|
}
|
|
|
|
// Memory helpers
|
|
fn fetch(&mut self, bus: &mut impl Bus) -> u8 {
|
|
let value: u8 = bus.read(self.pc);
|
|
self.pc = self.pc.wrapping_add(1);
|
|
value
|
|
}
|
|
|
|
fn fetch16(&mut self, bus: &mut impl Bus) -> u16 {
|
|
let low = self.fetch(bus) as u16;
|
|
let high = self.fetch(bus) as u16;
|
|
high << 8 | low
|
|
}
|
|
|
|
fn read(&self, bus: &mut impl Bus, address: u16) -> u8 {
|
|
bus.read(address)
|
|
}
|
|
|
|
fn write(&mut self, bus: &mut impl Bus, address: u16, value: u8) {
|
|
bus.write(address, value);
|
|
}
|
|
|
|
fn read16(&self, bus: &mut impl Bus, address: u16) -> u16 {
|
|
let low: u16 = bus.read(address) as u16;
|
|
let high: u16 = bus.read(address.wrapping_add(1)) as u16;
|
|
high << 8 | low // returt in little-endian format
|
|
}
|
|
|
|
// Stack related Memory helpers
|
|
fn push(&mut self, bus: &mut impl Bus, value: u8) {
|
|
self.write(bus, 0x0100 + self.sp as u16, value);
|
|
self.sp = self.sp.wrapping_sub(1);
|
|
}
|
|
|
|
fn pop(&mut self, bus: &mut impl Bus) -> u8 {
|
|
self.sp = self.sp.wrapping_add(1);
|
|
self.read(bus, 0x0100 + self.sp as u16)
|
|
}
|
|
|
|
// Step shell
|
|
pub fn step(&mut self, bus: &mut impl Bus) -> u8 {
|
|
let opcode: u8 = self.fetch(bus);
|
|
let cycles = match opcode {
|
|
0x4C => self.jmp_abs(bus),
|
|
0xA2 => self.ldx_imm(bus),
|
|
0x86 => self.stx_zp(bus),
|
|
0x20 => self.jsr_abs(bus),
|
|
0x60 => self.rts_imp(bus),
|
|
0x48 => self.pha_imp(bus),
|
|
0x68 => self.pla_imp(bus),
|
|
0x08 => self.php_imp(bus),
|
|
0x28 => self.plp_imp(bus),
|
|
0x40 => self.rti_imp(bus),
|
|
0x00 => self.brk_imp(bus),
|
|
0xEA => self.nop_imp(bus),
|
|
0x38 => self.sec_imp(bus),
|
|
0x90 => self.bcc_rel(bus),
|
|
0xB0 => self.bcs_rel(bus),
|
|
0xF0 => self.beq_rel(bus),
|
|
0xD0 => self.bne_rel(bus),
|
|
0x10 => self.bpl_rel(bus),
|
|
0x30 => self.bmi_rel(bus),
|
|
0x50 => self.bvc_rel(bus),
|
|
0x70 => self.bvs_rel(bus),
|
|
0x18 => self.clc_imp(bus),
|
|
0x58 => self.cli_imp(bus),
|
|
0xB8 => self.clv_imp(bus),
|
|
0xD8 => self.cld_imp(bus),
|
|
0x78 => self.sei_imp(bus),
|
|
0xF8 => self.sed_imp(bus),
|
|
0xA9 => self.lda_imm(bus),
|
|
0xA5 => self.lda_zp(bus),
|
|
0xB5 => self.lda_zpx(bus),
|
|
0xAD => self.lda_abs(bus),
|
|
0xBD => self.lda_abx(bus),
|
|
0xB9 => self.lda_aby(bus),
|
|
0xA1 => self.lda_izx(bus),
|
|
0xB1 => self.lda_izy(bus),
|
|
_ => panic!("illegal/unkown opcode {opcode:#04X} at {:#06X}", self.pc.wrapping_sub(1)),
|
|
};
|
|
self.cycles += cycles as u64;
|
|
cycles
|
|
}
|
|
|
|
// Trace for testing the cpu
|
|
pub fn trace(&self, bus: &mut impl Bus) -> String {
|
|
let opcode: u8 = bus.read(self.pc);
|
|
let len: u8 = Self::opcode_len(opcode);
|
|
let mut bytes: String = String::new();
|
|
for i in 0..len {
|
|
bytes.push_str(&format!("{:02X} ", bus.read(self.pc.wrapping_add(i as u16))));
|
|
}
|
|
format!(
|
|
"{:04X} {:<8} A:{:02X} X:{:02X} Y:{:02X} P:{:02X} SP:{:02X} CYC:{}",
|
|
self.pc, bytes, self.a, self.x, self.y, self.p, self.sp, self.cycles
|
|
)
|
|
}
|
|
|
|
// Address mode helpers
|
|
fn address_zp(&mut self, bus: &mut impl Bus) -> (u16, u8) { // Zero Page
|
|
(self.fetch(bus) as u16, 0)
|
|
}
|
|
|
|
fn address_zpx(&mut self, bus: &mut impl Bus) -> (u16, u8) { // Zero Page, X mode
|
|
(self.fetch(bus).wrapping_add(self.x) as u16, 0)
|
|
}
|
|
|
|
fn address_zpy(&mut self, bus: &mut impl Bus) -> (u16, u8) { // Zero Page, Y mode
|
|
(self.fetch(bus).wrapping_add(self.y) as u16, 0)
|
|
}
|
|
|
|
fn address_abs(&mut self, bus: &mut impl Bus) -> (u16, u8) { // Absolute address
|
|
(self.fetch16(bus), 0)
|
|
}
|
|
|
|
fn address_abx(&mut self, bus: &mut impl Bus) -> (u16, u8) { // Absolute, X address
|
|
let base: u16 = self.fetch16(bus);
|
|
let address: u16 = base.wrapping_add(self.x as u16);
|
|
let extra: u8 = if (base & 0xFF00) != (address & 0xFF00) { 1 } else { 0 };
|
|
(address, extra)
|
|
}
|
|
|
|
fn address_aby(&mut self, bus: &mut impl Bus) -> (u16, u8) { // Absolute, Y address
|
|
let base: u16 = self.fetch16(bus);
|
|
let address: u16 = base.wrapping_add(self.y as u16);
|
|
let extra: u8 = if (base & 0xFF00) != (address & 0xFF00) { 1 } else { 0 };
|
|
(address, extra)
|
|
}
|
|
|
|
fn address_izx(&mut self, bus: &mut impl Bus) -> (u16, u8) { // Index-Indirect mode + X
|
|
let zp: u8 = self.fetch(bus); // Operand: Address on Zero-Page
|
|
let ptr_address: u8 = zp.wrapping_add(self.x); // We add x to it wrapped
|
|
let low: u16 = self.read(bus, ptr_address as u16) as u16;
|
|
let high: u16 = self.read(bus, ptr_address.wrapping_add(1) as u16) as u16;
|
|
(high << 8 | low, 0) // Return in little endian, 16-bit pointer
|
|
}
|
|
|
|
fn address_izy(&mut self, bus: &mut impl Bus) -> (u16, u8) { // Index-Indirect mode + Y
|
|
let zp: u8 = self.fetch(bus); // Operand: Address on Zero-Page
|
|
let low: u16 = self.read(bus, zp as u16) as u16;
|
|
let high: u16 = self.read(bus, zp.wrapping_add(1) as u16) as u16;
|
|
let base: u16 = high << 8 | low; // 16-bit pointer to somewhere on WRAM, little-endian
|
|
let address: u16 = base.wrapping_add(self.y as u16); // Add Y to the 16-bit pointer
|
|
let extra: u8 = if (base & 0xFF00) != (address & 0xFF00) { 1 } else { 0 };
|
|
(address, extra)
|
|
}
|
|
|
|
fn address_ind(&mut self, bus: &mut impl Bus) -> (u16, u8) { // used by JMP instruction + hardware bug where the page wraps around
|
|
let ptr: u16 = self.fetch16(bus); // pointer itself
|
|
let low: u16 = self.read(bus, ptr) as u16;
|
|
let high_ptr: u16 = (ptr & 0xFF00) | (ptr.wrapping_add(1) & 0x00FF); // Hardware bug wrap here
|
|
let high = self.read(bus, high_ptr) as u16;
|
|
(high << 8 | low, 0) // little-endian Return
|
|
}
|
|
|
|
fn address_rel(&mut self, bus: &mut impl Bus) -> u16 { // Branch relative
|
|
let offset: u8 = self.fetch(bus); // signed 8-bit offset from argument
|
|
self.pc.wrapping_add((offset as i8) as u16) // sign-extend + add, this allows to move the PC backwards
|
|
}
|
|
|
|
// Branching helper for general Branch operations
|
|
|
|
fn branch(&mut self, bus: &mut impl Bus, condition: bool) -> u8 {
|
|
let target: u16 = self.address_rel(bus);
|
|
if !condition {
|
|
return 2;
|
|
}
|
|
let mut cycles: u8 = 3;
|
|
if (self.pc & 0xFF00) != (target & 0xFF00) {
|
|
cycles += 1;
|
|
}
|
|
self.pc = target;
|
|
cycles
|
|
}
|
|
|
|
// shared cores for LDx instructions
|
|
fn lda(&mut self,bus: &mut impl Bus, (address, extra): (u16, u8), base: u8) -> u8 {
|
|
self.a = self.read(bus, address);
|
|
self.set_zp_flags(self.a);
|
|
base + extra
|
|
}
|
|
|
|
// 0xA9, LDA immediate instructions: operand is inline, fetch consumes it, no address helper
|
|
fn lda_imm(&mut self, bus: &mut impl Bus) -> u8 {
|
|
self.a = self.fetch(bus);
|
|
self.set_zp_flags(self.a);
|
|
2
|
|
}
|
|
|
|
// NES 6502 opcode -> functions
|
|
fn jmp_abs(&mut self, bus: &mut impl Bus) -> u8 { // 0x4C, 3 bytes, 3 cycles
|
|
self.pc = self.fetch16(bus);
|
|
3
|
|
}
|
|
|
|
fn ldx_imm(&mut self, bus: &mut impl Bus) -> u8 { // 0xA2, 2 bytes, 2 cycles
|
|
self.x = self.fetch(bus);
|
|
self.set_zp_flags(self.x);
|
|
2
|
|
}
|
|
|
|
fn stx_zp(&mut self, bus: &mut impl Bus) -> u8 { // 0x86, 2 bytes, 3 cycles
|
|
let (addr, _) = self.address_zp(bus);
|
|
self.write(bus, addr, self.x);
|
|
3
|
|
}
|
|
|
|
fn jsr_abs(&mut self, bus: &mut impl Bus) -> u8 { // 0x20, 3 bytes, 6 cycles
|
|
let target = self.fetch16(bus);
|
|
let ret = self.pc.wrapping_sub(1);
|
|
self.push(bus, (ret >> 8) as u8);
|
|
self.push(bus, (ret &0xFF) as u8);
|
|
self.pc = target;
|
|
6
|
|
}
|
|
|
|
fn rts_imp(&mut self, bus: &mut impl Bus) -> u8 { // 0x60, 1 byte, 6 cycles
|
|
let low = self.pop(bus) as u16;
|
|
let high = self.pop(bus) as u16;
|
|
self.pc = (high << 8 | low).wrapping_add(1);
|
|
6
|
|
}
|
|
|
|
fn pha_imp(&mut self, bus: &mut impl Bus) -> u8 { // 0x48, 1 byte, 3 cycles
|
|
self.push(bus, self.a);
|
|
3
|
|
}
|
|
|
|
fn pla_imp(&mut self, bus: &mut impl Bus) -> u8 { // 0x68, 1 byte, 4 cycles
|
|
self.a = self.pop(bus);
|
|
self.set_zp_flags(self.a);
|
|
4
|
|
}
|
|
|
|
fn php_imp(&mut self, bus: &mut impl Bus) -> u8 { // 0x08, 1 byte, 3 cycles; B-flag model: B exists only in the pushed copy
|
|
self.push(bus, self.p | FLAG_BREAK);
|
|
3
|
|
}
|
|
|
|
fn plp_imp(&mut self, bus: &mut impl Bus) -> u8 { // 0x28, 1 byte, 4 cycles
|
|
self.p = (self.pop(bus) & !FLAG_BREAK) | FLAG_UNUSED;
|
|
4
|
|
}
|
|
|
|
fn rti_imp(&mut self, bus: &mut impl Bus) -> u8 { // 0x40, 1 byte, 6 cycles
|
|
self.p = (self.pop(bus) & !FLAG_BREAK) | FLAG_UNUSED;
|
|
let low: u16 = self.pop(bus) as u16;
|
|
let high: u16 = self.pop(bus) as u16;
|
|
self.pc = high << 8 | low;
|
|
6
|
|
}
|
|
|
|
fn brk_imp(&mut self, bus: &mut impl Bus) -> u8 { // 0x00, 1 byte, 7 cycles
|
|
self.pc = self.pc.wrapping_add(1); // Skip padding byte
|
|
let ret = self.pc;
|
|
self.push(bus, (ret >> 8) as u8);
|
|
self.push(bus, (ret & 0xFF) as u8);
|
|
self.push(bus, self.p | FLAG_BREAK);
|
|
self.set_flag(FLAG_INTERRUPT);
|
|
self.pc = self.read16(bus, 0xFFFE); // IRQ vector
|
|
7
|
|
}
|
|
|
|
fn nop_imp(&mut self, _bus: &mut impl Bus) -> u8 { // 0xEA, 1 byte, 2 cycles
|
|
2
|
|
}
|
|
|
|
fn sec_imp(&mut self, _bus: &mut impl Bus) -> u8 { // 0x38, 1 byte, 2 cycles
|
|
self.set_flag(FLAG_CARRY);
|
|
2
|
|
}
|
|
|
|
fn clc_imp(&mut self, _bus: &mut impl Bus) -> u8 { // 0x18, 1 byte, 2 cycles
|
|
self.clear_flag(FLAG_CARRY);
|
|
2
|
|
}
|
|
|
|
fn cli_imp(&mut self, _bus: &mut impl Bus) -> u8 { // 0x58, 1 byte, 2 cycles
|
|
self.clear_flag(FLAG_INTERRUPT);
|
|
2
|
|
}
|
|
|
|
fn clv_imp(&mut self, _bus: &mut impl Bus) -> u8 { // 0xB8, 1 byte, 2 cycles
|
|
self.clear_flag(FLAG_OVERFLOW);
|
|
2
|
|
}
|
|
|
|
fn cld_imp(&mut self, _bus: &mut impl Bus) -> u8 { // 0xD8, 1 byte, 2 cycles
|
|
self.clear_flag(FLAG_DECIMAL);
|
|
2
|
|
}
|
|
|
|
fn sei_imp(&mut self, _bus: &mut impl Bus) -> u8 { // 0x78, 1 byte, 2 cycles
|
|
self.set_flag(FLAG_INTERRUPT);
|
|
2
|
|
}
|
|
|
|
fn sed_imp(&mut self, _bus: &mut impl Bus) -> u8 { // 0xF8, 1 byte, 2 cycles
|
|
self.set_flag(FLAG_DECIMAL);
|
|
2
|
|
}
|
|
|
|
// LDA alternatives here
|
|
fn lda_zp(&mut self, bus: &mut impl Bus) -> u8 { // 0xA5, 2 bytes
|
|
let (address, extra) = self.address_zp(bus);
|
|
self.lda(bus, (address, extra), 3)
|
|
}
|
|
fn lda_zpx(&mut self, bus: &mut impl Bus) -> u8 { // 0xB5, 2 bytes
|
|
let (address, extra) = self.address_zpx(bus);
|
|
self.lda(bus, (address, extra), 4)
|
|
}
|
|
fn lda_abs(&mut self, bus: &mut impl Bus) -> u8 { // 0xAD, 3 bytes
|
|
let (address, extra) = self.address_abs(bus);
|
|
self.lda(bus, (address, extra), 4)
|
|
}
|
|
fn lda_abx(&mut self, bus: &mut impl Bus) -> u8 { // 0xBD, 3 bytes
|
|
let (address, extra) = self.address_abx(bus);
|
|
self.lda(bus, (address, extra), 4)
|
|
}
|
|
fn lda_aby(&mut self, bus: &mut impl Bus) -> u8 { // 0xB9, 3 bytes
|
|
let (address, extra) = self.address_aby(bus);
|
|
self.lda(bus, (address, extra), 4)
|
|
}
|
|
fn lda_izx(&mut self, bus: &mut impl Bus) -> u8 { // 0xA1, 2 bytes
|
|
let (address, extra) = self.address_izx(bus);
|
|
self.lda(bus, (address, extra), 6)
|
|
}
|
|
fn lda_izy(&mut self, bus: &mut impl Bus) -> u8 { // 0xB1, 2 bytes
|
|
let (address, extra) = self.address_izy(bus);
|
|
self.lda(bus, (address, extra), 5)
|
|
}
|
|
|
|
// all branching alternatives here all 1 bytes
|
|
fn bcs_rel(&mut self, bus: &mut impl Bus) -> u8 { self.branch(bus, self.flag(FLAG_CARRY)) } // 0xB0
|
|
fn bcc_rel(&mut self, bus: &mut impl Bus) -> u8 { self.branch(bus, !self.flag(FLAG_CARRY)) } // 0x90
|
|
fn beq_rel(&mut self, bus: &mut impl Bus) -> u8 { self.branch(bus, self.flag(FLAG_ZERO)) } // 0xF0
|
|
fn bne_rel(&mut self, bus: &mut impl Bus) -> u8 { self.branch(bus, !self.flag(FLAG_ZERO)) } // 0xD0
|
|
fn bpl_rel(&mut self, bus: &mut impl Bus) -> u8 { self.branch(bus, !self.flag(FLAG_NEGATIVE)) } // 0x10
|
|
fn bmi_rel(&mut self, bus: &mut impl Bus) -> u8 { self.branch(bus, self.flag(FLAG_NEGATIVE)) } // 0x30
|
|
fn bvc_rel(&mut self, bus: &mut impl Bus) -> u8 { self.branch(bus, !self.flag(FLAG_OVERFLOW)) } // 0x50
|
|
fn bvs_rel(&mut self, bus: &mut impl Bus) -> u8 { self.branch(bus, self.flag(FLAG_OVERFLOW)) } //0x70
|
|
|
|
// NES 6502 opcode length table/matcher
|
|
fn opcode_len(opcode: u8) -> u8 {
|
|
match opcode {
|
|
0x4C => 3, // JMP abs
|
|
0xA2 => 2, // LDX imm
|
|
0x86 => 2, // STX zp
|
|
0x20 => 3, // JSR abs
|
|
0x60 => 1, // RTS imp
|
|
0x48 => 1, // PHA imp
|
|
0x68 => 1, // PLA imp
|
|
0x08 => 1, // PHP imp
|
|
0x28 => 1, // PLP imp
|
|
0x40 => 1, // RTI imp
|
|
0x00 => 1, // BRK imp
|
|
0xEA => 1, // NOP
|
|
0x38 => 1, // SEC imp
|
|
0x90 => 2, // BCC rel
|
|
0xB0 => 2, // BCS rel
|
|
0xF0 => 2, // BEQ rel
|
|
0xD0 => 2, // BNE rel
|
|
0x10 => 2, // BPL rel
|
|
0x30 => 2, // BMI rel
|
|
0x50 => 2, // BVC rel
|
|
0x70 => 2, // BVS rel
|
|
0x18 => 1, // CLC imp
|
|
0x58 => 1, // CLI imp
|
|
0xB8 => 1, // CLV imp
|
|
0xD8 => 1, // CLD imp
|
|
0x78 => 1, // SEI imp
|
|
0xF8 => 1, // SED imp
|
|
0xA9 => 2, // LDA imm
|
|
0xA5 => 2, // LDA zp
|
|
0xB5 => 2, // LDA zpx
|
|
0xAD => 3, // LDA abs
|
|
0xBD => 3, // LDA abx
|
|
0xB9 => 3, // LDA aby
|
|
0xA1 => 2, // LDA izx
|
|
0xB1 => 2, // LDA izy
|
|
_ => 1, // fallback, would show the invalid/unknown opcode only not its parameters
|
|
}
|
|
}
|
|
|
|
}
|
|
|