mirror of
https://github.com/FranLMSP/snes.git
synced 2026-10-03 07:55:17 -04:00
CPX and CPY instructions
This commit is contained in:
@@ -6,21 +6,27 @@ type I = IndexRegister;
|
|||||||
|
|
||||||
enum Condition {
|
enum Condition {
|
||||||
MemorySelectFlag,
|
MemorySelectFlag,
|
||||||
DirectPageZero,
|
DirectPageIsZero,
|
||||||
IndexCrossesPageBoundary,
|
IndexCrossesPageBoundary,
|
||||||
DecimalMode,
|
DecimalMode,
|
||||||
|
IndexIs16Bit,
|
||||||
}
|
}
|
||||||
|
|
||||||
const ALL_CONDITIONS: [Condition; 4] = [
|
const ALL_CONDITIONS: [Condition; 4] = [
|
||||||
Condition::MemorySelectFlag,
|
Condition::MemorySelectFlag,
|
||||||
Condition::DirectPageZero,
|
Condition::DirectPageIsZero,
|
||||||
Condition::IndexCrossesPageBoundary,
|
Condition::IndexCrossesPageBoundary,
|
||||||
Condition::DecimalMode,
|
Condition::DecimalMode,
|
||||||
];
|
];
|
||||||
|
|
||||||
const BITWISE_CONDITIONS: [Condition; 3] = [
|
const BITWISE_CONDITIONS: [Condition; 3] = [
|
||||||
Condition::MemorySelectFlag,
|
Condition::MemorySelectFlag,
|
||||||
Condition::DirectPageZero,
|
Condition::DirectPageIsZero,
|
||||||
|
Condition::IndexCrossesPageBoundary,
|
||||||
|
];
|
||||||
|
|
||||||
|
const COMP_INDEX_CONDITIONS: [Condition; 2] = [
|
||||||
|
Condition::IndexIs16Bit,
|
||||||
Condition::IndexCrossesPageBoundary,
|
Condition::IndexCrossesPageBoundary,
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -42,7 +48,7 @@ impl CPU {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Add 1 cycle if low byte of Direct Page register is other than zero (DL< >0)
|
// Add 1 cycle if low byte of Direct Page register is other than zero (DL< >0)
|
||||||
Condition::DirectPageZero => {
|
Condition::DirectPageIsZero => {
|
||||||
match addressing_mode {
|
match addressing_mode {
|
||||||
A::DirectPage | A::DirectPageIndirect | A::DirectPageIndirectLong |
|
A::DirectPage | A::DirectPageIndirect | A::DirectPageIndirectLong |
|
||||||
A::DirectPageIndexed(_) | A::DirectPageIndexedIndirect(_) |
|
A::DirectPageIndexed(_) | A::DirectPageIndexedIndirect(_) |
|
||||||
@@ -77,6 +83,12 @@ impl CPU {
|
|||||||
cycles += 1;
|
cycles += 1;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// Add 1 byte if <index> = 0 (16-bit index registers)
|
||||||
|
Condition::IndexIs16Bit => {
|
||||||
|
if self.registers.is_16bit_index() {
|
||||||
|
bytes += 1; cycles += 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,6 +191,13 @@ impl CPU {
|
|||||||
pub fn increment_cycles_branch_long(&mut self) {
|
pub fn increment_cycles_branch_long(&mut self) {
|
||||||
self.registers.increment_pc(3); self.cycles += 4;
|
self.registers.increment_pc(3); self.cycles += 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn increment_cycles_comp_index(&mut self, addressing_mode: AddressingMode) {
|
||||||
|
let (bytes, cycles) = CPU::common_bytes_cycles_arithmetic(addressing_mode);
|
||||||
|
self.registers.increment_pc(bytes); self.cycles += cycles;
|
||||||
|
let (bytes, cycles) = self.common_conditions(addressing_mode, &COMP_INDEX_CONDITIONS);
|
||||||
|
self.registers.increment_pc(bytes); self.cycles += cycles;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -27,8 +27,6 @@ impl CPU {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn adc(&mut self, bus: &Bus, addressing_mode: AddressingMode) {
|
fn adc(&mut self, bus: &Bus, addressing_mode: AddressingMode) {
|
||||||
// if the M flag is set, perform 8 bit addition.
|
|
||||||
// Otherwise, 16 bit addition
|
|
||||||
let carry_flag = self.registers.get_carry_flag();
|
let carry_flag = self.registers.get_carry_flag();
|
||||||
let is_decimal_mode = self.registers.get_decimal_mode_flag();
|
let is_decimal_mode = self.registers.get_decimal_mode_flag();
|
||||||
let is_16bit = self.registers.is_16bit_mode();
|
let is_16bit = self.registers.is_16bit_mode();
|
||||||
@@ -54,8 +52,6 @@ impl CPU {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn sbc(&mut self, bus: &Bus, addressing_mode: AddressingMode) {
|
fn sbc(&mut self, bus: &Bus, addressing_mode: AddressingMode) {
|
||||||
// if the M flag is set, perform 8 bit addition.
|
|
||||||
// Otherwise, 16 bit addition
|
|
||||||
let carry_flag = self.registers.get_carry_flag();
|
let carry_flag = self.registers.get_carry_flag();
|
||||||
let is_decimal_mode = self.registers.get_decimal_mode_flag();
|
let is_decimal_mode = self.registers.get_decimal_mode_flag();
|
||||||
let is_16bit = self.registers.is_16bit_mode();
|
let is_16bit = self.registers.is_16bit_mode();
|
||||||
@@ -80,43 +76,61 @@ impl CPU {
|
|||||||
self.increment_cycles_arithmetic(addressing_mode);
|
self.increment_cycles_arithmetic(addressing_mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cmp(&mut self, bus: &Bus, addressing_mode: AddressingMode) {
|
fn do_comp<T: SnesNum>(&mut self, target: T, value: T) {
|
||||||
// if the M flag is set, perform 8 bit addition.
|
|
||||||
// Otherwise, 16 bit addition
|
|
||||||
let carry_flag = self.registers.get_carry_flag();
|
let carry_flag = self.registers.get_carry_flag();
|
||||||
let is_decimal_mode = self.registers.get_decimal_mode_flag();
|
let is_decimal_mode = self.registers.get_decimal_mode_flag();
|
||||||
let is_16bit = self.registers.is_16bit_mode();
|
let (_, affected_flags) = match is_decimal_mode {
|
||||||
let target = self.registers.a;
|
|
||||||
let affected_flags = match is_16bit {
|
|
||||||
true => {
|
|
||||||
let value = self.get_16bit_from_address(bus, addressing_mode);
|
|
||||||
let (_, flags) = match is_decimal_mode {
|
|
||||||
true => alu::sbc_bcd(target, value, carry_flag),
|
true => alu::sbc_bcd(target, value, carry_flag),
|
||||||
false => alu::sbc_bin(target, value, carry_flag),
|
false => alu::sbc_bin(target, value, carry_flag),
|
||||||
};
|
};
|
||||||
flags.to_vec()
|
|
||||||
},
|
|
||||||
false => {
|
|
||||||
let value = self.get_8bit_from_address(bus, addressing_mode);
|
|
||||||
let (_, flags) = match is_decimal_mode {
|
|
||||||
true => alu::sbc_bcd(target as u8, value, carry_flag),
|
|
||||||
false => alu::sbc_bin(target as u8, value, carry_flag),
|
|
||||||
};
|
|
||||||
flags.to_vec()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
for flag in affected_flags {
|
for flag in affected_flags {
|
||||||
match flag {
|
match flag {
|
||||||
Flags::Overflow(_) => {},
|
Flags::Overflow(_) => {},
|
||||||
_ => self.registers.set_flags(&[flag]),
|
_ => self.registers.set_flags(&[flag]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cmp(&mut self, bus: &Bus, addressing_mode: AddressingMode) {
|
||||||
|
let is_16bit = self.registers.is_16bit_mode();
|
||||||
|
let target = self.registers.a;
|
||||||
|
if is_16bit {
|
||||||
|
let value = self.get_16bit_from_address(bus, addressing_mode);
|
||||||
|
self.do_comp(target, value);
|
||||||
|
} else {
|
||||||
|
let value = self.get_8bit_from_address(bus, addressing_mode);
|
||||||
|
self.do_comp(target as u8, value);
|
||||||
|
}
|
||||||
self.increment_cycles_arithmetic(addressing_mode);
|
self.increment_cycles_arithmetic(addressing_mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn cpx(&mut self, bus: &Bus, addressing_mode: AddressingMode) {
|
||||||
|
let is_16bit = self.registers.is_16bit_index();
|
||||||
|
let target = self.registers.x;
|
||||||
|
if is_16bit {
|
||||||
|
let value = self.get_16bit_from_address(bus, addressing_mode);
|
||||||
|
self.do_comp(target, value);
|
||||||
|
} else {
|
||||||
|
let value = self.get_8bit_from_address(bus, addressing_mode);
|
||||||
|
self.do_comp(target as u8, value);
|
||||||
|
}
|
||||||
|
self.increment_cycles_comp_index(addressing_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cpy(&mut self, bus: &Bus, addressing_mode: AddressingMode) {
|
||||||
|
let is_16bit = self.registers.is_16bit_index();
|
||||||
|
let target = self.registers.y;
|
||||||
|
if is_16bit {
|
||||||
|
let value = self.get_16bit_from_address(bus, addressing_mode);
|
||||||
|
self.do_comp(target, value);
|
||||||
|
} else {
|
||||||
|
let value = self.get_8bit_from_address(bus, addressing_mode);
|
||||||
|
self.do_comp(target as u8, value);
|
||||||
|
}
|
||||||
|
self.increment_cycles_comp_index(addressing_mode);
|
||||||
|
}
|
||||||
|
|
||||||
fn and(&mut self, bus: &Bus, addressing_mode: AddressingMode) {
|
fn and(&mut self, bus: &Bus, addressing_mode: AddressingMode) {
|
||||||
// if the M flag is set, perform 8 bit addition.
|
|
||||||
// Otherwise, 16 bit addition
|
|
||||||
let target = self.registers.a;
|
let target = self.registers.a;
|
||||||
if self.registers.is_16bit_mode() {
|
if self.registers.is_16bit_mode() {
|
||||||
let value = self.get_16bit_from_address(bus, addressing_mode);
|
let value = self.get_16bit_from_address(bus, addressing_mode);
|
||||||
@@ -134,8 +148,6 @@ impl CPU {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn asl(&mut self, bus: &Bus, addressing_mode: AddressingMode) {
|
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 {
|
let target = match addressing_mode {
|
||||||
AddressingMode::Accumulator => self.registers.a,
|
AddressingMode::Accumulator => self.registers.a,
|
||||||
_ => match self.registers.is_16bit_mode() {
|
_ => match self.registers.is_16bit_mode() {
|
||||||
@@ -407,6 +419,16 @@ impl CPU {
|
|||||||
0xD7 => self.cmp(bus, A::DirectPageIndirectLongIndexed(I::Y)),
|
0xD7 => self.cmp(bus, A::DirectPageIndirectLongIndexed(I::Y)),
|
||||||
0xC3 => self.cmp(bus, A::StackRelative),
|
0xC3 => self.cmp(bus, A::StackRelative),
|
||||||
0xD3 => self.cmp(bus, A::StackRelativeIndirectIndexed(I::Y)),
|
0xD3 => self.cmp(bus, A::StackRelativeIndirectIndexed(I::Y)),
|
||||||
|
// COP
|
||||||
|
0x02 => unimplemented!("COP instruction not implemented yet"),
|
||||||
|
// CPX
|
||||||
|
0xE0 => self.cpx(bus, A::Immediate),
|
||||||
|
0xEC => self.cpx(bus, A::Absolute),
|
||||||
|
0xE4 => self.cpx(bus, A::DirectPage),
|
||||||
|
// CPY
|
||||||
|
0xC0 => self.cpy(bus, A::Immediate),
|
||||||
|
0xCC => self.cpy(bus, A::Absolute),
|
||||||
|
0xC4 => self.cpy(bus, A::DirectPage),
|
||||||
_ => println!("Invalid opcode: {:02X}", opcode),
|
_ => println!("Invalid opcode: {:02X}", opcode),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -880,6 +902,7 @@ mod cpu_instructions_tests {
|
|||||||
assert!(cpu.registers.get_zero_flag());
|
assert!(cpu.registers.get_zero_flag());
|
||||||
|
|
||||||
// check overflow flag is not affected
|
// check overflow flag is not affected
|
||||||
|
cpu.cycles = 0;
|
||||||
cpu.registers.a = 0x0050;
|
cpu.registers.a = 0x0050;
|
||||||
cpu.registers.pbr = 0x00;
|
cpu.registers.pbr = 0x00;
|
||||||
cpu.registers.pc = 0x0000;
|
cpu.registers.pc = 0x0000;
|
||||||
@@ -889,7 +912,79 @@ mod cpu_instructions_tests {
|
|||||||
cpu.cmp(&bus, AddressingMode::Immediate);
|
cpu.cmp(&bus, AddressingMode::Immediate);
|
||||||
assert_eq!(cpu.registers.a, 0x0050); // check A is not affected
|
assert_eq!(cpu.registers.a, 0x0050); // check A is not affected
|
||||||
assert_eq!(cpu.registers.pc, 0x02);
|
assert_eq!(cpu.registers.pc, 0x02);
|
||||||
assert_eq!(cpu.cycles, 4);
|
assert_eq!(cpu.cycles, 2);
|
||||||
|
assert!(cpu.registers.get_carry_flag());
|
||||||
|
assert!(!cpu.registers.get_zero_flag());
|
||||||
|
assert!(!cpu.registers.get_overflow_flag());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cpx() {
|
||||||
|
// CMP is basically an SBC instruction but it doesn't
|
||||||
|
// store the result nor it affects the overflow flag
|
||||||
|
let mut cpu = CPU::new();
|
||||||
|
let mut bus = Bus::new();
|
||||||
|
cpu.registers.x = 0x01;
|
||||||
|
cpu.registers.pbr = 0x00;
|
||||||
|
cpu.registers.pc = 0x0000;
|
||||||
|
cpu.registers.set_16bit_index(false);
|
||||||
|
bus.write(0x000001, 1);
|
||||||
|
cpu.cpx(&bus, AddressingMode::Immediate);
|
||||||
|
assert_eq!(cpu.registers.x, 0x01); // check A is not affected
|
||||||
|
assert_eq!(cpu.registers.pc, 0x02);
|
||||||
|
assert_eq!(cpu.cycles, 2);
|
||||||
|
assert!(!cpu.registers.get_carry_flag());
|
||||||
|
assert!(cpu.registers.get_zero_flag());
|
||||||
|
|
||||||
|
// check overflow flag is not affected
|
||||||
|
cpu.cycles = 0;
|
||||||
|
cpu.registers.x = 0x50;
|
||||||
|
cpu.registers.pbr = 0x00;
|
||||||
|
cpu.registers.pc = 0x0000;
|
||||||
|
cpu.registers.set_16bit_index(true);
|
||||||
|
cpu.registers.set_overflow_flag(false);
|
||||||
|
bus.write(0x000002, 0xB0);
|
||||||
|
bus.write(0x000001, 0x00);
|
||||||
|
cpu.cpx(&bus, AddressingMode::Immediate);
|
||||||
|
assert_eq!(cpu.registers.x, 0x50); // check X is not affected
|
||||||
|
assert_eq!(cpu.registers.pc, 0x03);
|
||||||
|
assert_eq!(cpu.cycles, 3);
|
||||||
|
assert!(cpu.registers.get_carry_flag());
|
||||||
|
assert!(!cpu.registers.get_zero_flag());
|
||||||
|
assert!(!cpu.registers.get_overflow_flag());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cpy() {
|
||||||
|
// CMP is basically an SBC instruction but it doesn't
|
||||||
|
// store the result nor it affects the overflow flag
|
||||||
|
let mut cpu = CPU::new();
|
||||||
|
let mut bus = Bus::new();
|
||||||
|
cpu.registers.y = 0x01;
|
||||||
|
cpu.registers.pbr = 0x00;
|
||||||
|
cpu.registers.pc = 0x0000;
|
||||||
|
cpu.registers.set_16bit_index(false);
|
||||||
|
bus.write(0x000001, 1);
|
||||||
|
cpu.cpy(&bus, AddressingMode::Immediate);
|
||||||
|
assert_eq!(cpu.registers.y, 0x01); // check A is not affected
|
||||||
|
assert_eq!(cpu.registers.pc, 0x02);
|
||||||
|
assert_eq!(cpu.cycles, 2);
|
||||||
|
assert!(!cpu.registers.get_carry_flag());
|
||||||
|
assert!(cpu.registers.get_zero_flag());
|
||||||
|
|
||||||
|
// check overflow flag is not affected
|
||||||
|
cpu.cycles = 0;
|
||||||
|
cpu.registers.y = 0x50;
|
||||||
|
cpu.registers.pbr = 0x00;
|
||||||
|
cpu.registers.pc = 0x0000;
|
||||||
|
cpu.registers.set_16bit_index(true);
|
||||||
|
cpu.registers.set_overflow_flag(false);
|
||||||
|
bus.write(0x000002, 0xB0);
|
||||||
|
bus.write(0x000001, 0x00);
|
||||||
|
cpu.cpy(&bus, AddressingMode::Immediate);
|
||||||
|
assert_eq!(cpu.registers.y, 0x50); // check X is not affected
|
||||||
|
assert_eq!(cpu.registers.pc, 0x03);
|
||||||
|
assert_eq!(cpu.cycles, 3);
|
||||||
assert!(cpu.registers.get_carry_flag());
|
assert!(cpu.registers.get_carry_flag());
|
||||||
assert!(!cpu.registers.get_zero_flag());
|
assert!(!cpu.registers.get_zero_flag());
|
||||||
assert!(!cpu.registers.get_overflow_flag());
|
assert!(!cpu.registers.get_overflow_flag());
|
||||||
|
|||||||
@@ -118,6 +118,14 @@ impl Registers {
|
|||||||
((self.pbr as u32) << 16) | (self.pc as u32)
|
((self.pbr as u32) << 16) | (self.pc as u32)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_16bit_index(&mut self, val: bool) {
|
||||||
|
self.set_index_register_select_flag(!val);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_16bit_index(&self) -> bool {
|
||||||
|
!self.get_index_register_select_flag()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_16bit_mode(&self) -> bool {
|
pub fn is_16bit_mode(&self) -> bool {
|
||||||
!self.get_memory_select_flag()
|
!self.get_memory_select_flag()
|
||||||
}
|
}
|
||||||
@@ -161,7 +169,7 @@ mod registers_tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_16bit_mode() {
|
fn test_is_16bit_mode() {
|
||||||
let mut registers = Registers::new();
|
let mut registers = Registers::new();
|
||||||
registers.set_memory_select_flag(false);
|
registers.set_memory_select_flag(false);
|
||||||
assert!(registers.is_16bit_mode());
|
assert!(registers.is_16bit_mode());
|
||||||
@@ -178,6 +186,24 @@ mod registers_tests {
|
|||||||
assert!(!registers.is_16bit_mode());
|
assert!(!registers.is_16bit_mode());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_is_16bit_index() {
|
||||||
|
let mut registers = Registers::new();
|
||||||
|
registers.set_index_register_select_flag(false);
|
||||||
|
assert!(registers.is_16bit_index());
|
||||||
|
registers.set_index_register_select_flag(true);
|
||||||
|
assert!(!registers.is_16bit_index());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_set_16bit_index() {
|
||||||
|
let mut registers = Registers::new();
|
||||||
|
registers.set_16bit_index(true);
|
||||||
|
assert!(registers.is_16bit_index());
|
||||||
|
registers.set_16bit_index(false);
|
||||||
|
assert!(!registers.is_16bit_index());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_set_low_a() {
|
fn test_set_low_a() {
|
||||||
let mut registers = Registers::new();
|
let mut registers = Registers::new();
|
||||||
|
|||||||
Reference in New Issue
Block a user