refactor: split into gameboy-rust workspace with core, demos, and desktop crates
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>
This commit is contained in:
parent
a8944d1d5f
commit
f8a0676748
113 changed files with 304 additions and 282 deletions
305
src/cpu/arm.rs
305
src/cpu/arm.rs
|
|
@ -1,305 +0,0 @@
|
|||
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)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue