From 29e65b3a9f1b9f14480b77bfcc11b5596d5b4856 Mon Sep 17 00:00:00 2001 From: Brooke Chalmers Date: Wed, 22 Nov 2023 20:30:47 -0500 Subject: [PATCH 1/5] Add field to track NMOS or CMOS variant --- src/cpu/mod.rs | 14 ++++++++++++-- src/systems/basic.rs | 4 ++-- src/systems/c64/mod.rs | 4 ++-- src/systems/easy.rs | 4 ++-- src/systems/klaus.rs | 4 ++-- src/systems/pet/mod.rs | 4 ++-- src/systems/vic/mod.rs | 4 ++-- 7 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/cpu/mod.rs b/src/cpu/mod.rs index ef258c74..b259eb27 100644 --- a/src/cpu/mod.rs +++ b/src/cpu/mod.rs @@ -8,12 +8,21 @@ use registers::{flags, Registers}; const CLOCKS_PER_POLL: u32 = 100; +#[derive(Copy, Clone)] +pub enum Mos6502Variant { + /// 6502 + NMOS, + /// 65C02 + CMOS, +} + /// The MOS 6502 CPU and its associated memory. pub struct Mos6502 { pub registers: Registers, pub memory: Box, cycle_count: u64, cycles_since_poll: u32, + variant: Mos6502Variant, } /// Read and write from the system's memory. @@ -122,12 +131,13 @@ impl InterruptHandler for Mos6502 { } impl Mos6502 { - pub fn new(memory: Box) -> Mos6502 { + pub fn new(memory: impl Memory + 'static, variant: Mos6502Variant) -> Mos6502 { Mos6502 { registers: Registers::new(), - memory, + memory: Box::new(memory), cycle_count: 0, cycles_since_poll: 0, + variant, } } diff --git a/src/systems/basic.rs b/src/systems/basic.rs index 408106b0..f4e90fc3 100644 --- a/src/systems/basic.rs +++ b/src/systems/basic.rs @@ -1,6 +1,6 @@ use instant::Duration; -use crate::cpu::Mos6502; +use crate::cpu::{Mos6502, Mos6502Variant}; use crate::memory::{ActiveInterrupt, Memory, SystemInfo}; use crate::memory::{BlockMemory, BranchMemory}; use crate::platform::{PlatformProvider, WindowConfig}; @@ -79,7 +79,7 @@ impl SystemBuilder for BasicSystemBuilder { .map(0x4000, io) .map(0x8000, rom); - let cpu = Mos6502::new(Box::new(memory)); + let cpu = Mos6502::new(memory, Mos6502Variant::NMOS); Box::new(BasicSystem { cpu }) } diff --git a/src/systems/c64/mod.rs b/src/systems/c64/mod.rs index 72665889..5670a09e 100644 --- a/src/systems/c64/mod.rs +++ b/src/systems/c64/mod.rs @@ -5,7 +5,7 @@ use std::{ }; use crate::{ - cpu::Mos6502, + cpu::{Mos6502, Mos6502Variant}, keyboard::{ commodore::{C64KeyboardAdapter, C64SymbolAdapter, C64VirtualAdapter}, KeyAdapter, KeyMappingStrategy, SymbolAdapter, @@ -303,7 +303,7 @@ impl SystemBuilder for C64SystemBuild .map(0xD000, region6) .map(0xE000, region7); - let cpu = Mos6502::new(Box::new(memory)); + let cpu = Mos6502::new(memory, Mos6502Variant::NMOS); Box::new(C64System { cpu, vic: vic_ii }) } diff --git a/src/systems/easy.rs b/src/systems/easy.rs index f335649d..a4ca9183 100644 --- a/src/systems/easy.rs +++ b/src/systems/easy.rs @@ -1,6 +1,6 @@ use instant::Duration; -use crate::cpu::{MemoryIO, Mos6502}; +use crate::cpu::{MemoryIO, Mos6502, Mos6502Variant}; use crate::keyboard::KeyPosition; use crate::memory::{ActiveInterrupt, BlockMemory, BranchMemory, Memory, SystemInfo}; use crate::platform::{Color, PlatformProvider, WindowConfig}; @@ -79,7 +79,7 @@ impl SystemBuilder for Easy6502SystemBuilder { .map(0x0600, high_ram) .map(0x8000, rom); - let cpu = Mos6502::new(Box::new(memory)); + let cpu = Mos6502::new(memory, Mos6502Variant::NMOS); Box::new(Easy6502System { cpu }) } diff --git a/src/systems/klaus.rs b/src/systems/klaus.rs index 3a26a000..cf3e618b 100644 --- a/src/systems/klaus.rs +++ b/src/systems/klaus.rs @@ -1,6 +1,6 @@ use instant::Duration; -use crate::cpu::Mos6502; +use crate::cpu::{Mos6502, Mos6502Variant}; use crate::memory::BlockMemory; use crate::platform::{PlatformProvider, WindowConfig}; use crate::roms::RomFile; @@ -21,7 +21,7 @@ impl SystemBuilder>>> for KlausSystemB _platform: Arc, ) -> Box { let rom = BlockMemory::from_file(0x10000, rom).set_writeable(true); - let mut cpu = Mos6502::new(Box::new(rom)); + let mut cpu = Mos6502::new(rom, Mos6502Variant::NMOS); cpu.registers.pc.load(0x0400); diff --git a/src/systems/pet/mod.rs b/src/systems/pet/mod.rs index 7809c57a..222f0433 100644 --- a/src/systems/pet/mod.rs +++ b/src/systems/pet/mod.rs @@ -1,4 +1,4 @@ -use crate::cpu::{MemoryIO, Mos6502}; +use crate::cpu::{MemoryIO, Mos6502, Mos6502Variant}; use crate::keyboard::{KeyAdapter, KeyMappingStrategy, SymbolAdapter}; use crate::memory::mos652x::{Pia, Via}; use crate::memory::{BlockMemory, BranchMemory, NullMemory, NullPort, Port, SystemInfo}; @@ -194,7 +194,7 @@ impl SystemBuilder for PetSystemBuild .map(0xE840, via) .map(0xF000, kernel_rom); - let cpu = Mos6502::new(Box::new(memory)); + let cpu = Mos6502::new(memory, Mos6502Variant::NMOS); Box::new(PetSystem { cpu, diff --git a/src/systems/vic/mod.rs b/src/systems/vic/mod.rs index 84c16230..8293a536 100644 --- a/src/systems/vic/mod.rs +++ b/src/systems/vic/mod.rs @@ -1,4 +1,4 @@ -use crate::cpu::Mos6502; +use crate::cpu::{Mos6502, Mos6502Variant}; use crate::keyboard::commodore::C64VirtualAdapter; use crate::keyboard::{ commodore::{C64KeyboardAdapter, C64SymbolAdapter}, @@ -298,7 +298,7 @@ impl SystemBuilder for Vic20Sys .map(0xC000, basic_rom) .map(0xE000, kernel_rom); - let cpu = Mos6502::new(Box::new(memory)); + let cpu = Mos6502::new(memory, Mos6502Variant::NMOS); Box::new(Vic20System { cpu, vic: vic_chip }) } From e5a40041252bfbf21312c933fdb4c9a111c26278 Mon Sep 17 00:00:00 2001 From: Brooke Chalmers Date: Wed, 22 Nov 2023 20:37:32 -0500 Subject: [PATCH 2/5] Implement NMOS quirks --- src/cpu/execute.rs | 31 ++++++++++++++++++++++++++++++- src/cpu/fetch.rs | 24 ++++++++++++++++++++---- src/cpu/mod.rs | 2 +- 3 files changed, 51 insertions(+), 6 deletions(-) diff --git a/src/cpu/execute.rs b/src/cpu/execute.rs index a2279a4f..9aaeb2c1 100644 --- a/src/cpu/execute.rs +++ b/src/cpu/execute.rs @@ -2,6 +2,8 @@ use crate::cpu::fetch::Fetch; use crate::cpu::registers::{flags, Alu}; use crate::cpu::{InterruptHandler, MemoryIO, Mos6502, Stack}; +use super::Mos6502Variant; + pub trait Execute { /// Execute the given opcode, returning either the number of cycles used or an error. fn execute(&mut self, opcode: u8) -> Result; @@ -133,6 +135,10 @@ impl Execute for Mos6502 { // ASL let (address, cycles) = self.fetch_operand_address(opcode); let value = self.read(address); + + if let Mos6502Variant::NMOS = self.variant { + self.write(address, value); + } let result = value << 1; self.registers.sr.write(flags::CARRY, value & 0x80 != 0); @@ -154,6 +160,11 @@ impl Execute for Mos6502 { // LSR let (address, cycles) = self.fetch_operand_address(opcode); let value = self.read(address); + + if let Mos6502Variant::NMOS = self.variant { + self.write(address, value); + } + let result = value >> 1; self.registers.sr.write(flags::CARRY, value & 0x01 != 0); @@ -176,6 +187,11 @@ impl Execute for Mos6502 { // ROL let (address, cycles) = self.fetch_operand_address(opcode); let value = self.read(address); + + if let Mos6502Variant::NMOS = self.variant { + self.write(address, value); + } + let result = (value << 1) | (self.registers.sr.read(flags::CARRY) as u8); self.registers.sr.write(flags::CARRY, value & 0x80 != 0); @@ -198,6 +214,11 @@ impl Execute for Mos6502 { // ROR let (address, cycles) = self.fetch_operand_address(opcode); let value = self.read(address); + + if let Mos6502Variant::NMOS = self.variant { + self.write(address, value); + } + let result = value >> 1 | (self.registers.sr.read(flags::CARRY) as u8) << 7; self.registers.sr.write(flags::CARRY, value & 0x01 != 0); @@ -341,7 +362,15 @@ impl Execute for Mos6502 { 0x4C => (self.fetch_word(), 3), 0x6C => { let indirect = self.fetch_word(); - (self.read_word(indirect), 5) + + if self.variant == Mos6502Variant::NMOS && indirect & 0xFF == 0xFF { + let lo = self.read(indirect); + let hi = self.read(indirect & 0xFF00); + ((hi as u16) << 8 | lo as u16, 5) + } else { + // normal behavior + (self.read_word(indirect), 5) + } } _ => unreachable!(), }; diff --git a/src/cpu/fetch.rs b/src/cpu/fetch.rs index d4afd398..ec6b73e7 100644 --- a/src/cpu/fetch.rs +++ b/src/cpu/fetch.rs @@ -1,5 +1,7 @@ use crate::cpu::{MemoryIO, Mos6502}; +use super::Mos6502Variant; + /// Fetch values or addresses from memory, optionally dependent on the current /// opcode. pub trait Fetch { @@ -88,15 +90,29 @@ impl Fetch for Mos6502 { 0x1C | 0x1D => { // Absolute,X let base = self.fetch_word(); - (base + self.registers.x as u16, 4) + let indexed = base + self.registers.x as u16; + + if self.variant == Mos6502Variant::NMOS && base & 0xFF00 != indexed & 0xFF00 { + self.read(base & 0xFF00 | indexed & 0x00FF); + (indexed, 5) + } else { + (indexed, 4) + } } 0x1E | 0x1F => { // Absolute,X or Absolute,Y let base = self.fetch_word(); - if opcode & 0xC0 == 0x80 { - (base + self.registers.y as u16, 4) + let indexed = if opcode & 0xC0 == 0x80 { + base + self.registers.y as u16 + } else { + base + self.registers.x as u16 + }; + + if self.variant == Mos6502Variant::NMOS && base & 0xFF00 != indexed & 0xFF00 { + self.read(base & 0xFF00 | indexed & 0x00FF); + (indexed, 5) } else { - (base + self.registers.x as u16, 4) + (indexed, 4) } } _ => unreachable!(), diff --git a/src/cpu/mod.rs b/src/cpu/mod.rs index b259eb27..aa6b9524 100644 --- a/src/cpu/mod.rs +++ b/src/cpu/mod.rs @@ -8,7 +8,7 @@ use registers::{flags, Registers}; const CLOCKS_PER_POLL: u32 = 100; -#[derive(Copy, Clone)] +#[derive(Copy, Clone, PartialEq)] pub enum Mos6502Variant { /// 6502 NMOS, From e124df724052050de2e8cad373bec5a4229acf2f Mon Sep 17 00:00:00 2001 From: Brooke Chalmers Date: Wed, 22 Nov 2023 20:43:35 -0500 Subject: [PATCH 3/5] Implement 65C02 indirect zero-page addressing mode --- src/cpu/execute.rs | 18 +++++++++--------- src/cpu/fetch.rs | 14 ++++++++++++-- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/cpu/execute.rs b/src/cpu/execute.rs index 9aaeb2c1..bd416b01 100644 --- a/src/cpu/execute.rs +++ b/src/cpu/execute.rs @@ -13,7 +13,7 @@ impl Execute for Mos6502 { fn execute(&mut self, opcode: u8) -> Result { match opcode { // === LOAD === - 0xA1 | 0xA5 | 0xA9 | 0xAD | 0xB1 | 0xB5 | 0xB9 | 0xBD => { + 0xA1 | 0xA5 | 0xA9 | 0xAD | 0xB1 | 0xB2 | 0xB5 | 0xB9 | 0xBD => { // LDA let (value, cycles) = self.fetch_operand_value(opcode); self.registers.a = value; @@ -38,7 +38,7 @@ impl Execute for Mos6502 { } // === STORE === - 0x81 | 0x85 | 0x8D | 0x91 | 0x95 | 0x99 | 0x9D => { + 0x81 | 0x85 | 0x8D | 0x91 | 0x92 | 0x95 | 0x99 | 0x9D => { // STA let (address, cycles) = self.fetch_operand_address(opcode); self.write(address, self.registers.a); @@ -228,7 +228,7 @@ impl Execute for Mos6502 { } // === LOGIC === - 0x21 | 0x25 | 0x29 | 0x2D | 0x31 | 0x35 | 0x39 | 0x3D => { + 0x21 | 0x25 | 0x29 | 0x2D | 0x31 | 0x32 | 0x35 | 0x39 | 0x3D => { // AND let (value, cycles) = self.fetch_operand_value(opcode); self.registers.a &= value; @@ -248,7 +248,7 @@ impl Execute for Mos6502 { Ok(cycles) } - 0x41 | 0x45 | 0x49 | 0x4D | 0x51 | 0x55 | 0x59 | 0x5D => { + 0x41 | 0x45 | 0x49 | 0x4D | 0x51 | 0x52 | 0x55 | 0x59 | 0x5D => { // EOR let (value, cycles) = self.fetch_operand_value(opcode); self.registers.a ^= value; @@ -256,7 +256,7 @@ impl Execute for Mos6502 { Ok(cycles) } - 0x01 | 0x05 | 0x09 | 0x0D | 0x11 | 0x15 | 0x19 | 0x1D => { + 0x01 | 0x05 | 0x09 | 0x0D | 0x11 | 0x12 | 0x15 | 0x19 | 0x1D => { // ORA let (value, cycles) = self.fetch_operand_value(opcode); self.registers.a |= value; @@ -265,14 +265,14 @@ impl Execute for Mos6502 { } // === ARITHMETIC === - 0x61 | 0x65 | 0x69 | 0x6D | 0x71 | 0x75 | 0x79 | 0x7D => { + 0x61 | 0x65 | 0x69 | 0x6D | 0x71 | 0x72 | 0x75 | 0x79 | 0x7D => { // ADC let (value, cycles) = self.fetch_operand_value(opcode); self.registers.alu_add(value); Ok(cycles) } - 0xC1 | 0xC5 | 0xC9 | 0xCD | 0xD1 | 0xD5 | 0xD9 | 0xDD => { + 0xC1 | 0xC5 | 0xC9 | 0xCD | 0xD1 | 0xD2 | 0xD5 | 0xD9 | 0xDD => { // CMP let (value, cycles) = self.fetch_operand_value(opcode); self.registers.alu_compare(self.registers.a, value); @@ -293,7 +293,7 @@ impl Execute for Mos6502 { Ok(cycles) } - 0xE1 | 0xE5 | 0xE9 | 0xED | 0xF1 | 0xF5 | 0xF9 | 0xFD => { + 0xE1 | 0xE5 | 0xE9 | 0xED | 0xF1 | 0xF2 | 0xF5 | 0xF9 | 0xFD => { // SBC let (value, cycles) = self.fetch_operand_value(opcode); self.registers.alu_subtract(value); @@ -474,7 +474,7 @@ impl Execute for Mos6502 { } } - 0x02 | 0x12 | 0x22 | 0x32 | 0x42 | 0x52 | 0x62 | 0x72 | 0x92 | 0xB2 | 0xD2 | 0xF2 => { + 0x02 | 0x22 | 0x42 | 0x62 => { // STP or KIL or JAM or HLT depending on who you ask println!("Execution stopped"); Err(()) diff --git a/src/cpu/fetch.rs b/src/cpu/fetch.rs index ec6b73e7..e1615ed8 100644 --- a/src/cpu/fetch.rs +++ b/src/cpu/fetch.rs @@ -38,7 +38,6 @@ impl Fetch for Mos6502 { match opcode & 0x1F { 0x00 | 0x02 | 0x09 | 0x0B => (self.fetch(), 2), // Immediate 0x08 | 0x18 | 0x1A => panic!("Implied operand has no value"), - 0x12 => panic!("Invalid opcode"), 0x0A => (self.registers.a, 0), _ => { let (address, cycles) = self.fetch_operand_address(opcode); @@ -67,7 +66,18 @@ impl Fetch for Mos6502 { let pointer = self.read_word(base as u16); (pointer + self.registers.y as u16, 5) } - 0x12 => panic!("Invalid opcode"), + 0x12 => match self.variant { + Mos6502Variant::NMOS => { + // These all halt the processor on an NMOS chip + panic!("Invalid opcode"); + } + Mos6502Variant::CMOS => { + // (Indirect) + let base = self.fetch(); + let pointer = self.read_word(base as u16); + (pointer, 5) + } + }, 0x14 | 0x15 => { // Zero page,X let base = self.fetch(); From f102c47794d609b6f2cb7217d1000be693104827 Mon Sep 17 00:00:00 2001 From: Brooke Chalmers Date: Wed, 22 Nov 2023 21:30:53 -0500 Subject: [PATCH 4/5] Implement 65C02 opcodes --- src/cpu/execute.rs | 169 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 151 insertions(+), 18 deletions(-) diff --git a/src/cpu/execute.rs b/src/cpu/execute.rs index bd416b01..905bc01e 100644 --- a/src/cpu/execute.rs +++ b/src/cpu/execute.rs @@ -455,25 +455,18 @@ impl Execute for Mos6502 { Ok(2) } - // === ILLEGAL OPCODES === - // TODO: Verify cycle counts - 0x04 | 0x0C | 0x14 | 0x1A | 0x1C | 0x34 | 0x3A | 0x3C | 0x44 | 0x54 | 0x5A | 0x5C | 0x64 - | 0x74 | 0x7A | 0x7C | 0x80 | 0x82 | 0x89 | 0xC2 | 0xD4 | 0xDA | 0xDC | 0xE2 | 0xF4 - | 0xFA | 0xFC => { - // NOP - match opcode { - 0x1A | 0x3A | 0x5A | 0x7A | 0xDA | 0xFA => { - // No address - Ok(2) - } - _ => { - // Address - let (_value, cycles) = self.fetch_operand_value(opcode); - Ok(cycles) - } - } - } + _ => match self.variant { + Mos6502Variant::NMOS => self.execute_nmos_extensions(opcode), + Mos6502Variant::CMOS => self.execute_cmos_extensions(opcode), + }, + } + } +} +impl Mos6502 { + fn execute_nmos_extensions(&mut self, opcode: u8) -> Result { + match opcode { + // === ILLEGAL OPCODES === 0x02 | 0x22 | 0x42 | 0x62 => { // STP or KIL or JAM or HLT depending on who you ask println!("Execution stopped"); @@ -741,6 +734,146 @@ impl Execute for Mos6502 { Ok(cycles) } + + // TODO: Verify cycle counts + _ => { + // NOP + match opcode { + 0x1A | 0x3A | 0x5A | 0x7A | 0xDA | 0xFA => { + // No address + Ok(2) + } + _ => { + // Address + let (_value, cycles) = self.fetch_operand_value(opcode); + Ok(cycles) + } + } + } + } + } + + fn execute_cmos_extensions(&mut self, opcode: u8) -> Result { + match opcode { + 0x89 | 0x34 | 0x3C => { + // BIT (3 extra addressing modes) + let (value, cycles) = self.fetch_operand_value(opcode); + self.registers.sr.write(flags::NEGATIVE, value & 0x80 != 0); + self.registers.sr.write(flags::OVERFLOW, value & 0x40 != 0); + self + .registers + .sr + .write(flags::ZERO, value & self.registers.a == 0); + Ok(cycles) + } + + 0x3A => { + // DEC (like DEX/DEY but for accumulator) + self.registers.a = self.registers.a.wrapping_sub(1); + self.registers.sr.set_nz(self.registers.a); + Ok(2) + } + + 0x1A => { + // INC (like INX/INY but for accumulator) + self.registers.a = self.registers.a.wrapping_add(1); + self.registers.sr.set_nz(self.registers.a); + Ok(2) + } + + 0x7C => { + // JMP (abs,X) + let address = self.fetch_word(); + let pointer = address + self.registers.x as u16; + let address = self.read_word(pointer); + self.registers.pc.load(address); + Ok(6) + } + + 0x80 => { + // BRA (branch Always) + let offset = self.fetch() as i8; + self.registers.pc.offset(offset); + Ok(3) + } + + // New Stack Instructions + 0xDA => { + // PHX (push X onto stack) + self.push(self.registers.x); + Ok(3) + } + 0x5A => { + // PHY (push Y onto stack) + self.push(self.registers.y); + Ok(3) + } + 0xFA => { + // PLX (pull X from stack) + let value = self.pop(); + self.registers.x = value; + self.registers.sr.set_nz(value); + Ok(4) + } + 0x7A => { + // PLY (pull Y from stack) + let value = self.pop(); + self.registers.y = value; + self.registers.sr.set_nz(value); + Ok(4) + } + + 0x64 | 0x74 | 0x9C | 0x9E => { + // STZ (store zero) + // Note: 0x9C breaks the typical addressing mode pattern + let (address, cycles) = match opcode { + 0x9C => (self.fetch_word(), 4), + _ => self.fetch_operand_address(opcode), + }; + + self.write(address, 0); + Ok(cycles) + } + + 0x14 | 0x1C => { + // TRB (test and reset bits) + let (address, cycles) = match opcode { + 0x14 => (self.fetch() as u16, 3), + 0x1C => (self.fetch_word(), 4), + _ => unreachable!(), + }; + let value = self.read(address); + + self + .registers + .sr + .write(flags::ZERO, value & self.registers.a == 0); + + self.write(address, value & !self.registers.a); + Ok(cycles) + } + + 0x04 | 0x0C => { + // TSB (test and set bits) + let (address, cycles) = match opcode { + 0x04 => (self.fetch() as u16, 3), + 0x0C => (self.fetch_word(), 4), + _ => unreachable!(), + }; + let value = self.read(address); + + self + .registers + .sr + .write(flags::ZERO, value & self.registers.a == 0); + + self.write(address, value | self.registers.a); + Ok(cycles) + } + + _ => { + todo!(); + } } } } From ad5810e09c9874d63843189014c2f5d4dbb1d4b6 Mon Sep 17 00:00:00 2001 From: Brooke Chalmers Date: Wed, 22 Nov 2023 22:50:36 -0500 Subject: [PATCH 5/5] Add Klaus tests for 65C02 --- bin/{klaus.bin => klaus_6502.bin} | Bin bin/klaus_65C02.bin | Bin 0 -> 65536 bytes src/cpu/execute.rs | 76 ++++++++++++++++++++++++++++-- src/cpu/mod.rs | 4 ++ src/main.rs | 11 ++++- src/systems/klaus.rs | 52 +++++++++++++++++--- 6 files changed, 132 insertions(+), 11 deletions(-) rename bin/{klaus.bin => klaus_6502.bin} (100%) create mode 100644 bin/klaus_65C02.bin diff --git a/bin/klaus.bin b/bin/klaus_6502.bin similarity index 100% rename from bin/klaus.bin rename to bin/klaus_6502.bin diff --git a/bin/klaus_65C02.bin b/bin/klaus_65C02.bin new file mode 100644 index 0000000000000000000000000000000000000000..b1ea946a1767f9b453325cf02df0bbbaba0cbf1e GIT binary patch literal 65536 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 { // BIT (3 extra addressing modes) let (value, cycles) = self.fetch_operand_value(opcode); - self.registers.sr.write(flags::NEGATIVE, value & 0x80 != 0); - self.registers.sr.write(flags::OVERFLOW, value & 0x40 != 0); + + if opcode != 0x89 { + // N, V flags not set for immediate + self.registers.sr.write(flags::NEGATIVE, value & 0x80 != 0); + self.registers.sr.write(flags::OVERFLOW, value & 0x40 != 0); + } + self .registers .sr @@ -828,6 +833,11 @@ impl Mos6502 { // Note: 0x9C breaks the typical addressing mode pattern let (address, cycles) = match opcode { 0x9C => (self.fetch_word(), 4), + 0x9E => { + let base = self.fetch_word(); + let indexed = base + self.registers.x as u16; + (indexed, 4) + } _ => self.fetch_operand_address(opcode), }; @@ -871,8 +881,68 @@ impl Mos6502 { Ok(cycles) } + 0x0F | 0x1F | 0x2F | 0x3F | 0x4F | 0x5F | 0x6F | 0x7F | 0x8F | 0x9F | 0xAF | 0xBF | 0xCF + | 0xDF | 0xEF | 0xFF => { + // BBS and BBR + let address = self.fetch() as u16; + let value = self.read(address); + let offset = self.fetch() as i8; + + let bit = (opcode >> 4) & 0b111; + let bit_value = ((1 << bit) & value) != 0; + let target_value = opcode & 0x80 != 0; + + if target_value == bit_value { + self.registers.pc.offset(offset); + Ok(3) + } else { + Ok(2) + } + } + + 0x07 | 0x17 | 0x27 | 0x37 | 0x47 | 0x57 | 0x67 | 0x77 | 0x87 | 0x97 | 0xA7 | 0xB7 | 0xC7 + | 0xD7 | 0xE7 | 0xF7 => { + // RMB and SMB + let address = self.fetch() as u16; + let value = self.read(address); + + let bit = (opcode >> 4) & 0b111; + + let value = if opcode & 0x80 == 0 { + value & !(1 << bit) + } else { + value | (1 << bit) + }; + self.write(address, value); + + Ok(2) + } + + 0x02 | 0x22 | 0x42 | 0x62 | 0x82 | 0xA2 | 0xC2 | 0xE2 => { + // NOP (2-byte) + self.fetch(); + Ok(2) + } + 0x44 => { + self.fetch(); + Ok(3) + } + 0x54 | 0xD4 | 0xF4 => { + self.fetch(); + Ok(4) + } + 0x5C => { + self.fetch_word(); + Ok(8) + } + 0xDC | 0xFC => { + self.fetch_word(); + Ok(4) + } + _ => { - todo!(); + // NOP + Ok(1) } } } diff --git a/src/cpu/mod.rs b/src/cpu/mod.rs index aa6b9524..616ac7d9 100644 --- a/src/cpu/mod.rs +++ b/src/cpu/mod.rs @@ -119,6 +119,10 @@ impl InterruptHandler for Mos6502 { self.push(self.registers.sr.get() & !flags::BREAK); } + if let Mos6502Variant::CMOS = self.variant { + self.registers.sr.clear(flags::DECIMAL); + } + self.registers.sr.set(flags::INTERRUPT); let dest = match maskable { diff --git a/src/main.rs b/src/main.rs index d214f350..77c4e014 100644 --- a/src/main.rs +++ b/src/main.rs @@ -55,6 +55,8 @@ struct Args { #[cfg(not(target_arch = "wasm32"))] fn main() { + use libnoentiendo::{cpu::Mos6502Variant, systems::klaus::KlausSystemConfig}; + let args = Args::parse(); let mut platform: Box = match args.platform { @@ -75,7 +77,14 @@ fn main() { let system = match args.system { SystemArg::Basic => BasicSystemBuilder::build(romfile.unwrap(), (), platform.provider()), SystemArg::Easy => Easy6502SystemBuilder::build(romfile.unwrap(), (), platform.provider()), - SystemArg::Klaus => KlausSystemBuilder::build(romfile.unwrap(), None, platform.provider()), + SystemArg::Klaus => KlausSystemBuilder::build( + romfile.unwrap(), + KlausSystemConfig { + pc_report: None, + variant: Mos6502Variant::NMOS, + }, + platform.provider(), + ), SystemArg::Pet => PetSystemBuilder::build( PetSystemRoms::from_disk(), PetSystemConfig { mapping }, diff --git a/src/systems/klaus.rs b/src/systems/klaus.rs index cf3e618b..a43f1d93 100644 --- a/src/systems/klaus.rs +++ b/src/systems/klaus.rs @@ -11,21 +11,29 @@ use std::sync::Arc; use super::SystemBuilder; +pub struct KlausSystemConfig { + pub pc_report: Option>>, + pub variant: Mos6502Variant, +} + /// A factory for creating a system that runs Klaus Dormann's 6502 CPU test suite. pub struct KlausSystemBuilder; -impl SystemBuilder>>> for KlausSystemBuilder { +impl SystemBuilder for KlausSystemBuilder { fn build( rom: RomFile, - config: Option>>, + config: KlausSystemConfig, _platform: Arc, ) -> Box { let rom = BlockMemory::from_file(0x10000, rom).set_writeable(true); - let mut cpu = Mos6502::new(rom, Mos6502Variant::NMOS); + let mut cpu = Mos6502::new(rom, config.variant); cpu.registers.pc.load(0x0400); - Box::new(KlausSystem { cpu, pc: config }) + Box::new(KlausSystem { + cpu, + pc: config.pc_report, + }) } } @@ -61,11 +69,19 @@ mod tests { use super::*; #[test] - fn test_klaus() { - let roms = RomFile::from_file("bin/klaus.bin"); + fn test_klaus_6502() { + let roms = RomFile::from_file("bin/klaus_6502.bin"); let platform = TextPlatform::new(); let pc = Rc::new(Cell::new(0)); - let mut system = KlausSystemBuilder::build(roms, Some(pc.clone()), platform.provider()); + + let mut system = KlausSystemBuilder::build( + roms, + KlausSystemConfig { + pc_report: Some(pc.clone()), + variant: Mos6502Variant::NMOS, + }, + platform.provider(), + ); for _ in 0..=100000000 { system.tick(); @@ -73,4 +89,26 @@ mod tests { assert_eq!(pc.get(), 0x3469); } + + #[test] + fn test_klaus_65c02() { + let roms = RomFile::from_file("bin/klaus_65C02.bin"); + let platform = TextPlatform::new(); + let pc = Rc::new(Cell::new(0)); + + let mut system = KlausSystemBuilder::build( + roms, + KlausSystemConfig { + pc_report: Some(pc.clone()), + variant: Mos6502Variant::CMOS, + }, + platform.provider(), + ); + + for _ in 0..=100000000 { + system.tick(); + } + + assert_eq!(pc.get(), 0x24f1); + } }