From 20747f5fad0f51e044298d34d477e1c4db2d8143 Mon Sep 17 00:00:00 2001 From: Franco Colmenarez Date: Sun, 20 Nov 2022 23:19:39 -0500 Subject: [PATCH] ASL instruction --- snes-core/src/cpu/cycles.rs | 30 +++++++++++++++++++ snes-core/src/cpu/instructions.rs | 49 +++++++++++++++++++++++++++++++ snes-core/src/utils/addressing.rs | 2 ++ snes-core/src/utils/alu.rs | 44 +++++++++++++++++++++++++++ 4 files changed, 125 insertions(+) diff --git a/snes-core/src/cpu/cycles.rs b/snes-core/src/cpu/cycles.rs index e5942f3..0a8cadf 100644 --- a/snes-core/src/cpu/cycles.rs +++ b/snes-core/src/cpu/cycles.rs @@ -100,6 +100,23 @@ impl CPU { A::DirectPageIndirectLongIndexed(_) => (2, 6), A::StackRelative => (2, 4), A::StackRelativeIndirectIndexed(_) => (2, 7), + _ => unreachable!(), + } + } + + fn common_bytes_cycles_shift(addressing_mode: AddressingMode) -> (u16, usize) { + match addressing_mode { + A::Accumulator => (1, 2), + A::Absolute => (3, 6), + A::DirectPage => (2, 5), + // Note: in some documentations you will find that this addressing mode has + // 7 cycles for shift instructions, and then it says to substract + // 1 cycles if no page boundary is crossed. + // But to make it simpler, we are assigning 6 cycles here and then incrementing + // it by 1 later if a page boundary is crossed. + A::AbsoluteIndexed(_) => (3, 6), + A::DirectPageIndexed(_) => (2, 6), + _ => unreachable!(), } } @@ -116,6 +133,19 @@ impl CPU { self.cycles += cycles; self.common_conditions(addressing_mode, &BITWISE_CONDITIONS); } + + pub fn increment_cycles_shift(&mut self, addressing_mode: AddressingMode) { + let (bytes, cycles) = CPU::common_bytes_cycles_shift(addressing_mode); + self.registers.increment_pc(bytes); + self.cycles += cycles; + // Add 2 cycles if m = 1 + self.common_conditions(addressing_mode, &[Condition::MemorySelectFlag]); + self.common_conditions(addressing_mode, &[ + Condition::MemorySelectFlag, + Condition::DirectPageZero, + Condition::IndexCrossesPageBoundary, + ]); + } } #[cfg(test)] diff --git a/snes-core/src/cpu/instructions.rs b/snes-core/src/cpu/instructions.rs index 66ce013..5762aa1 100644 --- a/snes-core/src/cpu/instructions.rs +++ b/snes-core/src/cpu/instructions.rs @@ -113,6 +113,32 @@ impl CPU { self.increment_cycles_bitwise(addressing_mode); } + fn asl(&mut self, bus: &Bus, addressing_mode: AddressingMode) { + // if the M flag is set, perform 8 bit addition. + // Otherwise, 16 bit addition + let target = match addressing_mode { + AddressingMode::Accumulator => self.registers.a, + _ => match self.registers.is_16bit_mode() { + true => self.get_16bit_from_address(bus, addressing_mode), + false => self.get_8bit_from_address(bus, addressing_mode) as u16, + } + }; + if self.registers.is_16bit_mode() { + let (result, is_negative, is_zero, is_carry) = alu::asl16bit(target); + self.registers.a = result; + self.registers.set_negative_flag(is_negative); + self.registers.set_zero_flag(is_zero); + self.registers.set_carry_flag(is_carry); + } else { + let (result, is_negative, is_zero, is_carry) = alu::asl8bit(target as u8); + self.registers.set_low_a(result); + self.registers.set_negative_flag(is_negative); + self.registers.set_zero_flag(is_zero); + self.registers.set_carry_flag(is_carry); + } + self.increment_cycles_shift(addressing_mode); + } + pub fn execute_opcode(&mut self, opcode: u8, bus: &Bus) { type A = AddressingMode; type I = IndexRegister; @@ -165,6 +191,12 @@ impl CPU { 0x37 => self.and(bus, A::DirectPageIndirectLongIndexed(I::Y)), 0x23 => self.and(bus, A::StackRelative), 0x33 => self.and(bus, A::StackRelativeIndirectIndexed(I::Y)), + // ASL + 0x0A => self.asl(bus, A::Accumulator), + 0x0E => self.asl(bus, A::Absolute), + 0x06 => self.asl(bus, A::DirectPage), + 0x1E => self.asl(bus, A::AbsoluteIndexed(I::X)), + 0x16 => self.asl(bus, A::DirectPageIndexed(I::X)), _ => println!("Invalid opcode: {:02X}", opcode), } } @@ -224,4 +256,21 @@ mod cpu_instructions_tests { assert!(!cpu.registers.get_carry_flag()); assert!(!cpu.registers.get_zero_flag()); } + + #[test] + fn test_asl() { + let mut cpu = CPU::new(); + let bus = Bus::new(); + cpu.registers.a = 0b01010000_00000000; + cpu.registers.pbr = 0x00; + cpu.registers.pc = 0x0000; + cpu.registers.set_memory_select_flag(false); + cpu.asl(&bus, AddressingMode::Accumulator); + assert_eq!(cpu.registers.a, 0b10100000_00000000); + assert_eq!(cpu.registers.pc, 0x01); + assert_eq!(cpu.cycles, 4); + assert!(!cpu.registers.get_carry_flag()); + assert!(!cpu.registers.get_zero_flag()); + assert!(cpu.registers.get_negative_flag()); + } } diff --git a/snes-core/src/utils/addressing.rs b/snes-core/src/utils/addressing.rs index e7e76cd..8dd7c6c 100644 --- a/snes-core/src/utils/addressing.rs +++ b/snes-core/src/utils/addressing.rs @@ -93,6 +93,7 @@ pub enum IndexRegister { #[derive(Copy, Clone)] pub enum AddressingMode { + Accumulator, Immediate, Absolute, AbsoluteLong, @@ -114,6 +115,7 @@ impl AddressingMode { use IndexRegister::X as X; // TODO: maybe use impl Immediate {pub fn effective_address} to prevent this match statement? match self { + Self::Accumulator => pc_addr, Self::Immediate => immediate(pc_addr), Self::Absolute => absolute(bus, pc_addr), Self::AbsoluteLong => absolute_long(bus, pc_addr), diff --git a/snes-core/src/utils/alu.rs b/snes-core/src/utils/alu.rs index 2b16483..46257f5 100644 --- a/snes-core/src/utils/alu.rs +++ b/snes-core/src/utils/alu.rs @@ -165,6 +165,22 @@ pub fn and16bit(target: u16, value: u16) -> (u16, bool, bool) { (result, is_negative, is_zero) } +pub fn asl8bit(target: u8) -> (u8, bool, bool, bool) { + let result = target << 1; + let is_negative = (result >> 7) == 1; + let is_zero = result == 0; + let is_carry = (target >> 7) == 1; + (result, is_negative, is_zero, is_carry) +} + +pub fn asl16bit(target: u16) -> (u16, bool, bool, bool) { + let result = target << 1; + let is_negative = (result >> 15) == 1; + let is_zero = result == 0; + let is_carry = (target >> 15) == 1; + (result, is_negative, is_zero, is_carry) +} + #[cfg(test)] mod alu_tests { use super::*; @@ -406,4 +422,32 @@ mod alu_tests { assert_eq!(is_negative, false); assert_eq!(is_zero, false); } + + #[test] + fn test_asl8bit() { + let (result, is_negative, is_zero, is_carry) = asl8bit(0b0101_0101); + assert_eq!(result, 0b1010_1010); + assert_eq!(is_negative, true); + assert_eq!(is_zero, false); + assert_eq!(is_carry, false); + let (result, is_negative, is_zero, is_carry) = asl8bit(0b1000_0000); + assert_eq!(result, 0b0000_0000); + assert_eq!(is_negative, false); + assert_eq!(is_zero, true); + assert_eq!(is_carry, true); + } + + #[test] + fn test_asl16bit() { + let (result, is_negative, is_zero, is_carry) = asl16bit(0b01000000_00000000); + assert_eq!(result, 0b10000000_00000000); + assert_eq!(is_negative, true); + assert_eq!(is_zero, false); + assert_eq!(is_carry, false); + let (result, is_negative, is_zero, is_carry) = asl16bit(0b10000000_00000000); + assert_eq!(result, 0b00000000_00000000); + assert_eq!(is_negative, false); + assert_eq!(is_zero, true); + assert_eq!(is_carry, true); + } }