commit d606446c12fbdc3dda601941ae7c3618570547c7 Author: william Date: Sat Jan 11 22:02:13 2025 -0500 New project, some CPU operations emulated diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..3b25eff --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/nesrust.iml b/.idea/nesrust.iml new file mode 100644 index 0000000..535f815 --- /dev/null +++ b/.idea/nesrust.iml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..ac6c8e7 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,16 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "bitflags" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1be3f42a67d6d345ecd59f675f3f012d6974981560836e938c22b424b85ce1be" + +[[package]] +name = "core" +version = "0.1.0" +dependencies = [ + "bitflags", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..c4d96f4 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +#[package] +#name = "nesrust" +#version = "0.1.0" +#edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[workspace] +members = ["core"] \ No newline at end of file diff --git a/core/Cargo.lock b/core/Cargo.lock new file mode 100644 index 0000000..6c9ecbe --- /dev/null +++ b/core/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "core" +version = "0.1.0" diff --git a/core/Cargo.toml b/core/Cargo.toml new file mode 100644 index 0000000..6ea236e --- /dev/null +++ b/core/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "core" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bitflags = "2.7.0" \ No newline at end of file diff --git a/core/src/cpu/mod.rs b/core/src/cpu/mod.rs new file mode 100644 index 0000000..13375b9 --- /dev/null +++ b/core/src/cpu/mod.rs @@ -0,0 +1,211 @@ +mod op; +mod operations; + +use crate::memory::MemoryBus; +use bitflags::bitflags; + +const STACK_ADDR: u16 = 0x0100; + +/// Represents a 6502 CPU +struct Cpu<'a> { + /// The registers of the CPU + registers: CpuRegisters, + + /// The memory bus accessible by the CPU + memory_bus: &'a MemoryBus, + + /// The amount of cycles the CPU will be busy for (won't execute any instruction) + busy_cycle_count: u16, + + /// Whether an OAM DMA was triggered (data transfer to the PPU OAM memory) + oam_dma_triggered: bool, + + /// Whether an interrupt was requested + nmi_requested: bool, +} + +/// Represents the registers of the 6502 CPU +struct CpuRegisters { + /// The program counter + pc: u16, + + /// The stack pointer + sp: u8, + + /// The accumulator + a: u8, + + /// The X general purpose register + x: u8, + + /// The Y general purpose register + y: u8, + + /// The status flags + status: u8, +} + +bitflags! { + pub struct CpuStatus: u8 { + const Carry = 0b00000001; + const Zero = 0b00000010; + const InterruptDisable = 0b00000100; + const Decimal = 0b00001000; + const B = 0b00010000; + const Overflow = 0b01000000; + const Negative = 0b10000000; + } +} + +pub trait CpuInternals { + /// Gets a status flag + /// + /// # Arguments + /// * `flag` - The status flag to get + fn get_status_flag(&self, flag: CpuStatus) -> bool; + + /// Gets a status flag as a byte + /// + /// # Arguments + /// * `flag` - The status flag to get + /// + /// # Returns + /// `1` if the flag is `true`, `0` if `false` + fn get_status_flag_u8(&self, flag: CpuStatus) -> u8; + + /// Gets the next byte in the program + fn program_get_next_byte(&mut self) -> u8; + + /// Gets the next word in the program + fn program_get_next_word(&mut self) -> u16; + + /// Sets a status flag + /// + /// # Arguments + /// * `flag` - The status flag to set + /// * `value` - Whether the flag is set + fn set_status_flag(&mut self, flag: CpuStatus, set: bool); + + /// Sets a status flag from a byte. + /// The value `0` will become `false`, and any other value will be `true`. + /// + /// # Arguments + /// * `flag` - The status flag to set + /// * `value` - The value of the flag + fn set_status_flag_u8(&mut self, flag: CpuStatus, value: u8); + + /// Pushes a value to the top of the stack + /// + /// # Arguments + /// * `value` - The value to push + fn stack_push(&mut self, value: u8); + + /// Pops the value on the top of the stack + /// + /// # Returns + /// The byte on the top of the stack + fn stack_pop(&mut self) -> u8; + + /// Pushes the context to the top of the stack + /// The context consists of the program counter and the status register. + fn stack_push_context(&mut self); + + /// Pops the context from the top of the stack + fn stack_pop_context(&mut self); +} + +impl Cpu { + pub fn new<'a>(memory_bus: &mut MemoryBus) -> Self { + Cpu { + registers: CpuRegisters { + pc: 0x8000, + sp: 0xFD, + a: 0, + x: 0, + y: 0, + status: 0x04, + }, + memory_bus, + busy_cycle_count: 0, + oam_dma_triggered: false, + nmi_requested: false, + } + } +} + +impl CpuInternals for Cpu { + fn get_status_flag(&self, flag: CpuStatus) -> bool { + *self.registers.status & flag + } + + fn get_status_flag_u8(&self, flag: CpuStatus) -> u8 { + let status = self.get_status_flag(flag); + if status { + 1 + } else { + 0 + } + } + + fn program_get_next_byte(&mut self) -> u8 { + let byte = self.memory_bus.get_byte(self.registers.pc); + self.registers.pc += 1; + + byte + } + + fn program_get_next_word(&mut self) -> u16 { + let word = self.memory_bus.get_word(self.registers.pc); + self.registers.pc += 2; + + word + } + + fn set_status_flag(&mut self, flag: CpuStatus, set: bool) { + if set { + *self.registers.status |= flag; + } else { + *self.registers.status &= !flag; + } + } + + fn set_status_flag_u8(&mut self, flag: CpuStatus, value: u8) { + let set = if value == 0 { false } else { true }; + self.set_status_flag(flag, set); + } + + fn stack_push(&mut self, value: u8) { + assert!(self.registers.sp > 0); + + let addr = STACK_ADDR | self.registers.sp as u16; + self.memory_bus.set_byte(addr, value); + self.registers.sp -= 1; + } + + fn stack_pop(&mut self) -> u8 { + assert!(self.registers.sp < 0xff); + + self.registers.sp += 1; + let addr = STACK_ADDR | self.registers.sp as u16; + + self.memory_bus.get_byte(addr) + } + + fn stack_push_context(&mut self) { + self.stack_push((self.registers.pc >> 8) as u8); + self.stack_push((self.registers.pc & 0xff) as u8); + self.stack_push(self.registers.status); + } + + fn stack_pop_context(&mut self) { + let mut status = self.stack_pop(); + status &= 0xEF; + status |= 0x20; + + let mut pc = self.stack_pop() as u16; + pc += (self.stack_pop() as u16) << 8; + + self.registers.status = status; + self.registers.pc = pc; + } +} diff --git a/core/src/cpu/op.rs b/core/src/cpu/op.rs new file mode 100644 index 0000000..41eee8c --- /dev/null +++ b/core/src/cpu/op.rs @@ -0,0 +1,273 @@ +// From https://github.com/lukexor/tetanes/blob/main/tetanes-core/src/cpu/instr.rs +#[derive(Default, Copy, Clone, Debug, PartialEq, Eq)] +#[rustfmt::skip] +pub enum OperationType { + ADC, AND, ASL, BCC, BCS, BEQ, BIT, BMI, BNE, BPL, BRK, BVC, BVS, CLC, CLD, CLI, CLV, CMP, CPX, + CPY, DEC, DEX, DEY, EOR, INC, INX, INY, JMP, JSR, LDA, LDX, LDY, LSR, NOP, ORA, PHA, PHP, PLA, + PLP, ROL, ROR, RTI, RTS, SBC, SEC, SED, SEI, STA, STX, STY, TAX, TAY, TSX, TXA, TXS, TYA, + // "Unofficial" opcodes + SKB, IGN, ISB, DCP, AXS, LAS, LAX, AHX, SAX, XAA, SXA, RRA, TAS, SYA, ARR, SRE, ALR, RLA, ANC, + SLO, #[default] XXX +} + +#[derive(Default, Copy, Clone, Debug, PartialEq, Eq)] +#[rustfmt::skip] +pub enum AddressingMode { + #[default] + IMM, + ZP0, ZPX, ZPY, + ABS, ABX, ABY, + IND, IDX, IDY, + REL, ACC, IMP, +} + +use crate::cpu::{Cpu, CpuInternals}; +use AddressingMode::{ABS, ABX, ABY, ACC, IDX, IDY, IMM, IMP, IND, REL, ZP0, ZPX, ZPY}; +use OperationType::{ + ADC, AHX, ALR, ANC, AND, ARR, ASL, AXS, BCC, BCS, BEQ, BIT, BMI, BNE, BPL, BRK, BVC, BVS, CLC, + CLD, CLI, CLV, CMP, CPX, CPY, DCP, DEC, DEX, DEY, EOR, IGN, INC, INX, INY, ISB, JMP, JSR, LAS, + LAX, LDA, LDX, LDY, LSR, NOP, ORA, PHA, PHP, PLA, PLP, RLA, ROL, ROR, RRA, RTI, RTS, SAX, SBC, + SEC, SED, SEI, SKB, SLO, SRE, STA, STX, STY, SXA, SYA, TAS, TAX, TAY, TSX, TXA, TXS, TYA, XAA, + XXX, +}; + +/// CPU Instruction. +pub struct Instruction(u8, AddressingMode, OperationType, usize); + +impl Instruction { + pub const fn opcode(&self) -> u8 { + self.0 + } + + pub const fn addr_mode(&self) -> AddressingMode { + self.1 + } + + pub const fn op(&self) -> OperationType { + self.2 + } + + pub const fn cycles(&self) -> usize { + self.3 + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +enum OperandType { + Accumulator, + Address, + Immediate, +} + +pub struct Operand { + operand_type: OperandType, + pub value: u16, + is_page_crossing: bool, +} + +impl Operand { + pub const fn value_u8(&self) -> u8 { + self.value as u8 + } + + pub const fn is_address(&self) -> bool { + self.operand_type == OperandType::Address + } + + pub fn read(&self, cpu: &Cpu) -> u8 { + match self.operand_type { + OperandType::Accumulator => cpu.registers.a, + OperandType::Immediate => self.value_u8(), + OperandType::Address => { + let addr = self.value; + cpu.memory_bus.get_byte(addr) + } + } + } + + pub fn write(&self, value: u8, cpu: &mut Cpu) { + match self.operand_type { + OperandType::Accumulator => cpu.registers.a = value, + OperandType::Address => { + let addr = self.value; + cpu.memory_bus.set_byte(addr, value); + } + OperandType::Immediate => unreachable!("Cannot write to immediate operand"), + } + } +} + +// Operands +impl Cpu { + /// 16x16 grid of 6502 opcodes. Matches datasheet matrix for easy lookup + #[rustfmt::skip] + pub const INSTRUCTIONS: [Instruction; 256] = [ + Instruction(0x00, IMM, BRK, 7), Instruction(0x01, IDX, ORA, 6), Instruction(0x02, IMP, XXX, 2), Instruction(0x03, IDX, SLO, 8), Instruction(0x04, ZP0, NOP, 3), Instruction(0x05, ZP0, ORA, 3), Instruction(0x06, ZP0, ASL, 5), Instruction(0x07, ZP0, SLO, 5), Instruction(0x08, IMP, PHP, 3), Instruction(0x09, IMM, ORA, 2), Instruction(0x0A, ACC, ASL, 2), Instruction(0x0B, IMM, ANC, 2), Instruction(0x0C, ABS, NOP, 4), Instruction(0x0D, ABS, ORA, 4), Instruction(0x0E, ABS, ASL, 6), Instruction(0x0F, ABS, SLO, 6), + Instruction(0x10, REL, BPL, 2), Instruction(0x11, IDY, ORA, 5), Instruction(0x12, IMP, XXX, 2), Instruction(0x13, IDY, SLO, 8), Instruction(0x14, ZPX, NOP, 4), Instruction(0x15, ZPX, ORA, 4), Instruction(0x16, ZPX, ASL, 6), Instruction(0x17, ZPX, SLO, 6), Instruction(0x18, IMP, CLC, 2), Instruction(0x19, ABY, ORA, 4), Instruction(0x1A, IMP, NOP, 2), Instruction(0x1B, ABY, SLO, 7), Instruction(0x1C, ABX, IGN, 4), Instruction(0x1D, ABX, ORA, 4), Instruction(0x1E, ABX, ASL, 7), Instruction(0x1F, ABX, SLO, 7), + Instruction(0x20, ABS, JSR, 6), Instruction(0x21, IDX, AND, 6), Instruction(0x22, IMP, XXX, 2), Instruction(0x23, IDX, RLA, 8), Instruction(0x24, ZP0, BIT, 3), Instruction(0x25, ZP0, AND, 3), Instruction(0x26, ZP0, ROL, 5), Instruction(0x27, ZP0, RLA, 5), Instruction(0x28, IMP, PLP, 4), Instruction(0x29, IMM, AND, 2), Instruction(0x2A, ACC, ROL, 2), Instruction(0x2B, IMM, ANC, 2), Instruction(0x2C, ABS, BIT, 4), Instruction(0x2D, ABS, AND, 4), Instruction(0x2E, ABS, ROL, 6), Instruction(0x2F, ABS, RLA, 6), + Instruction(0x30, REL, BMI, 2), Instruction(0x31, IDY, AND, 5), Instruction(0x32, IMP, XXX, 2), Instruction(0x33, IDY, RLA, 8), Instruction(0x34, ZPX, NOP, 4), Instruction(0x35, ZPX, AND, 4), Instruction(0x36, ZPX, ROL, 6), Instruction(0x37, ZPX, RLA, 6), Instruction(0x38, IMP, SEC, 2), Instruction(0x39, ABY, AND, 4), Instruction(0x3A, IMP, NOP, 2), Instruction(0x3B, ABY, RLA, 7), Instruction(0x3C, ABX, IGN, 4), Instruction(0x3D, ABX, AND, 4), Instruction(0x3E, ABX, ROL, 7), Instruction(0x3F, ABX, RLA, 7), + Instruction(0x40, IMP, RTI, 6), Instruction(0x41, IDX, EOR, 6), Instruction(0x42, IMP, XXX, 2), Instruction(0x43, IDX, SRE, 8), Instruction(0x44, ZP0, NOP, 3), Instruction(0x45, ZP0, EOR, 3), Instruction(0x46, ZP0, LSR, 5), Instruction(0x47, ZP0, SRE, 5), Instruction(0x48, IMP, PHA, 3), Instruction(0x49, IMM, EOR, 2), Instruction(0x4A, ACC, LSR, 2), Instruction(0x4B, IMM, ALR, 2), Instruction(0x4C, ABS, JMP, 3), Instruction(0x4D, ABS, EOR, 4), Instruction(0x4E, ABS, LSR, 6), Instruction(0x4F, ABS, SRE, 6), + Instruction(0x50, REL, BVC, 2), Instruction(0x51, IDY, EOR, 5), Instruction(0x52, IMP, XXX, 2), Instruction(0x53, IDY, SRE, 8), Instruction(0x54, ZPX, NOP, 4), Instruction(0x55, ZPX, EOR, 4), Instruction(0x56, ZPX, LSR, 6), Instruction(0x57, ZPX, SRE, 6), Instruction(0x58, IMP, CLI, 2), Instruction(0x59, ABY, EOR, 4), Instruction(0x5A, IMP, NOP, 2), Instruction(0x5B, ABY, SRE, 7), Instruction(0x5C, ABX, IGN, 4), Instruction(0x5D, ABX, EOR, 4), Instruction(0x5E, ABX, LSR, 7), Instruction(0x5F, ABX, SRE, 7), + Instruction(0x60, IMP, RTS, 6), Instruction(0x61, IDX, ADC, 6), Instruction(0x62, IMP, XXX, 2), Instruction(0x63, IDX, RRA, 8), Instruction(0x64, ZP0, NOP, 3), Instruction(0x65, ZP0, ADC, 3), Instruction(0x66, ZP0, ROR, 5), Instruction(0x67, ZP0, RRA, 5), Instruction(0x68, IMP, PLA, 4), Instruction(0x69, IMM, ADC, 2), Instruction(0x6A, ACC, ROR, 2), Instruction(0x6B, IMM, ARR, 2), Instruction(0x6C, IND, JMP, 5), Instruction(0x6D, ABS, ADC, 4), Instruction(0x6E, ABS, ROR, 6), Instruction(0x6F, ABS, RRA, 6), + Instruction(0x70, REL, BVS, 2), Instruction(0x71, IDY, ADC, 5), Instruction(0x72, IMP, XXX, 2), Instruction(0x73, IDY, RRA, 8), Instruction(0x74, ZPX, NOP, 4), Instruction(0x75, ZPX, ADC, 4), Instruction(0x76, ZPX, ROR, 6), Instruction(0x77, ZPX, RRA, 6), Instruction(0x78, IMP, SEI, 2), Instruction(0x79, ABY, ADC, 4), Instruction(0x7A, IMP, NOP, 2), Instruction(0x7B, ABY, RRA, 7), Instruction(0x7C, ABX, IGN, 4), Instruction(0x7D, ABX, ADC, 4), Instruction(0x7E, ABX, ROR, 7), Instruction(0x7F, ABX, RRA, 7), + Instruction(0x80, IMM, SKB, 2), Instruction(0x81, IDX, STA, 6), Instruction(0x82, IMM, SKB, 2), Instruction(0x83, IDX, SAX, 6), Instruction(0x84, ZP0, STY, 3), Instruction(0x85, ZP0, STA, 3), Instruction(0x86, ZP0, STX, 3), Instruction(0x87, ZP0, SAX, 3), Instruction(0x88, IMP, DEY, 2), Instruction(0x89, IMM, SKB, 2), Instruction(0x8A, IMP, TXA, 2), Instruction(0x8B, IMM, XAA, 2), Instruction(0x8C, ABS, STY, 4), Instruction(0x8D, ABS, STA, 4), Instruction(0x8E, ABS, STX, 4), Instruction(0x8F, ABS, SAX, 4), + Instruction(0x90, REL, BCC, 2), Instruction(0x91, IDY, STA, 6), Instruction(0x92, IMP, XXX, 2), Instruction(0x93, IDY, AHX, 6), Instruction(0x94, ZPX, STY, 4), Instruction(0x95, ZPX, STA, 4), Instruction(0x96, ZPY, STX, 4), Instruction(0x97, ZPY, SAX, 4), Instruction(0x98, IMP, TYA, 2), Instruction(0x99, ABY, STA, 5), Instruction(0x9A, IMP, TXS, 2), Instruction(0x9B, ABY, TAS, 5), Instruction(0x9C, ABX, SYA, 5), Instruction(0x9D, ABX, STA, 5), Instruction(0x9E, ABY, SXA, 5), Instruction(0x9F, ABY, AHX, 5), + Instruction(0xA0, IMM, LDY, 2), Instruction(0xA1, IDX, LDA, 6), Instruction(0xA2, IMM, LDX, 2), Instruction(0xA3, IDX, LAX, 6), Instruction(0xA4, ZP0, LDY, 3), Instruction(0xA5, ZP0, LDA, 3), Instruction(0xA6, ZP0, LDX, 3), Instruction(0xA7, ZP0, LAX, 3), Instruction(0xA8, IMP, TAY, 2), Instruction(0xA9, IMM, LDA, 2), Instruction(0xAA, IMP, TAX, 2), Instruction(0xAB, IMM, LAX, 2), Instruction(0xAC, ABS, LDY, 4), Instruction(0xAD, ABS, LDA, 4), Instruction(0xAE, ABS, LDX, 4), Instruction(0xAF, ABS, LAX, 4), + Instruction(0xB0, REL, BCS, 2), Instruction(0xB1, IDY, LDA, 5), Instruction(0xB2, IMP, XXX, 2), Instruction(0xB3, IDY, LAX, 5), Instruction(0xB4, ZPX, LDY, 4), Instruction(0xB5, ZPX, LDA, 4), Instruction(0xB6, ZPY, LDX, 4), Instruction(0xB7, ZPY, LAX, 4), Instruction(0xB8, IMP, CLV, 2), Instruction(0xB9, ABY, LDA, 4), Instruction(0xBA, IMP, TSX, 2), Instruction(0xBB, ABY, LAS, 4), Instruction(0xBC, ABX, LDY, 4), Instruction(0xBD, ABX, LDA, 4), Instruction(0xBE, ABY, LDX, 4), Instruction(0xBF, ABY, LAX, 4), + Instruction(0xC0, IMM, CPY, 2), Instruction(0xC1, IDX, CMP, 6), Instruction(0xC2, IMM, SKB, 2), Instruction(0xC3, IDX, DCP, 8), Instruction(0xC4, ZP0, CPY, 3), Instruction(0xC5, ZP0, CMP, 3), Instruction(0xC6, ZP0, DEC, 5), Instruction(0xC7, ZP0, DCP, 5), Instruction(0xC8, IMP, INY, 2), Instruction(0xC9, IMM, CMP, 2), Instruction(0xCA, IMP, DEX, 2), Instruction(0xCB, IMM, AXS, 2), Instruction(0xCC, ABS, CPY, 4), Instruction(0xCD, ABS, CMP, 4), Instruction(0xCE, ABS, DEC, 6), Instruction(0xCF, ABS, DCP, 6), + Instruction(0xD0, REL, BNE, 2), Instruction(0xD1, IDY, CMP, 5), Instruction(0xD2, IMP, XXX, 2), Instruction(0xD3, IDY, DCP, 8), Instruction(0xD4, ZPX, NOP, 4), Instruction(0xD5, ZPX, CMP, 4), Instruction(0xD6, ZPX, DEC, 6), Instruction(0xD7, ZPX, DCP, 6), Instruction(0xD8, IMP, CLD, 2), Instruction(0xD9, ABY, CMP, 4), Instruction(0xDA, IMP, NOP, 2), Instruction(0xDB, ABY, DCP, 7), Instruction(0xDC, ABX, IGN, 4), Instruction(0xDD, ABX, CMP, 4), Instruction(0xDE, ABX, DEC, 7), Instruction(0xDF, ABX, DCP, 7), + Instruction(0xE0, IMM, CPX, 2), Instruction(0xE1, IDX, SBC, 6), Instruction(0xE2, IMM, SKB, 2), Instruction(0xE3, IDX, ISB, 8), Instruction(0xE4, ZP0, CPX, 3), Instruction(0xE5, ZP0, SBC, 3), Instruction(0xE6, ZP0, INC, 5), Instruction(0xE7, ZP0, ISB, 5), Instruction(0xE8, IMP, INX, 2), Instruction(0xE9, IMM, SBC, 2), Instruction(0xEA, IMP, NOP, 2), Instruction(0xEB, IMM, SBC, 2), Instruction(0xEC, ABS, CPX, 4), Instruction(0xED, ABS, SBC, 4), Instruction(0xEE, ABS, INC, 6), Instruction(0xEF, ABS, ISB, 6), + Instruction(0xF0, REL, BEQ, 2), Instruction(0xF1, IDY, SBC, 5), Instruction(0xF2, IMP, XXX, 2), Instruction(0xF3, IDY, ISB, 8), Instruction(0xF4, ZPX, NOP, 4), Instruction(0xF5, ZPX, SBC, 4), Instruction(0xF6, ZPX, INC, 6), Instruction(0xF7, ZPX, ISB, 6), Instruction(0xF8, IMP, SED, 2), Instruction(0xF9, ABY, SBC, 4), Instruction(0xFA, IMP, NOP, 2), Instruction(0xFB, ABY, ISB, 7), Instruction(0xFC, ABX, IGN, 4), Instruction(0xFD, ABX, SBC, 4), Instruction(0xFE, ABX, INC, 7), Instruction(0xFF, ABX, ISB, 7), + ]; + + pub fn read_next_instruction(&mut self) -> Instruction { + let op_code = self.program_get_next_byte(); + let instruction = Instruction[op_code]; + + instruction + } + + /// Decodes the next operand in memory + /// + /// # Arguments + /// * `addr_mode` - The addressing mode of the instruction + fn operand_decode(&mut self, addr_mode: AddressingMode) -> Operand { + if addr_mode == ACC { + Self::operand_decode_acc() + } else if addr_mode == IMM { + self.operand_decode_immediate() + } else { + // Other modes are addresses + let mut page_crossed = false; + let addr = match addr_mode { + ZP0 => self.operand_addr_zp(), + ZPX => self.operand_addr_zpx(), + ZPY => self.operand_addr_zpy(), + ABS => self.operand_addr_abs(), + ABX => self.operand_addr_absx(&mut page_crossed), + ABY => self.operand_addr_absy(&mut page_crossed), + IND => self.operand_addr_indj(), + IDX => self.operand_addr_indx(), + IDY => self.operand_addr_indy(&mut page_crossed), + REL => self.operand_addr_rel(), + _ => unreachable!("Already handled"), + }; + + Operand { + operand_type: OperandType::Address, + value: addr, + is_page_crossing: page_crossed, + } + } + } + + /// A - A + fn operand_decode_acc() -> Operand { + Operand { + operand_type: OperandType::Accumulator, + value: 0, + is_page_crossing: false, + } + } + + /// #v - byte + fn operand_decode_immediate(&mut self) -> Operand { + Operand { + operand_type: OperandType::Immediate, + value: self.program_get_next_byte() as u16, + is_page_crossing: false, + } + } + + /// d - byte % 256 + fn operand_addr_zp(&mut self) -> u16 { + self.program_get_next_byte() as u16 + } + + /// d,x - (byte + X) % 256 + fn operand_addr_zpx(&mut self) -> u16 { + let addr = self.program_get_next_byte() as u16; + (addr + self.registers.x as u16) & 0xFF + } + + /// d,y - (byte + Y) % 256 + fn operand_addr_zpy(&mut self) -> u16 { + let addr = self.program_get_next_byte() as u16; + (addr + self.registers.y as u16) & 0xFF + } + + /// a - word + fn operand_addr_abs(&mut self) -> u16 { + self.program_get_next_word() + } + + /// a,x - word + X + fn operand_addr_absx(&mut self, page_crossed: &mut bool) -> u16 { + let addr = self.program_get_next_word(); + let indexed_addr = addr + self.registers.x as u16; + + *page_crossed = is_page_crossed(addr, indexed_addr); + + indexed_addr + } + + /// a,y - word + Y + fn operand_addr_absy(&mut self, page_crossed: &mut bool) -> u16 { + let addr = self.program_get_next_word(); + let indexed_addr = addr + self.registers.y as u16; + + *page_crossed = is_page_crossed(addr, indexed_addr); + + indexed_addr + } + + /// (a) - PEEK(word) + fn operand_addr_indj(&mut self) -> u16 { + let ref_addr = self.program_get_next_word(); + + if ref_addr & 0xFF == 0xFF { + // Error in NES CPU for JMP op + let low = self.memory_bus.get_byte(ref_addr) as u16; + let high = self.memory_bus.get_byte(ref_addr & 0xFF00) as u16; + low + (high << 8) + } else { + self.memory_bus.get_word(ref_addr) + } + } + + /// (d,x) - PEEK((byte + X) % 256) + fn operand_addr_indx(&mut self) -> u16 { + let mut ref_addr = self.program_get_next_byte() as u16; + ref_addr += self.registers.x as u16; + + let low = self.memory_bus.get_byte(ref_addr & 0xFF) as u16; + let high = self.memory_bus.get_byte((ref_addr + 1) & 0xFF) as u16; + low + (high << 8) + } + + /// (d),y - PEEK(byte) + Y + fn operand_addr_indy(&mut self, page_crossed: &mut bool) -> u16 { + let mut ref_addr = self.program_get_next_byte() as u16; + + let low = self.memory_bus.get_byte(ref_addr) as u16; + let high = self.memory_bus.get_byte((ref_addr + 1) & 0xFF) as u16; + let addr = low + (high << 8); + let incr_addr = addr + self.registers.y as u16; + + *page_crossed = is_page_crossed(addr, incr_addr); + + incr_addr + } + + /// label - PC + byte + fn operand_addr_rel(&mut self) -> u16 { + let base_addr = self.registers.pc; + let offset = self.program_get_next_byte() as u16; + + base_addr + offset + } +} + +fn is_page_crossed(a: u16, b: u16) -> bool { + (a & 0xFF00) != (b & 0xFF00) +} diff --git a/core/src/cpu/operations.rs b/core/src/cpu/operations.rs new file mode 100644 index 0000000..c4e351e --- /dev/null +++ b/core/src/cpu/operations.rs @@ -0,0 +1,181 @@ +use crate::cpu::op::Operand; +use crate::cpu::{Cpu, CpuInternals, CpuStatus}; + +fn is_sign_overflow(val1: u8, val2: u8, result: u8) -> bool { + (val1 & 0x80 == val2 & 0x80) && (val1 & 0x80 != result & 0x80) +} + +impl Cpu { + fn add_with_carry(&mut self, value: u8) { + let a = self.registers.a; + + let addition = a.wrapping_add(value); + let mut overflow = value < a; + + let result = addition.wrapping_add(self.get_status_flag_u8(CpuStatus::Carry)); + if result < addition { + // The addition resulted in a smaller number, there was overflow + overflow = true; + } + + self.registers.a = result; + self.set_status_flag(CpuStatus::Carry, overflow); + self.set_status_flag(CpuStatus::Overflow, is_sign_overflow(a, value, result)); + } + + fn set_common_flags(&mut self, result: u8) { + self.set_status_flag(CpuStatus::Zero, result == 0); + self.set_status_flag(CpuStatus::Negative, result & 0x80 != 0); + } + + // ADC operations + /// Add with carry to accumulator + fn op_adc(&mut self, operand: Operand) { + let value = operand.read(self); + self.add_with_carry(value); + } + + /// Unofficial, stores A, X and (high byte of address + 1) + /// Unstable in the hardware + fn op_ahx(&mut self, operand: Operand) { + assert!(operand.is_address()); + + let addr = operand.value; + let a = self.registers.a; + let x = self.registers.x; + let h = (addr >> 8) as u8; + + let result = a & x & h; + operand.write(result, self); + } + + /// Unofficial, AND + LSR + fn op_alr(&mut self, operand: Operand) { + let value = operand.read(self); + let a = self.registers.a; + + let result = a & value; + + // Sets the dropped bit in the carry register + self.set_status_flag_u8(CpuStatus::Carry, result & 0x01); + + let result_shift = result >> 1; + operand.write(result_shift, self); + + self.set_common_flags(result_shift); + } + + /// Unofficial, bitwise AND and set carry from shift left + fn op_anc(&mut self, operand: Operand) { + let value = operand.read(self); + let a = self.registers.a; + + let result = a & value; + self.registers.a = result; + + self.set_common_flags(result); + self.set_status_flag_u8(CpuStatus::Carry, result & 0x80); + } + + /// Bitwise AND with accumulator + fn op_and(&mut self, operand: Operand) { + let value = operand.read(self); + let a = self.registers.a; + + let result = a & value; + self.registers.a = result; + + self.set_common_flags(result); + } + + /// Unofficial, depends on analog effects and cannot be emulated easily. + fn op_ane(&self, _: Operand) { + assert!(false); + } + + /// Unofficial, AND + ROR + fn op_arr(&mut self, operand: Operand) { + let value = operand.read(self); + let a = self.registers.a; + let carry = self.get_status_flag_u8(CpuStatus::Carry); + + let result = a & value; + let result_shift = result >> 1 | (carry << 7); + self.registers.a = result_shift; + + let result_added = result.wrapping_add(value); + + self.set_common_flags(result_shift); + self.set_status_flag_u8(CpuStatus::Carry, result & 0x01); + self.set_status_flag( + CpuStatus::Overflow, + is_sign_overflow(result, value, result_added), + ); + } + + /// Arithmetic shift left + fn op_asl(&mut self, operand: Operand) { + let value = operand.read(self); + + let result = value << 1; + operand.write(result, self); + + self.set_common_flags(result); + self.set_status_flag_u8(CpuStatus::Carry, value & 0x80); + } + + /// Compare to accumulator + fn op_cmp(&mut self, operand: Operand) { + let value = operand.read(self); + let a = self.registers.a; + + let result = a.wrapping_sub(value); + + self.set_status_flag(CpuStatus::Carry, a >= value); + self.set_common_flags(result); + } + + /// Bitwise exclusive OR with accumulator + fn op_eor(&mut self, operand: Operand) { + let value = operand.read(self); + let a = self.registers.a; + + let result = a ^ value; + self.registers.a = result; + + self.set_common_flags(result); + } + + /// Load to accumulator + fn op_lda(&mut self, operand: Operand) { + let value = operand.read(self); + + self.registers.a = value; + + self.set_common_flags(value); + } + + /// Bitwise OR with accumulator + fn op_ora(&mut self, operand: Operand) { + let value = operand.read(self); + let a = self.registers.a; + + let result = a | value; + self.registers.a = result; + } + + /// Subtract with carry from accumulator + fn op_sbc(&mut self, operand: Operand) { + let value = operand.read(self); + self.add_with_carry(!value); + } + + /// Store accumulator + fn op_sta(&mut self, operand: Operand) { + assert!(operand.is_address()); + + let a = self.registers.a; + operand.write(a, self); + // TODO: C code enabled page crossing in the operand, but I didn't find it in the docs + } +} diff --git a/core/src/lib.rs b/core/src/lib.rs new file mode 100644 index 0000000..a617641 --- /dev/null +++ b/core/src/lib.rs @@ -0,0 +1,13 @@ +mod cpu; +mod memory; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +} diff --git a/core/src/memory.rs b/core/src/memory.rs new file mode 100644 index 0000000..7399eef --- /dev/null +++ b/core/src/memory.rs @@ -0,0 +1,32 @@ +const MEMORY_SIZE: usize = 0x0800; + +pub struct MemoryBus { + ram: [u8; MEMORY_SIZE], +} + +impl MemoryBus { + /// Gets a byte from memory. + /// + /// # Arguments + /// * `addr` - The address of the byte + pub fn get_byte(&self, addr: u16) -> u8 { + unimplemented!("MemoryBus::get_byte"); + } + + /// Gets a word from memory. + /// + /// # Arguments + /// * `addr` - The address of the word + pub fn get_word(&self, addr: u16) -> u16 { + unimplemented!("MemoryBus::get_word"); + } + + /// Sets a byte in memory + /// + /// # Arguments + /// * `addr` - The address of the byte + /// * `value` - The value to set + pub fn set_byte(&mut self, addr: u16, value: u8) { + unimplemented!("MemoryBus::set_byte"); + } +} diff --git a/core/target/.rustc_info.json b/core/target/.rustc_info.json new file mode 100644 index 0000000..4533273 --- /dev/null +++ b/core/target/.rustc_info.json @@ -0,0 +1 @@ +{"rustc_fingerprint":52135951790503613,"outputs":{"4614504638168534921":{"success":true,"status":"","code":0,"stdout":"rustc 1.74.0-nightly (62ebe3a2b 2023-09-08)\nbinary: rustc\ncommit-hash: 62ebe3a2b177d50ec664798d731b8a8d1a9120d1\ncommit-date: 2023-09-08\nhost: x86_64-unknown-linux-gnu\nrelease: 1.74.0-nightly\nLLVM version: 17.0.0\n","stderr":""},"14371922958718593042":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/home/william/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu\noff\npacked\nunpacked\n___\ndebug_assertions\noverflow_checks\npanic=\"unwind\"\nproc_macro\nrelocation_model=\"pic\"\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"gnu\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_has_atomic\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_has_atomic_equal_alignment=\"16\"\ntarget_has_atomic_equal_alignment=\"32\"\ntarget_has_atomic_equal_alignment=\"64\"\ntarget_has_atomic_equal_alignment=\"8\"\ntarget_has_atomic_equal_alignment=\"ptr\"\ntarget_has_atomic_load_store\ntarget_has_atomic_load_store=\"16\"\ntarget_has_atomic_load_store=\"32\"\ntarget_has_atomic_load_store=\"64\"\ntarget_has_atomic_load_store=\"8\"\ntarget_has_atomic_load_store=\"ptr\"\ntarget_os=\"linux\"\ntarget_pointer_width=\"64\"\ntarget_thread_local\ntarget_vendor=\"unknown\"\nunix\n","stderr":""}},"successes":{}} \ No newline at end of file diff --git a/core/target/CACHEDIR.TAG b/core/target/CACHEDIR.TAG new file mode 100644 index 0000000..20d7c31 --- /dev/null +++ b/core/target/CACHEDIR.TAG @@ -0,0 +1,3 @@ +Signature: 8a477f597d28d172789f06886806bc55 +# This file is a cache directory tag created by cargo. +# For information about cache directory tags see https://bford.info/cachedir/ diff --git a/core/target/debug/.cargo-lock b/core/target/debug/.cargo-lock new file mode 100644 index 0000000..e69de29 diff --git a/core/target/debug/.fingerprint/core-0d1f6aefe433fb28/invoked.timestamp b/core/target/debug/.fingerprint/core-0d1f6aefe433fb28/invoked.timestamp new file mode 100644 index 0000000..e00328d --- /dev/null +++ b/core/target/debug/.fingerprint/core-0d1f6aefe433fb28/invoked.timestamp @@ -0,0 +1 @@ +This file has an mtime of when this was started. \ No newline at end of file diff --git a/core/target/debug/.fingerprint/core-0d1f6aefe433fb28/lib-core b/core/target/debug/.fingerprint/core-0d1f6aefe433fb28/lib-core new file mode 100644 index 0000000..8d05a71 --- /dev/null +++ b/core/target/debug/.fingerprint/core-0d1f6aefe433fb28/lib-core @@ -0,0 +1 @@ +8876810ef228e07e \ No newline at end of file diff --git a/core/target/debug/.fingerprint/core-0d1f6aefe433fb28/lib-core.json b/core/target/debug/.fingerprint/core-0d1f6aefe433fb28/lib-core.json new file mode 100644 index 0000000..bca75ea --- /dev/null +++ b/core/target/debug/.fingerprint/core-0d1f6aefe433fb28/lib-core.json @@ -0,0 +1 @@ +{"rustc":12738513403806206521,"features":"[]","target":16474754454797904296,"profile":13126374248311259211,"path":17523903030608720598,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/core-0d1f6aefe433fb28/dep-lib-core"}}],"rustflags":[],"metadata":7797948686568424061,"config":2202906307356721367,"compile_kind":0} \ No newline at end of file diff --git a/core/target/debug/.fingerprint/core-9be0639a870abcef/invoked.timestamp b/core/target/debug/.fingerprint/core-9be0639a870abcef/invoked.timestamp new file mode 100644 index 0000000..e00328d --- /dev/null +++ b/core/target/debug/.fingerprint/core-9be0639a870abcef/invoked.timestamp @@ -0,0 +1 @@ +This file has an mtime of when this was started. \ No newline at end of file diff --git a/core/target/debug/.fingerprint/core-9be0639a870abcef/test-lib-core b/core/target/debug/.fingerprint/core-9be0639a870abcef/test-lib-core new file mode 100644 index 0000000..185aebd --- /dev/null +++ b/core/target/debug/.fingerprint/core-9be0639a870abcef/test-lib-core @@ -0,0 +1 @@ +d2244b0cb93f6bbb \ No newline at end of file diff --git a/core/target/debug/.fingerprint/core-9be0639a870abcef/test-lib-core.json b/core/target/debug/.fingerprint/core-9be0639a870abcef/test-lib-core.json new file mode 100644 index 0000000..14bf3fc --- /dev/null +++ b/core/target/debug/.fingerprint/core-9be0639a870abcef/test-lib-core.json @@ -0,0 +1 @@ +{"rustc":12738513403806206521,"features":"[]","target":16474754454797904296,"profile":18326522262828315194,"path":17523903030608720598,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/core-9be0639a870abcef/dep-test-lib-core"}}],"rustflags":[],"metadata":7797948686568424061,"config":2202906307356721367,"compile_kind":0} \ No newline at end of file