feat(cpu): implement ARM7TDMI ARM and Thumb execution

Add ALU, transfers, banked status, exceptions, instruction timing, and CPU regression tests.
This commit is contained in:
0xrsydn 2026-10-05 12:55:35 +07:00
commit 535c69e09f
17 changed files with 4838 additions and 0 deletions

305
src/cpu/arm.rs Normal file
View file

@ -0,0 +1,305 @@
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)
}
}