gameboy-rust/crates/gba-core/src/cpu.rs

319 lines
9.5 KiB
Rust

use std::{error::Error, fmt};
use crate::memory::{Memory, MemoryError};
// Semantic tests compare architectural state after successful instructions.
// Error/rollback tests keep full Cpu equality, including buffered fetch results.
#[cfg(test)]
macro_rules! assert_cpu_arch_eq {
($actual:expr, $expected:expr $(, $($message:tt)+)?) => {{
let mut actual = $actual.clone();
let mut expected = $expected.clone();
actual.invalidate_pipeline();
expected.invalidate_pipeline();
assert_eq!(actual, expected $(, $($message)+)?);
}};
}
mod alu;
mod arm;
mod exception;
mod pipeline;
mod status;
mod thumb;
mod timing;
pub use exception::Exception;
pub use status::Mode;
mod transfer;
#[cfg(test)]
mod arm_pipeline_tests;
#[cfg(test)]
mod bios_access_tests;
#[cfg(test)]
mod compare_psr_tests;
#[cfg(test)]
mod dma_resume_tests;
#[cfg(test)]
mod exception_tests;
#[cfg(test)]
mod fetch_boundary_tests;
#[cfg(test)]
mod fetch_history_tests;
#[cfg(test)]
mod instruction_tests;
#[cfg(test)]
mod iwram_dma_tests;
#[cfg(test)]
mod iwram_open_bus_tests;
#[cfg(test)]
mod iwram_refill_tests;
#[cfg(test)]
mod load_alias_tests;
#[cfg(test)]
mod open_bus_tests;
#[cfg(test)]
mod status_tests;
#[cfg(test)]
mod tests;
#[cfg(test)]
mod thumb_empty_tests;
#[cfg(test)]
mod thumb_open_bus_tests;
#[cfg(test)]
mod thumb_pipeline_tests;
#[cfg(test)]
mod thumb_tests;
#[cfg(test)]
mod timing_tests;
#[cfg(test)]
mod transfer_tests;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CpuError {
Memory(MemoryError),
UnsupportedInstruction { address: u32, instruction: u32 },
UnsupportedThumbInstruction { address: u32, instruction: u16 },
}
impl fmt::Display for CpuError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Memory(error) => write!(f, "{error}"),
Self::UnsupportedThumbInstruction {
address,
instruction,
} => {
write!(
f,
"unsupported Thumb instruction {instruction:#06x} at {address:#010x}"
)
}
Self::UnsupportedInstruction {
address,
instruction,
} => {
write!(
f,
"unsupported instruction {instruction:#010x} at {address:#010x}"
)
}
}
}
}
impl Error for CpuError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::Memory(error) => Some(error),
Self::UnsupportedInstruction { .. } | Self::UnsupportedThumbInstruction { .. } => None,
}
}
}
impl From<MemoryError> for CpuError {
fn from(error: MemoryError) -> Self {
Self::Memory(error)
}
}
/// Arithmetic status flags from the current program status register.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct Flags {
pub negative: bool,
pub zero: bool,
/// For subtraction, carry means no unsigned borrow occurred.
pub carry: bool,
pub overflow: bool,
}
impl Flags {
fn condition_passed(self, condition: u32) -> bool {
match condition {
0x0 => self.zero, // EQ
0x1 => !self.zero, // NE
0x2 => self.carry, // CS/HS
0x3 => !self.carry, // CC/LO
0x4 => self.negative, // MI
0x5 => !self.negative, // PL
0x6 => self.overflow, // VS
0x7 => !self.overflow, // VC
0x8 => self.carry && !self.zero, // HI
0x9 => !self.carry || self.zero, // LS
0xa => self.negative == self.overflow, // GE
0xb => self.negative != self.overflow, // LT
0xc => !self.zero && self.negative == self.overflow, // GT
0xd => self.zero || self.negative != self.overflow, // LE
0xe => true, // AL
_ => false, // Reserved condition; rejected by Cpu::step.
}
}
}
/// Instruction encoding selected by the processor's T bit.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InstructionSet {
Arm,
Thumb,
}
impl InstructionSet {
pub(crate) fn width(self) -> u32 {
match self {
Self::Arm => 4,
Self::Thumb => 2,
}
}
fn align(self, address: u32) -> u32 {
address & !(self.width() - 1)
}
}
/// ARM/Thumb interpreter with register banks, instruction buffering, and nominal costs.
/// Clone/equality include buffered fetch results and their instruction-set tag.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Cpu {
registers: [u32; 16],
flags: Flags,
instruction_set: InstructionSet,
mode: Mode,
irq_disabled: bool,
fiq_disabled: bool,
banks: status::Banks,
pipeline: Option<pipeline::Pipeline>,
}
impl Cpu {
/// Start directly at ARM-state test code in System mode, with interrupts enabled.
/// This does not perform a GBA reset.
pub fn new(entry_point: u32) -> Self {
let mut registers = [0; 16];
registers[15] = entry_point;
Self {
registers,
flags: Flags::default(),
instruction_set: InstructionSet::Arm,
mode: Mode::System,
irq_disabled: false,
fiq_disabled: false,
banks: status::Banks::default(),
pipeline: None,
}
}
/// r15 stores the next instruction address, not the pipeline-adjusted value.
pub fn registers(&self) -> &[u32; 16] {
&self.registers
}
pub fn pc(&self) -> u32 {
self.registers[15]
}
pub fn flags(&self) -> Flags {
self.flags
}
pub fn instruction_set(&self) -> InstructionSet {
self.instruction_set
}
/// Execute one ARM instruction or one 16-bit Thumb instruction.
/// Each half of a Thumb BL is a separate step.
/// This CPU-only API does not execute DMA, advance clocks, or sample device interrupts.
/// It records HALTCNT writes but does not enforce HALT. Use Machine::step for integration.
/// Unsupported operations and memory errors leave CPU state unchanged.
/// These errors are development diagnostics, not emulated CPU exceptions.
pub fn step(&mut self, memory: &mut Memory) -> Result<(), CpuError> {
let instruction = self.fetch(memory)?;
self.execute_fetched(instruction, memory)
}
fn execute_fetched(
&mut self,
fetched: pipeline::Fetched,
memory: &mut Memory,
) -> Result<(), CpuError> {
let pipeline::Fetched {
instruction,
continuation,
lookahead,
fill,
} = fetched;
// Record the executing instruction, not an operand's pipelined PC value.
// Always clear the access context, including on diagnostic errors.
let pc = self.pc();
let instruction_set = self.instruction_set;
let refill = self.instruction_refills(instruction);
memory.begin_cpu_access(pc, self.instruction_set, fill.as_ref(), &lookahead);
let result = self.execute_instruction(instruction, memory);
memory.end_cpu_access(result.is_ok());
if result.is_ok() {
if refill {
self.refill_pipeline(memory);
} else {
// A state change without an ordinary refill starts a cold sequence.
self.pipeline = if self.instruction_set == instruction_set
&& self.pc() == pc.wrapping_add(instruction_set.width())
{
Some(continuation)
} else {
None
};
}
}
result
}
fn execute_instruction(
&mut self,
instruction: u32,
memory: &mut Memory,
) -> Result<(), CpuError> {
if self.instruction_set == InstructionSet::Thumb {
return self.execute_thumb(instruction as u16, memory);
}
let condition = instruction >> 28;
// Reserved in ARMv4. Do not decode ARMv5 unconditional extensions.
if condition == 0xf {
return Err(self.unsupported(instruction));
}
if !self.flags.condition_passed(condition) {
self.registers[15] = self.pc().wrapping_add(4);
return Ok(());
}
self.execute_arm(instruction, memory)
}
fn unsupported(&self, instruction: u32) -> CpuError {
match self.instruction_set {
InstructionSet::Arm => CpuError::UnsupportedInstruction {
address: self.pc(),
instruction,
},
InstructionSet::Thumb => CpuError::UnsupportedThumbInstruction {
address: self.pc(),
instruction: instruction as u16,
},
}
}
fn branch_exchange(&mut self, target: u32) {
self.instruction_set = if target & 1 == 0 {
InstructionSet::Arm
} else {
InstructionSet::Thumb
};
self.registers[15] = self.instruction_set.align(target);
}
fn operand_register(&self, register: usize, pc_offset: u32) -> u32 {
if register == 15 {
self.pc().wrapping_add(pc_offset)
} else {
self.registers[register]
}
}
}