From 11499b6bc8042c9b10e523110fb05df20faa1aa9 Mon Sep 17 00:00:00 2001 From: omarandlorraine <64254276+omarandlorraine@users.noreply.github.com> Date: Tue, 31 Oct 2023 15:45:15 +0000 Subject: [PATCH] Starting on implementing different variants (#88) * start on separating 6502 variants from cpu itself * add a single variant: the NMOS one * get examples & tests running again * Add the Revision A variant, one that has no ROR * disable failing lint in build-time dependencies * Variant with no decimal mode * Revert "disable failing lint in build-time dependencies" This reverts commit c87975e9374e99b593ab20e6f87d7c01cc7ffd03. * some doc comments * specify the variant in unit test now the API has changed --------- Co-authored-by: Sam M W --- README.md | 6 +- examples/euclid.rs | 3 +- examples/euclid_bytes.rs | 3 +- examples/mos6502.rs | 3 +- src/cpu.rs | 233 +++++++--- src/instruction.rs | 922 +++++++++++++++------------------------ src/lib.rs | 11 + 7 files changed, 544 insertions(+), 637 deletions(-) diff --git a/README.md b/README.md index fc3570b..1e55e56 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ Source: [Wikipedia](https://en.wikipedia.org/wiki/MOS_Technology_6502) ```rust use mos6502::memory::Bus; use mos6502::memory::Memory; +use mos6502::instruction::Nmos6502; use mos6502::cpu; fn main() { @@ -52,7 +53,7 @@ fn main() { 0x4c, 0x10, 0x00, // Jump to .algo ]; - let mut cpu = cpu::CPU::new(Memory::new()); + let mut cpu = cpu::CPU::new(Memory::new(), Nmos6502); cpu.memory.set_bytes(0x00, &zero_page_data); cpu.memory.set_bytes(0x10, &program); @@ -86,6 +87,7 @@ This will create a binary file `euclid.bin` that you can load into the emulator: ```rust use mos6502::memory::Bus; use mos6502::memory::Memory; +use mos6502::instruction::Nmos6502; use mos6502::cpu; use std::fs::read; @@ -103,7 +105,7 @@ fn main() { } }; - let mut cpu = cpu::CPU::new(Memory::new()); + let mut cpu = cpu::CPU::new(Memory::new(), Nmos6502); cpu.memory.set_bytes(0x00, &zero_page_data); cpu.memory.set_bytes(0x10, &program); diff --git a/examples/euclid.rs b/examples/euclid.rs index 5636deb..8955389 100644 --- a/examples/euclid.rs +++ b/examples/euclid.rs @@ -1,4 +1,5 @@ use mos6502::cpu; +use mos6502::instruction::Nmos6502; use mos6502::memory::Bus; use mos6502::memory::Memory; use std::fs::read; @@ -22,7 +23,7 @@ fn main() { } }; - let mut cpu = cpu::CPU::new(Memory::new()); + let mut cpu = cpu::CPU::new(Memory::new(), Nmos6502); cpu.memory.set_bytes(0x00, &zero_page_data); cpu.memory.set_bytes(0x10, &program); diff --git a/examples/euclid_bytes.rs b/examples/euclid_bytes.rs index d261fa0..d2718a3 100644 --- a/examples/euclid_bytes.rs +++ b/examples/euclid_bytes.rs @@ -1,6 +1,7 @@ extern crate mos6502; use mos6502::cpu; +use mos6502::instruction::Nmos6502; use mos6502::memory::Bus; use mos6502::memory::Memory; @@ -35,7 +36,7 @@ fn main() { 0x4c, 0x10, 0x00, // Jump to .algo ]; - let mut cpu = cpu::CPU::new(Memory::new()); + let mut cpu = cpu::CPU::new(Memory::new(), Nmos6502); cpu.memory.set_bytes(0x00, &zero_page_data); cpu.memory.set_bytes(0x10, &program); diff --git a/examples/mos6502.rs b/examples/mos6502.rs index 2d0a788..1572003 100644 --- a/examples/mos6502.rs +++ b/examples/mos6502.rs @@ -29,12 +29,13 @@ extern crate mos6502; #[cfg(not(test))] use mos6502::cpu; +use mos6502::instruction::Nmos6502; use mos6502::memory::Bus; use mos6502::memory::Memory; #[cfg(not(test))] fn main() { - let mut cpu = cpu::CPU::new(Memory::new()); + let mut cpu = cpu::CPU::new(Memory::new(), Nmos6502); // "Load" a program diff --git a/src/cpu.rs b/src/cpu.rs index 4514369..dea2f5f 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -25,8 +25,9 @@ // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. -use crate::instruction::{self, AddressingMode, DecodedInstr, Instruction, OpInput}; +use crate::instruction::{AddressingMode, DecodedInstr, Instruction, OpInput}; use crate::memory::Bus; +use crate::Variant; use crate::registers::{Registers, StackPointer, Status, StatusArgs}; @@ -37,19 +38,22 @@ fn arr_to_addr(arr: &[u8]) -> u16 { } #[derive(Clone)] -pub struct CPU +pub struct CPU where M: Bus, + V: Variant, { pub registers: Registers, pub memory: M, + variant: core::marker::PhantomData, } -impl CPU { - pub fn new(memory: M) -> CPU { +impl CPU { + pub fn new(memory: M, _variant: V) -> CPU { CPU { registers: Registers::new(), memory, + variant: core::marker::PhantomData::, } } @@ -60,7 +64,7 @@ impl CPU { pub fn fetch_next_and_decode(&mut self) -> Option { let x: u8 = self.memory.get_byte(self.registers.program_counter); - match instruction::OPCODES[x as usize] { + match V::decode(x) { Some((instr, am)) => { let extra_bytes = am.extra_bytes(); let num_bytes = extra_bytes + 1; @@ -192,6 +196,15 @@ impl CPU { debug!("add with carry. address: {:?}. value: {}", addr, val); self.add_with_carry(val); } + (Instruction::ADCnd, OpInput::UseImmediate(val)) => { + debug!("add with carry immediate: {}", val); + self.add_with_no_decimal(val); + } + (Instruction::ADCnd, OpInput::UseAddress(addr)) => { + let val = self.memory.get_byte(addr); + debug!("add with carry. address: {:?}. value: {}", addr, val); + self.add_with_no_decimal(val); + } (Instruction::AND, OpInput::UseImmediate(val)) => { self.and(val); @@ -204,12 +217,12 @@ impl CPU { (Instruction::ASL, OpInput::UseImplied) => { // Accumulator mode let mut val = self.registers.accumulator; - CPU::::shift_left_with_flags(&mut val, &mut self.registers.status); + CPU::::shift_left_with_flags(&mut val, &mut self.registers.status); self.registers.accumulator = val; } (Instruction::ASL, OpInput::UseAddress(addr)) => { let mut operand: u8 = self.memory.get_byte(addr); - CPU::::shift_left_with_flags(&mut operand, &mut self.registers.status); + CPU::::shift_left_with_flags(&mut operand, &mut self.registers.status); self.memory.set_byte(addr, operand); } @@ -329,16 +342,16 @@ impl CPU { (Instruction::DEC, OpInput::UseAddress(addr)) => { let mut operand: u8 = self.memory.get_byte(addr); - CPU::::decrement(&mut operand, &mut self.registers.status); + CPU::::decrement(&mut operand, &mut self.registers.status); self.memory.set_byte(addr, operand); } (Instruction::DEY, OpInput::UseImplied) => { - CPU::::decrement(&mut self.registers.index_y, &mut self.registers.status); + CPU::::decrement(&mut self.registers.index_y, &mut self.registers.status); } (Instruction::DEX, OpInput::UseImplied) => { - CPU::::decrement(&mut self.registers.index_x, &mut self.registers.status); + CPU::::decrement(&mut self.registers.index_x, &mut self.registers.status); } (Instruction::EOR, OpInput::UseImmediate(val)) => { @@ -351,14 +364,14 @@ impl CPU { (Instruction::INC, OpInput::UseAddress(addr)) => { let mut operand: u8 = self.memory.get_byte(addr); - CPU::::increment(&mut operand, &mut self.registers.status); + CPU::::increment(&mut operand, &mut self.registers.status); self.memory.set_byte(addr, operand); } (Instruction::INX, OpInput::UseImplied) => { - CPU::::increment(&mut self.registers.index_x, &mut self.registers.status); + CPU::::increment(&mut self.registers.index_x, &mut self.registers.status); } (Instruction::INY, OpInput::UseImplied) => { - CPU::::increment(&mut self.registers.index_y, &mut self.registers.status); + CPU::::increment(&mut self.registers.index_y, &mut self.registers.status); } (Instruction::JMP, OpInput::UseAddress(addr)) => self.jump(addr), @@ -403,12 +416,12 @@ impl CPU { (Instruction::LSR, OpInput::UseImplied) => { // Accumulator mode let mut val = self.registers.accumulator; - CPU::::shift_right_with_flags(&mut val, &mut self.registers.status); + CPU::::shift_right_with_flags(&mut val, &mut self.registers.status); self.registers.accumulator = val; } (Instruction::LSR, OpInput::UseAddress(addr)) => { let mut operand: u8 = self.memory.get_byte(addr); - CPU::::shift_right_with_flags(&mut operand, &mut self.registers.status); + CPU::::shift_right_with_flags(&mut operand, &mut self.registers.status); self.memory.set_byte(addr, operand); } @@ -457,23 +470,23 @@ impl CPU { (Instruction::ROL, OpInput::UseImplied) => { // Accumulator mode let mut val = self.registers.accumulator; - CPU::::rotate_left_with_flags(&mut val, &mut self.registers.status); + CPU::::rotate_left_with_flags(&mut val, &mut self.registers.status); self.registers.accumulator = val; } (Instruction::ROL, OpInput::UseAddress(addr)) => { let mut operand: u8 = self.memory.get_byte(addr); - CPU::::rotate_left_with_flags(&mut operand, &mut self.registers.status); + CPU::::rotate_left_with_flags(&mut operand, &mut self.registers.status); self.memory.set_byte(addr, operand); } (Instruction::ROR, OpInput::UseImplied) => { // Accumulator mode let mut val = self.registers.accumulator; - CPU::::rotate_right_with_flags(&mut val, &mut self.registers.status); + CPU::::rotate_right_with_flags(&mut val, &mut self.registers.status); self.registers.accumulator = val; } (Instruction::ROR, OpInput::UseAddress(addr)) => { let mut operand: u8 = self.memory.get_byte(addr); - CPU::::rotate_right_with_flags(&mut operand, &mut self.registers.status); + CPU::::rotate_right_with_flags(&mut operand, &mut self.registers.status); self.memory.set_byte(addr, operand); } (Instruction::RTI, OpInput::UseImplied) => { @@ -505,6 +518,16 @@ impl CPU { self.subtract_with_carry(val); } + (Instruction::SBCnd, OpInput::UseImmediate(val)) => { + debug!("subtract with carry immediate: {}", val); + self.subtract_with_no_decimal(val); + } + (Instruction::SBCnd, OpInput::UseAddress(addr)) => { + let val = self.memory.get_byte(addr); + debug!("subtract with carry. address: {:?}. value: {}", addr, val); + self.subtract_with_no_decimal(val); + } + (Instruction::SEC, OpInput::UseImplied) => { self.registers.status.or(Status::PS_CARRY); } @@ -617,7 +640,7 @@ impl CPU { ..StatusArgs::none() }), ); - CPU::::set_flags_from_i8(status, *p_val as i8); + CPU::::set_flags_from_i8(status, *p_val as i8); } fn shift_right_with_flags(p_val: &mut u8, status: &mut Status) { @@ -631,7 +654,7 @@ impl CPU { ..StatusArgs::none() }), ); - CPU::::set_flags_from_i8(status, *p_val as i8); + CPU::::set_flags_from_i8(status, *p_val as i8); } fn rotate_left_with_flags(p_val: &mut u8, status: &mut Status) { @@ -647,7 +670,7 @@ impl CPU { ..StatusArgs::none() }), ); - CPU::::set_flags_from_i8(status, *p_val as i8); + CPU::::set_flags_from_i8(status, *p_val as i8); } fn rotate_right_with_flags(p_val: &mut u8, status: &mut Status) { @@ -663,16 +686,16 @@ impl CPU { ..StatusArgs::none() }), ); - CPU::::set_flags_from_i8(status, *p_val as i8); + CPU::::set_flags_from_i8(status, *p_val as i8); } fn set_u8_with_flags(mem: &mut u8, status: &mut Status, value: u8) { *mem = value; - CPU::::set_flags_from_u8(status, value); + CPU::::set_flags_from_u8(status, value); } fn load_x_register(&mut self, value: u8) { - CPU::::set_u8_with_flags( + CPU::::set_u8_with_flags( &mut self.registers.index_x, &mut self.registers.status, value, @@ -680,7 +703,7 @@ impl CPU { } fn load_y_register(&mut self, value: u8) { - CPU::::set_u8_with_flags( + CPU::::set_u8_with_flags( &mut self.registers.index_y, &mut self.registers.status, value, @@ -688,7 +711,7 @@ impl CPU { } fn load_accumulator(&mut self, value: u8) { - CPU::::set_u8_with_flags( + CPU::::set_u8_with_flags( &mut self.registers.accumulator, &mut self.registers.status, value, @@ -696,7 +719,6 @@ impl CPU { } fn add_with_carry(&mut self, value: u8) { - #[cfg(feature = "decimal_mode")] fn decimal_adjust(result: u8) -> u8 { let bcd1: u8 = if (result & 0x0f) > 0x09 { 0x06 } else { 0x00 }; @@ -715,15 +737,43 @@ impl CPU { debug_assert_eq!(a_after, a_before.wrapping_add(c_before).wrapping_add(value)); - #[cfg(feature = "decimal_mode")] let result: u8 = if self.registers.status.contains(Status::PS_DECIMAL_MODE) { decimal_adjust(a_after) } else { a_after }; - #[cfg(not(feature = "decimal_mode"))] - let result: u8 = a_after; + let did_carry = (result) < (a_before) + || (a_after == 0 && c_before == 0x01) + || (value == 0xff && c_before == 0x01); + + let did_overflow = (a_before > 127 && value > 127 && a_after < 128) + || (a_before < 128 && value < 128 && a_after > 127); + + let mask = Status::PS_CARRY | Status::PS_OVERFLOW; + + self.registers.status.set_with_mask( + mask, + Status::new(StatusArgs { + carry: did_carry, + overflow: did_overflow, + ..StatusArgs::none() + }), + ); + + self.load_accumulator(result); + + debug!("accumulator: {}", self.registers.accumulator); + } + + fn add_with_no_decimal(&mut self, value: u8) { + let a_before: u8 = self.registers.accumulator; + let c_before: u8 = u8::from(self.registers.status.contains(Status::PS_CARRY)); + let a_after: u8 = a_before.wrapping_add(c_before).wrapping_add(value); + + debug_assert_eq!(a_after, a_before.wrapping_add(c_before).wrapping_add(value)); + + let result = a_after; let did_carry = (result) < (a_before) || (a_after == 0 && c_before == 0x01) @@ -753,6 +803,52 @@ impl CPU { self.load_accumulator(a_after); } + fn subtract_with_no_decimal(&mut self, value: u8) { + // A - M - (1 - C) + + // nc -- 'not carry' + let nc: u8 = if self.registers.status.contains(Status::PS_CARRY) { + 0 + } else { + 1 + }; + + let a_before = self.registers.accumulator; + + let a_after = a_before.wrapping_sub(value).wrapping_sub(nc); + + // The overflow flag is set on two's-complement overflow. + // + // range of A is -128 to 127 + // range of - M - (1 - C) is -128 to 128 + // -(127 + 1) to -(-128 + 0) + // + let over = (nc == 0 && value > 127) && a_before < 128 && a_after > 127; + + let under = + (a_before > 127) && (0u8.wrapping_sub(value).wrapping_sub(nc) > 127) && a_after < 128; + + let did_overflow = over || under; + + let mask = Status::PS_CARRY | Status::PS_OVERFLOW; + + let result = a_after; + + // The carry flag is set on unsigned overflow. + let did_carry = (result) > (a_before); + + self.registers.status.set_with_mask( + mask, + Status::new(StatusArgs { + carry: did_carry, + overflow: did_overflow, + ..StatusArgs::none() + }), + ); + + self.load_accumulator(result); + } + fn subtract_with_carry(&mut self, value: u8) { // A - M - (1 - C) @@ -794,16 +890,12 @@ impl CPU { 0x00 }; - #[cfg(feature = "decimal_mode")] let result: u8 = if self.registers.status.contains(Status::PS_DECIMAL_MODE) { a_after.wrapping_sub(bcd1).wrapping_sub(bcd2) } else { a_after }; - #[cfg(not(feature = "decimal_mode"))] - let result = a_after; - // The carry flag is set on unsigned overflow. let did_carry = (result) > (a_before); @@ -980,7 +1072,7 @@ impl CPU { } } -impl core::fmt::Debug for CPU { +impl core::fmt::Debug for CPU { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { write!( f, @@ -994,11 +1086,12 @@ impl core::fmt::Debug for CPU { mod tests { use super::*; + use crate::instruction::Nmos6502; use crate::memory::Memory as Ram; #[test] fn dont_panic_for_overflow() { - let mut cpu = CPU::new(Ram::new()); + let mut cpu = CPU::new(Ram::new(), Nmos6502); cpu.add_with_carry(0x80); assert_eq!(cpu.registers.accumulator, 0x80); cpu.add_with_carry(0x80); @@ -1012,7 +1105,7 @@ mod tests { #[cfg_attr(feature = "decimal_mode", test)] fn decimal_add_test() { - let mut cpu = CPU::new(Ram::new()); + let mut cpu = CPU::new(Ram::new(), Nmos6502); cpu.registers.status.or(Status::PS_DECIMAL_MODE); cpu.add_with_carry(0x09); @@ -1039,7 +1132,7 @@ mod tests { #[cfg_attr(feature = "decimal_mode", test)] fn decimal_subtract_test() { - let mut cpu = CPU::new(Ram::new()); + let mut cpu = CPU::new(Ram::new(), Nmos6502); cpu.registers .status .or(Status::PS_DECIMAL_MODE | Status::PS_CARRY); @@ -1061,7 +1154,7 @@ mod tests { #[test] fn add_with_carry_test() { - let mut cpu = CPU::new(Ram::new()); + let mut cpu = CPU::new(Ram::new(), Nmos6502); cpu.add_with_carry(1); assert_eq!(cpu.registers.accumulator, 1); @@ -1084,7 +1177,7 @@ mod tests { assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE)); assert!(!cpu.registers.status.contains(Status::PS_OVERFLOW)); - let mut cpu = CPU::new(Ram::new()); + let mut cpu = CPU::new(Ram::new(), Nmos6502); assert_eq!(cpu.registers.accumulator, 0); cpu.add_with_carry(127); @@ -1116,7 +1209,7 @@ mod tests { assert!(cpu.registers.status.contains(Status::PS_NEGATIVE)); assert!(!cpu.registers.status.contains(Status::PS_OVERFLOW)); - let mut cpu = CPU::new(Ram::new()); + let mut cpu = CPU::new(Ram::new(), Nmos6502); cpu.add_with_carry(127); assert_eq!(cpu.registers.accumulator, 127); @@ -1132,7 +1225,7 @@ mod tests { assert!(cpu.registers.status.contains(Status::PS_NEGATIVE)); assert!(cpu.registers.status.contains(Status::PS_OVERFLOW)); - let mut cpu = CPU::new(Ram::new()); + let mut cpu = CPU::new(Ram::new(), Nmos6502); cpu.registers.status.or(Status::PS_CARRY); cpu.add_with_carry(0xff); assert_eq!(cpu.registers.accumulator, 0); @@ -1141,7 +1234,7 @@ mod tests { #[test] fn solid65_adc_immediate() { - let mut cpu = CPU::new(Ram::new()); + let mut cpu = CPU::new(Ram::new(), Nmos6502); // Adding $FF plus carry should be the same as adding $00 and no carry, so these three // instructions should leave the carry flags unaffected, i.e. set. @@ -1155,7 +1248,7 @@ mod tests { #[test] fn php_sets_bits_4_and_5() { - let mut cpu = CPU::new(Ram::new()); + let mut cpu = CPU::new(Ram::new(), Nmos6502); cpu.execute_instruction((Instruction::PHP, OpInput::UseImplied)); cpu.execute_instruction((Instruction::PLA, OpInput::UseImplied)); cpu.execute_instruction((Instruction::AND, OpInput::UseImmediate(0x30))); @@ -1165,7 +1258,7 @@ mod tests { #[test] fn and_test() { - let mut cpu = CPU::new(Ram::new()); + let mut cpu = CPU::new(Ram::new(), Nmos6502); cpu.registers.accumulator = 0; cpu.and(0xff); @@ -1194,7 +1287,7 @@ mod tests { #[test] fn subtract_with_carry_test() { - let mut cpu = CPU::new(Ram::new()); + let mut cpu = CPU::new(Ram::new(), Nmos6502); cpu.execute_instruction((Instruction::SEC, OpInput::UseImplied)); cpu.registers.accumulator = 0; @@ -1254,7 +1347,7 @@ mod tests { #[test] fn decrement_memory_test() { - let mut cpu = CPU::new(Ram::new()); + let mut cpu = CPU::new(Ram::new(), Nmos6502); let addr: u16 = 0xA1B2; cpu.memory.set_byte(addr, 5); @@ -1291,7 +1384,7 @@ mod tests { #[test] fn decrement_x_test() { - let mut cpu = CPU::new(Ram::new()); + let mut cpu = CPU::new(Ram::new(), Nmos6502); cpu.registers.index_x = 0x80; cpu.execute_instruction((Instruction::DEX, OpInput::UseImplied)); assert_eq!(cpu.registers.index_x, 127); @@ -1301,7 +1394,7 @@ mod tests { #[test] fn decrement_y_test() { - let mut cpu = CPU::new(Ram::new()); + let mut cpu = CPU::new(Ram::new(), Nmos6502); cpu.registers.index_y = 0x80; cpu.execute_instruction((Instruction::DEY, OpInput::UseImplied)); assert_eq!(cpu.registers.index_y, 127); @@ -1313,7 +1406,7 @@ mod tests { fn logical_shift_right_test() { // Testing UseImplied version (which targets the accumulator) only, for now - let mut cpu = CPU::new(Ram::new()); + let mut cpu = CPU::new(Ram::new(), Nmos6502); cpu.execute_instruction((Instruction::LDA, OpInput::UseImmediate(0))); cpu.execute_instruction((Instruction::LSR, OpInput::UseImplied)); assert_eq!(cpu.registers.accumulator, 0); @@ -1349,7 +1442,7 @@ mod tests { #[test] fn dec_x_test() { - let mut cpu = CPU::new(Ram::new()); + let mut cpu = CPU::new(Ram::new(), Nmos6502); cpu.execute_instruction((Instruction::DEX, OpInput::UseImplied)); assert_eq!(cpu.registers.index_x, 0xff); @@ -1394,7 +1487,7 @@ mod tests { #[test] fn jump_test() { - let mut cpu = CPU::new(Ram::new()); + let mut cpu = CPU::new(Ram::new(), Nmos6502); let addr: u16 = 0xA1B1; cpu.jump(addr); @@ -1403,7 +1496,7 @@ mod tests { #[test] fn branch_if_carry_clear_test() { - let mut cpu = CPU::new(Ram::new()); + let mut cpu = CPU::new(Ram::new(), Nmos6502); cpu.execute_instruction((Instruction::SEC, OpInput::UseImplied)); cpu.branch_if_carry_clear(0xABCD); @@ -1416,7 +1509,7 @@ mod tests { #[test] fn branch_if_carry_set_test() { - let mut cpu = CPU::new(Ram::new()); + let mut cpu = CPU::new(Ram::new(), Nmos6502); cpu.execute_instruction((Instruction::CLC, OpInput::UseImplied)); cpu.branch_if_carry_set(0xABCD); @@ -1429,7 +1522,7 @@ mod tests { #[test] fn branch_if_equal_test() { - let mut cpu = CPU::new(Ram::new()); + let mut cpu = CPU::new(Ram::new(), Nmos6502); cpu.branch_if_equal(0xABCD); assert_eq!(cpu.registers.program_counter, (0)); @@ -1442,7 +1535,7 @@ mod tests { #[test] fn branch_if_minus_test() { { - let mut cpu = CPU::new(Ram::new()); + let mut cpu = CPU::new(Ram::new(), Nmos6502); let registers_before = cpu.registers; cpu.branch_if_minus(0xABCD); @@ -1451,7 +1544,7 @@ mod tests { } { - let mut cpu = CPU::new(Ram::new()); + let mut cpu = CPU::new(Ram::new(), Nmos6502); cpu.registers.status.or(Status::PS_NEGATIVE); let registers_before = cpu.registers; @@ -1464,7 +1557,7 @@ mod tests { #[test] fn branch_if_positive_test() { - let mut cpu = CPU::new(Ram::new()); + let mut cpu = CPU::new(Ram::new(), Nmos6502); cpu.registers.status.insert(Status::PS_NEGATIVE); cpu.branch_if_positive(0xABCD); @@ -1477,7 +1570,7 @@ mod tests { #[test] fn branch_if_overflow_clear_test() { - let mut cpu = CPU::new(Ram::new()); + let mut cpu = CPU::new(Ram::new(), Nmos6502); cpu.registers.status.insert(Status::PS_OVERFLOW); cpu.branch_if_overflow_clear(0xABCD); @@ -1490,7 +1583,7 @@ mod tests { #[test] fn branch_across_end_of_address_space() { - let mut cpu = CPU::new(Ram::new()); + let mut cpu = CPU::new(Ram::new(), Nmos6502); cpu.registers.program_counter = 0xffff; cpu.registers.status.insert(Status::PS_OVERFLOW); @@ -1500,7 +1593,7 @@ mod tests { #[test] fn branch_if_overflow_set_test() { - let mut cpu = CPU::new(Ram::new()); + let mut cpu = CPU::new(Ram::new(), Nmos6502); cpu.branch_if_overflow_set(0xABCD); assert_eq!(cpu.registers.program_counter, (0)); @@ -1513,9 +1606,9 @@ mod tests { #[cfg(test)] fn compare_test_helper(compare: &mut F, load_instruction: Instruction) where - F: FnMut(&mut CPU, u8), + F: FnMut(&mut CPU, u8), { - let mut cpu = CPU::new(Ram::new()); + let mut cpu = CPU::new(Ram::new(), Nmos6502); cpu.execute_instruction((load_instruction, OpInput::UseImmediate(127))); @@ -1563,7 +1656,7 @@ mod tests { #[test] fn compare_with_a_register_test() { compare_test_helper( - &mut |cpu: &mut CPU, val: u8| { + &mut |cpu: &mut CPU, val: u8| { cpu.compare_with_a_register(val); }, Instruction::LDA, @@ -1573,7 +1666,7 @@ mod tests { #[test] fn compare_with_x_register_test() { compare_test_helper( - &mut |cpu: &mut CPU, val: u8| { + &mut |cpu: &mut CPU, val: u8| { cpu.compare_with_x_register(val); }, Instruction::LDX, @@ -1583,7 +1676,7 @@ mod tests { #[test] fn compare_with_y_register_test() { compare_test_helper( - &mut |cpu: &mut CPU, val: u8| { + &mut |cpu: &mut CPU, val: u8| { cpu.compare_with_y_register(val); }, Instruction::LDY, @@ -1592,7 +1685,7 @@ mod tests { #[test] fn exclusive_or_test() { - let mut cpu = CPU::new(Ram::new()); + let mut cpu = CPU::new(Ram::new(), Nmos6502); for a_before in 0u8..=255u8 { for val in 0u8..=255u8 { @@ -1620,7 +1713,7 @@ mod tests { #[test] fn inclusive_or_test() { - let mut cpu = CPU::new(Ram::new()); + let mut cpu = CPU::new(Ram::new(), Nmos6502); for a_before in 0u8..=255u8 { for val in 0u8..=255u8 { @@ -1648,7 +1741,7 @@ mod tests { #[test] fn stack_underflow() { - let mut cpu = CPU::new(Ram::new()); + let mut cpu = CPU::new(Ram::new(), Nmos6502); let _val: u8 = cpu.pull_from_stack(); } } diff --git a/src/instruction.rs b/src/instruction.rs index 885452e..b579c6d 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -44,39 +44,40 @@ #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum Instruction { - ADC, // ADd with Carry................ | NV ...ZC A = A + M + C - AND, // logical AND (bitwise)......... | N. ...Z. A = A && M - ASL, // Arithmetic Shift Left......... | N. ...ZC A = M << 1 - BCC, // Branch if Carry Clear......... | .. ..... PC = !C - BCS, // Branch if Carry Set........... | .. ..... PC = C - BEQ, // Branch if Equal (to zero?).... | .. ..... PC = Z - BIT, // BIT test...................... | NV ...Z. = A & M - BMI, // Branch if Minus............... | .. ..... PC = N - BNE, // Branch if Not Equal........... | .. ..... PC = !Z - BPL, // Branch if Positive............ | .. ..... PC = Z - BRK, // BReaK......................... | .. B.... S PC = - BVC, // Branch if oVerflow Clear...... | .. ..... PC = !V - BVS, // Branch if oVerflow Set........ | .. ..... PC = V - CLC, // CLear Carry flag.............. | .. ....C = 0 - CLD, // Clear Decimal Mode............ | .. .D... = 0 - CLI, // Clear Interrupt Disable....... | .. ..I.. = 0 - CLV, // Clear oVerflow flag........... | .V ..... = 0 - CMP, // Compare....................... | N. ...ZC = A - M - CPX, // Compare X register............ | N. ...ZC = X - M - CPY, // Compare Y register............ | N. ...ZC = Y - M - DEC, // DECrement memory.............. | N. ...Z. M = M - 1 - DEX, // DEcrement X register.......... | N. ...Z. X = X - 1 - DEY, // DEcrement Y register.......... | N. ...Z. Y = Y - 1 - EOR, // Exclusive OR (bitwise)........ | N. ...Z. A = A ^ M - INC, // INCrement memory.............. | N. ...Z. M = M + 1 - INX, // INcrement X register.......... | N. ...Z. X = X + 1 - INY, // INcrement Y register.......... | N. ...Z. Y = Y + 1 - JMP, // JuMP.......................... | .. ..... S PC = - JSR, // Jump to SubRoutine............ | .. ..... S PC = - LDA, // LoaD Accumulator.............. | N. ...Z. A = M - LDX, // LoaD X register............... | N. ...Z. X = M - LDY, // LoaD Y register............... | N. ...Z. Y = M - LSR, // Logical Shift Right........... | N. ...ZC A = A/2 + ADC, // ADd with Carry................ | NV ...ZC A = A + M + C + ADCnd, // ADd with Carry................ | NV ...ZC A = A + M + C + AND, // logical AND (bitwise)......... | N. ...Z. A = A && M + ASL, // Arithmetic Shift Left......... | N. ...ZC A = M << 1 + BCC, // Branch if Carry Clear......... | .. ..... PC = !C + BCS, // Branch if Carry Set........... | .. ..... PC = C + BEQ, // Branch if Equal (to zero?).... | .. ..... PC = Z + BIT, // BIT test...................... | NV ...Z. = A & M + BMI, // Branch if Minus............... | .. ..... PC = N + BNE, // Branch if Not Equal........... | .. ..... PC = !Z + BPL, // Branch if Positive............ | .. ..... PC = Z + BRK, // BReaK......................... | .. B.... S PC = + BVC, // Branch if oVerflow Clear...... | .. ..... PC = !V + BVS, // Branch if oVerflow Set........ | .. ..... PC = V + CLC, // CLear Carry flag.............. | .. ....C = 0 + CLD, // Clear Decimal Mode............ | .. .D... = 0 + CLI, // Clear Interrupt Disable....... | .. ..I.. = 0 + CLV, // Clear oVerflow flag........... | .V ..... = 0 + CMP, // Compare....................... | N. ...ZC = A - M + CPX, // Compare X register............ | N. ...ZC = X - M + CPY, // Compare Y register............ | N. ...ZC = Y - M + DEC, // DECrement memory.............. | N. ...Z. M = M - 1 + DEX, // DEcrement X register.......... | N. ...Z. X = X - 1 + DEY, // DEcrement Y register.......... | N. ...Z. Y = Y - 1 + EOR, // Exclusive OR (bitwise)........ | N. ...Z. A = A ^ M + INC, // INCrement memory.............. | N. ...Z. M = M + 1 + INX, // INcrement X register.......... | N. ...Z. X = X + 1 + INY, // INcrement Y register.......... | N. ...Z. Y = Y + 1 + JMP, // JuMP.......................... | .. ..... S PC = + JSR, // Jump to SubRoutine............ | .. ..... S PC = + LDA, // LoaD Accumulator.............. | N. ...Z. A = M + LDX, // LoaD X register............... | N. ...Z. X = M + LDY, // LoaD Y register............... | N. ...Z. Y = M + LSR, // Logical Shift Right........... | N. ...ZC A = A/2 // or N. ...ZC M = M/2 NOP, // No OPeration.................. | .. ..... = ORA, // inclusive OR (bitwise)........ | N. ...Z. A = A | M @@ -88,21 +89,22 @@ pub enum Instruction { // or N. ...ZC M = C M rotated ROR, // ROtate Right.................. | N. ...ZC A = C A rotated // or N. ...ZC M = C M rotated - RTI, // ReTurn from Interrupt......... | NV BDIZC PC = M (stack) - RTS, // ReTurn from Subroutine........ | .. ..... PC = M (stack) - SBC, // SuBtract with Carry........... | NV ...ZC A = A-M-(1-C) - SEC, // SEt Carry flag................ | .. ....C = 1 - SED, // SEt Decimal flag.............. | .. .D... = 1 - SEI, // SEt Interrupt disable......... | .. ..I.. = 1 - STA, // STore Accumulator............. | .. ..... M = A - STX, // STore X register.............. | .. ..... M = X - STY, // STore Y register.............. | .. ..... M = Y - TAX, // Transfer Accumulator to X..... | N. ...Z. X = A - TAY, // Transfer Accumulator to Y..... | N. ...Z. Y = A - TSX, // Transfer Stack pointer to X... | N. ...Z. X = S - TXA, // Transfer X to Accumulator..... | N. ...Z. A = X - TXS, // Transfer X to Stack pointer... | .. ..... S = X - TYA, // Transfer Y to Accumulator..... | N. ...Z. A = Y + RTI, // ReTurn from Interrupt......... | NV BDIZC PC = M (stack) + RTS, // ReTurn from Subroutine........ | .. ..... PC = M (stack) + SBC, // SuBtract with Carry........... | NV ...ZC A = A-M-(1-C) + SBCnd, // SuBtract with Carry........... | NV ...ZC A = A-M-(1-C) + SEC, // SEt Carry flag................ | .. ....C = 1 + SED, // SEt Decimal flag.............. | .. .D... = 1 + SEI, // SEt Interrupt disable......... | .. ..I.. = 1 + STA, // STore Accumulator............. | .. ..... M = A + STX, // STore X register.............. | .. ..... M = X + STY, // STore Y register.............. | .. ..... M = Y + TAX, // Transfer Accumulator to X..... | N. ...Z. X = A + TAY, // Transfer Accumulator to Y..... | N. ...Z. Y = A + TSX, // Transfer Stack pointer to X... | N. ...Z. X = S + TXA, // Transfer X to Accumulator..... | N. ...Z. A = X + TXS, // Transfer X to Stack pointer... | .. ..... S = X + TYA, // Transfer Y to Accumulator..... | N. ...Z. A = Y } #[derive(Copy, Clone)] @@ -154,517 +156,313 @@ impl AddressingMode { pub type DecodedInstr = (Instruction, OpInput); -pub static OPCODES: [Option<(Instruction, AddressingMode)>; 256] = [ - /*0x00*/ - Some((Instruction::BRK, AddressingMode::Implied)), - /*0x01*/ - Some((Instruction::ORA, AddressingMode::IndexedIndirectX)), - /*0x02*/ - None, - /*0x03*/ - None, - /*0x04*/ - None, - /*0x05*/ - Some((Instruction::ORA, AddressingMode::ZeroPage)), - /*0x06*/ - Some((Instruction::ASL, AddressingMode::ZeroPage)), - /*0x07*/ - None, - /*0x08*/ - Some((Instruction::PHP, AddressingMode::Implied)), - /*0x09*/ - Some((Instruction::ORA, AddressingMode::Immediate)), - /*0x0A*/ - Some((Instruction::ASL, AddressingMode::Accumulator)), - /*0x0B*/ - None, - /*0x0C*/ - None, - /*0x0D*/ - Some((Instruction::ORA, AddressingMode::Absolute)), - /*0x0E*/ - Some((Instruction::ASL, AddressingMode::Absolute)), - /*0x0F*/ - None, - /*0x10*/ - Some((Instruction::BPL, AddressingMode::Relative)), - /*0x11*/ - Some((Instruction::ORA, AddressingMode::IndirectIndexedY)), - /*0x12*/ - None, - /*0x13*/ - None, - /*0x14*/ - None, - /*0x15*/ - Some((Instruction::ORA, AddressingMode::ZeroPageX)), - /*0x16*/ - Some((Instruction::ASL, AddressingMode::ZeroPageX)), - /*0x17*/ - None, - /*0x18*/ - Some((Instruction::CLC, AddressingMode::Implied)), - /*0x19*/ - Some((Instruction::ORA, AddressingMode::AbsoluteY)), - /*0x1A*/ - None, - /*0x1B*/ - None, - /*0x1C*/ - None, - /*0x1D*/ - Some((Instruction::ORA, AddressingMode::AbsoluteX)), - /*0x1E*/ - Some((Instruction::ASL, AddressingMode::AbsoluteX)), - /*0x1F*/ - None, - /*0x20*/ - Some((Instruction::JSR, AddressingMode::Absolute)), - /*0x21*/ - Some((Instruction::AND, AddressingMode::IndexedIndirectX)), - /*0x22*/ - None, - /*0x23*/ - None, - /*0x24*/ - Some((Instruction::BIT, AddressingMode::ZeroPage)), - /*0x25*/ - Some((Instruction::AND, AddressingMode::ZeroPage)), - /*0x26*/ - Some((Instruction::ROL, AddressingMode::ZeroPage)), - /*0x27*/ - None, - /*0x28*/ - Some((Instruction::PLP, AddressingMode::Implied)), - /*0x29*/ - Some((Instruction::AND, AddressingMode::Immediate)), - /*0x2A*/ - Some((Instruction::ROL, AddressingMode::Accumulator)), - /*0x2B*/ - None, - /*0x2C*/ - Some((Instruction::BIT, AddressingMode::Absolute)), - /*0x2D*/ - Some((Instruction::AND, AddressingMode::Absolute)), - /*0x2E*/ - Some((Instruction::ROL, AddressingMode::Absolute)), - /*0x2F*/ - None, - /*0x30*/ - Some((Instruction::BMI, AddressingMode::Relative)), - /*0x31*/ - Some((Instruction::AND, AddressingMode::IndirectIndexedY)), - /*0x32*/ - None, - /*0x33*/ - None, - /*0x34*/ - None, - /*0x35*/ - Some((Instruction::AND, AddressingMode::ZeroPageX)), - /*0x36*/ - Some((Instruction::ROL, AddressingMode::ZeroPageX)), - /*0x37*/ - None, - /*0x38*/ - Some((Instruction::SEC, AddressingMode::Implied)), - /*0x39*/ - Some((Instruction::AND, AddressingMode::AbsoluteY)), - /*0x3A*/ - None, - /*0x3B*/ - None, - /*0x3C*/ - None, - /*0x3D*/ - Some((Instruction::AND, AddressingMode::AbsoluteX)), - /*0x3E*/ - Some((Instruction::ROL, AddressingMode::AbsoluteX)), - /*0x3F*/ - None, - /*0x40*/ - Some((Instruction::RTI, AddressingMode::Implied)), - /*0x41*/ - Some((Instruction::EOR, AddressingMode::IndexedIndirectX)), - /*0x42*/ - None, - /*0x43*/ - None, - /*0x44*/ - None, - /*0x45*/ - Some((Instruction::EOR, AddressingMode::ZeroPage)), - /*0x46*/ - Some((Instruction::LSR, AddressingMode::ZeroPage)), - /*0x47*/ - None, - /*0x48*/ - Some((Instruction::PHA, AddressingMode::Implied)), - /*0x49*/ - Some((Instruction::EOR, AddressingMode::Immediate)), - /*0x4A*/ - Some((Instruction::LSR, AddressingMode::Accumulator)), - /*0x4B*/ - None, - /*0x4C*/ - Some((Instruction::JMP, AddressingMode::Absolute)), - /*0x4D*/ - Some((Instruction::EOR, AddressingMode::Absolute)), - /*0x4E*/ - Some((Instruction::LSR, AddressingMode::Absolute)), - /*0x4F*/ - None, - /*0x50*/ - Some((Instruction::BVC, AddressingMode::Relative)), - /*0x51*/ - Some((Instruction::EOR, AddressingMode::IndirectIndexedY)), - /*0x52*/ - None, - /*0x53*/ - None, - /*0x54*/ - None, - /*0x55*/ - Some((Instruction::EOR, AddressingMode::ZeroPageX)), - /*0x56*/ - Some((Instruction::LSR, AddressingMode::ZeroPageX)), - /*0x57*/ - None, - /*0x58*/ - Some((Instruction::CLI, AddressingMode::Implied)), - /*0x59*/ - Some((Instruction::EOR, AddressingMode::AbsoluteY)), - /*0x5A*/ - None, - /*0x5B*/ - None, - /*0x5C*/ - None, - /*0x5D*/ - Some((Instruction::EOR, AddressingMode::AbsoluteX)), - /*0x5E*/ - Some((Instruction::LSR, AddressingMode::AbsoluteX)), - /*0x5F*/ - None, - /*0x60*/ - Some((Instruction::RTS, AddressingMode::Implied)), - /*0x61*/ - Some((Instruction::ADC, AddressingMode::IndexedIndirectX)), - /*0x62*/ - None, - /*0x63*/ - None, - /*0x64*/ - None, - /*0x65*/ - Some((Instruction::ADC, AddressingMode::ZeroPage)), - /*0x66*/ - Some((Instruction::ROR, AddressingMode::ZeroPage)), - /*0x67*/ - None, - /*0x68*/ - Some((Instruction::PLA, AddressingMode::Implied)), - /*0x69*/ - Some((Instruction::ADC, AddressingMode::Immediate)), - /*0x6A*/ - Some((Instruction::ROR, AddressingMode::Accumulator)), - /*0x6B*/ - None, - /*0x6C*/ - Some((Instruction::JMP, AddressingMode::Indirect)), - /*0x6D*/ - Some((Instruction::ADC, AddressingMode::Absolute)), - /*0x6E*/ - Some((Instruction::ROR, AddressingMode::Absolute)), - /*0x6F*/ - None, - /*0x70*/ - Some((Instruction::BVS, AddressingMode::Relative)), - /*0x71*/ - Some((Instruction::ADC, AddressingMode::IndirectIndexedY)), - /*0x72*/ - None, - /*0x73*/ - None, - /*0x74*/ - None, - /*0x75*/ - Some((Instruction::ADC, AddressingMode::ZeroPageX)), - /*0x76*/ - Some((Instruction::ROR, AddressingMode::ZeroPageX)), - /*0x77*/ - None, - /*0x78*/ - Some((Instruction::SEI, AddressingMode::Implied)), - /*0x79*/ - Some((Instruction::ADC, AddressingMode::AbsoluteY)), - /*0x7A*/ - None, - /*0x7B*/ - None, - /*0x7C*/ - None, - /*0x7D*/ - Some((Instruction::ADC, AddressingMode::AbsoluteX)), - /*0x7E*/ - Some((Instruction::ROR, AddressingMode::AbsoluteX)), - /*0x7F*/ - None, - /*0x80*/ - None, - /*0x81*/ - Some((Instruction::STA, AddressingMode::IndexedIndirectX)), - /*0x82*/ - None, - /*0x83*/ - None, - /*0x84*/ - Some((Instruction::STY, AddressingMode::ZeroPage)), - /*0x85*/ - Some((Instruction::STA, AddressingMode::ZeroPage)), - /*0x86*/ - Some((Instruction::STX, AddressingMode::ZeroPage)), - /*0x87*/ - None, - /*0x88*/ - Some((Instruction::DEY, AddressingMode::Implied)), - /*0x89*/ - None, - /*0x8A*/ - Some((Instruction::TXA, AddressingMode::Implied)), - /*0x8B*/ - None, - /*0x8C*/ - Some((Instruction::STY, AddressingMode::Absolute)), - /*0x8D*/ - Some((Instruction::STA, AddressingMode::Absolute)), - /*0x8E*/ - Some((Instruction::STX, AddressingMode::Absolute)), - /*0x8F*/ - None, - /*0x90*/ - Some((Instruction::BCC, AddressingMode::Relative)), - /*0x91*/ - Some((Instruction::STA, AddressingMode::IndirectIndexedY)), - /*0x92*/ - None, - /*0x93*/ - None, - /*0x94*/ - Some((Instruction::STY, AddressingMode::ZeroPageX)), - /*0x95*/ - Some((Instruction::STA, AddressingMode::ZeroPageX)), - /*0x96*/ - Some((Instruction::STX, AddressingMode::ZeroPageY)), - /*0x97*/ - None, - /*0x98*/ - Some((Instruction::TYA, AddressingMode::Implied)), - /*0x99*/ - Some((Instruction::STA, AddressingMode::AbsoluteY)), - /*0x9A*/ - Some((Instruction::TXS, AddressingMode::Implied)), - /*0x9B*/ - None, - /*0x9C*/ - None, - /*0x9D*/ - Some((Instruction::STA, AddressingMode::AbsoluteX)), - /*0x9E*/ - None, - /*0x9F*/ - None, - /*0xA0*/ - Some((Instruction::LDY, AddressingMode::Immediate)), - /*0xA1*/ - Some((Instruction::LDA, AddressingMode::IndexedIndirectX)), - /*0xA2*/ - Some((Instruction::LDX, AddressingMode::Immediate)), - /*0xA3*/ - None, - /*0xA4*/ - Some((Instruction::LDY, AddressingMode::ZeroPage)), - /*0xA5*/ - Some((Instruction::LDA, AddressingMode::ZeroPage)), - /*0xA6*/ - Some((Instruction::LDX, AddressingMode::ZeroPage)), - /*0xA7*/ - None, - /*0xA8*/ - Some((Instruction::TAY, AddressingMode::Implied)), - /*0xA9*/ - Some((Instruction::LDA, AddressingMode::Immediate)), - /*0xAA*/ - Some((Instruction::TAX, AddressingMode::Implied)), - /*0xAB*/ - None, - /*0xAC*/ - Some((Instruction::LDY, AddressingMode::Absolute)), - /*0xAD*/ - Some((Instruction::LDA, AddressingMode::Absolute)), - /*0xAE*/ - Some((Instruction::LDX, AddressingMode::Absolute)), - /*0xAF*/ - None, - /*0xB0*/ - Some((Instruction::BCS, AddressingMode::Relative)), - /*0xB1*/ - Some((Instruction::LDA, AddressingMode::IndirectIndexedY)), - /*0xB2*/ - None, - /*0xB3*/ - None, - /*0xB4*/ - Some((Instruction::LDY, AddressingMode::ZeroPageX)), - /*0xB5*/ - Some((Instruction::LDA, AddressingMode::ZeroPageX)), - /*0xB6*/ - Some((Instruction::LDX, AddressingMode::ZeroPageY)), - /*0xB7*/ - None, - /*0xB8*/ - Some((Instruction::CLV, AddressingMode::Implied)), - /*0xB9*/ - Some((Instruction::LDA, AddressingMode::AbsoluteY)), - /*0xBA*/ - Some((Instruction::TSX, AddressingMode::Implied)), - /*0xBB*/ - None, - /*0xBC*/ - Some((Instruction::LDY, AddressingMode::AbsoluteX)), - /*0xBD*/ - Some((Instruction::LDA, AddressingMode::AbsoluteX)), - /*0xBE*/ - Some((Instruction::LDX, AddressingMode::AbsoluteY)), - /*0xBF*/ - None, - /*0xC0*/ - Some((Instruction::CPY, AddressingMode::Immediate)), - /*0xC1*/ - Some((Instruction::CMP, AddressingMode::IndexedIndirectX)), - /*0xC2*/ - None, - /*0xC3*/ - None, - /*0xC4*/ - Some((Instruction::CPY, AddressingMode::ZeroPage)), - /*0xC5*/ - Some((Instruction::CMP, AddressingMode::ZeroPage)), - /*0xC6*/ - Some((Instruction::DEC, AddressingMode::ZeroPage)), - /*0xC7*/ - None, - /*0xC8*/ - Some((Instruction::INY, AddressingMode::Implied)), - /*0xC9*/ - Some((Instruction::CMP, AddressingMode::Immediate)), - /*0xCA*/ - Some((Instruction::DEX, AddressingMode::Implied)), - /*0xCB*/ - None, - /*0xCC*/ - Some((Instruction::CPY, AddressingMode::Absolute)), - /*0xCD*/ - Some((Instruction::CMP, AddressingMode::Absolute)), - /*0xCE*/ - Some((Instruction::DEC, AddressingMode::Absolute)), - /*0xCF*/ - None, - /*0xD0*/ - Some((Instruction::BNE, AddressingMode::Relative)), - /*0xD1*/ - Some((Instruction::CMP, AddressingMode::IndirectIndexedY)), - /*0xD2*/ - None, - /*0xD3*/ - None, - /*0xD4*/ - None, - /*0xD5*/ - Some((Instruction::CMP, AddressingMode::ZeroPageX)), - /*0xD6*/ - Some((Instruction::DEC, AddressingMode::ZeroPageX)), - /*0xD7*/ - None, - /*0xD8*/ - Some((Instruction::CLD, AddressingMode::Implied)), - /*0xD9*/ - Some((Instruction::CMP, AddressingMode::AbsoluteY)), - /*0xDA*/ - None, - /*0xDB*/ - None, - /*0xDC*/ - None, - /*0xDD*/ - Some((Instruction::CMP, AddressingMode::AbsoluteX)), - /*0xDE*/ - Some((Instruction::DEC, AddressingMode::AbsoluteX)), - /*0xDF*/ - None, - /*0xE0*/ - Some((Instruction::CPX, AddressingMode::Immediate)), - /*0xE1*/ - Some((Instruction::SBC, AddressingMode::IndexedIndirectX)), - /*0xE2*/ - None, - /*0xE3*/ - None, - /*0xE4*/ - Some((Instruction::CPX, AddressingMode::ZeroPage)), - /*0xE5*/ - Some((Instruction::SBC, AddressingMode::ZeroPage)), - /*0xE6*/ - Some((Instruction::INC, AddressingMode::ZeroPage)), - /*0xE7*/ - None, - /*0xE8*/ - Some((Instruction::INX, AddressingMode::Implied)), - /*0xE9*/ - Some((Instruction::SBC, AddressingMode::Immediate)), - /*0xEA*/ - Some((Instruction::NOP, AddressingMode::Implied)), - /*0xEB*/ - None, - /*0xEC*/ - Some((Instruction::CPX, AddressingMode::Absolute)), - /*0xED*/ - Some((Instruction::SBC, AddressingMode::Absolute)), - /*0xEE*/ - Some((Instruction::INC, AddressingMode::Absolute)), - /*0xEF*/ - None, - /*0xF0*/ - Some((Instruction::BEQ, AddressingMode::Relative)), - /*0xF1*/ - Some((Instruction::SBC, AddressingMode::IndirectIndexedY)), - /*0xF2*/ - None, - /*0xF3*/ - None, - /*0xF4*/ - None, - /*0xF5*/ - Some((Instruction::SBC, AddressingMode::ZeroPageX)), - /*0xF6*/ - Some((Instruction::INC, AddressingMode::ZeroPageX)), - /*0xF7*/ - None, - /*0xF8*/ - Some((Instruction::SED, AddressingMode::Implied)), - /*0xF9*/ - Some((Instruction::SBC, AddressingMode::AbsoluteY)), - /*0xFA*/ - None, - /*0xFB*/ - None, - /*0xFC*/ - None, - /*0xFD*/ - Some((Instruction::SBC, AddressingMode::AbsoluteX)), - /*0xFE*/ - Some((Instruction::INC, AddressingMode::AbsoluteX)), - /*0xFF*/ - None, -]; +/// The NMOS 6502 variant. This one is present in the Commodore 64, early Apple IIs, etc. +pub struct Nmos6502; + +impl crate::Variant for Nmos6502 { + fn decode(opcode: u8) -> Option<(Instruction, AddressingMode)> { + match opcode { + 0x00 => Some((Instruction::BRK, AddressingMode::Implied)), + 0x01 => Some((Instruction::ORA, AddressingMode::IndexedIndirectX)), + 0x02 => None, + 0x03 => None, + 0x04 => None, + 0x05 => Some((Instruction::ORA, AddressingMode::ZeroPage)), + 0x06 => Some((Instruction::ASL, AddressingMode::ZeroPage)), + 0x07 => None, + 0x08 => Some((Instruction::PHP, AddressingMode::Implied)), + 0x09 => Some((Instruction::ORA, AddressingMode::Immediate)), + 0x0a => Some((Instruction::ASL, AddressingMode::Accumulator)), + 0x0b => None, + 0x0c => None, + 0x0d => Some((Instruction::ORA, AddressingMode::Absolute)), + 0x0e => Some((Instruction::ASL, AddressingMode::Absolute)), + 0x0f => None, + 0x10 => Some((Instruction::BPL, AddressingMode::Relative)), + 0x11 => Some((Instruction::ORA, AddressingMode::IndirectIndexedY)), + 0x12 => None, + 0x13 => None, + 0x14 => None, + 0x15 => Some((Instruction::ORA, AddressingMode::ZeroPageX)), + 0x16 => Some((Instruction::ASL, AddressingMode::ZeroPageX)), + 0x17 => None, + 0x18 => Some((Instruction::CLC, AddressingMode::Implied)), + 0x19 => Some((Instruction::ORA, AddressingMode::AbsoluteY)), + 0x1a => None, + 0x1b => None, + 0x1c => None, + 0x1d => Some((Instruction::ORA, AddressingMode::AbsoluteX)), + 0x1e => Some((Instruction::ASL, AddressingMode::AbsoluteX)), + 0x1f => None, + 0x20 => Some((Instruction::JSR, AddressingMode::Absolute)), + 0x21 => Some((Instruction::AND, AddressingMode::IndexedIndirectX)), + 0x22 => None, + 0x23 => None, + 0x24 => Some((Instruction::BIT, AddressingMode::ZeroPage)), + 0x25 => Some((Instruction::AND, AddressingMode::ZeroPage)), + 0x26 => Some((Instruction::ROL, AddressingMode::ZeroPage)), + 0x27 => None, + 0x28 => Some((Instruction::PLP, AddressingMode::Implied)), + 0x29 => Some((Instruction::AND, AddressingMode::Immediate)), + 0x2a => Some((Instruction::ROL, AddressingMode::Accumulator)), + 0x2b => None, + 0x2c => Some((Instruction::BIT, AddressingMode::Absolute)), + 0x2d => Some((Instruction::AND, AddressingMode::Absolute)), + 0x2e => Some((Instruction::ROL, AddressingMode::Absolute)), + 0x2f => None, + 0x30 => Some((Instruction::BMI, AddressingMode::Relative)), + 0x31 => Some((Instruction::AND, AddressingMode::IndirectIndexedY)), + 0x32 => None, + 0x33 => None, + 0x34 => None, + 0x35 => Some((Instruction::AND, AddressingMode::ZeroPageX)), + 0x36 => Some((Instruction::ROL, AddressingMode::ZeroPageX)), + 0x37 => None, + 0x38 => Some((Instruction::SEC, AddressingMode::Implied)), + 0x39 => Some((Instruction::AND, AddressingMode::AbsoluteY)), + 0x3a => None, + 0x3b => None, + 0x3c => None, + 0x3d => Some((Instruction::AND, AddressingMode::AbsoluteX)), + 0x3e => Some((Instruction::ROL, AddressingMode::AbsoluteX)), + 0x3f => None, + 0x40 => Some((Instruction::RTI, AddressingMode::Implied)), + 0x41 => Some((Instruction::EOR, AddressingMode::IndexedIndirectX)), + 0x42 => None, + 0x43 => None, + 0x44 => None, + 0x45 => Some((Instruction::EOR, AddressingMode::ZeroPage)), + 0x46 => Some((Instruction::LSR, AddressingMode::ZeroPage)), + 0x47 => None, + 0x48 => Some((Instruction::PHA, AddressingMode::Implied)), + 0x49 => Some((Instruction::EOR, AddressingMode::Immediate)), + 0x4a => Some((Instruction::LSR, AddressingMode::Accumulator)), + 0x4b => None, + 0x4c => Some((Instruction::JMP, AddressingMode::Absolute)), + 0x4d => Some((Instruction::EOR, AddressingMode::Absolute)), + 0x4e => Some((Instruction::LSR, AddressingMode::Absolute)), + 0x4f => None, + 0x50 => Some((Instruction::BVC, AddressingMode::Relative)), + 0x51 => Some((Instruction::EOR, AddressingMode::IndirectIndexedY)), + 0x52 => None, + 0x53 => None, + 0x54 => None, + 0x55 => Some((Instruction::EOR, AddressingMode::ZeroPageX)), + 0x56 => Some((Instruction::LSR, AddressingMode::ZeroPageX)), + 0x57 => None, + 0x58 => Some((Instruction::CLI, AddressingMode::Implied)), + 0x59 => Some((Instruction::EOR, AddressingMode::AbsoluteY)), + 0x5a => None, + 0x5b => None, + 0x5c => None, + 0x5d => Some((Instruction::EOR, AddressingMode::AbsoluteX)), + 0x5e => Some((Instruction::LSR, AddressingMode::AbsoluteX)), + 0x5f => None, + 0x60 => Some((Instruction::RTS, AddressingMode::Implied)), + 0x61 => Some((Instruction::ADC, AddressingMode::IndexedIndirectX)), + 0x62 => None, + 0x63 => None, + 0x64 => None, + 0x65 => Some((Instruction::ADC, AddressingMode::ZeroPage)), + 0x66 => Some((Instruction::ROR, AddressingMode::ZeroPage)), + 0x67 => None, + 0x68 => Some((Instruction::PLA, AddressingMode::Implied)), + 0x69 => Some((Instruction::ADC, AddressingMode::Immediate)), + 0x6a => Some((Instruction::ROR, AddressingMode::Accumulator)), + 0x6b => None, + 0x6c => Some((Instruction::JMP, AddressingMode::Indirect)), + 0x6d => Some((Instruction::ADC, AddressingMode::Absolute)), + 0x6e => Some((Instruction::ROR, AddressingMode::Absolute)), + 0x6f => None, + 0x70 => Some((Instruction::BVS, AddressingMode::Relative)), + 0x71 => Some((Instruction::ADC, AddressingMode::IndirectIndexedY)), + 0x72 => None, + 0x73 => None, + 0x74 => None, + 0x75 => Some((Instruction::ADC, AddressingMode::ZeroPageX)), + 0x76 => Some((Instruction::ROR, AddressingMode::ZeroPageX)), + 0x77 => None, + 0x78 => Some((Instruction::SEI, AddressingMode::Implied)), + 0x79 => Some((Instruction::ADC, AddressingMode::AbsoluteY)), + 0x7a => None, + 0x7b => None, + 0x7c => None, + 0x7d => Some((Instruction::ADC, AddressingMode::AbsoluteX)), + 0x7e => Some((Instruction::ROR, AddressingMode::AbsoluteX)), + 0x7f => None, + 0x80 => None, + 0x81 => Some((Instruction::STA, AddressingMode::IndexedIndirectX)), + 0x82 => None, + 0x83 => None, + 0x84 => Some((Instruction::STY, AddressingMode::ZeroPage)), + 0x85 => Some((Instruction::STA, AddressingMode::ZeroPage)), + 0x86 => Some((Instruction::STX, AddressingMode::ZeroPage)), + 0x87 => None, + 0x88 => Some((Instruction::DEY, AddressingMode::Implied)), + 0x89 => None, + 0x8a => Some((Instruction::TXA, AddressingMode::Implied)), + 0x8b => None, + 0x8c => Some((Instruction::STY, AddressingMode::Absolute)), + 0x8d => Some((Instruction::STA, AddressingMode::Absolute)), + 0x8e => Some((Instruction::STX, AddressingMode::Absolute)), + 0x8f => None, + 0x90 => Some((Instruction::BCC, AddressingMode::Relative)), + 0x91 => Some((Instruction::STA, AddressingMode::IndirectIndexedY)), + 0x92 => None, + 0x93 => None, + 0x94 => Some((Instruction::STY, AddressingMode::ZeroPageX)), + 0x95 => Some((Instruction::STA, AddressingMode::ZeroPageX)), + 0x96 => Some((Instruction::STX, AddressingMode::ZeroPageY)), + 0x97 => None, + 0x98 => Some((Instruction::TYA, AddressingMode::Implied)), + 0x99 => Some((Instruction::STA, AddressingMode::AbsoluteY)), + 0x9a => Some((Instruction::TXS, AddressingMode::Implied)), + 0x9b => None, + 0x9c => None, + 0x9d => Some((Instruction::STA, AddressingMode::AbsoluteX)), + 0x9e => None, + 0x9f => None, + 0xa0 => Some((Instruction::LDY, AddressingMode::Immediate)), + 0xa1 => Some((Instruction::LDA, AddressingMode::IndexedIndirectX)), + 0xa2 => Some((Instruction::LDX, AddressingMode::Immediate)), + 0xa3 => None, + 0xa4 => Some((Instruction::LDY, AddressingMode::ZeroPage)), + 0xa5 => Some((Instruction::LDA, AddressingMode::ZeroPage)), + 0xa6 => Some((Instruction::LDX, AddressingMode::ZeroPage)), + 0xa7 => None, + 0xa8 => Some((Instruction::TAY, AddressingMode::Implied)), + 0xa9 => Some((Instruction::LDA, AddressingMode::Immediate)), + 0xaa => Some((Instruction::TAX, AddressingMode::Implied)), + 0xab => None, + 0xac => Some((Instruction::LDY, AddressingMode::Absolute)), + 0xad => Some((Instruction::LDA, AddressingMode::Absolute)), + 0xae => Some((Instruction::LDX, AddressingMode::Absolute)), + 0xaf => None, + 0xb0 => Some((Instruction::BCS, AddressingMode::Relative)), + 0xb1 => Some((Instruction::LDA, AddressingMode::IndirectIndexedY)), + 0xb2 => None, + 0xb3 => None, + 0xb4 => Some((Instruction::LDY, AddressingMode::ZeroPageX)), + 0xb5 => Some((Instruction::LDA, AddressingMode::ZeroPageX)), + 0xb6 => Some((Instruction::LDX, AddressingMode::ZeroPageY)), + 0xb7 => None, + 0xb8 => Some((Instruction::CLV, AddressingMode::Implied)), + 0xb9 => Some((Instruction::LDA, AddressingMode::AbsoluteY)), + 0xba => Some((Instruction::TSX, AddressingMode::Implied)), + 0xbb => None, + 0xbc => Some((Instruction::LDY, AddressingMode::AbsoluteX)), + 0xbd => Some((Instruction::LDA, AddressingMode::AbsoluteX)), + 0xbe => Some((Instruction::LDX, AddressingMode::AbsoluteY)), + 0xbf => None, + 0xc0 => Some((Instruction::CPY, AddressingMode::Immediate)), + 0xc1 => Some((Instruction::CMP, AddressingMode::IndexedIndirectX)), + 0xc2 => None, + 0xc3 => None, + 0xc4 => Some((Instruction::CPY, AddressingMode::ZeroPage)), + 0xc5 => Some((Instruction::CMP, AddressingMode::ZeroPage)), + 0xc6 => Some((Instruction::DEC, AddressingMode::ZeroPage)), + 0xc7 => None, + 0xc8 => Some((Instruction::INY, AddressingMode::Implied)), + 0xc9 => Some((Instruction::CMP, AddressingMode::Immediate)), + 0xca => Some((Instruction::DEX, AddressingMode::Implied)), + 0xcb => None, + 0xcc => Some((Instruction::CPY, AddressingMode::Absolute)), + 0xcd => Some((Instruction::CMP, AddressingMode::Absolute)), + 0xce => Some((Instruction::DEC, AddressingMode::Absolute)), + 0xcf => None, + 0xd0 => Some((Instruction::BNE, AddressingMode::Relative)), + 0xd1 => Some((Instruction::CMP, AddressingMode::IndirectIndexedY)), + 0xd2 => None, + 0xd3 => None, + 0xd4 => None, + 0xd5 => Some((Instruction::CMP, AddressingMode::ZeroPageX)), + 0xd6 => Some((Instruction::DEC, AddressingMode::ZeroPageX)), + 0xd7 => None, + 0xd8 => Some((Instruction::CLD, AddressingMode::Implied)), + 0xd9 => Some((Instruction::CMP, AddressingMode::AbsoluteY)), + 0xda => None, + 0xdb => None, + 0xdc => None, + 0xdd => Some((Instruction::CMP, AddressingMode::AbsoluteX)), + 0xde => Some((Instruction::DEC, AddressingMode::AbsoluteX)), + 0xdf => None, + 0xe0 => Some((Instruction::CPX, AddressingMode::Immediate)), + 0xe1 => Some((Instruction::SBC, AddressingMode::IndexedIndirectX)), + 0xe2 => None, + 0xe3 => None, + 0xe4 => Some((Instruction::CPX, AddressingMode::ZeroPage)), + 0xe5 => Some((Instruction::SBC, AddressingMode::ZeroPage)), + 0xe6 => Some((Instruction::INC, AddressingMode::ZeroPage)), + 0xe7 => None, + 0xe8 => Some((Instruction::INX, AddressingMode::Implied)), + 0xe9 => Some((Instruction::SBC, AddressingMode::Immediate)), + 0xea => Some((Instruction::NOP, AddressingMode::Implied)), + 0xeb => None, + 0xec => Some((Instruction::CPX, AddressingMode::Absolute)), + 0xed => Some((Instruction::SBC, AddressingMode::Absolute)), + 0xee => Some((Instruction::INC, AddressingMode::Absolute)), + 0xef => None, + 0xf0 => Some((Instruction::BEQ, AddressingMode::Relative)), + 0xf1 => Some((Instruction::SBC, AddressingMode::IndirectIndexedY)), + 0xf2 => None, + 0xf3 => None, + 0xf4 => None, + 0xf5 => Some((Instruction::SBC, AddressingMode::ZeroPageX)), + 0xf6 => Some((Instruction::INC, AddressingMode::ZeroPageX)), + 0xf7 => None, + 0xf8 => Some((Instruction::SED, AddressingMode::Implied)), + 0xf9 => Some((Instruction::SBC, AddressingMode::AbsoluteY)), + 0xfa => None, + 0xfb => None, + 0xfc => None, + 0xfd => Some((Instruction::SBC, AddressingMode::AbsoluteX)), + 0xfe => Some((Instruction::INC, AddressingMode::AbsoluteX)), + 0xff => None, + } + } +} + +/// The Ricoh variant which has no decimal mode. This is what to use if you want to emulate the +/// NES. +pub struct Ricoh2a03; + +impl crate::Variant for Ricoh2a03 { + fn decode(opcode: u8) -> Option<(Instruction, AddressingMode)> { + match opcode { + 0x61 => Some((Instruction::ADCnd, AddressingMode::IndexedIndirectX)), + 0x65 => Some((Instruction::ADCnd, AddressingMode::ZeroPage)), + 0x69 => Some((Instruction::ADCnd, AddressingMode::Immediate)), + 0x6d => Some((Instruction::ADCnd, AddressingMode::Absolute)), + 0x71 => Some((Instruction::ADCnd, AddressingMode::IndirectIndexedY)), + 0x75 => Some((Instruction::ADCnd, AddressingMode::ZeroPageX)), + 0x79 => Some((Instruction::ADCnd, AddressingMode::AbsoluteY)), + 0x7d => Some((Instruction::ADCnd, AddressingMode::AbsoluteX)), + 0xe1 => Some((Instruction::SBCnd, AddressingMode::IndexedIndirectX)), + 0xe5 => Some((Instruction::SBCnd, AddressingMode::ZeroPage)), + 0xe9 => Some((Instruction::SBCnd, AddressingMode::Immediate)), + 0xed => Some((Instruction::SBCnd, AddressingMode::Absolute)), + 0xf1 => Some((Instruction::SBCnd, AddressingMode::IndirectIndexedY)), + 0xf5 => Some((Instruction::SBCnd, AddressingMode::ZeroPageX)), + 0xf9 => Some((Instruction::SBCnd, AddressingMode::AbsoluteY)), + 0xfd => Some((Instruction::SBCnd, AddressingMode::AbsoluteX)), + _ => Nmos6502::decode(opcode), + } + } +} + +/// Emulates some very early 6502s which have no ROR instruction. This one is used in very early +/// KIM-1s. +pub struct RevisionA; + +impl crate::Variant for RevisionA { + fn decode(opcode: u8) -> Option<(Instruction, AddressingMode)> { + match opcode { + 0x66 => None, + 0x6a => None, + 0x6e => None, + 0x76 => None, + 0x7e => None, + _ => Nmos6502::decode(opcode), + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 1a981bb..302a8c1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,3 +38,14 @@ pub mod cpu; pub mod instruction; pub mod memory; pub mod registers; + +/// Trait for 6502 variant. This is the mechanism allowing the different 6502-like CPUs to be +/// emulated. It allows a struct to decode an opcode into its instruction and addressing mode. +pub trait Variant { + fn decode( + opcode: u8, + ) -> Option<( + crate::instruction::Instruction, + crate::instruction::AddressingMode, + )>; +}