Move the emulator into crates/gba-core, the original demo programs into crates/gba-demos, and the minifb frontend into crates/desktop. The binary is now gameboy-rust. Imports change from gba_rust to gba_core/gba_demos; no behavior changes. gba-core has no dependencies. Its integration tests use gba-demos as a development dependency. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
305 lines
12 KiB
Rust
305 lines
12 KiB
Rust
use super::{
|
|
alu::{self, Shift},
|
|
Cpu, CpuError,
|
|
};
|
|
use crate::memory::Memory;
|
|
|
|
impl Cpu {
|
|
pub(super) fn execute_arm(
|
|
&mut self,
|
|
instruction: u32,
|
|
memory: &mut Memory,
|
|
) -> Result<(), CpuError> {
|
|
if instruction & 0x0f00_0000 == 0x0f00_0000 {
|
|
self.enter_exception(super::Exception::SoftwareInterrupt);
|
|
return Ok(());
|
|
}
|
|
if instruction & 0x0fbf_0fff == 0x010f_0000 {
|
|
return self.read_status(instruction);
|
|
}
|
|
if instruction & 0x0fb0_fff0 == 0x0120_f000 || instruction & 0x0fb0_f000 == 0x0320_f000 {
|
|
return self.write_status(instruction);
|
|
}
|
|
if instruction & 0x0fff_fff0 == 0x012f_ff10 {
|
|
let target = self.operand_register((instruction & 15) as usize, 8);
|
|
self.branch_exchange(target);
|
|
return Ok(());
|
|
}
|
|
if instruction & 0x0e00_0000 == 0x0a00_0000 {
|
|
let address = self.pc();
|
|
let offset = (((instruction & 0x00ff_ffff) << 8) as i32 >> 6) as u32;
|
|
if instruction & (1 << 24) != 0 {
|
|
self.registers[14] = address.wrapping_add(4); // BL return address
|
|
}
|
|
self.registers[15] = address.wrapping_add(8).wrapping_add(offset);
|
|
return Ok(());
|
|
}
|
|
if instruction & 0x0e00_0000 == 0x0800_0000 {
|
|
return self.block_transfer(instruction, memory);
|
|
}
|
|
// SWP shares the 1001 low bits with multiply and halfword transfers.
|
|
if instruction & 0x0fb0_0ff0 == 0x0100_0090 {
|
|
return self.swap(instruction, memory);
|
|
}
|
|
if instruction & 0x0f80_00f0 == 0x0080_0090 {
|
|
return self.multiply_long(instruction);
|
|
}
|
|
if instruction & 0x0fc0_00f0 == 0x0000_0090 {
|
|
return self.multiply(instruction);
|
|
}
|
|
if instruction & 0x0e00_0090 == 0x0000_0090 {
|
|
return self.halfword_transfer(instruction, memory);
|
|
}
|
|
if instruction & 0x0c00_0000 == 0x0400_0000 {
|
|
return self.single_transfer(instruction, memory);
|
|
}
|
|
if instruction & 0x0c00_0000 == 0 {
|
|
return self.data_processing(instruction);
|
|
}
|
|
Err(self.unsupported(instruction))
|
|
}
|
|
|
|
fn data_processing(&mut self, instruction: u32) -> Result<(), CpuError> {
|
|
let opcode = (instruction >> 21) & 15;
|
|
let set_flags = instruction & (1 << 20) != 0;
|
|
let test = (8..=11).contains(&opcode);
|
|
let source = ((instruction >> 16) & 15) as usize;
|
|
let destination = ((instruction >> 12) & 15) as usize;
|
|
// S=0 in the test/compare opcode range is PSR transfer or an extension.
|
|
if test && !set_flags {
|
|
return Err(self.unsupported(instruction));
|
|
}
|
|
let restore = if !test && destination == 15 && set_flags {
|
|
Some(self.return_status(instruction)?)
|
|
} else {
|
|
None
|
|
};
|
|
let immediate = instruction & (1 << 25) != 0;
|
|
let register_shift = !immediate && instruction & (1 << 4) != 0;
|
|
let pc_offset = if register_shift { 12 } else { 8 };
|
|
let (operand, carry) = if immediate {
|
|
let rotation = ((instruction >> 8) & 15) * 2;
|
|
let value = (instruction & 255).rotate_right(rotation);
|
|
(
|
|
value,
|
|
if rotation == 0 {
|
|
self.flags.carry
|
|
} else {
|
|
value >> 31 != 0
|
|
},
|
|
)
|
|
} else {
|
|
let amount = if register_shift {
|
|
let shift_register = ((instruction >> 8) & 15) as usize;
|
|
if instruction & (1 << 7) != 0 || shift_register == 15 {
|
|
return Err(self.unsupported(instruction));
|
|
}
|
|
self.registers[shift_register] & 255
|
|
} else {
|
|
(instruction >> 7) & 31
|
|
};
|
|
alu::shift(
|
|
self.operand_register((instruction & 15) as usize, pc_offset),
|
|
Shift::decode(instruction >> 5),
|
|
amount,
|
|
self.flags.carry,
|
|
register_shift,
|
|
)
|
|
};
|
|
let (result, flags) = alu::execute(
|
|
opcode,
|
|
self.operand_register(source, pc_offset),
|
|
operand,
|
|
carry,
|
|
self.flags,
|
|
);
|
|
if let Some((status, mode)) = restore {
|
|
self.apply_status(status, mode);
|
|
} else if set_flags {
|
|
self.flags = flags;
|
|
}
|
|
let next = if !test && destination == 15 {
|
|
self.instruction_set.align(result)
|
|
} else {
|
|
self.pc().wrapping_add(4)
|
|
};
|
|
if !test && destination != 15 {
|
|
self.registers[destination] = result;
|
|
}
|
|
self.registers[15] = next;
|
|
Ok(())
|
|
}
|
|
|
|
fn single_transfer(&mut self, instruction: u32, memory: &mut Memory) -> Result<(), CpuError> {
|
|
let base_register = ((instruction >> 16) & 15) as usize;
|
|
let destination = ((instruction >> 12) & 15) as usize;
|
|
let pre = instruction & (1 << 24) != 0;
|
|
let write_bit = instruction & (1 << 21) != 0;
|
|
let write_back = !pre || write_bit;
|
|
let byte = instruction & (1 << 22) != 0;
|
|
let load = instruction & (1 << 20) != 0;
|
|
if (!pre && write_bit) // LDRT/STRT access semantics are not implemented.
|
|
|| (write_back && (base_register == 15 || (load && base_register == destination)))
|
|
|| (byte && destination == 15)
|
|
{
|
|
return Err(self.unsupported(instruction));
|
|
}
|
|
let offset = if instruction & (1 << 25) != 0 {
|
|
let rm = (instruction & 15) as usize;
|
|
if instruction & (1 << 4) != 0 || rm == 15 {
|
|
return Err(self.unsupported(instruction));
|
|
}
|
|
alu::shift(
|
|
self.registers[rm],
|
|
Shift::decode(instruction >> 5),
|
|
(instruction >> 7) & 31,
|
|
self.flags.carry,
|
|
false,
|
|
)
|
|
.0
|
|
} else {
|
|
instruction & 0xfff
|
|
};
|
|
let base = self.operand_register(base_register, 8);
|
|
let adjusted = offset_address(base, offset, instruction);
|
|
let address = if pre { adjusted } else { base };
|
|
let next = self.pc().wrapping_add(4);
|
|
// All validation precedes memory writes. CPU changes follow successful access.
|
|
let value = if load {
|
|
if byte {
|
|
u32::from(memory.read8(address)?)
|
|
} else {
|
|
memory.read32(address & !3)?.rotate_right((address & 3) * 8)
|
|
}
|
|
} else {
|
|
let value = self.operand_register(destination, 12);
|
|
if byte {
|
|
memory.write8(address, value as u8)?;
|
|
} else {
|
|
memory.write32(address & !3, value)?;
|
|
}
|
|
value
|
|
};
|
|
if write_back {
|
|
self.registers[base_register] = adjusted;
|
|
}
|
|
if load && destination != 15 {
|
|
self.registers[destination] = value;
|
|
}
|
|
// ARMv4 LDR PC does not switch to Thumb, even if bit zero is set.
|
|
self.registers[15] = if load && destination == 15 {
|
|
value & !3
|
|
} else {
|
|
next
|
|
};
|
|
Ok(())
|
|
}
|
|
|
|
fn halfword_transfer(&mut self, instruction: u32, memory: &mut Memory) -> Result<(), CpuError> {
|
|
let kind = (instruction >> 5) & 3;
|
|
let load = instruction & (1 << 20) != 0;
|
|
let pre = instruction & (1 << 24) != 0;
|
|
let write_bit = instruction & (1 << 21) != 0;
|
|
let write_back = !pre || write_bit;
|
|
let base_register = ((instruction >> 16) & 15) as usize;
|
|
let destination = ((instruction >> 12) & 15) as usize;
|
|
if kind == 0
|
|
|| (!load && kind != 1)
|
|
|| (!pre && write_bit)
|
|
|| destination == 15
|
|
|| (write_back && (base_register == 15 || (load && base_register == destination)))
|
|
{
|
|
return Err(self.unsupported(instruction));
|
|
}
|
|
let offset = if instruction & (1 << 22) != 0 {
|
|
((instruction >> 4) & 0xf0) | (instruction & 15)
|
|
} else {
|
|
let rm = (instruction & 15) as usize;
|
|
if instruction & 0xf00 != 0 || rm == 15 {
|
|
return Err(self.unsupported(instruction));
|
|
}
|
|
self.registers[rm]
|
|
};
|
|
let base = self.operand_register(base_register, 8);
|
|
let adjusted = offset_address(base, offset, instruction);
|
|
let address = if pre { adjusted } else { base };
|
|
let value = if load {
|
|
match kind {
|
|
1 => u32::from(memory.read16(address & !1)?).rotate_right((address & 1) * 8),
|
|
2 => memory.read8(address)? as i8 as i32 as u32,
|
|
_ if address & 1 != 0 => memory.read8(address)? as i8 as i32 as u32,
|
|
_ => memory.read16(address)? as i16 as i32 as u32,
|
|
}
|
|
} else {
|
|
memory.write16(address & !1, self.registers[destination] as u16)?;
|
|
0
|
|
};
|
|
if write_back {
|
|
self.registers[base_register] = adjusted;
|
|
}
|
|
if load {
|
|
self.registers[destination] = value;
|
|
}
|
|
self.registers[15] = self.pc().wrapping_add(4);
|
|
Ok(())
|
|
}
|
|
|
|
fn multiply(&mut self, instruction: u32) -> Result<(), CpuError> {
|
|
let rd = ((instruction >> 16) & 15) as usize;
|
|
let rn = ((instruction >> 12) & 15) as usize;
|
|
let rs = ((instruction >> 8) & 15) as usize;
|
|
let rm = (instruction & 15) as usize;
|
|
let accumulate = instruction & (1 << 21) != 0;
|
|
if [rd, rn, rs, rm].contains(&15) || rd == rm || (!accumulate && rn != 0) {
|
|
return Err(self.unsupported(instruction));
|
|
}
|
|
let mut result = self.registers[rm].wrapping_mul(self.registers[rs]);
|
|
if accumulate {
|
|
result = result.wrapping_add(self.registers[rn]);
|
|
}
|
|
self.registers[rd] = result;
|
|
if instruction & (1 << 20) != 0 {
|
|
self.flags.negative = result >> 31 != 0;
|
|
self.flags.zero = result == 0;
|
|
// ARM7 carry is unspecified after multiply; preserve it deterministically.
|
|
}
|
|
self.registers[15] = self.pc().wrapping_add(4);
|
|
Ok(())
|
|
}
|
|
|
|
fn multiply_long(&mut self, instruction: u32) -> Result<(), CpuError> {
|
|
let hi = ((instruction >> 16) & 15) as usize;
|
|
let lo = ((instruction >> 12) & 15) as usize;
|
|
let rs = ((instruction >> 8) & 15) as usize;
|
|
let rm = (instruction & 15) as usize;
|
|
if [hi, lo, rs, rm].contains(&15) || hi == lo || hi == rm || lo == rm {
|
|
return Err(self.unsupported(instruction));
|
|
}
|
|
let mut result = if instruction & (1 << 22) != 0 {
|
|
(i64::from(self.registers[rm] as i32) * i64::from(self.registers[rs] as i32)) as u64
|
|
} else {
|
|
u64::from(self.registers[rm]) * u64::from(self.registers[rs])
|
|
};
|
|
if instruction & (1 << 21) != 0 {
|
|
let accumulator = (u64::from(self.registers[hi]) << 32) | u64::from(self.registers[lo]);
|
|
result = result.wrapping_add(accumulator);
|
|
}
|
|
self.registers[hi] = (result >> 32) as u32;
|
|
self.registers[lo] = result as u32;
|
|
if instruction & (1 << 20) != 0 {
|
|
self.flags.negative = result >> 63 != 0;
|
|
self.flags.zero = result == 0;
|
|
// Unspecified multiply C/V outputs are preserved, not hardware-verified.
|
|
}
|
|
self.registers[15] = self.pc().wrapping_add(4);
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
fn offset_address(base: u32, offset: u32, instruction: u32) -> u32 {
|
|
if instruction & (1 << 23) != 0 {
|
|
base.wrapping_add(offset)
|
|
} else {
|
|
base.wrapping_sub(offset)
|
|
}
|
|
}
|