From 323501405a81b9603004263cda912085de6aef2f Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Wed, 19 Jul 2023 09:32:47 +0800 Subject: [PATCH 1/6] Add GameBoy Advance --- src/gba/mod.rs | 3106 +++++++++++++++++++++++++++++++ src/io/applicationstate.rs | 76 + src/io/constants.rs | 3 + src/io/deferred_renderer_gba.rs | 64 + src/io/dr_sdl2.rs | 73 + src/io/graphics/renderer.rs | 7 +- src/io/mod.rs | 1 + src/main.rs | 31 +- 8 files changed, 3356 insertions(+), 5 deletions(-) create mode 100644 src/gba/mod.rs create mode 100644 src/io/deferred_renderer_gba.rs diff --git a/src/gba/mod.rs b/src/gba/mod.rs new file mode 100644 index 0000000..8c1fa7e --- /dev/null +++ b/src/gba/mod.rs @@ -0,0 +1,3106 @@ +//! Experimental GBA support + +pub struct GameboyAdvance { + r: Registers, + // TODO: move this later + entire_rom: Vec, + bios: [u8; 0x4000], + iw_ram: [u8; 0x8000], + wram: [u8; 0x40000], + io_registers: IoRegisters, + pub obj_palette_ram: [u8; 0x400], + pub vram: [u8; 0x18000], + pub oam: [u8; 0x400], +} + +#[derive(Debug, Clone, Copy)] +pub struct PpuBgControl { + /// 2 bit value + pub priority: u8, + /// 2 bit value, in units of 16kb + pub character_base_block: u8, + pub mosaic: bool, + /// false = 16/16, true = 256/1 + pub color_mode: bool, + /// 5 bit value in units of 2kb + pub screen_base_block: u8, + /// only used in BG2 and Bg3 + pub display_area_overflow: bool, + /* + Internal Screen Size (dots) and size of BG Map (bytes): + + Value Text Mode Rotation/Scaling Mode + 0 256x256 (2K) 128x128 (256 bytes) + 1 512x256 (4K) 256x256 (1K) + 2 256x512 (4K) 512x512 (4K) + 3 512x512 (8K) 1024x1024 (16K) + + */ + /// 2 bit value + pub screen_size: u8, +} + +impl PpuBgControl { + pub fn from_bits(bits: u16) -> Self { + Self { + priority: (bits & 0b11) as u8, + character_base_block: ((bits >> 2) & 0b11) as u8, + mosaic: (bits & 0x40) != 0, + color_mode: (bits & 0x80) != 0, + screen_base_block: ((bits >> 8) & 0b11111) as u8, + display_area_overflow: (bits & 0x2000) != 0, + screen_size: ((bits >> 14) & 0b11) as u8, + } + } +} + +#[derive(Debug, Clone, Copy)] +pub enum DmaAddrControl { + Increment, + Decrement, + Fixed, + IncrementReload, +} + +impl DmaAddrControl { + pub fn from_bits(v: u8) -> Self { + match v { + 0 => Self::Increment, + 1 => Self::Decrement, + 2 => Self::Fixed, + 3 => Self::IncrementReload, + _ => unreachable!(), + } + } +} + +#[derive(Debug, Clone, Copy)] +pub enum DmaStartTiming { + Immediately, + VBlank, + HBlank, + Special, +} + +impl DmaStartTiming { + pub fn from_bits(v: u8) -> Self { + match v { + 0 => Self::Immediately, + 1 => Self::VBlank, + 2 => Self::HBlank, + 3 => Self::Special, + _ => unreachable!(), + } + } +} + +#[derive(Debug, Clone, Copy)] +pub struct DmaControl { + pub dest_addr_control: DmaAddrControl, + pub source_addr_control: DmaAddrControl, + pub repeat: bool, + /// false = 16bit, true = 32bit + pub transfer_type: bool, + /// DMA 3 only + pub game_pak_drq: bool, + pub start_timing: DmaStartTiming, + pub irq_at_end: bool, + pub enabled: bool, +} + +impl DmaControl { + pub fn from_bits(bits: u16) -> Self { + Self { + dest_addr_control: DmaAddrControl::from_bits(((bits >> 5) & 0b11) as u8), + source_addr_control: DmaAddrControl::from_bits(((bits >> 7) & 0b11) as u8), + repeat: (bits >> 9) & 1 == 1, + transfer_type: (bits >> 10) & 1 == 1, + game_pak_drq: (bits >> 11) & 1 == 1, + start_timing: DmaStartTiming::from_bits(((bits >> 12) & 0b11) as u8), + irq_at_end: (bits >> 14) & 1 == 1, + enabled: (bits >> 15) & 1 == 1, + } + } +} + +pub struct IoRegisters { + io_registers: [u8; 0x400], + dma0_enabled: bool, + dma1_enabled: bool, + dma2_enabled: bool, + dma3_enabled: bool, +} + +impl IoRegisters { + pub fn new() -> Self { + IoRegisters { + io_registers: [0; 0x400], + dma0_enabled: false, + dma1_enabled: false, + dma2_enabled: false, + dma3_enabled: false, + } + } + + pub fn dma_waiting(&self) -> bool { + self.dma0_enabled || self.dma1_enabled || self.dma2_enabled || self.dma3_enabled + } + + pub fn dma0(&self) -> DmaControl { + DmaControl::from_bits( + self.io_registers[0xBA] as u16 | ((self.io_registers[0xBB] as u16) << 8), + ) + } + pub fn dma1(&self) -> DmaControl { + DmaControl::from_bits( + self.io_registers[0xC6] as u16 | ((self.io_registers[0xC7] as u16) << 8), + ) + } + pub fn dma2(&self) -> DmaControl { + DmaControl::from_bits( + self.io_registers[0xD2] as u16 | ((self.io_registers[0xD3] as u16) << 8), + ) + } + pub fn dma3(&self) -> DmaControl { + DmaControl::from_bits( + self.io_registers[0xDE] as u16 | ((self.io_registers[0xDF] as u16) << 8), + ) + } + pub fn dma0_source_addr(&self) -> u32 { + self.io_registers[0xB0] as u32 + | ((self.io_registers[0xB1] as u32) << 8) + | ((self.io_registers[0xB2] as u32) << 16) + | ((self.io_registers[0xB3] as u32) << 24) + } + pub fn dma0_dest_addr(&self) -> u32 { + self.io_registers[0xB4] as u32 + | ((self.io_registers[0xB5] as u32) << 8) + | ((self.io_registers[0xB6] as u32) << 16) + | ((self.io_registers[0xB7] as u32) << 24) + } + pub fn dma0_word_count(&self) -> u16 { + self.io_registers[0xB8] as u16 | ((self.io_registers[0xB9] as u16) << 8) + } + pub fn dma1_source_addr(&self) -> u32 { + self.io_registers[0xBC] as u32 + | ((self.io_registers[0xBD] as u32) << 8) + | ((self.io_registers[0xBE] as u32) << 16) + | ((self.io_registers[0xBF] as u32) << 24) + } + pub fn dma1_dest_addr(&self) -> u32 { + self.io_registers[0xC2] as u32 + | ((self.io_registers[0xC3] as u32) << 8) + | ((self.io_registers[0xC4] as u32) << 16) + | ((self.io_registers[0xC5] as u32) << 24) + } + pub fn dma1_word_count(&self) -> u16 { + self.io_registers[0xC4] as u16 | ((self.io_registers[0xC5] as u16) << 8) + } + pub fn dma2_source_addr(&self) -> u32 { + self.io_registers[0xC8] as u32 + | ((self.io_registers[0xC9] as u32) << 8) + | ((self.io_registers[0xCA] as u32) << 16) + | ((self.io_registers[0xCB] as u32) << 24) + } + pub fn dma2_dest_addr(&self) -> u32 { + self.io_registers[0xCC] as u32 + | ((self.io_registers[0xCD] as u32) << 8) + | ((self.io_registers[0xCE] as u32) << 16) + | ((self.io_registers[0xCF] as u32) << 24) + } + pub fn dma2_word_count(&self) -> u16 { + self.io_registers[0xD0] as u16 | ((self.io_registers[0xD1] as u16) << 8) + } + pub fn dma3_source_addr(&self) -> u32 { + self.io_registers[0xD4] as u32 + | ((self.io_registers[0xD5] as u32) << 8) + | ((self.io_registers[0xD6] as u32) << 16) + | ((self.io_registers[0xD7] as u32) << 24) + } + pub fn dma3_dest_addr(&self) -> u32 { + self.io_registers[0xD8] as u32 + | ((self.io_registers[0xD9] as u32) << 8) + | ((self.io_registers[0xDA] as u32) << 16) + | ((self.io_registers[0xDB] as u32) << 24) + } + pub fn dma3_word_count(&self) -> u16 { + self.io_registers[0xDC] as u16 | ((self.io_registers[0xDD] as u16) << 8) + } + pub fn disable_dma0(&mut self) { + self.io_registers[0xBB] &= !0x80; + } + pub fn disable_dma1(&mut self) { + self.io_registers[0xC7] &= !0x80; + } + pub fn disable_dma2(&mut self) { + self.io_registers[0xD3] &= !0x80; + } + pub fn disable_dma3(&mut self) { + self.io_registers[0xDF] &= !0x80; + } + + pub fn set_mem8(&mut self, addr: u32, val: u8) { + debug_assert!((0x4000000..=0x4FFFFFF).contains(&addr)); + let addr = addr & 0x3FF; + match addr { + // IF: interrupt request flags + // writes to this register clear set bits to aknowledge interrupts + 0x200..=0x203 => { + self.io_registers[addr as usize] &= !val; + } + 0x0B0..=0x0E0 => { + match addr { + 0xBB if val & 0x80 != 0 => { + // DMA 0 start + self.dma0_enabled = true; + } + 0xC7 if val & 0x80 != 0 => { + // DMA 1 start + self.dma1_enabled = true; + } + 0xD3 if val & 0x80 != 0 => { + // DMA 2 start + self.dma2_enabled = true; + } + 0xDF if val & 0x80 != 0 => { + // DMA3 start + self.dma3_enabled = true; + } + _ => (), + } + //println!("DMA: {:X} = {:X}", addr, val); + self.io_registers[addr as usize] = val; + } + 0x100..=0x110 => { + println!("TIMER: {:X} = {:X}", addr, val); + self.io_registers[addr as usize] = val; + } + // TODO: 4000204h - WAITCNT - Waitstate Control (R/W) + _ => { + self.io_registers[addr as usize] = val; + } + } + } + pub fn set_mem16(&mut self, addr: u32, val: u16) { + debug_assert!((0x4000000..=0x4FFFFFF).contains(&addr)); + let lo_addr = addr & !1; + let hi_addr = addr | 1; + + let lo_byte = val as u8; + let hi_byte = (val >> 8) as u8; + self.set_mem8(lo_addr, lo_byte); + self.set_mem8(hi_addr, hi_byte); + } + pub fn set_mem32(&mut self, addr: u32, val: u32) { + debug_assert!((0x4000000..=0x4FFFFFF).contains(&addr)); + // REVIEW: make sure this is correct + let lo_addr = addr & !3; + let hi_addr = addr | 2; + + let lo_half_word = val as u16; + let hi_half_word = (val >> 16) as u16; + self.set_mem16(lo_addr, lo_half_word); + self.set_mem16(hi_addr, hi_half_word); + } +} + +impl std::ops::Index for IoRegisters { + type Output = u8; + + fn index(&self, index: usize) -> &Self::Output { + &self.io_registers[index] + } +} + +impl std::ops::IndexMut for IoRegisters { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + &mut self.io_registers[index] + } +} + +pub struct Registers { + pub r0: u32, + pub r1: u32, + pub r2: u32, + pub r3: u32, + pub r4: u32, + pub r5: u32, + pub r6: u32, + pub r7: u32, + pub r8: u32, + pub r9: u32, + pub r10: u32, + pub r11: u32, + pub r12: u32, + /// r13 + pub sp: u32, + /// r14 + pub lr: u32, + /// r15 + pub pc: u32, + pub r8_fiq: u32, + pub r9_fiq: u32, + pub r10_fiq: u32, + pub r11_fiq: u32, + pub r12_fiq: u32, + /// fiq stack pointer + pub r13_fiq: u32, + pub r14_fiq: u32, + /// svc stack pointer + pub r13_svc: u32, + pub r14_svc: u32, + /// abt stack pointer + pub r13_abt: u32, + pub r14_abt: u32, + /// irq stack pointer + pub r13_irq: u32, + pub r14_irq: u32, + /// und stack pointer + pub r13_und: u32, + pub r14_und: u32, + + /// Flags, etc + pub cpsr: u32, + + pub spsr_fiq: u32, + pub spsr_svc: u32, + pub spsr_abt: u32, + pub spsr_irq: u32, + pub spsr_und: u32, +} + +impl std::ops::Index for Registers { + type Output = u32; + + fn index(&self, index: u8) -> &Self::Output { + // TODO: is user or undefined a better default? + let mode = self.register_mode().unwrap_or(RegisterMode::Undefined); + match index { + 0 => &self.r0, + 1 => &self.r1, + 2 => &self.r2, + 3 => &self.r3, + 4 => &self.r4, + 5 => &self.r5, + 6 => &self.r6, + 7 => &self.r7, + 8 => { + if mode == RegisterMode::FIQ { + &self.r8_fiq + } else { + &self.r8 + } + } + 9 => { + if mode == RegisterMode::FIQ { + &self.r9_fiq + } else { + &self.r9 + } + } + 10 => { + if mode == RegisterMode::FIQ { + &self.r10_fiq + } else { + &self.r10 + } + } + 11 => { + if mode == RegisterMode::FIQ { + &self.r11_fiq + } else { + &self.r11 + } + } + 12 => { + if mode == RegisterMode::FIQ { + &self.r12_fiq + } else { + &self.r12 + } + } + 13 => match mode { + RegisterMode::FIQ => &self.r13_fiq, + RegisterMode::Supervisor => &self.r13_svc, + RegisterMode::Abort => &self.r13_abt, + RegisterMode::IRQ => &self.r13_irq, + RegisterMode::Undefined => &self.r13_und, + RegisterMode::User => &self.sp, + }, + 14 => match mode { + RegisterMode::FIQ => &self.r14_fiq, + RegisterMode::Supervisor => &self.r14_svc, + RegisterMode::Abort => &self.r14_abt, + RegisterMode::IRQ => &self.r14_irq, + RegisterMode::Undefined => &self.r14_und, + RegisterMode::User => &self.lr, + }, + 15 => &self.pc, + _ => unimplemented!("invalid register read"), + } + } +} + +impl std::ops::IndexMut for Registers { + fn index_mut(&mut self, index: u8) -> &mut Self::Output { + // TODO: is user or undefined a better default? + let mode = self.register_mode().unwrap_or(RegisterMode::Undefined); + match index { + 0 => &mut self.r0, + 1 => &mut self.r1, + 2 => &mut self.r2, + 3 => &mut self.r3, + 4 => &mut self.r4, + 5 => &mut self.r5, + 6 => &mut self.r6, + 7 => &mut self.r7, + 8 => { + if mode == RegisterMode::FIQ { + &mut self.r8_fiq + } else { + &mut self.r8 + } + } + 9 => { + if mode == RegisterMode::FIQ { + &mut self.r9_fiq + } else { + &mut self.r9 + } + } + 10 => { + if mode == RegisterMode::FIQ { + &mut self.r10_fiq + } else { + &mut self.r10 + } + } + 11 => { + if mode == RegisterMode::FIQ { + &mut self.r11_fiq + } else { + &mut self.r11 + } + } + 12 => { + if mode == RegisterMode::FIQ { + &mut self.r12_fiq + } else { + &mut self.r12 + } + } + 13 => match mode { + RegisterMode::FIQ => &mut self.r13_fiq, + RegisterMode::Supervisor => &mut self.r13_svc, + RegisterMode::Abort => &mut self.r13_abt, + RegisterMode::IRQ => &mut self.r13_irq, + RegisterMode::Undefined => &mut self.r13_und, + RegisterMode::User => &mut self.sp, + }, + 14 => match mode { + RegisterMode::FIQ => &mut self.r14_fiq, + RegisterMode::Supervisor => &mut self.r14_svc, + RegisterMode::Abort => &mut self.r14_abt, + RegisterMode::IRQ => &mut self.r14_irq, + RegisterMode::Undefined => &mut self.r14_und, + RegisterMode::User => &mut self.lr, + }, + 15 => &mut self.pc, + _ => unimplemented!("invalid register write"), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Mode { + User, + FIQ, + IRQ, + Supervisor, +} + +impl Mode { + pub fn from_u8(val: u8) -> Self { + match val { + 0b00 => Mode::User, + 0b01 => Mode::FIQ, + 0b10 => Mode::IRQ, + 0b11 => Mode::Supervisor, + _ => unimplemented!(), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum RegisterMode { + User, + FIQ, + IRQ, + Supervisor, + Abort, + Undefined, +} + +impl RegisterMode { + pub fn from_u8(val: u8) -> Option { + match val { + // non-privileged user + 0b10000 => Some(Self::User), + 0b10001 => Some(Self::FIQ), + 0b10010 => Some(Self::IRQ), + 0b10011 => Some(Self::Supervisor), + 0b10111 => Some(Self::Abort), + 0b11011 => Some(Self::Undefined), + // privileged user + 0b11111 => Some(Self::User), + _ => None, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Cond { + Eq, + Ne, + CsHs, + CcLo, + Mi, + Pl, + Vs, + Vc, + Hi, + Ls, + Ge, + Lt, + Gt, + Le, + Al, + Nv, +} + +impl Cond { + fn from_u8(val: u8) -> Self { + match val { + 0b0000 => Cond::Eq, + 0b0001 => Cond::Ne, + 0b0010 => Cond::CsHs, + 0b0011 => Cond::CcLo, + 0b0100 => Cond::Mi, + 0b0101 => Cond::Pl, + 0b0110 => Cond::Vs, + 0b0111 => Cond::Vc, + 0b1000 => Cond::Hi, + 0b1001 => Cond::Ls, + 0b1010 => Cond::Ge, + 0b1011 => Cond::Lt, + 0b1100 => Cond::Gt, + 0b1101 => Cond::Le, + 0b1110 => Cond::Al, + 0b1111 => Cond::Nv, + _ => unreachable!(), + } + } +} + +impl Registers { + pub fn new() -> Self { + Self { + r0: 0, + r1: 0, + r2: 0, + r3: 0, + r4: 0, + r5: 0, + r6: 0, + r7: 0, + r8: 0, + r9: 0, + r10: 0, + r11: 0, + r12: 0, + sp: 0, + lr: 0, + pc: 0, //0x08000000, + r8_fiq: 0, + r9_fiq: 0, + r10_fiq: 0, + r11_fiq: 0, + r12_fiq: 0, + r13_fiq: 0, + r14_fiq: 0, + r13_svc: 0, + r14_svc: 0, + r13_abt: 0, + r14_abt: 0, + r13_irq: 0, + r14_irq: 0, + r13_und: 0, + r14_und: 0, + // Disable IRQ, FIQ, and set supervisor mode + cpsr: 0b11010011, + spsr_fiq: 0, + spsr_svc: 0, + spsr_abt: 0, + spsr_irq: 0, + spsr_und: 0, + } + } + + pub fn cpsr_sign_flag(&self) -> bool { + (self.cpsr >> 31) & 1 != 0 + } + pub fn cpsr_set_sign_flag(&mut self, v: bool) { + self.cpsr &= !(1 << 31); + self.cpsr |= (v as u32) << 31; + } + pub fn cpsr_zero_flag(&self) -> bool { + (self.cpsr >> 30) & 1 != 0 + } + pub fn cpsr_set_zero_flag(&mut self, v: bool) { + self.cpsr &= !(1 << 30); + self.cpsr |= (v as u32) << 30; + } + pub fn cpsr_carry_flag(&self) -> bool { + (self.cpsr >> 29) & 1 != 0 + } + pub fn cpsr_set_carry_flag(&mut self, v: bool) { + self.cpsr &= !(1 << 29); + self.cpsr |= (v as u32) << 29; + } + pub fn cpsr_overflow_flag(&self) -> bool { + (self.cpsr >> 28) & 1 != 0 + } + pub fn cpsr_set_overflow_flag(&mut self, v: bool) { + self.cpsr &= !(1 << 28); + self.cpsr |= (v as u32) << 28; + } + pub fn irq_disabled(&self) -> bool { + (self.cpsr >> 7) & 1 != 0 + } + pub fn fiq_disabled(&self) -> bool { + (self.cpsr >> 6) & 1 != 0 + } + pub fn thumb_enabled(&self) -> bool { + (self.cpsr >> 5) & 1 != 0 + } + pub fn set_thumb(&mut self, enabled: bool) { + self.cpsr &= !(1 << 5); + self.cpsr |= (enabled as u32) << 5; + } + pub fn cpsr_disable_irq(&mut self) { + self.cpsr |= 1 << 7; + } + // bit 27 is sticky overflow, not relevant on GBA CPU I think + pub fn mode_bits(&self) -> u8 { + (self.cpsr & 0x1F) as u8 + } + pub fn register_mode(&self) -> Option { + RegisterMode::from_u8(self.mode_bits()) + } + // TODO: review this later, all details fuzzy + pub fn get_spsr(&self) -> u32 { + let mode = self.register_mode().unwrap(); + match mode { + RegisterMode::User => unimplemented!("Is this possible?"), + RegisterMode::FIQ => self.spsr_fiq, + RegisterMode::Supervisor => self.spsr_svc, + RegisterMode::Abort => self.spsr_abt, + RegisterMode::IRQ => self.spsr_irq, + RegisterMode::Undefined => self.spsr_und, + } + } + pub fn get_spsr_mut(&mut self) -> &mut u32 { + let mode = self.register_mode().unwrap(); + match mode { + RegisterMode::User => unimplemented!("Is this possible?"), + RegisterMode::FIQ => &mut self.spsr_fiq, + RegisterMode::Supervisor => &mut self.spsr_svc, + RegisterMode::Abort => &mut self.spsr_abt, + RegisterMode::IRQ => &mut self.spsr_irq, + RegisterMode::Undefined => &mut self.spsr_und, + } + } + pub fn set_svc_mode(&mut self) { + self.cpsr &= !0x1F; + self.cpsr |= 0b10011; + } + pub fn set_irq_mode(&mut self) { + self.cpsr &= !0x1F; + self.cpsr |= 0b10010; + } + pub fn lr(&self) -> u32 { + let mode = self.register_mode().unwrap(); + match mode { + RegisterMode::User => self.lr, + RegisterMode::FIQ => self.r14_fiq, + RegisterMode::Supervisor => self.r14_svc, + RegisterMode::Abort => self.r14_abt, + RegisterMode::IRQ => self.r14_irq, + RegisterMode::Undefined => self.r14_und, + } + } + pub fn lr_mut(&mut self) -> &mut u32 { + let mode = self.register_mode().unwrap(); + match mode { + RegisterMode::User => &mut self.lr, + RegisterMode::FIQ => &mut self.r14_fiq, + RegisterMode::Supervisor => &mut self.r14_svc, + RegisterMode::Abort => &mut self.r14_abt, + RegisterMode::IRQ => &mut self.r14_irq, + RegisterMode::Undefined => &mut self.r14_und, + } + } + pub fn sp(&self) -> u32 { + let mode = self.register_mode().unwrap(); + match mode { + RegisterMode::User => self.sp, + RegisterMode::FIQ => self.r13_fiq, + RegisterMode::Supervisor => self.r13_svc, + RegisterMode::Abort => self.r13_abt, + RegisterMode::IRQ => self.r13_irq, + RegisterMode::Undefined => self.r13_und, + } + } + pub fn sp_mut(&mut self) -> &mut u32 { + let mode = self.register_mode().unwrap(); + match mode { + RegisterMode::User => &mut self.sp, + RegisterMode::FIQ => &mut self.r13_fiq, + RegisterMode::Supervisor => &mut self.r13_svc, + RegisterMode::Abort => &mut self.r13_abt, + RegisterMode::IRQ => &mut self.r13_irq, + RegisterMode::Undefined => &mut self.r13_und, + } + } + pub fn split_pc(&self) -> (u8, (bool, bool), u32, Mode) { + let mode = Mode::from_u8((self.pc & 0x3) as u8); + let pc = (self.pc >> 2) & 0xFF_FFFF; + // TODO: proper type + let flags = (self.pc >> 28) & 0xF; + let irq_disabled = (self.pc >> 27) & 1 == 1; + let fiq_disabled = (self.pc >> 26) & 1 == 1; + + (flags as u8, (irq_disabled, fiq_disabled), pc, mode) + } +} + +impl GameboyAdvance { + pub fn new() -> GameboyAdvance { + GameboyAdvance { + r: Registers::new(), + entire_rom: vec![], + bios: [0u8; 0x4000], + iw_ram: [0u8; 0x8000], + wram: [0u8; 0x40000], + io_registers: IoRegisters::new(), + obj_palette_ram: [0u8; 0x400], + vram: [0u8; 0x18000], + oam: [0u8; 0x400], + } + } + + /// Very useful link: + /// https://problemkaputt.de/gbatek.htm#gbacartridgeheader + pub fn load_rom(&mut self, bytes: Vec) { + let mut title = String::new(); + bytes[0xA0..(0xA0 + 12)].iter().for_each(|b| { + if *b != 0 { + title.push(*b as char); + } + }); + + let mut game_code = String::new(); + bytes[0xAC..(0xAC + 4)].iter().for_each(|b| { + if *b != 0 { + game_code.push(*b as char); + } + }); + + info!("Found GBA ROM: `{}` ({})", title, game_code); + + let fixed_value = bytes[0xB2]; + if fixed_value != 0x96 { + error!("GBA ROM Header 0xB2 must be 0x96, found 0x{:X}. This may not be a GBA ROM or it may be corrupt", fixed_value); + } + + let main_unit_code = bytes[0xB3]; + if main_unit_code != 0 { + warn!("Non-zero main unit code: {} found!", main_unit_code); + } + + // TODO: 0xBD is the complement check, check header for validity + let first_opcode = bytes[0]; + self.entire_rom = bytes; + } + + pub fn load_bios(&mut self, data: &[u8]) { + self.bios.copy_from_slice(data); + } + + pub fn cond_should_execute(&self, cond: Cond) -> bool { + match cond { + Cond::Eq => self.r.cpsr_zero_flag(), + Cond::Ne => !self.r.cpsr_zero_flag(), + Cond::CsHs => self.r.cpsr_carry_flag(), + Cond::CcLo => !self.r.cpsr_carry_flag(), + Cond::Mi => self.r.cpsr_sign_flag(), + Cond::Pl => !self.r.cpsr_sign_flag(), + Cond::Vs => self.r.cpsr_overflow_flag(), + Cond::Vc => !self.r.cpsr_overflow_flag(), + Cond::Hi => self.r.cpsr_carry_flag() && !self.r.cpsr_zero_flag(), + Cond::Ls => !self.r.cpsr_carry_flag() || self.r.cpsr_zero_flag(), + Cond::Ge => self.r.cpsr_sign_flag() == self.r.cpsr_overflow_flag(), + Cond::Lt => self.r.cpsr_sign_flag() != self.r.cpsr_overflow_flag(), + Cond::Gt => { + !self.r.cpsr_zero_flag() && (self.r.cpsr_sign_flag() == self.r.cpsr_overflow_flag()) + } + Cond::Le => { + self.r.cpsr_zero_flag() || (self.r.cpsr_sign_flag() != self.r.cpsr_overflow_flag()) + } + Cond::Al => true, + Cond::Nv => false, + } + } + + pub fn ppu_bg0_x_scroll(&self) -> u16 { + ((self.io_registers[0x10] as u16) << 8) | (self.io_registers[0x11] as u16) & 0x1F + } + pub fn ppu_bg0_y_scroll(&self) -> u16 { + ((self.io_registers[0x12] as u16) << 8) | (self.io_registers[0x13] as u16) & 0x1F + } + pub fn ppu_bg1_x_scroll(&self) -> u16 { + ((self.io_registers[0x14] as u16) << 8) | (self.io_registers[0x15] as u16) & 0x1F + } + pub fn ppu_bg1_y_scroll(&self) -> u16 { + ((self.io_registers[0x16] as u16) << 8) | (self.io_registers[0x17] as u16) & 0x1F + } + pub fn ppu_bg2_x_scroll(&self) -> u16 { + ((self.io_registers[0x18] as u16) << 8) | (self.io_registers[0x19] as u16) & 0x1F + } + pub fn ppu_bg2_y_scroll(&self) -> u16 { + ((self.io_registers[0x1A] as u16) << 8) | (self.io_registers[0x1B] as u16) & 0x1F + } + pub fn ppu_bg3_x_scroll(&self) -> u16 { + ((self.io_registers[0x1C] as u16) << 8) | (self.io_registers[0x1D] as u16) & 0x1F + } + pub fn ppu_bg3_y_scroll(&self) -> u16 { + ((self.io_registers[0x1E] as u16) << 8) | (self.io_registers[0x1F] as u16) & 0x1F + } + pub fn ppu_bg0_control(&self) -> PpuBgControl { + let bits = ((self.io_registers[0x8] as u16) << 8) | (self.io_registers[0x9] as u16) & 0x1F; + PpuBgControl::from_bits(bits) + } + pub fn ppu_bg1_control(&self) -> PpuBgControl { + let bits = ((self.io_registers[0xA] as u16) << 8) | (self.io_registers[0xB] as u16) & 0x1F; + PpuBgControl::from_bits(bits) + } + pub fn ppu_bg2_control(&self) -> PpuBgControl { + let bits = ((self.io_registers[0xC] as u16) << 8) | (self.io_registers[0xD] as u16) & 0x1F; + PpuBgControl::from_bits(bits) + } + pub fn ppu_bg3_control(&self) -> PpuBgControl { + let bits = ((self.io_registers[0xE] as u16) << 8) | (self.io_registers[0xF] as u16) & 0x1F; + PpuBgControl::from_bits(bits) + } + pub fn ppu_bg_mode(&self) -> u8 { + self.io_registers[0x0] & 0x7 + } + pub fn ppu_bg0_enabled(&self) -> bool { + self.io_registers[0x1] & 1 == 1 + } + pub fn ppu_bg1_enabled(&self) -> bool { + (self.io_registers[0x1] >> 1) & 1 == 1 + } + pub fn ppu_bg2_enabled(&self) -> bool { + (self.io_registers[0x1] >> 2) & 1 == 1 + } + pub fn ppu_bg3_enabled(&self) -> bool { + (self.io_registers[0x1] >> 3) & 1 == 1 + } + pub fn ppu_obj_enabled(&self) -> bool { + (self.io_registers[0x1] >> 4) & 1 == 1 + } + pub fn ppu_win0_enabled(&self) -> bool { + (self.io_registers[0x1] >> 5) & 1 == 1 + } + pub fn ppu_win1_enabled(&self) -> bool { + (self.io_registers[0x1] >> 6) & 1 == 1 + } + pub fn ppu_obj_win_enabled(&self) -> bool { + (self.io_registers[0x1] >> 7) & 1 == 1 + } + + pub fn ppu_set_vblank(&mut self, v: bool) { + self.io_registers[0x4] &= !1; + self.io_registers[0x4] |= v as u8; + } + pub fn ppu_set_hblank(&mut self, v: bool) { + self.io_registers[0x4] &= !0b10; + self.io_registers[0x4] |= (v as u8) << 1; + } + pub fn ppu_set_vcounter(&mut self, v: bool) { + self.io_registers[0x4] &= !0b100; + self.io_registers[0x4] |= (v as u8) << 2; + } + pub fn ppu_hblank_irq_enabled(&self) -> bool { + (self.io_registers[0x4] >> 3) & 1 == 1 + } + pub fn ppu_vblank_irq_enabled(&self) -> bool { + (self.io_registers[0x4] >> 4) & 1 == 1 + } + pub fn ppu_vcounter_irq_enabled(&self) -> bool { + (self.io_registers[0x4] >> 5) & 1 == 1 + } + pub fn ppu_vcounter_setting(&self) -> u8 { + self.io_registers[0x5] + } + pub fn ppu_set_readonly_vcounter(&mut self, ly: u8) { + self.io_registers[0x6] = ly; + } + + pub fn master_interrupts_enabled(&self) -> bool { + self.io_registers[0x208] & 1 == 1 + } + + pub fn lcdc_vblank_interrupt_enabled(&self) -> bool { + self.io_registers[0x200] & 1 == 1 + } + pub fn lcdc_hblank_interrupt_enabled(&self) -> bool { + (self.io_registers[0x200] >> 1) & 1 == 1 + } + pub fn lcdc_vcounter_interrupt_enabled(&self) -> bool { + (self.io_registers[0x200] >> 2) & 1 == 1 + } + pub fn timer0_interrupt_enabled(&self) -> bool { + (self.io_registers[0x200] >> 3) & 1 == 1 + } + pub fn timer1_interrupt_enabled(&self) -> bool { + (self.io_registers[0x200] >> 4) & 1 == 1 + } + pub fn timer2_interrupt_enabled(&self) -> bool { + (self.io_registers[0x200] >> 5) & 1 == 1 + } + pub fn timer3_interrupt_enabled(&self) -> bool { + (self.io_registers[0x200] >> 6) & 1 == 1 + } + pub fn serial_interrupt_enabled(&self) -> bool { + (self.io_registers[0x200] >> 7) & 1 == 1 + } + pub fn dma0_interrupt_enabled(&self) -> bool { + self.io_registers[0x201] & 1 == 1 + } + pub fn dma1_interrupt_enabled(&self) -> bool { + (self.io_registers[0x201] >> 1) & 1 == 1 + } + pub fn dma2_interrupt_enabled(&self) -> bool { + (self.io_registers[0x201] >> 2) & 1 == 1 + } + pub fn dma3_interrupt_enabled(&self) -> bool { + (self.io_registers[0x201] >> 3) & 1 == 1 + } + pub fn keypad_interrupt_enabled(&self) -> bool { + (self.io_registers[0x201] >> 4) & 1 == 1 + } + pub fn game_pak_interrupt_enabled(&self) -> bool { + (self.io_registers[0x201] >> 5) & 1 == 1 + } + + pub fn lcdc_vblank_interrupt_requested(&self) -> bool { + self.io_registers[0x202] & 1 == 1 + } + pub fn lcdc_hblank_interrupt_requested(&self) -> bool { + (self.io_registers[0x202] >> 1) & 1 == 1 + } + pub fn lcdc_vcounter_interrupt_requested(&self) -> bool { + (self.io_registers[0x202] >> 2) & 1 == 1 + } + pub fn timer0_interrupt_requested(&self) -> bool { + (self.io_registers[0x202] >> 3) & 1 == 1 + } + pub fn timer1_interrupt_requested(&self) -> bool { + (self.io_registers[0x202] >> 4) & 1 == 1 + } + pub fn timer2_interrupt_requested(&self) -> bool { + (self.io_registers[0x202] >> 5) & 1 == 1 + } + pub fn timer3_interrupt_requested(&self) -> bool { + (self.io_registers[0x202] >> 6) & 1 == 1 + } + pub fn serial_interrupt_requested(&self) -> bool { + (self.io_registers[0x202] >> 7) & 1 == 1 + } + pub fn dma0_interrupt_requested(&self) -> bool { + self.io_registers[0x203] & 1 == 1 + } + pub fn dma1_interrupt_requested(&self) -> bool { + (self.io_registers[0x203] >> 1) & 1 == 1 + } + pub fn dma2_interrupt_requested(&self) -> bool { + (self.io_registers[0x203] >> 2) & 1 == 1 + } + pub fn dma3_interrupt_requested(&self) -> bool { + (self.io_registers[0x203] >> 3) & 1 == 1 + } + pub fn keypad_interrupt_requested(&self) -> bool { + (self.io_registers[0x203] >> 4) & 1 == 1 + } + pub fn game_pak_interrupt_requested(&self) -> bool { + (self.io_registers[0x203] >> 5) & 1 == 1 + } + + pub fn set_lcdc_vblank_interrupt(&mut self, value: bool) { + self.io_registers[0x202] &= !1; + self.io_registers[0x202] |= value as u8; + } + pub fn set_lcdc_hblank_interrupt(&mut self, value: bool) { + self.io_registers[0x202] &= !(1 << 1); + self.io_registers[0x202] |= (value as u8) << 1; + } + pub fn set_lcdc_vcounter_interrupt(&mut self, value: bool) { + self.io_registers[0x202] &= !(1 << 2); + self.io_registers[0x202] |= (value as u8) << 2; + } + pub fn set_timer0_interrupt(&mut self, value: bool) { + self.io_registers[0x202] &= !(1 << 3); + self.io_registers[0x202] |= (value as u8) << 3; + } + pub fn set_timer1_interrupt(&mut self, value: bool) { + self.io_registers[0x202] &= !(1 << 4); + self.io_registers[0x202] |= (value as u8) << 4; + } + pub fn set_timer2_interrupt(&mut self, value: bool) { + self.io_registers[0x202] &= !(1 << 5); + self.io_registers[0x202] |= (value as u8) << 5; + } + pub fn set_timer3_interrupt(&mut self, value: bool) { + self.io_registers[0x202] &= !(1 << 6); + self.io_registers[0x202] |= (value as u8) << 6; + } + pub fn set_serial_interrupt(&mut self, value: bool) { + self.io_registers[0x202] &= !(1 << 7); + self.io_registers[0x202] |= (value as u8) << 7; + } + pub fn set_dma0_interrupt(&mut self, value: bool) { + self.io_registers[0x203] &= !1; + self.io_registers[0x203] |= value as u8; + } + pub fn set_dma1_interrupt(&mut self, value: bool) { + self.io_registers[0x203] &= !(1 << 1); + self.io_registers[0x203] |= (value as u8) << 1; + } + pub fn set_dma2_interrupt(&mut self, value: bool) { + self.io_registers[0x203] &= !(1 << 2); + self.io_registers[0x203] |= (value as u8) << 2; + } + pub fn set_dma3_interrupt(&mut self, value: bool) { + self.io_registers[0x203] &= !(1 << 3); + self.io_registers[0x203] |= (value as u8) << 3; + } + pub fn set_keypad_interrupt(&mut self, value: bool) { + self.io_registers[0x203] &= !(1 << 4); + self.io_registers[0x203] |= (value as u8) << 4; + } + pub fn set_game_pak_interrupt(&mut self, value: bool) { + self.io_registers[0x203] &= !(1 << 5); + self.io_registers[0x203] |= (value as u8) << 5; + } + + pub fn dma3_source_address(&self) -> u32 { + (self.io_registers[0x0D7] as u32) << 24 + | (self.io_registers[0x0D6] as u32) << 16 + | (self.io_registers[0x0D5] as u32) << 8 + | (self.io_registers[0x0D4] as u32) + } + pub fn dma3_dest_address(&self) -> u32 { + (self.io_registers[0x0DE] as u32) << 24 + | (self.io_registers[0x0DC] as u32) << 16 + | (self.io_registers[0x0DB] as u32) << 8 + | (self.io_registers[0x0DA] as u32) + } + pub fn dma3_count(&self) -> u16 { + (self.io_registers[0x0DD] as u16) << 8 | (self.io_registers[0x0DC] as u16) + } + + pub fn get_mem8(&self, address: u32) -> (u8, u8) { + match address { + //0x00000000..=0x00003FFF => { + 0x00000000..=0x01FFFFFF => { + // bios system ROM + // todo!("bios system ROM") + // HACK: + (0, 0) + } + 0x02000000..=0x0203FFFF => { + // on-board work ram + (self.wram[(address & 0x3FFFF) as usize], 3) + } + //0x03000000..=0x03007FFF => (self.iw_ram[(address & 0x7FFF) as usize], 1), + 0x03000000..=0x03FFFFFF => (self.iw_ram[(address & 0x7FFF) as usize], 1), + //0x04000000..=0x040003FE => (self.io_registers[(address & 0x3FE) as usize], 1), + 0x04000000..=0x04FFFFFE => (self.io_registers[(address & 0x3FF) as usize], 1), + //0x05000000..=0x050003FF => (self.obj_palette_ram[(address & 0x3FF) as usize], 1), + 0x05000000..=0x05FFFFFF => (self.obj_palette_ram[(address & 0x3FF) as usize], 1), + //0x06000000..=0x06017FFF => (self.vram[(address & 0x17FFF) as usize], 1), + 0x06000000..=0x06FFFFFF => (self.vram[(address & 0x17FFF) as usize], 1), + //0x07000000..=0x070003FF => (self.oam[(address & 0x3FF) as usize], 1), + 0x07000000..=0x07FFFFFF => (self.oam[(address & 0x3FF) as usize], 1), + 0x08000000..=0x09FFFFFF => (self.entire_rom[(address & 0x1FFFFFF) as usize], 5), + 0x0A000000..=0x0BFFFFFF => todo!("Game Pak ROM/FlashROM (max 32MB) - Wait State 1"), + 0x0C000000..=0x0DFFFFFF => todo!("Game Pak ROM/FlashROM (max 32MB) - Wait State 2"), + 0x0E000000..=0x0E00FFFF => todo!("Game Pak SRAM (max 64 KBytes) - 8bit Bus width"), + _ => (0, 0), + } + } + pub fn get_mem16(&self, address: u32) -> (u16, u8) { + let lo_bit_idx = address & !0x1; + let hi_bit_idx = address | 0x1; + match address { + 0x00000000..=0x01FF3FFF => { + //0x00000000..=0x00003FFF => { + // bios system ROM + // TODO: separate opcoed reading logic from these getters + let lo_bit = self.bios[(lo_bit_idx & 0x3FFF) as usize] as u16; + let hi_bit = self.bios[(hi_bit_idx & 0x3FFF) as usize] as u16; + ((hi_bit << 8) | lo_bit, 1) + } + 0x02000000..=0x02FFFFFF => { + //0x02000000..=0x0203FFFF => { + // on-board work ram + let lo_bit = self.wram[(lo_bit_idx & 0x3FFFF) as usize] as u16; + let hi_bit = self.wram[(hi_bit_idx & 0x3FFFF) as usize] as u16; + ((hi_bit << 8) | lo_bit, 3) + } + 0x03000000..=0x03FFFFFF => { + //0x03000000..=0x03007FFF => { + let lo_bit = self.iw_ram[(lo_bit_idx & 0x7FFF) as usize] as u16; + let hi_bit = self.iw_ram[(hi_bit_idx & 0x7FFF) as usize] as u16; + ((hi_bit << 8) | lo_bit, 1) + } + 0x04000000..=0x04FFFFFF => { + //0x04000000..=0x040003FF => { + let lo_bit = self.io_registers[(lo_bit_idx & 0x3FF) as usize] as u16; + let hi_bit = self.io_registers[(hi_bit_idx & 0x3FF) as usize] as u16; + ((hi_bit << 8) | lo_bit, 1) + } + 0x05000000..=0x05FFFFFF => { + //0x05000000..=0x050003FF => { + let lo_bit = self.obj_palette_ram[(lo_bit_idx & 0x3FF) as usize] as u16; + let hi_bit = self.obj_palette_ram[(hi_bit_idx & 0x3FF) as usize] as u16; + ((hi_bit << 8) | lo_bit, 1) + } + 0x06000000..=0x06FFFFFF => { + //0x06000000..=0x06017FFF => { + let lo_bit = self.vram[(lo_bit_idx & 0x17FFF) as usize] as u16; + let hi_bit = self.vram[(hi_bit_idx & 0x17FFF) as usize] as u16; + ((hi_bit << 8) | lo_bit, 1) + } + 0x07000000..=0x07FFFFFF => { + //0x07000000..=0x070003FF => { + let lo_bit = self.oam[(lo_bit_idx & 0x3FF) as usize] as u16; + let hi_bit = self.oam[(hi_bit_idx & 0x3FF) as usize] as u16; + ((hi_bit << 8) | lo_bit, 1) + } + 0x08000000..=0x09FFFFFF => { + if (hi_bit_idx - 0x0800_0000) > self.entire_rom.len() as u32 { + return (0, 5); + } + let lo_bit = self.entire_rom[(lo_bit_idx & 0x1FFFFFF) as usize] as u16; + let hi_bit = self.entire_rom[(hi_bit_idx & 0x1FFFFFF) as usize] as u16; + ((hi_bit << 8) | lo_bit, 5) + } + 0x0A000000..=0x0BFFFFFF => { + if (hi_bit_idx - 0x0A00_0000) > self.entire_rom.len() as u32 { + return (0, 5); + } + let lo_bit = self.entire_rom[(lo_bit_idx & 0x1FFFFFF) as usize] as u16; + let hi_bit = self.entire_rom[(hi_bit_idx & 0x1FFFFFF) as usize] as u16; + ((hi_bit << 8) | lo_bit, 5) + } + 0x0C000000..=0x0DFFFFFF => { + if (hi_bit_idx - 0x0C00_0000) > self.entire_rom.len() as u32 { + return (0, 5); + } + let lo_bit = self.entire_rom[(lo_bit_idx & 0x1FFFFFF) as usize] as u16; + let hi_bit = self.entire_rom[(hi_bit_idx & 0x1FFFFFF) as usize] as u16; + ((hi_bit << 8) | lo_bit, 5) + } + 0x0E000000..=0x0E00FFFF => todo!("Game Pak SRAM (max 64 KBytes) - 8bit Bus width"), + _ => (0, 0), + } + } + pub fn get_mem32(&self, address: u32) -> (u32, u8) { + let bit1_idx = address & !0x3; + let bit2_idx = (address & !0x3) | 0b01; + let bit3_idx = (address & !0x3) | 0b10; + let bit4_idx = (address & !0x3) | 0b11; + match address { + 0x00000000..=0x01FFFFFF => { + //0x00000000..=0x00003FFF => { + // bios system ROM + let bit1 = self.bios[(bit1_idx & 0x3FFF) as usize] as u32; + let bit2 = self.bios[(bit2_idx & 0x3FFF) as usize] as u32; + let bit3 = self.bios[(bit3_idx & 0x3FFF) as usize] as u32; + let bit4 = self.bios[(bit4_idx & 0x3FFF) as usize] as u32; + let out = (bit4 << 24) | (bit3 << 16) | (bit2 << 8) | bit1; + (out, 1) + } + 0x02000000..=0x02FFFFFF => { + //0x02000000..=0x0203FFFF => { + // on-board work ram + let bit1 = self.wram[(bit1_idx & 0x3FFFF) as usize] as u32; + let bit2 = self.wram[(bit2_idx & 0x3FFFF) as usize] as u32; + let bit3 = self.wram[(bit3_idx & 0x3FFFF) as usize] as u32; + let bit4 = self.wram[(bit4_idx & 0x3FFFF) as usize] as u32; + let out = (bit4 << 24) | (bit3 << 16) | (bit2 << 8) | bit1; + (out, 6) + } + 0x03000000..=0x03FFFFFF => { + //0x03000000..=0x03007FFF => { + let bit1 = self.iw_ram[(bit1_idx & 0x7FFF) as usize] as u32; + let bit2 = self.iw_ram[(bit2_idx & 0x7FFF) as usize] as u32; + let bit3 = self.iw_ram[(bit3_idx & 0x7FFF) as usize] as u32; + let bit4 = self.iw_ram[(bit4_idx & 0x7FFF) as usize] as u32; + let out = (bit4 << 24) | (bit3 << 16) | (bit2 << 8) | bit1; + (out, 1) + } + 0x04000000..=0x04FFFFFE => { + //0x04000000..=0x040003FE => { + let bit1 = self.io_registers[(bit1_idx & 0x3FF) as usize] as u32; + let bit2 = self.io_registers[(bit2_idx & 0x3FF) as usize] as u32; + let bit3 = self.io_registers[(bit3_idx & 0x3FF) as usize] as u32; + let bit4 = self.io_registers[(bit4_idx & 0x3FF) as usize] as u32; + let out = (bit4 << 24) | (bit3 << 16) | (bit2 << 8) | bit1; + (out, 1) + } + 0x05000000..=0x05FFFFFF => { + //0x05000000..=0x050003FF => { + let bit1 = self.obj_palette_ram[(bit1_idx & 0x3FF) as usize] as u32; + let bit2 = self.obj_palette_ram[(bit2_idx & 0x3FF) as usize] as u32; + let bit3 = self.obj_palette_ram[(bit3_idx & 0x3FF) as usize] as u32; + let bit4 = self.obj_palette_ram[(bit4_idx & 0x3FF) as usize] as u32; + let out = (bit4 << 24) | (bit3 << 16) | (bit2 << 8) | bit1; + (out, 2) + } + 0x06000000..=0x06FFFFFF => { + //0x06000000..=0x06017FFF => { + let bit1 = self.vram[(bit1_idx & 0x17FFF) as usize] as u32; + let bit2 = self.vram[(bit2_idx & 0x17FFF) as usize] as u32; + let bit3 = self.vram[(bit3_idx & 0x17FFF) as usize] as u32; + let bit4 = self.vram[(bit4_idx & 0x17FFF) as usize] as u32; + let out = (bit4 << 24) | (bit3 << 16) | (bit2 << 8) | bit1; + (out, 2) + } + 0x07000000..=0x07FFFFFF => { + //0x07000000..=0x070003FF => { + let bit1 = self.oam[(bit1_idx & 0x3FF) as usize] as u32; + let bit2 = self.oam[(bit2_idx & 0x3FF) as usize] as u32; + let bit3 = self.oam[(bit3_idx & 0x3FF) as usize] as u32; + let bit4 = self.oam[(bit4_idx & 0x3FF) as usize] as u32; + let out = (bit4 << 24) | (bit3 << 16) | (bit2 << 8) | bit1; + (out, 1) + } + 0x08000000..=0x09FFFFFF => { + let bit1 = self.entire_rom[(bit1_idx & 0x1FFFFFF) as usize] as u32; + let bit2 = self.entire_rom[(bit2_idx & 0x1FFFFFF) as usize] as u32; + let bit3 = self.entire_rom[(bit3_idx & 0x1FFFFFF) as usize] as u32; + let bit4 = self.entire_rom[(bit4_idx & 0x1FFFFFF) as usize] as u32; + let out = (bit4 << 24) | (bit3 << 16) | (bit2 << 8) | bit1; + (out, 8) + } + 0x0A000000..=0x0BFFFFFF => todo!("Game Pak ROM/FlashROM (max 32MB) - Wait State 1"), + 0x0C000000..=0x0DFFFFFF => todo!("Game Pak ROM/FlashROM (max 32MB) - Wait State 2"), + 0x0E000000..=0x0E00FFFF => todo!("Game Pak SRAM (max 64 KBytes) - 8bit Bus width"), + _ => (0, 0), + } + } + + pub fn set_mem8(&mut self, address: u32, val: u8) -> u8 { + match address { + 0x00000000..=0x00003FFF => { + // bios system ROM + //todo!("bios system ROM") + 1 + } + 0x02000000..=0x02FFFFFF => { + //0x02000000..=0x0203FFFF => { + // on-board work ram + self.wram[(address & 0x3FFFF) as usize] = val; + 3 + } + 0x03000000..=0x03FFFFFF => { + //0x03000000..=0x03007FFF => { + self.iw_ram[(address & 0x7FFF) as usize] = val; + 1 + } + 0x04000000..=0x04FFFFFF => { + //0x04000000..=0x040003FE => { + self.io_registers.set_mem8(address, val); + 1 + } + 0x05000000..=0x050003FF => { + todo!("OBJ Pallete ram") + } + 0x06000000..=0x06017FFF => { + todo!("VRAM") + } + 0x07000000..=0x070003FF => { + todo!("OAM") + } + 0x08000000..=0x09FFFFFF => todo!("Game Pak ROM/FlashROM (max 32MB) - Wait State 0"), + 0x0A000000..=0x0BFFFFFF => todo!("Game Pak ROM/FlashROM (max 32MB) - Wait State 1"), + 0x0C000000..=0x0DFFFFFF => todo!("Game Pak ROM/FlashROM (max 32MB) - Wait State 2"), + 0x0E000000..=0x0E00FFFF => todo!("Game Pak SRAM (max 64 KBytes) - 8bit Bus width"), + _ => 0, + } + } + + pub fn set_mem16(&mut self, address: u32, val: u16) -> u8 { + let lo_bit_idx = address & !0x1; + let hi_bit_idx = address | 0x1; + let lo_val = (val & 0xFF) as u8; + let hi_val = ((val >> 8) & 0xFF) as u8; + match address { + 0x00000000..=0x00003FFF => { + // bios system ROM + // HACK: disable + //todo!("bios system ROM") + 0 + } + 0x02000000..=0x02FFFFFF => { + //0x02000000..=0x0203FFFF => { + // on-board work ram + self.wram[(lo_bit_idx & 0x3FFFF) as usize] = lo_val; + self.wram[(hi_bit_idx & 0x3FFFF) as usize] = hi_val; + 3 + } + 0x03000000..=0x03FFFFFF => { + //0x03000000..=0x03007FFF => { + self.iw_ram[(lo_bit_idx & 0x7FFF) as usize] = lo_val; + self.iw_ram[(hi_bit_idx & 0x7FFF) as usize] = hi_val; + 1 + } + 0x04000000..=0x04FFFFFE => { + //0x04000000..=0x040003FE => { + self.io_registers.set_mem16(address, val); + 1 + } + 0x05000000..=0x05FFFFFF => { + //0x05000000..=0x050003FF => { + self.obj_palette_ram[(lo_bit_idx & 0x3FF) as usize] = lo_val; + self.obj_palette_ram[(hi_bit_idx & 0x3FF) as usize] = hi_val; + 1 + } + 0x06000000..=0x06FFFFFF => { + //0x06000000..=0x06017FFF => { + self.vram[(lo_bit_idx & 0x17FFF) as usize] = lo_val; + self.vram[(hi_bit_idx & 0x17FFF) as usize] = hi_val; + 1 + } + 0x07000000..=0x07FFFFFF => { + //0x07000000..=0x070003FF => { + self.oam[(lo_bit_idx & 0x3FF) as usize] = lo_val; + self.oam[(hi_bit_idx & 0x3FF) as usize] = hi_val; + 1 + } + 0x08000000..=0x09FFFFFF => todo!("Game Pak ROM/FlashROM (max 32MB) - Wait State 0"), + 0x0A000000..=0x0BFFFFFF => todo!("Game Pak ROM/FlashROM (max 32MB) - Wait State 1"), + 0x0C000000..=0x0DFFFFFF => todo!("Game Pak ROM/FlashROM (max 32MB) - Wait State 2"), + 0x0E000000..=0x0E00FFFF => todo!("Game Pak SRAM (max 64 KBytes) - 8bit Bus width"), + _ => 0, + } + } + + pub fn set_mem32(&mut self, address: u32, val: u32) -> u8 { + let bit1_idx = address & !0x3; + let bit2_idx = (address & !0x3) | 0b01; + let bit3_idx = (address & !0x3) | 0b10; + let bit4_idx = (address & !0x3) | 0b11; + let val1 = (val & 0xFF) as u8; + let val2 = ((val >> 8) & 0xFF) as u8; + let val3 = ((val >> 16) & 0xFF) as u8; + let val4 = ((val >> 24) & 0xFF) as u8; + match address { + 0x00000000..=0x00003FFF => { + // bios system ROM + // HACK: disable this for now, we don't want to write but something is trying to + //todo!("bios system ROM") + 0 + } + 0x02000000..=0x02FFFFFF => { + //0x02000000..=0x0203FFFF => { + // on-board work ram + self.wram[(bit1_idx & 0x3FFFF) as usize] = val1; + self.wram[(bit2_idx & 0x3FFFF) as usize] = val2; + self.wram[(bit3_idx & 0x3FFFF) as usize] = val3; + self.wram[(bit4_idx & 0x3FFFF) as usize] = val4; + 6 + } + 0x03000000..=0x03FFFFFF => { + //0x03000000..=0x03007FFF => { + self.iw_ram[(bit1_idx & 0x7FFF) as usize] = val1; + self.iw_ram[(bit2_idx & 0x7FFF) as usize] = val2; + self.iw_ram[(bit3_idx & 0x7FFF) as usize] = val3; + self.iw_ram[(bit4_idx & 0x7FFF) as usize] = val4; + 1 + } + 0x04000000..=0x04FFFFFE => { + //0x04000000..=0x040003FE => { + self.io_registers.set_mem32(address, val); + 1 + } + 0x05000000..=0x05FFFFFF => { + //0x05000000..=0x050003FF => { + self.obj_palette_ram[(bit1_idx & 0x3FF) as usize] = val1; + self.obj_palette_ram[(bit2_idx & 0x3FF) as usize] = val2; + self.obj_palette_ram[(bit3_idx & 0x3FF) as usize] = val3; + self.obj_palette_ram[(bit4_idx & 0x3FF) as usize] = val4; + 2 + } + 0x06000000..=0x06FFFFFF => { + //0x06000000..=0x06017FFF => { + self.vram[(bit1_idx & 0x17FFF) as usize] = val1; + self.vram[(bit2_idx & 0x17FFF) as usize] = val2; + self.vram[(bit3_idx & 0x17FFF) as usize] = val3; + self.vram[(bit4_idx & 0x17FFF) as usize] = val4; + 2 + } + 0x07000000..=0x07FFFFFF => { + //0x07000000..=0x070003FF => { + self.oam[(bit1_idx & 0x3FF) as usize] = val1; + self.oam[(bit2_idx & 0x3FF) as usize] = val2; + self.oam[(bit3_idx & 0x3FF) as usize] = val3; + self.oam[(bit4_idx & 0x3FF) as usize] = val4; + 1 + } + // TODO: 4000204h - WAITCNT - Waitstate Control (R/W) + // HACK: test ROM is writing here, can't find clear docs + 0x08000000..=0x09FFFFFF => 0, /*todo!( + "Game Pak ROM/FlashROM (max 32MB) - Wait State 0: 0x{:X}", + address + ),*/ + 0x0A000000..=0x0BFFFFFF => todo!("Game Pak ROM/FlashROM (max 32MB) - Wait State 1"), + 0x0C000000..=0x0DFFFFFF => todo!("Game Pak ROM/FlashROM (max 32MB) - Wait State 2"), + 0x0E000000..=0x0E00FFFF => todo!("Game Pak SRAM (max 64 KBytes) - 8bit Bus width"), + _ => 0, + } + } + + fn irq_interrupt(&mut self) { + self.r.set_irq_mode(); + // Docs say `SUBS PC,R14,4 ;both PC=R14_irq-4, and CPSR=SPSR_irq' + // to return from IRQ, so we add 4 + self.r[14] = self.r.pc + 4; + *self.r.get_spsr_mut() = self.r.cpsr; + self.r.set_thumb(false); + self.r.cpsr_disable_irq(); + self.r.pc = 0x18; + } + + pub fn maybe_handle_interrupts(&mut self) { + // TODO: when this function handles more than just IRQ, this logic must be changed + if self.r.irq_disabled() { + return; + } + if self.lcdc_vblank_interrupt_requested() && self.lcdc_hblank_interrupt_enabled() { + trace!("VBLANK interrupt started"); + self.irq_interrupt(); + } else if self.lcdc_hblank_interrupt_requested() && self.lcdc_hblank_interrupt_enabled() { + trace!("HBLANK interrupt started"); + self.irq_interrupt(); + } else if self.lcdc_vcounter_interrupt_requested() && self.lcdc_vcounter_interrupt_enabled() + { + trace!("VCOUNTER interrupt started"); + self.irq_interrupt(); + } else if self.timer0_interrupt_requested() && self.timer0_interrupt_enabled() { + trace!("TIMER0 interrupt started"); + self.irq_interrupt(); + } else if self.timer1_interrupt_requested() && self.timer1_interrupt_enabled() { + trace!("TIMER1 interrupt started"); + self.irq_interrupt(); + } else if self.timer2_interrupt_requested() && self.timer2_interrupt_enabled() { + trace!("TIMER2 interrupt started"); + self.irq_interrupt(); + } else if self.timer3_interrupt_requested() && self.timer3_interrupt_enabled() { + trace!("TIMER3 interrupt started"); + self.irq_interrupt(); + } else if self.serial_interrupt_requested() && self.serial_interrupt_enabled() { + trace!("SERIAL interrupt started"); + self.irq_interrupt(); + } else if self.dma0_interrupt_requested() && self.dma0_interrupt_enabled() { + trace!("DMA0 interrupt started"); + self.irq_interrupt(); + } else if self.dma1_interrupt_requested() && self.dma1_interrupt_enabled() { + trace!("DMA1 interrupt started"); + self.irq_interrupt(); + } else if self.dma2_interrupt_requested() && self.dma2_interrupt_enabled() { + trace!("DMA2 interrupt started"); + self.irq_interrupt(); + } else if self.dma3_interrupt_requested() && self.dma3_interrupt_enabled() { + trace!("DMA3 interrupt started"); + self.irq_interrupt(); + } else if self.keypad_interrupt_requested() && self.keypad_interrupt_enabled() { + trace!("KEYPAD interrupt started"); + self.irq_interrupt(); + } else if self.game_pak_interrupt_requested() && self.game_pak_interrupt_enabled() { + trace!("GAMEPAK interrupt started"); + self.irq_interrupt(); + } + } + + pub fn run_dma(&mut self, control: DmaControl, src: u32, dest: u32, count: u32) -> u32 { + let mut cycles = 0; + match control.start_timing { + DmaStartTiming::Immediately => { + println!( + "Transfering data from 0x{:X} to 0x{:X}: 0x{:X} bytes", + src, dest, count + ); + if control.transfer_type { + for i in 0..count { + let val = self.get_mem32(src + (i * 4) as u32); + let o = self.set_mem32(dest + (i * 4) as u32, val.0); + cycles += val.1 as u32 + o as u32; + } + } else { + for i in 0..count { + let val = self.get_mem16(src + (i * 2) as u32); + let o = self.set_mem16(dest + (i * 2) as u32, val.0); + cycles += val.1 as u32 + o as u32; + } + } + if control.repeat { + todo!("repeat dma"); + } + } + DmaStartTiming::VBlank => { + todo!("VBlank DMA timing not implemented") + } + DmaStartTiming::HBlank => { + todo!("HBlank DMA timing not implemented") + } + DmaStartTiming::Special => { + todo!("Special DMA timing not implemented"); + } + } + cycles + } + + pub fn handle_dma(&mut self) -> u32 { + if self.io_registers.dma0_enabled { + self.io_registers.dma0_enabled = false; + let dma = self.io_registers.dma0(); + let src = self.io_registers.dma0_source_addr(); + let dest = self.io_registers.dma0_dest_addr(); + let mut count = self.io_registers.dma0_word_count(); + if count == 0 { + count = 0x4000; + } + let out = self.run_dma(dma, src, dest, count as u32); + if dma.irq_at_end && self.dma0_interrupt_enabled() { + self.set_dma0_interrupt(true); + } + self.io_registers.disable_dma0(); + out + } else if self.io_registers.dma1_enabled { + self.io_registers.dma1_enabled = false; + let dma = self.io_registers.dma1(); + let src = self.io_registers.dma1_source_addr(); + let dest = self.io_registers.dma1_dest_addr(); + let mut count = self.io_registers.dma1_word_count(); + if count == 0 { + count = 0x4000; + } + let out = self.run_dma(dma, src, dest, count as u32); + if dma.irq_at_end && self.dma1_interrupt_enabled() { + self.set_dma1_interrupt(true); + } + self.io_registers.disable_dma1(); + out + } else if self.io_registers.dma2_enabled { + self.io_registers.dma2_enabled = false; + let dma = self.io_registers.dma2(); + let src = self.io_registers.dma2_source_addr(); + let dest = self.io_registers.dma2_dest_addr(); + let mut count = self.io_registers.dma2_word_count(); + if count == 0 { + count = 0x4000; + } + let out = self.run_dma(dma, src, dest, count as u32); + if dma.irq_at_end && self.dma2_interrupt_enabled() { + self.set_dma2_interrupt(true); + } + self.io_registers.disable_dma2(); + out + } else if self.io_registers.dma3_enabled { + self.io_registers.dma3_enabled = false; + let dma = self.io_registers.dma3(); + let src = self.io_registers.dma3_source_addr(); + let dest = self.io_registers.dma3_dest_addr(); + let mut count = self.io_registers.dma3_word_count() as u32; + if count == 0 { + count = 0x10000; + } + let out = self.run_dma(dma, src, dest, count); + if dma.irq_at_end && self.dma3_interrupt_enabled() { + self.set_dma3_interrupt(true); + } + self.io_registers.disable_dma3(); + out + } else { + 0 + } + } + + // TODO: aligned reads + pub fn get_opcode(&self) -> u32 { + // TODO: the timing of reading from mem for PC should be handled? + self.get_mem32(self.r.pc).0 + } + + pub fn get_thumb_opcode(&self) -> u16 { + // TODO: the timing of reading from mem for PC should be handled? + self.get_mem16(self.r.pc).0 + } + + pub fn dispatch(&mut self) -> u32 { + if self.io_registers.dma_waiting() { + return self.handle_dma(); + } + if self.get_mem16(0x4000202).0 != 0 && self.master_interrupts_enabled() { + self.maybe_handle_interrupts(); + } + if self.r.thumb_enabled() { + return self.dispatch_thumb() as u32; + } + let opcode = self.get_opcode(); + let opcode_idx = (opcode >> 25) & 0x7; + // TODO: some instructions can't be skipped, handle those + if opcode == 0 { + self.r.pc += 4; + //self.r.pc = self.r.pc.wrapping_add(4); + return 4; + } + //println!("opcode: {:032b} at 0x{:X}", opcode, self.r.pc); + let cond = Cond::from_u8(((opcode >> 28) & 0xF) as u8); + if !self.cond_should_execute(cond) { + //println!("Skipped!"); + self.r.pc += 4; + return 1; + } + + if (opcode >> 8) & 0xF_FFFF == 0b0001_0010_1111_1111_1111 { + let cycles = self.dispatch_branch_and_exchange(opcode); + // don't increment PC when switching execution modes + // TODO: this might be more complicated than this + //self.r.pc += 4; + return cycles as u32; + } + + let cycles = match opcode_idx { + 0b101 => self.dispatch_branch(opcode), + // TODO: add 10 to end of above and PSR + // TODO: add 000 to end of above and multiply + // TODO: add 01 for mul long + // |_Cond__|0_0_0_0_1|U|A|S|_RdHi__|_RdLo__|__Rs___|1_0_0_1|__Rm___| MulLong + 0b001 | 0b000 => self.dispatch_alu(opcode), + 0b010 | 0b011 => self.dispatch_mem(opcode), + 0b100 => self.dispatch_block_data(opcode), + // TODO: 0b100 block trans + // TODO: 0b110 co data trans + 0b111 => self.dispatch_codata_op(opcode), + /* + |_Cond__|1_1_1_0|_CPopc_|__CRn__|__CRd__|__CP#__|_CP__|0|__CRm__| CoDataOp + |_Cond__|1_1_1_0|CPopc|L|__CRn__|__Rd___|__CP#__|_CP__|1|__CRm__| CoRegTrans + |_Cond__|1_1_1_1|_____________Ignored_by_Processor______________| SWI + */ + _ => { + unimplemented!("0x{:X} ({:b}) at 0x{:X}", opcode, opcode_idx, self.r.pc); + } + }; + + self.r.pc += 4; + + cycles as u32 + } + + pub fn dispatch_thumb(&mut self) -> u8 { + let opcode = self.get_thumb_opcode(); + let opcode_idx = (opcode >> 13) & 0x7; + if opcode == 0 { + self.r.pc += 2; + return 4; + } + //println!("THUMB opcode: {:016b} at 0x{:X}", opcode, self.r.pc); + + let cycles = match opcode_idx { + 0b000 => self.dispatch_thumb_shift_add_sub(opcode), + 0b001 => self.dispatch_thumb_imm(opcode), + 0b010 => { + let next_bit = (opcode >> 12) & 0x1 == 1; + if next_bit { + if (opcode >> 9) & 1 == 1 { + self.dispatch_thumb_load_store_halfword_sign_extend(opcode) + } else { + self.dispatch_thumb_load_store_reg(opcode) + } + } else { + let sub_op = (opcode >> 10) & 0x3; + match sub_op { + 0b00 => self.dispatch_thumb_alu(opcode), + 0b01 => self.dispatch_thumb_hi_reg_branch(opcode), + _ => self.dispatch_thumb_load_pc_relative(opcode), + } + } + } + 0b011 => self.dispatch_thumb_load_store_imm(opcode), + 0b100 => { + let next_bit = (opcode >> 12) & 0x1 == 1; + if next_bit { + self.dispatch_thumb_load_store_sp_relative(opcode) + } else { + self.dispatch_thumb_load_store_halfword(opcode) + } + } + 0b101 => { + let next_bit = (opcode >> 12) & 0x1 == 1; + let sub_op = (opcode >> 9) & 0x3; + match (next_bit, sub_op) { + (false, _) => self.dispatch_thumb_get_relative_address(opcode), + (true, 0b10) => self.dispatch_thumb_push_pop(opcode), + // technically more instructions are here but not for the armv4 CPU + (true, _) => self.dispatch_thumb_add_offset_to_sp(opcode), + } + } + 0b110 => { + let next_bit = (opcode >> 12) & 0x1 == 1; + if next_bit { + let cond = (opcode >> 8) & 0xF; + if cond == 0b1111 { + todo!("THUMB SWI"); + //self.dispatch_thumb_software_interrupt(opcode) + } else { + self.dispatch_thumb_conditional_branch(opcode) + } + } else { + self.dispatch_thumb_load_store_multiple(opcode) + } + } + 0b111 => self.dispatch_thumb_branch(opcode), + _ => unimplemented!( + "THUMB 0x{:X} ({:b}) at 0x{:X}", + opcode, + opcode_idx, + self.r.pc + ), + }; + + self.r.pc += 2; + + cycles + } + + pub fn dispatch_thumb_alu(&mut self, opcode: u16) -> u8 { + let sub_op = (opcode >> 6) & 0xF; + let rs = ((opcode >> 3) & 0x7) as u8; + let rd = (opcode & 0x7) as u8; + let mut skip_end_flags = false; + + let cycles = match sub_op { + 0b0000 => { + trace!("AND r{} = r{} & r{}", rd, rs, rd); + self.r[rd] = self.r[rs] & self.r[rd]; + 1 + } + 0b0001 => { + trace!("EOR r{} = r{} ^ r{}", rd, rs, rd); + self.r[rd] = self.r[rs] ^ self.r[rd]; + 1 + } + 0b0010 => { + trace!("LSL r{} = r{} << r{}", rd, rs, rd); + let result = self.r[rd] << (self.r[rs] & 0xFF); + self.r[rd] = result; + if self.r[rs] & 0xFF != 0 { + // TODO: REVIEW + self.r + .cpsr_set_carry_flag(self.r[rs] & (1 << (32 - (self.r[rs] & 0xFF))) != 0); + } + 2 + } + 0b0011 => { + trace!("LSR r{} = r{} >> r{}", rd, rs, rd); + self.r[rd] = self.r[rd] >> (self.r[rs] & 0xFF); + if self.r[rs] & 0xFF != 0 { + // TODO: REVIEW + self.r + .cpsr_set_carry_flag(self.r[rs] & (1 << ((self.r[rs] & 0xFF) - 1)) != 0); + } + 2 + } + 0b0100 => { + trace!("ASR r{} = r{} >> r{}", rd, rs, rd); + self.r[rd] = ((self.r[rd] as i32) >> (self.r[rs] & 0xFF)) as u32; + if self.r[rs] & 0xFF != 0 { + // TODO: REVIEW + self.r + .cpsr_set_carry_flag(self.r[rs] & (1 << ((self.r[rs] & 0xFF) - 1)) != 0); + } + 2 + } + 0b0101 => { + trace!("ADC r{} = r{} + r{} + C", rd, rs, rd); + let overflow_check = self.r[rd] + .checked_add(self.r[rs]) + .and_then(|v| v.checked_add(self.r.cpsr_carry_flag() as u32)); + let old_val = self.r[rd]; + self.r[rd] = self.r[rs] + .wrapping_add(self.r[rd]) + .wrapping_add(self.r.cpsr_carry_flag() as u32); + self.r.cpsr_set_carry_flag(overflow_check.is_none()); + // TODO: what does overflow mean here? + // TODO: include carry in overflow check + self.r.cpsr_set_overflow_flag( + ((old_val ^ self.r[rs]) & 0x8000_0000 == 0) + && ((old_val ^ self.r[rd]) & 0x8000_0000 != 0), + ); + 1 + } + 0b0110 => { + trace!("SBC r{} = r{} - r{} - C", rd, rs, rd); + // TODO: review what not carry means, does it just mean add the carry? or is it 32bit negation? + self.r[rd] = self.r[rd] + .wrapping_sub(self.r[rs]) + .wrapping_sub(self.r.cpsr_carry_flag() as u32); + // TODO: review this + self.r.cpsr_set_overflow_flag(false); + self.r.cpsr_set_carry_flag(false); + 1 + } + 0b0111 => { + trace!("ROR r{} = r{} ROR r{}", rd, rs, rd); + self.r[rd] = self.r[rd].rotate_right(self.r[rs] & 0xFF); + if self.r[rs] & 0xFF != 0 { + todo!("Carry flag for THUMB ROR"); + //self.r.cpsr_set_carry_flag() + } + 2 + } + 0b1000 => { + trace!("TST r{} & r{}", rs, rd); + let result = self.r[rs] & self.r[rd]; + self.r.cpsr_set_zero_flag(result == 0); + self.r.cpsr_set_sign_flag(result & 0x8000_0000 != 0); + skip_end_flags = true; + 1 + } + 0b1001 => { + trace!("NEG r{} = -r{}", rd, rs); + let old_val = self.r[rd]; + self.r[rd] = 0i32.wrapping_sub(self.r[rs] as i32) as u32; + // TODO: review + self.r.cpsr_set_overflow_flag( + old_val & 0x8000_0000 == 0 && self.r[rd] & 0x8000_0000 != 0, + ); + self.r.cpsr_set_carry_flag(false); + 1 + } + 0b1010 => { + trace!("CMP r{} - r{}", rs, rd); + let result = self.r[rd].wrapping_sub(self.r[rs]); + self.r.cpsr_set_zero_flag(result == 0); + self.r.cpsr_set_sign_flag(result & 0x8000_0000 != 0); + // TODO: review this + self.r.cpsr_set_overflow_flag(false); + self.r.cpsr_set_carry_flag(false); + skip_end_flags = true; + 1 + } + 0b1011 => { + trace!("CMN r{} + r{}", rs, rd); + let old_val = self.r[rd]; + let result = self.r[rd].wrapping_add(self.r[rs]); + self.r.cpsr_set_zero_flag(result == 0); + self.r.cpsr_set_sign_flag(result & 0x8000_0000 != 0); + self.r.cpsr_set_overflow_flag( + ((old_val ^ self.r[rs]) & 0x8000_0000 == 0) + && ((old_val ^ result) & 0x8000_0000 != 0), + ); + self.r + .cpsr_set_carry_flag(old_val.checked_add(self.r[rs]).is_none()); + skip_end_flags = true; + 1 + } + 0b1100 => { + trace!("ORR r{} = r{} | r{}", rd, rs, rd); + self.r[rd] = self.r[rd] | self.r[rs]; + 1 + } + 0b1101 => { + trace!("MUL r{} = r{} * r{}", rd, rs, rd); + // TODO: update carry flag if GBA should be ARMv4. ARMv5 and above does not need to + self.r[rd] = self.r[rd].wrapping_mul(self.r[rs]); + 4 + } + 0b1110 => { + trace!("BIC r{} = r{} & ~r{}", rd, rs, rd); + self.r[rd] = self.r[rd] & !self.r[rs]; + 1 + } + 0b1111 => { + trace!("MVN r{} = ~r{}", rd, rs); + self.r[rd] = !self.r[rs]; + 1 + } + _ => unreachable!(), + }; + if !skip_end_flags { + self.r.cpsr_set_zero_flag(self.r[rd] == 0); + self.r.cpsr_set_sign_flag(self.r[rd] & 0x8000_0000 != 0); + } + cycles + } + + pub fn dispatch_thumb_load_pc_relative(&mut self, opcode: u16) -> u8 { + let rd = ((opcode >> 8) & 0x7) as u8; + let nn = (opcode & 0xFF) << 2; + let pc = (self.r.pc + 4) & !2; + trace!("LDR r{}, [PC, #{}]", rd, nn); + + let o = self.get_mem32(pc + nn as u32); + self.r[rd] = o.0; + + 2 + o.1 + } + + pub fn dispatch_thumb_hi_reg_branch(&mut self, opcode: u16) -> u8 { + let subop = (opcode >> 8) & 0x3; + let rs = ((opcode >> 3) & 0xF) as u8; + let mut rd = (opcode & 0x7) as u8; + if subop != 3 { + let hi_bit = (opcode >> 7) & 1 == 1; + rd |= (hi_bit as u8) << 3; + } + let cycles = match subop { + 0b00 => { + trace!("ADD r{}, r{}", rd, rs); + self.r[rd] = self.r[rd].wrapping_add(self.r[rs]); + 1 + } + 0b01 => { + trace!("CMP r{}, r{}", rd, rs); + let result = self.r[rd].wrapping_sub(self.r[rs]); + self.r.cpsr_set_zero_flag(result == 0); + self.r.cpsr_set_sign_flag(result & 0x8000_0000 != 0); + // TODO: review this + self.r.cpsr_set_overflow_flag(false); + self.r.cpsr_set_carry_flag(false); + 1 + } + 0b10 => { + trace!("MOV r{}, r{}", rd, rs); + self.r[rd] = self.r[rs]; + 1 + } + 0b11 => { + let x_flag = (opcode >> 7) & 1 == 1; + let thumb_mode = self.r[rs] & 1 == 1; + if self.r.thumb_enabled() != thumb_mode { + if thumb_mode { + info!("Enabling Thumb mode"); + } else { + info!("Enabling ARM mode!"); + } + } + self.r.set_thumb(thumb_mode); + if x_flag { + trace!("BLX r{}", rs); + let old_pc = self.r.pc; + self.r.pc = (self.r[rs] + 4) & !2; + *self.r.lr_mut() = old_pc + 3; + } else { + trace!("BX r{}", rs); + //self.r.pc = (self.r[rs] + 4) & !2; + self.r.pc = (self.r[rs] + 2) & !1; + } + 3 + } + _ => unreachable!(), + }; + cycles + } + + pub fn dispatch_thumb_load_store_reg(&mut self, opcode: u16) -> u8 { + let sub_op_idx = (opcode >> 11) & 0x3; + let ro = (opcode >> 6 & 0x7) as u8; + let rb = (opcode >> 3 & 0x7) as u8; + let rd = (opcode & 0x7) as u8; + + match sub_op_idx { + 0b00 => { + trace!("STR r{}, [r{}, r{}]", rd, rb, ro); + let o = self.set_mem32(self.r[rb] + self.r[ro], self.r[rd]); + 1 + o + } + 0b01 => { + trace!("STRB r{}, [r{}, r{}]", rd, rb, ro); + let o = self.set_mem8(self.r[rb] + self.r[ro], self.r[rd] as u8); + 1 + o + } + 0b10 => { + trace!("LDR r{}, [r{}, r{}]", rd, rb, ro); + let o = self.get_mem32(self.r[rb].wrapping_add(self.r[ro])); + self.r[rd] = o.0; + 2 + o.1 + } + 0b11 => { + trace!("LDRB r{}, [r{}, r{}]", rd, rb, ro); + let o = self.get_mem8(self.r[rb].wrapping_add(self.r[ro])); + self.r[rd] = o.0 as u32; + 2 + o.1 + } + _ => unreachable!(), + } + } + + pub fn dispatch_thumb_load_store_halfword_sign_extend(&mut self, opcode: u16) -> u8 { + let sub_opcode = (opcode >> 10) & 0x3; + let ro = (opcode >> 6 & 0x7) as u8; + let rb = (opcode >> 3 & 0x7) as u8; + let rd = (opcode & 0x7) as u8; + + match sub_opcode { + 0b00 => { + trace!("STRH r{}, [r{}, r{}]", rd, rb, ro); + let o = self.set_mem16(self.r[rb].wrapping_add(self.r[ro]), self.r[rd] as u16); + 1 + o + } + 0b01 => { + trace!("LDSB r{}, [r{}, r{}]", rd, rb, ro); + let o = self.get_mem8(self.r[rb].wrapping_add(self.r[ro])); + // TODO: double check that this sign extends properly + self.r[rd] = (o.0 as i8) as i32 as u32; + 2 + o.1 + } + 0b10 => { + trace!("LDRH r{}, [r{}, r{}]", rd, rb, ro); + let o = self.get_mem16(self.r[rb].wrapping_add(self.r[ro])); + self.r[rd] = o.0 as u32; + 2 + o.1 + } + 0b11 => { + trace!("LDSH r{}, [r{}, r{}]", rd, rb, ro); + let o = self.get_mem16(self.r[rb].wrapping_add(self.r[ro])); + // TODO: double check that this sign extends properly + self.r[rd] = (o.0 as i16) as i32 as u32; + 2 + o.1 + } + _ => unreachable!(), + } + } + + pub fn dispatch_thumb_load_store_halfword(&mut self, opcode: u16) -> u8 { + let sub_opcode = (opcode >> 11) & 0x1 == 1; + let offset = (opcode >> 6 & 0x1F) as u32; + let rb = (opcode >> 3 & 0x7) as u8; + let rd = (opcode & 0x7) as u8; + if sub_opcode { + // LDRH + trace!("LDRH r{}, [r{}, r{}]", rd, rb, offset); + let o = self.get_mem16(self.r[rb] + (offset * 2)); + self.r[rd] = o.0 as u32; + 2 + o.1 + } else { + // STRH + trace!("STRH r{}, [r{}, r{}]", rd, rb, offset); + let o = self.set_mem16(self.r[rb] + (offset * 2), self.r[rd] as u16); + 1 + o + } + } + + pub fn dispatch_thumb_load_store_imm(&mut self, opcode: u16) -> u8 { + let sub_op_idx = (opcode >> 11) & 0x3; + let offset = (opcode >> 6 & 0x1F) as u32; + let rb = (opcode >> 3 & 0x7) as u8; + let rd = (opcode & 0x7) as u8; + + match sub_op_idx { + 0b00 => { + trace!("STR r{}, [r{}, #{}]", rd, rb, offset); + let o = self.set_mem32(self.r[rb].wrapping_add(offset * 4), self.r[rd]); + // 2N? + 1 + o + } + 0b01 => { + trace!("LDR r{}, [r{}, #{}]", rd, rb, offset); + let o = self.get_mem32(self.r[rb].wrapping_add(offset * 4)); + self.r[rd] = o.0; + 2 + o.1 + } + 0b10 => { + trace!("STRB r{}, [r{}, #{}]", rd, rb, offset); + let o = self.set_mem8(self.r[rb].wrapping_add(offset), self.r[rd] as u8); + // 2N? + 1 + o + } + 0b11 => { + trace!("LDRB r{}, [r{}, #{}]", rd, rb, offset); + // TODO: do we clear the upper 24 bits here? + let o = self.get_mem8(self.r[rb].wrapping_add(offset)); + self.r[rd] = o.0 as u32; + 2 + o.1 + } + _ => unreachable!(), + } + } + + pub fn dispatch_thumb_load_store_multiple(&mut self, opcode: u16) -> u8 { + let subop = (opcode >> 11) & 0x1 == 1; + let rb = (opcode >> 8 & 0x7) as u8; + let r_list = opcode & 0xFF; + // Execution Time: nS+1N+1I for LDM, or (n-1)S+2N for STM. + let mut cycles = 0; + + if subop { + trace!("LDMIA r{}, {{{}}}", rb, r_list); + for i in 0..8 { + if r_list & (1 << i) != 0 { + cycles += 2; + let o = self.get_mem32(self.r[rb]); + self.r[rb] = self.r[rb].wrapping_add(4); + self.r[i as u8] = o.0; + cycles += o.1; + } + } + } else { + trace!("STMIA r{}, {{{}}}", rb, r_list); + for i in 0..8 { + if r_list & (1 << i) != 0 { + cycles += 1; + cycles += self.set_mem32(self.r[rb], self.r[i as u8]); + self.r[rb] = self.r[rb].wrapping_add(4); + } + } + } + cycles + } + + pub fn dispatch_thumb_load_store_sp_relative(&mut self, opcode: u16) -> u8 { + let subop = (opcode >> 11) & 0x1 == 1; + let rd = (opcode >> 8 & 0x7) as u8; + let nn = (opcode & 0xFF) as u32; + + if subop { + trace!("LDR r{}, [SP, #{}]", rd, nn); + let o = self.get_mem32(self.r.sp() + (nn * 4)); + self.r[rd] = o.0; + o.1 + 2 + } else { + trace!("STR r{}, [SP, #{}]", rd, nn); + let o = self.set_mem32(self.r.sp() + (nn * 4), self.r[rd]); + o * 2 + } + } + + pub fn dispatch_thumb_get_relative_address(&mut self, opcode: u16) -> u8 { + let subop = (opcode >> 11) & 0x1 == 1; + let rd = (opcode >> 8 & 0x7) as u8; + let nn = (opcode & 0xFF) as u32; + + if subop { + trace!("ADD r{}, SP, #{}", rd, nn); + self.r[rd] = self.r.sp() + (nn * 4); + } else { + trace!("ADD r{}, PC, #{}", rd, nn); + self.r[rd] = ((self.r.pc + 4) & !2) + (nn * 4); + } + + 1 + } + + pub fn dispatch_thumb_push_pop(&mut self, opcode: u16) -> u8 { + let subop = (opcode >> 11) & 0x1 == 1; + let pc_lr = (opcode >> 8) & 0x1 == 1; + let r_list = opcode & 0xFF; + let mut cycles = 0; + + if subop { + trace!("POP"); + if pc_lr { + let o = self.get_mem32(self.r.sp()); + *self.r.sp_mut() += 4; + self.r.pc = o.0 & !1; + cycles += o.1; + cycles += 2; + } + // is reverse correct? do we need to do it elsewhere? + for i in (0..8).rev() { + if r_list & (1 << i) != 0 { + let o = self.get_mem32(self.r.sp()); + *self.r.sp_mut() += 4; + self.r[i as u8] = o.0; + cycles += o.1; + cycles += 2; + } + } + // 0 1 2 3 4 + // 4 3 2 1 0 + } else { + trace!("PUSH"); + for i in 0..8 { + if r_list & (1 << i) != 0 { + cycles += 1; + cycles += self.set_mem32(self.r.sp(), self.r[i as u8]); + *self.r.sp_mut() -= 4; + } + } + if pc_lr { + cycles += 1; + cycles += self.set_mem32(self.r.sp(), self.r.lr()); + *self.r.sp_mut() -= 4; + } + } + + cycles + } + + pub fn dispatch_thumb_add_offset_to_sp(&mut self, opcode: u16) -> u8 { + let sign = (opcode >> 7) & 0x1 == 1; + let nn = (opcode & 0x7F) as u32; + if sign { + trace!("SUB SP, #{}", nn); + *self.r.sp_mut() -= nn * 4; + } else { + trace!("ADD SP, #{}", nn); + *self.r.sp_mut() += nn * 4; + } + 1 + } + + pub fn dispatch_thumb_imm(&mut self, opcode: u16) -> u8 { + let sub_op_idx = (opcode >> 11) & 0x3; + let rd = (opcode >> 8 & 0x7) as u8; + let imm = (opcode & 0xFF) as u32; + let old_val = self.r[rd]; + + match sub_op_idx { + // MOV + 0b00 => { + trace!("MOV r{}, #{}", rd, imm); + self.r[rd] = imm; + self.r.cpsr_set_zero_flag(self.r[rd] == 0); + self.r.cpsr_set_sign_flag(self.r[rd] & 0x8000_0000 != 0); + } + // CMP + 0b01 => { + trace!("CMP r{}, #{}", rd, imm); + let result = self.r[rd].wrapping_sub(imm); + self.r.cpsr_set_zero_flag(result == 0); + self.r.cpsr_set_sign_flag(result & 0x8000_0000 != 0); + // TODO: review this + self.r.cpsr_set_overflow_flag(false); + self.r.cpsr_set_carry_flag(false); + } + // ADD + 0b10 => { + trace!("ADD r{}, #{}", rd, imm); + self.r[rd] = self.r[rd].wrapping_add(imm); + self.r.cpsr_set_zero_flag(self.r[rd] == 0); + self.r.cpsr_set_sign_flag(self.r[rd] & 0x8000_0000 != 0); + self.r.cpsr_set_overflow_flag( + ((old_val ^ imm) & 0x8000_0000 == 0) + && ((old_val ^ self.r[rd]) & 0x8000_0000 != 0), + ); + self.r + .cpsr_set_carry_flag(old_val.checked_add(imm).is_none()); + } + // SUB + 0b11 => { + trace!("SUB r{}, #{}", rd, imm); + self.r[rd] = self.r[rd].wrapping_sub(imm); + self.r.cpsr_set_zero_flag(self.r[rd] == 0); + self.r.cpsr_set_sign_flag(self.r[rd] & 0x8000_0000 != 0); + // TODO: review this + self.r.cpsr_set_overflow_flag(false); + self.r.cpsr_set_carry_flag(false); + } + _ => unreachable!(), + } + + 1 + } + + pub fn dispatch_thumb_shift_add_sub(&mut self, opcode: u16) -> u8 { + let sub_op_idx = (opcode >> 11) & 0x3; + let offset = opcode >> 6 & 0x1F; + let rs = (opcode >> 3 & 0x7) as u8; + let rd = (opcode & 0x7) as u8; + match sub_op_idx { + // LSL + 0b00 => { + trace!("LSL r{}, r{}, #{}", rd, rs, offset); + self.r[rd] = self.r[rs] << offset; + // shift of 0 = don't modify carry flag + if offset != 0 { + // TODO: review this + self.r + .cpsr_set_carry_flag(self.r[rs] & (1 << (32 - offset)) != 0); + } + self.r.cpsr_set_sign_flag(self.r[rd] & 0x8000_0000 != 0); + self.r.cpsr_set_zero_flag(self.r[rd] == 0); + } + 0b01 => { + trace!("LSR r{}, r{}, #{}", rd, rs, offset); + self.r[rd] = self.r[rs] >> offset; + // shift of 0 = don't modify carry flag + if offset != 0 { + // TODO: review this + self.r + .cpsr_set_carry_flag(self.r[rs] & (1 << (offset - 1)) != 0); + } + self.r.cpsr_set_sign_flag(self.r[rd] & 0x8000_0000 != 0); + self.r.cpsr_set_zero_flag(self.r[rd] == 0); + } + 0b10 => { + trace!("ASR r{}, r{}, #{}", rd, rs, offset); + self.r[rd] = ((self.r[rs] as i32) >> offset) as u32; + // shift of 0 = don't modify carry flag + if offset != 0 { + // TODO: review this + self.r + .cpsr_set_carry_flag(self.r[rs] & (1 << (offset - 1)) != 0); + } + self.r.cpsr_set_sign_flag(self.r[rd] & 0x8000_0000 != 0); + self.r.cpsr_set_zero_flag(self.r[rd] == 0); + } + 0b11 => { + let sub_sub_op_idx = (opcode >> 9) & 0x3; + let reg_or_imm = ((opcode >> 6) & 0x7) as u32; + let result; + let op2; + let old_value = self.r[rd]; + match sub_sub_op_idx { + 0b00 => { + trace!("ADD r{}, r{}, r{}", rd, rs, reg_or_imm); + op2 = self.r[reg_or_imm as u8]; + result = self.r[rs].wrapping_add(op2); + self.r[rd] = result; + self.r + .cpsr_set_carry_flag(self.r[rs].checked_add(op2).is_none()); + } + 0b01 => { + trace!("SUB r{}, r{}, r{}", rd, rs, reg_or_imm); + op2 = self.r[reg_or_imm as u8]; + result = self.r[rs].wrapping_sub(op2); + self.r[rd] = result; + // REVIEW: + self.r.cpsr_set_carry_flag(false); + } + 0b10 => { + trace!("ADD r{}, r{}, #{}", rd, rs, reg_or_imm); + op2 = reg_or_imm; + result = self.r[rs].wrapping_add(op2); + self.r[rd] = result; + self.r + .cpsr_set_carry_flag(self.r[rs].checked_add(op2).is_none()); + } + 0b11 => { + trace!("SUB r{}, r{}, #{}", rd, rs, reg_or_imm); + op2 = reg_or_imm; + result = self.r[rs].wrapping_sub(op2); + self.r[rd] = result; + // REVIEW: + self.r.cpsr_set_carry_flag(false); + } + _ => unreachable!(), + } + + self.r.cpsr_set_zero_flag(self.r[rd] == 0); + self.r.cpsr_set_sign_flag(self.r[rd] & 0x8000_0000 != 0); + self.r.cpsr_set_overflow_flag( + ((self.r[rs] ^ op2) & 0x8000_0000 == 0) + && ((self.r[rs] ^ result) & 0x8000_0000 != 0), + ); + } + _ => unreachable!(), + } + + 1 + } + + pub fn dispatch_thumb_conditional_branch(&mut self, opcode: u16) -> u8 { + let cond = Cond::from_u8(((opcode >> 8) & 0xF) as u8); + let nn = (opcode & 0xFF) as i8 as i32 * 2; + if self.cond_should_execute(cond) { + self.r.pc = ((self.r.pc as i32) + 2 + nn) as u32; + 3 + } else { + 1 + } + } + + pub fn dispatch_thumb_branch(&mut self, opcode: u16) -> u8 { + let sub_op_idx = (opcode >> 11) & 0x3; + match sub_op_idx { + 0b00 => { + // unconditional branch, thumb.18 + let sign = (opcode >> 10) & 1 == 1; + let signed_offset = (opcode & 0x7FF) as i16 | ((if sign { 0xF8 } else { 0 }) << 8); + //println!("{} {:b} ({:b})", sign, signed_offset, signed_offset as i32); + let signed_offset = signed_offset as i32 * 2; + trace!("B #{:X} + 2 + #{:X}", self.r.pc, signed_offset); + self.r.pc = (self.r.pc as i32 + 2 + signed_offset) as u32; + 3 + } + 0b10 => { + trace!("BL (part 1)"); + let n = (opcode & 0x7FF) as u32; + *self.r.lr_mut() = self.r.pc + 4 + /*2 +*/ (n << 12); + 1 + } + 0b11 | 0b01 => { + let n = (opcode & 0x7FF) as u32; + let old_pc = self.r.pc; //+ 2; + // add 2 here? + // as this is the second half of the instruction, probably not... + self.r.pc = self.r.lr() + (n << 1); + trace!("BL to 0x{:X}", self.r.pc); + *self.r.lr_mut() = old_pc; + 3 + } + // I think 0b01 is only on ARM9, so let's just route it to the same BL + /* + 0b01 => { + todo!("Second opcode for THUMB branch long with link"); + } + */ + _ => unimplemented!("unknown sub-op-idx for THUMB branch: {:b}", sub_op_idx), + } + } + + pub fn dispatch_codata_op(&mut self, opcode: u32) -> u8 { + let final_bit = (opcode >> 24) & 1 == 1; + if final_bit { + // software interrupt + // HACK: sub 4 so PC increments to correct address + self.r.pc = 0x8 - 4; + //self.r.pc = 0x03007F08 - 4; + self.r.set_svc_mode(); + } else { + let coproc_opcode = (opcode >> 21) & 0x7; + todo!() + } + // TODO: timing + 5 + } + + // branch and branch and link + pub fn dispatch_branch(&mut self, opcode: u32) -> u8 { + let sub_opcode = (opcode >> 24) & 1 == 1; + let sign = (opcode >> 23) & 1 == 1; + let signed_offset = (opcode & 0xFF_FFFF) as i32 | (if sign { 0xFF } else { 0 } << 24); + /* + let signed_offset = if sign { + let bit = 1 << 23; + let num24bit = (opcode & 0xFF_FFFF) as i32; + (num24bit ^ bit) - bit + + } else { + (opcode & 0xFF_FFFF) as i32 + }; + */ + + if sub_opcode { + *self.r.lr_mut() = self.r.pc + 4; + } + let new_pc = self.r.pc as i32 + 4 + (signed_offset * 4); + trace!( + "Branching at 0x{:X} to 0x{:X} with offset {} {:b}", + self.r.pc, + new_pc, + signed_offset, + signed_offset + ); + self.r.pc = new_pc as u32; + + // TODO: timing + // 2S + 1N + 3 + } + + pub fn dispatch_alu(&mut self, opcode: u32) -> u8 { + let sub_opcode = ((opcode >> 21) as u8) & 0xF; + + let s = (opcode >> 20) & 1 == 1; + let imm = (opcode >> 25) & 1 == 1; + let op_reg = (opcode >> 16) & 0xF; + // if it's the PC, we do extra logic + let op_reg = if op_reg == 0xE { 0 } else { op_reg }; + let dest_reg = (opcode >> 12) & 0xF; + let mut op1 = self.r[op_reg as u8]; + let mut op2; + + if op_reg == 0xF { + if !imm && (opcode >> 4) & 1 == 1 { + op1 += 12; + } else { + op1 += 8; + } + } + + if imm { + let ror_shift = (opcode >> 8) & 0xF; + op2 = opcode & 0xFF; + if ror_shift != 0 { + op2 = op2.rotate_right(ror_shift * 2); + self.r.cpsr_set_carry_flag(op2 & 0x8000_0000 != 0); + } + } else { + let shift_by_register = (opcode >> 4) & 1 == 1; + let rm = opcode & 0xF; + let shift_amt = if shift_by_register { + let shift_reg_idx = (opcode >> 8) & 0xF; + // docs say this must be true, TODO: deal with this later + assert_eq!((opcode >> 7) & 1, 0); + if shift_reg_idx > 14 { + warn!( + "shift reg too high in ALU shift by reg: {} in {:X}", + shift_reg_idx, sub_opcode + ); + //panic!("shift reg too high in ALU shift by reg: {}", shift_reg_idx); + } + (self.r[shift_reg_idx as u8] & 0xFF) as u8 + } else { + ((opcode >> 7) & 0x1F) as u8 + }; + let shift_type = (opcode >> 5) & 0b11; + + if shift_amt == 0 { + // shift amount of 0 is a special case + /* + Zero Shift Amount (Shift Register by Immediate, with Immediate=0) + + LSL#0: No shift performed, ie. directly Op2=Rm, the C flag is NOT affected. + LSR#0: Interpreted as LSR#32, ie. Op2 becomes zero, C becomes Bit 31 of Rm. + ASR#0: Interpreted as ASR#32, ie. Op2 and C are filled by Bit 31 of Rm. + ROR#0: Interpreted as RRX#1 (RCR), like ROR#1, but Op2 Bit 31 set to old C. + + */ + match shift_type { + // LSL + 0 => { + op2 = self.r[rm as u8]; + } + // LSR + 1 => todo!("LSR zero shift amount handling"), + // ASR + 2 => { + op2 = ((self.r[rm as u8] as i32) >> 31) as u32; + self.r.cpsr_set_carry_flag(op2 & 1 == 1); + } + // ROR + 3 => todo!("ROR zero shift amount handling"), + _ => unreachable!(), + } + } else { + match shift_type { + // LSL + 0 => { + // TODO: review overflowing here + op2 = self.r[rm as u8].overflowing_shl(shift_amt as _).0; + } + // LSR + 1 => { + op2 = self.r[rm as u8] >> shift_amt; + } + // ASR + 2 => todo!("ASR"), + // ROR + 3 => todo!("ROR"), + _ => unreachable!(), + } + } + } + + // detect if ALU instruction is actually an MRS/MSR: PSR transfer + // TODO: op_reg != 0xF = SWP + if (sub_opcode >> 2) == 0b10 && !s { + let psr_src_dest = (opcode >> 22) & 1 == 1; + let psr_subopcode = (opcode >> 21) & 1 == 1; + if psr_subopcode { + trace!("MSR"); + // MSR: Psr[field] = Op + let write_flags = (opcode >> 19) & 1 == 1; + let write_status = (opcode >> 18) & 1 == 1; + let write_extension = (opcode >> 17) & 1 == 1; + let write_control = (opcode >> 16) & 1 == 1; + let mask = { + let mut mask = 0; + if write_flags { + mask |= 0xFF << 24; + } + if write_status { + mask |= 0xFF << 16; + } + if write_extension { + mask |= 0xFF << 8; + } + if write_control { + mask |= 0xFF; + } + mask + }; + let val = if imm { + let shift_amt = (opcode >> 8) & 0xF; + (opcode & 0xFF).rotate_right(shift_amt * 2) + // TODO: set flags? + } else { + debug_assert_eq!((opcode >> 4) & 0xFF, 0); + let reg_idx = opcode & 0xF; + debug_assert!(reg_idx < 15); + self.r[reg_idx as u8] + }; + // HACK: if in user mode, just force CPSR + if psr_src_dest && self.r.register_mode() != Some(RegisterMode::User) { + *self.r.get_spsr_mut() &= !mask; + *self.r.get_spsr_mut() |= val & mask; + } else { + self.r.cpsr &= !mask; + self.r.cpsr |= val & mask; + } + } else { + if op_reg != 0xF { + let byte = (opcode >> 22) & 1 == 1; + let rn = ((opcode >> 16) & 0xF) as u8; + let rd = ((opcode >> 12) & 0xF) as u8; + let rm = (opcode & 0xF) as u8; + let mut cycles = 0; + + trace!("SWP r{} r{} [r{}]", rd, rm, rn); + + if byte { + let o = self.get_mem8(self.r[rn]); + self.r[rd] = o.0 as u32; + cycles += o.1; + cycles += self.set_mem8(self.r[rn], self.r[rm] as u8); + } else { + let o = self.get_mem32(self.r[rn]); + self.r[rd] = o.0; + cycles += o.1; + cycles += self.set_mem32(self.r[rn], self.r[rm]); + } + + return 4 + cycles; + } + + trace!("MRS"); + debug_assert_eq!(opcode & 0xFFF, 0); + debug_assert!(dest_reg < 15); + debug_assert_eq!(imm, false); + + // HACK: if in user mode just force CPSR + // MRS: Rd = Psr + let v = if psr_src_dest && self.r.register_mode() != Some(RegisterMode::User) { + self.r.get_spsr() + } else { + self.r.cpsr + }; + + self.r[dest_reg as u8] = v; + } + return 1; + } + match sub_opcode { + // AND + 0x0 => { + trace!("AND r{:X} = {:X} & {:X}", dest_reg, op1, op2); + let result = op1 & op2; + self.r[dest_reg as u8] = result; + if s { + self.r.cpsr_set_zero_flag(result == 0); + self.r.cpsr_set_sign_flag((result >> 31) == 1); + } + } + // EOR + 0x1 => { + trace!("EOR r{:X} = {:X} ^ {:X}", dest_reg, op1, op2); + let result = op1 ^ op2; + self.r[dest_reg as u8] = result; + if s { + self.r.cpsr_set_zero_flag(result == 0); + self.r.cpsr_set_sign_flag((result >> 31) == 1); + } + } + // SUB + 0x2 => { + trace!("SUB r{:X} = {:X} - {:X}", dest_reg, op1, op2); + let result = op1.wrapping_sub(op2); + self.r[dest_reg as u8] = result; + if s { + self.r.cpsr_set_zero_flag(result == 0); + self.r.cpsr_set_sign_flag(result & 0x8000_0000 != 0); + // TODO: review this + self.r.cpsr_set_overflow_flag(false); + self.r.cpsr_set_carry_flag(false); + } + } + // ADD + 0x4 => { + trace!("ADD r{:X} = {:X} + {:X}", dest_reg, op1, op2); + let result = op1.wrapping_add(op2); + self.r[dest_reg as u8] = result; + if s { + self.r.cpsr_set_zero_flag(result == 0); + self.r.cpsr_set_sign_flag((result >> 31) == 1); + self.r.cpsr_set_overflow_flag( + ((op1 ^ op2) & 0x8000_0000 == 0) && ((op1 ^ result) & 0x8000_0000 != 0), + ); + self.r + .cpsr_set_carry_flag(((op1 as u64) + (op2 as u64)) > 0xFFFF_FFFF); + } + } + // ADC + 0x5 => { + trace!("ADC r{:X} = {:X} + {:X} + C", dest_reg, op1, op2); + let result = op1 + .wrapping_add(op2) + .wrapping_add(self.r.cpsr_carry_flag() as u32); + self.r[dest_reg as u8] = result; + if s { + self.r.cpsr_set_zero_flag(result == 0); + self.r.cpsr_set_sign_flag((result >> 31) == 1); + self.r.cpsr_set_overflow_flag( + ((op1 ^ op2) & 0x8000_0000 == 0) && ((op1 ^ result) & 0x8000_0000 != 0), + ); + self.r + .cpsr_set_carry_flag(((op1 as u64) + (op2 as u64)) > 0xFFFF_FFFF); + } + } + // SBC + 0x6 => { + trace!("SBC r{:X} = {:X} - {:X} + C", dest_reg, op1, op2); + let result = op1 + .wrapping_sub(op2) + .wrapping_add(self.r.cpsr_carry_flag() as u32) + .wrapping_sub(1); + self.r[dest_reg as u8] = result; + if s { + self.r.cpsr_set_zero_flag(result == 0); + self.r.cpsr_set_sign_flag((result >> 31) == 1); + self.r.cpsr_set_overflow_flag( + ((op1 ^ op2) & 0x8000_0000 == 0) && ((op1 ^ result) & 0x8000_0000 != 0), + ); + // TODO: carry flag + } + } + // RSC + 0x7 => { + trace!("RSC r{:X} = {:X} - {:X} + C", dest_reg, op2, op1); + let result = op2 + .wrapping_sub(op1) + .wrapping_add(self.r.cpsr_carry_flag() as u32) + .wrapping_sub(1); + self.r[dest_reg as u8] = result; + if s { + self.r.cpsr_set_zero_flag(result == 0); + self.r.cpsr_set_sign_flag((result >> 31) == 1); + self.r.cpsr_set_overflow_flag( + ((op1 ^ op2) & 0x8000_0000 == 0) && ((op1 ^ result) & 0x8000_0000 != 0), + ); + // TODO: carry flag + } + } + // TST + 0x8 => { + trace!("TST r{:X} = {:X} & {:X}", dest_reg, op1, op2); + let result = op1 & op2; + if s { + self.r.cpsr_set_zero_flag(result == 0); + self.r.cpsr_set_sign_flag((result >> 31) == 1); + } + } + // TEQ + 0x9 => { + trace!("TEQ r{:X} = {:X} ^ {:X}", dest_reg, op1, op2); + let result = op1 ^ op2; + if s { + self.r.cpsr_set_zero_flag(result == 0); + self.r.cpsr_set_sign_flag((result >> 31) == 1); + } + } + // CMP + 0xA => { + trace!("CMP r{:X} = {:X} - {:X}", dest_reg, op1, op2); + let result = op1.wrapping_sub(op2); + if s { + self.r.cpsr_set_zero_flag(result == 0); + self.r.cpsr_set_sign_flag((result >> 31) == 1); + self.r.cpsr_set_overflow_flag(false); + self.r.cpsr_set_carry_flag(false); + } + } + // ORR + 0xC => { + trace!("ORR r{:X} = {:X} | {:X}", dest_reg, op1, op2); + let result = op1 | op2; + self.r[dest_reg as u8] = result; + if s { + self.r.cpsr_set_zero_flag(result == 0); + self.r.cpsr_set_sign_flag((result >> 31) == 1); + } + } + // MOV + 0xD => { + trace!("MOV r{:X} = {:X}", dest_reg, op2); + self.r[dest_reg as u8] = op2; + if s { + self.r.cpsr_set_zero_flag(op2 == 0); + self.r.cpsr_set_sign_flag((op2 >> 31) == 1); + } + } + // BIC + 0xE => { + trace!("BIC r{:X} = {:X} & !{:X}", dest_reg, op1, op2); + let result = op1 & !op2; + self.r[dest_reg as u8] = result; + if s { + self.r.cpsr_set_zero_flag(result == 0); + self.r.cpsr_set_sign_flag((result >> 31) == 1); + } + } + // MVN + 0xF => { + trace!("MVN r{:X} = !{:X}", dest_reg, op2); + let result = !op2; + self.r[dest_reg as u8] = result; + if s { + self.r.cpsr_set_zero_flag(result == 0); + self.r.cpsr_set_sign_flag((result >> 31) == 1); + // carry flag from shift + } + } + _ => unimplemented!("ALU instruction 0x{:X} at 0x{:X}", sub_opcode, self.r.pc), + } + + // HACK: don't set CPSR if in user mode + // TODO: this is probably wrong, we should avoid modifying flags in user mode too + if s && dest_reg == 0xF && self.r.register_mode() != Some(RegisterMode::User) { + self.r.cpsr = self.r.get_spsr(); + } + + // TODO: writing to PC affects things + // TODO: timing + // "Execution Time: (1+p)S+rI+pN. Whereas r=1 if I=0 and R=1 (ie. shift by register); otherwise r=0. And p=1 if Rd=R15; otherwise p=0."" + + 4 + } + + // ARM Opcodes: Memory: Single Data Transfer (LDR, STR, PLD) + pub fn dispatch_mem(&mut self, opcode: u32) -> u8 { + let mut cycles = 0; + let imm = (opcode >> 25) & 1 == 1; + let p = (opcode >> 24) & 1 == 1; + let up = (opcode >> 23) & 1 == 1; + let byte = (opcode >> 22) & 1 == 1; + // only when P is false + let force_nonpriviliged = (opcode >> 21) & 1 == 1; + let write_back = !p || ((opcode >> 21) & 1 == 1); + let load = (opcode >> 20) & 1 == 1; + // TODO: R15 may need special logic here + let base_reg = (opcode >> 16) & 0xF; + // TODO: R15 may need special logic here + let src_dest_reg = (opcode >> 12) & 0xF; + + let offset = if imm { + let shift_type = (opcode >> 5) & 3; + let rm = (opcode & 0xF) as u8; + let shift_amount = (opcode >> 7) & 0x1F; + if shift_amount == 0 { + // TODO: + //panic!("Special logic here? docs don't describe it much, maybe we ignore it here"); + warn!("Special logic here? docs don't describe it much, maybe we ignore it here"); + } + match shift_type { + 0b00 => self.r[rm] << shift_amount, + 0b01 => self.r[rm] >> shift_amount, + 0b10 => ((self.r[rm] as i32) >> shift_amount) as u32, + 0b11 => self.r[rm].rotate_right(shift_amount), + _ => unreachable!(), + } + } else { + opcode & 0xFFF + }; + + let mut val = self.r[base_reg as u8]; + if base_reg == 15 { + val += 8; + } + if p { + if up { + val = val.wrapping_add(offset); + } else { + val = val.wrapping_sub(offset); + } + } + + if load { + if byte { + trace!("LDRB r{:X} = mem[{:X}]", src_dest_reg, val); + let o = self.get_mem8(val); + cycles += o.1; + // TODO: does this zero the high bits? + /* + self.r[src_dest_reg as u8] &= !0xFF; + self.r[src_dest_reg as u8] |= o.0 as u32; + */ + self.r[src_dest_reg as u8] = o.0 as u32; + } else { + trace!("LDR r{:X} = mem[{:X}]", src_dest_reg, val); + let o = self.get_mem32(val); + cycles += o.1; + self.r[src_dest_reg as u8] = o.0; + } + } else { + if byte { + trace!("STRB mem[{:X}] = r{:X}", val, src_dest_reg); + cycles += self.set_mem8(val, self.r[src_dest_reg as u8] as u8); + } else { + trace!("STR mem[{:X}] = r{:X}", val, src_dest_reg); + cycles += self.set_mem32(val, self.r[src_dest_reg as u8]); + } + } + if !p { + if up { + val = val.wrapping_add(offset); + } else { + val = val.wrapping_sub(offset); + } + } + + if write_back || !p { + self.r[base_reg as u8] = val; + } + if !p && write_back { + warn!("Write back bit has special meaning in post-inc mode, figure this out"); + //todo!("Write back bit has special meaning in post-inc mode, figure this out"); + } + + cycles + } + + pub fn dispatch_block_data(&mut self, opcode: u32) -> u8 { + let p = (opcode >> 24) & 1 == 1; + let up = (opcode >> 23) & 1 == 1; + let force_user_mode = (opcode >> 22) & 1 == 1; + let write_back = (opcode >> 21) & 1 == 1; + let load = (opcode >> 20) & 1 == 1; + let rn = ((opcode >> 16) & 0xF) as u8; + let r_list = opcode & 0xFFFF; + let mut cycles = 0; + + let mut base = self.r[rn]; + + if force_user_mode { + //todo!("Figure out force user mode"); + warn!("Figure out force user mode"); + } + + if load { + trace!("LDM"); + for i in 0..16 { + if r_list & (1 << i) == 0 { + continue; + } + if p { + base = if up { + base.wrapping_add(4) + } else { + base.wrapping_sub(4) + } + } + let o = self.get_mem32(base); + self.r[i as u8] = o.0; + cycles += o.1 + 2; + if !p { + base = if up { + base.wrapping_add(4) + } else { + base.wrapping_sub(4) + } + } + } + } else { + trace!("STM"); + for i in 0..16 { + if r_list & (1 << i) == 0 { + continue; + } + if p { + base = if up { + base.wrapping_add(4) + } else { + base.wrapping_sub(4) + } + } + cycles += self.set_mem32(base, self.r[i as u8]); + cycles += 1; + if !p { + base = if up { + base.wrapping_add(4) + } else { + base.wrapping_sub(4) + } + } + } + } + if write_back { + self.r[rn] = base; + } + cycles + } + + pub fn dispatch_branch_and_exchange(&mut self, opcode: u32) -> u8 { + let subopcode = (opcode >> 4) & 0xF; + let op_reg = opcode & 0xF; + debug_assert!(op_reg < 15); + let op = self.r[op_reg as u8]; + let thumb_mode = op & 1 == 1; + let new_pc = op - (thumb_mode as u32); + if self.r.thumb_enabled() != thumb_mode { + if thumb_mode { + info!( + "Enabling Thumb mode from 0x{:X} to 0x{:X}!", + self.r.pc, new_pc + ); + } else { + info!("Enabling ARM mode!"); + } + } + match subopcode { + 0b0001 => { + // BX + trace!("BX pc = r{} (0x{:X})", op_reg, op); + self.r.pc = new_pc; + self.r.set_thumb(thumb_mode); + } + 0b0010 => { + panic!("Change to Jazelle mode not implemented"); + } + 0b0011 => { + // BLX + trace!( + "BLX pc = r{} (0x{:X}), lr = 0x{:X}", + op_reg, + op, + self.r.pc + 4 + ); + let old_pc = self.r.pc; + self.r.set_thumb(thumb_mode); + self.r.pc = new_pc; + *self.r.lr_mut() = old_pc + 4; + } + _ => panic!("Unknown branch and exchange subopcode 0x{:X}", subopcode), + } + // 2S + 1N + 3 + } +} diff --git a/src/io/applicationstate.rs b/src/io/applicationstate.rs index 2cd919d..a262b7f 100644 --- a/src/io/applicationstate.rs +++ b/src/io/applicationstate.rs @@ -9,6 +9,7 @@ use crate::cpu; use crate::io::constants::*; use crate::io::deferred_renderer::deferred_renderer_draw_scanline; +use crate::io::deferred_renderer_gba::deferred_renderer_draw_gba_scanline; use crate::io::graphics::renderer::Renderer; use std::num::Wrapping; @@ -16,6 +17,8 @@ use std::num::Wrapping; /// Holds all the data needed to use the emulator in meaningful ways pub struct ApplicationState { pub gameboy: cpu::Cpu, + //pub gba: Option, + pub gba: crate::gba::GameboyAdvance, //sound_system: AudioDevice, //renderer: render::Renderer<'static>, cycle_count: u64, @@ -37,9 +40,12 @@ impl ApplicationState { pub fn new(renderer: Box) -> Result { // Set up gameboy and other state let gameboy = cpu::Cpu::new(); + let gba = crate::gba::GameboyAdvance::new(); Ok(ApplicationState { gameboy, + //gba: Some(gba), + gba, //sound_system: device, cycle_count: 0, prev_time: 0, @@ -61,6 +67,76 @@ impl ApplicationState { } */ + pub fn step_gba(&mut self) { + let cycles_per_frame = 83776 + (160 * /*1232*/ 960); + let mut cycles = 0; + let mut frame = [[(0u8, 0u8, 0u8); GBA_SCREEN_WIDTH]; GBA_SCREEN_HEIGHT]; + let mut y = 0; + + #[derive(Debug, Clone, Copy)] + enum GameBoyAdvanceMode { + HBlank, + VBlank, + } + let mut in_hblank = false; + let mut hblank_cycles = 0; + + while y < 227 { + let cycles_from_opcode = self.gba.dispatch() as u64; + cycles += cycles_from_opcode; + hblank_cycles += cycles_from_opcode; + + if in_hblank { + if hblank_cycles >= 272 { + in_hblank = false; + hblank_cycles -= 272; + self.gba.ppu_set_hblank(false); + } + continue; + } + + if hblank_cycles >= 960 { + hblank_cycles -= 960; + if y < 160 { + let scanline = deferred_renderer_draw_gba_scanline(y, &mut self.gba); + frame[y as usize] = scanline; + } + + y += 1; + in_hblank = true; + self.gba.ppu_set_hblank(true); + if self.gba.ppu_hblank_irq_enabled() { + self.gba.set_lcdc_hblank_interrupt(true); + } + self.gba.ppu_set_readonly_vcounter(y); + if y == self.gba.ppu_vcounter_setting() && self.gba.ppu_vcounter_irq_enabled() { + self.gba.set_lcdc_vcounter_interrupt(true); + } + if y == 160 { + self.gba.ppu_set_vblank(true); + if self.gba.ppu_vblank_irq_enabled() { + self.gba.set_lcdc_vblank_interrupt(true); + } + } + } + } + self.gba.ppu_set_vblank(false); + /* + dbg!( + self.gba.ppu_bg_mode(), + self.gba.ppu_bg0_enabled(), + self.gba.ppu_bg1_enabled(), + self.gba.ppu_bg2_enabled(), + self.gba.ppu_bg3_enabled(), + self.gba.ppu_obj_enabled(), + self.gba.ppu_win0_enabled(), + self.gba.ppu_win1_enabled(), + self.gba.ppu_obj_win_enabled(), + ); + */ + self.renderer.draw_gba_frame(&frame); + } + /// Runs the emulator for 1 frame and requests that frame to be drawn. pub fn step(&mut self) { let ( diff --git a/src/io/constants.rs b/src/io/constants.rs index 777848a..070bcee 100644 --- a/src/io/constants.rs +++ b/src/io/constants.rs @@ -102,6 +102,9 @@ pub const SCREEN_BUFFER_TILES_Y: u32 = 32; pub const GB_SCREEN_WIDTH: usize = 160; pub const GB_SCREEN_HEIGHT: usize = 144; +pub const GBA_SCREEN_HEIGHT: usize = 160; +pub const GBA_SCREEN_WIDTH: usize = 240; + pub const OBJECT_ATTRIBUTE_START: u16 = 0xFE00; pub const OBJECT_ATTRIBUTE_END: u16 = 0xFE9F; pub const OBJECT_ATTRIBUTE_BLOCK_SIZE: u16 = 4; diff --git a/src/io/deferred_renderer_gba.rs b/src/io/deferred_renderer_gba.rs new file mode 100644 index 0000000..b92c652 --- /dev/null +++ b/src/io/deferred_renderer_gba.rs @@ -0,0 +1,64 @@ +use crate::gba; +use crate::io::constants::*; + +pub fn deferred_renderer_draw_gba_scanline( + y: u8, + gba: &mut gba::GameboyAdvance, +) -> [(u8, u8, u8); GBA_SCREEN_WIDTH] { + let mut bg_pixels = [(0u8, 0u8, 0u8); GBA_SCREEN_WIDTH]; + + let scx = gba.ppu_bg0_x_scroll(); + let scy = gba.ppu_bg0_y_scroll(); + let adj_y = (y as u16).wrapping_add(scy) as u16 & 0x1FF; + let bg0_control = gba.ppu_bg0_control(); + let map_base_ptr = bg0_control.screen_base_block as u32 * 0x800; + let tile_base_ptr = bg0_control.character_base_block as u32 * 0x4000; + + let row = (adj_y >> 3) as u32; + for x in 0..GBA_SCREEN_WIDTH { + let adj_x = (x as u16).wrapping_add(scx) as u16 & 0x1FF; + let col = (adj_x >> 3) as u32; + let idx_into_tile_idx_mem = map_base_ptr + (row << 5) + col; + let tile_idx_lo = gba.vram[idx_into_tile_idx_mem as usize] as u16; + let tile_idx_hi = gba.vram[idx_into_tile_idx_mem as usize + 1] as u16; + let tile_num = ((tile_idx_hi & 0x3) << 8) | tile_idx_lo; + let horizontal_flip = (tile_idx_hi & 0x4) != 0; + let vertical_flip = (tile_idx_hi & 0x8) != 0; + let palette_num = tile_idx_hi >> 4; + + // Lower 3 bits determine which line of the tile we're on + let mut nth_line = adj_y & 0x7; + // 8 choices for which pixel on the line we're on, so we take 3 bits here + let tile_pixel = adj_x & 0x7; + // pixels go from MSB to LSB within a tile + let mut nth_pixel = 7 - tile_pixel; + if vertical_flip { + nth_line = 7 - nth_line; + } + if horizontal_flip { + nth_pixel = 7 - nth_pixel; + } + + // 16/16 mode + let tile_line = nth_line * 4; + + let tile_start = tile_base_ptr as usize + (tile_num as usize * 32); + let tile_line_start = tile_start + tile_line as usize; + let tile_byte_start = tile_line_start + (nth_pixel >> 1) as usize; + let color_4bit = gba.vram[tile_byte_start] >> (nth_pixel & 0x1); + + let palette_start = palette_num as usize * 16; + let color_lo = gba.obj_palette_ram[palette_start + (color_4bit as usize * 2)]; + let color_hi = gba.obj_palette_ram[palette_start + (color_4bit as usize * 2) + 1]; + let red = color_lo & 0x1F; + let green = ((color_hi & 0x3) << 3) | (color_lo >> 5); + let blue = (color_hi >> 2) & 0x1F; + if (red | green | blue) != 0 { + panic!("COLOR!"); + } + + bg_pixels[x as usize] = (red << 3, green << 3, blue << 3); + } + + bg_pixels +} diff --git a/src/io/dr_sdl2.rs b/src/io/dr_sdl2.rs index 994b561..e24fbd8 100644 --- a/src/io/dr_sdl2.rs +++ b/src/io/dr_sdl2.rs @@ -72,10 +72,17 @@ impl Sdl2Renderer { let video_subsystem = sdl_context.video()?; let window = { + // TODO: flag to toggle this + /* let (window_width, window_height) = ( ((GB_SCREEN_WIDTH as f32) * 3.0) as u32, ((GB_SCREEN_HEIGHT as f32) * 3.0) as u32, ); + */ + let (window_width, window_height) = ( + ((GBA_SCREEN_WIDTH as f32) * 3.0) as u32, + ((GBA_SCREEN_HEIGHT as f32) * 3.0) as u32, + ); match video_subsystem .window( @@ -198,6 +205,72 @@ impl Renderer for Sdl2Renderer { self.canvas.present(); } + fn draw_gba_frame(&mut self, frame: &[[(u8, u8, u8); GBA_SCREEN_WIDTH]; GBA_SCREEN_HEIGHT]) { + let scale = 3.0; + //app_settings.ui_scale; + match self.canvas.set_scale(scale, scale) { + Ok(_) => (), + Err(_) => error!("Could not set render scale"), + } + + self.canvas.set_draw_color(NICER_COLOR); + self.canvas.clear(); + + let tc = self.canvas.texture_creator(); + let temp_surface = Surface::new( + (GBA_SCREEN_WIDTH as f32) as u32, + (GBA_SCREEN_HEIGHT as f32) as u32, + PixelFormatEnum::RGBA8888, + ) + .unwrap(); + + let mut temp_canvas = temp_surface.into_canvas().unwrap(); + + for y in 0..GBA_SCREEN_HEIGHT { + for x in 0..GBA_SCREEN_WIDTH { + let (r, g, b) = frame[y][x]; + let color = sdl2::pixels::Color::RGB(r, g, b); + + temp_canvas.set_draw_color(color); + temp_canvas + .draw_point(Point::new(x as i32, y as i32)) + .unwrap(); + } + } + + let mut texture = tc + .create_texture_from_surface(&temp_canvas.into_surface()) + .unwrap(); + + texture.set_blend_mode(sdl2::render::BlendMode::None); + + self.canvas + .copy( + &texture, + None, + Some(Rect::new( + 0, + 0, + GBA_SCREEN_WIDTH as u32, + GBA_SCREEN_HEIGHT as u32, + //MEM_DISP_WIDTH as u32, + //MEM_DISP_HEIGHT as u32, + )), + ) + .unwrap(); + + // feature disabled while graphics are being generalized + // TODO add a way to enable/disable this while running + /*let record_screen = false; + if record_screen { + save_screenshot(&self.canvas, + format!("screen{:010}.bmp", self.screenshot_frame_num.0).as_ref()); + self.screenshot_frame_num += Wrapping(1); + }*/ + + self.canvas.present(); + } + fn handle_events(&mut self, gameboy: &mut Cpu) -> Vec { let mut ret_vec: Vec = vec![]; for event in self.sdl_context.event_pump().unwrap().poll_iter() { diff --git a/src/io/graphics/renderer.rs b/src/io/graphics/renderer.rs index f3641b9..fdc87c2 100644 --- a/src/io/graphics/renderer.rs +++ b/src/io/graphics/renderer.rs @@ -1,5 +1,7 @@ use crate::cpu::Cpu; -use crate::io::constants::{GB_SCREEN_HEIGHT, GB_SCREEN_WIDTH}; +use crate::io::constants::{ + GBA_SCREEN_HEIGHT, GBA_SCREEN_WIDTH, GB_SCREEN_HEIGHT, GB_SCREEN_WIDTH, +}; #[derive(Debug, Copy, Clone)] pub enum EventResponse { @@ -9,6 +11,9 @@ pub enum EventResponse { pub trait Renderer { fn draw_frame(&mut self, frame: &[[(u8, u8, u8); GB_SCREEN_WIDTH]; GB_SCREEN_HEIGHT]); + fn draw_gba_frame(&mut self, frame: &[[(u8, u8, u8); GBA_SCREEN_WIDTH]; GBA_SCREEN_HEIGHT]) { + unimplemented!("No GBA support"); + } // TOOD: readd important data to args here later fn draw_memory_visualization(&mut self, _: &Cpu) { unimplemented!(); diff --git a/src/io/mod.rs b/src/io/mod.rs index 7520ff1..87a7a4b 100644 --- a/src/io/mod.rs +++ b/src/io/mod.rs @@ -7,6 +7,7 @@ pub mod applicationstate; pub mod arguments; pub mod constants; pub mod deferred_renderer; +pub mod deferred_renderer_gba; #[cfg(feature = "desktop")] pub mod dr_sdl2; pub mod events; diff --git a/src/main.rs b/src/main.rs index a118542..fb26dd7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,6 +28,8 @@ pub mod disasm; /// Functionality for making the Gameboy emulator useful pub mod io; +pub mod gba; + use crate::debugger::graphics::Debugger; use crate::io::applicationsettings::*; use crate::io::applicationstate::*; @@ -78,6 +80,7 @@ fn main() { }; trace!("loading ROM"); + let is_gba = application_settings.rom_file_name.ends_with("gba"); let rom_bytes = { use std::fs::File; use std::io::Read; @@ -91,7 +94,23 @@ fn main() { .unwrap(); rom_buffer }; - appstate.gameboy.load_rom(rom_bytes); + if is_gba { + info!("GameBoy Advance ROM detected... Attempting to run"); + appstate.gba.load_rom(rom_bytes); + if std::path::Path::new("../roms/gba_bios.bin").exists() { + use std::io::Read; + info!("Loading BIOS"); + let mut bios = std::fs::File::open("../roms/gba_bios.bin").unwrap(); + let mut bios_buffer = Vec::with_capacity(0x4000); + bios.read_to_end(&mut bios_buffer).unwrap(); + + appstate.gba.load_bios(&bios_buffer); + } else { + info!("No BIOS found, skipping"); + } + } else { + appstate.gameboy.load_rom(rom_bytes); + } // application_settings.data_path.clone(), // delay debugger so loading rom can be logged if need be @@ -126,9 +145,13 @@ fn main() { } } - appstate.step(); - if let Some(ref mut dbg) = debugger { - dbg.step(&mut appstate.gameboy); + if is_gba { + appstate.step_gba(); + } else { + appstate.step(); + if let Some(ref mut dbg) = debugger { + dbg.step(&mut appstate.gameboy); + } } /*//check for new controller every frame From 0a94b97f8b1e97f17320600d8990ca6c5a5c1294 Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Wed, 19 Jul 2023 23:14:52 +0800 Subject: [PATCH 2/6] Add significant progress --- src/cpu/mod.rs | 40 ++ src/gba/mod.rs | 1011 +++++++++++++++++++++++-------- src/io/applicationstate.rs | 25 + src/io/deferred_renderer_gba.rs | 33 +- src/io/dr_sdl2.rs | 83 +-- src/io/graphics/renderer.rs | 24 +- src/io/graphics/sdl2/mod.rs | 96 +-- src/main.rs | 50 +- 8 files changed, 1004 insertions(+), 358 deletions(-) diff --git a/src/cpu/mod.rs b/src/cpu/mod.rs index 92b30e9..371ef8a 100644 --- a/src/cpu/mod.rs +++ b/src/cpu/mod.rs @@ -2290,3 +2290,43 @@ impl Cpu { } } } + +use crate::io::graphics::renderer::Button; + +impl crate::io::graphics::renderer::InputReceiver for Cpu { + fn press(&mut self, button: Button) { + match button { + Button::A => self.press_a(), + Button::B => self.press_b(), + Button::Start => self.press_start(), + Button::Select => self.press_select(), + Button::Up => self.press_up(), + Button::Down => self.press_down(), + Button::Left => self.press_left(), + Button::Right => self.press_right(), + _ => (), + } + } + fn unpress(&mut self, button: Button) { + match button { + Button::A => self.unpress_a(), + Button::B => self.unpress_b(), + Button::Start => self.unpress_start(), + Button::Select => self.unpress_select(), + Button::Up => self.unpress_up(), + Button::Down => self.unpress_down(), + Button::Left => self.unpress_left(), + Button::Right => self.unpress_right(), + _ => (), + } + } + fn reset(&mut self) { + Cpu::reset(self); + } + fn toggle_logger(&mut self) { + Cpu::toggle_logger(self); + } + fn reinit_logger(&mut self) { + Cpu::reinit_logger(self); + } +} diff --git a/src/gba/mod.rs b/src/gba/mod.rs index 8c1fa7e..a3c750c 100644 --- a/src/gba/mod.rs +++ b/src/gba/mod.rs @@ -7,7 +7,7 @@ pub struct GameboyAdvance { bios: [u8; 0x4000], iw_ram: [u8; 0x8000], wram: [u8; 0x40000], - io_registers: IoRegisters, + pub io_registers: IoRegisters, pub obj_palette_ram: [u8; 0x400], pub vram: [u8; 0x18000], pub oam: [u8; 0x400], @@ -129,16 +129,27 @@ pub struct IoRegisters { dma1_enabled: bool, dma2_enabled: bool, dma3_enabled: bool, + timer0: u16, + timer1: u16, + timer2: u16, + timer3: u16, } impl IoRegisters { pub fn new() -> Self { + let mut io_registers = [0; 0x400]; + io_registers[0x130] = 0xFF; + io_registers[0x131] = 0x3; IoRegisters { - io_registers: [0; 0x400], + io_registers, dma0_enabled: false, dma1_enabled: false, dma2_enabled: false, dma3_enabled: false, + timer0: 0, + timer1: 0, + timer2: 0, + timer3: 0, } } @@ -238,6 +249,123 @@ impl IoRegisters { pub fn disable_dma3(&mut self) { self.io_registers[0xDF] &= !0x80; } + pub fn timer0_running(&self) -> bool { + (self.io_registers[0x102] >> 7) == 1 + } + pub fn timer1_running(&self) -> bool { + (self.io_registers[0x106] >> 7) == 1 + } + pub fn timer2_running(&self) -> bool { + (self.io_registers[0x10A] >> 7) == 1 + } + pub fn timer3_running(&self) -> bool { + (self.io_registers[0x10E] >> 7) == 1 + } + pub fn timer0_irq_enabled(&self) -> bool { + (self.io_registers[0x102] >> 6) & 1 == 1 + } + pub fn timer1_irq_enabled(&self) -> bool { + (self.io_registers[0x106] >> 6) & 1 == 1 + } + pub fn timer2_irq_enabled(&self) -> bool { + (self.io_registers[0x10A] >> 6) & 1 == 1 + } + pub fn timer3_irq_enabled(&self) -> bool { + (self.io_registers[0x10E] >> 6) & 1 == 1 + } + pub fn timer_irq_enabled(&self, timer: u8) -> bool { + match timer { + 0 => self.timer0_irq_enabled(), + 1 => self.timer1_irq_enabled(), + 2 => self.timer2_irq_enabled(), + 3 => self.timer3_irq_enabled(), + _ => unreachable!(), + } + } + pub fn timer_enabled(&self, timer: u8) -> bool { + match timer { + 0 => self.timer0_running(), + 1 => self.timer1_running(), + 2 => self.timer2_running(), + 3 => self.timer3_running(), + _ => unreachable!(), + } + } + pub fn increment_timer(&mut self, timer: u8) -> bool { + match timer { + 0 => { + if let Some(v) = self.timer0.checked_add(1) { + self.timer0 = v; + } else { + self.timer0 = self.get_mem16(0x4000100); + return true; + } + } + 1 => { + if let Some(v) = self.timer1.checked_add(1) { + self.timer1 = v; + } else { + self.timer1 = self.get_mem16(0x4000104); + return true; + } + } + 2 => { + if let Some(v) = self.timer2.checked_add(1) { + self.timer2 = v; + } else { + self.timer2 = self.get_mem16(0x4000108); + return true; + } + } + 3 => { + if let Some(v) = self.timer3.checked_add(1) { + self.timer3 = v; + } else { + self.timer3 = self.get_mem16(0x400010C); + return true; + } + } + _ => unreachable!(), + } + false + } + // TODO: bit 2 count-up mode + pub fn timer0_prescaler(&self) -> u16 { + match self.io_registers[0x102] & 3 { + 0b00 => 1, + 0b01 => 64, + 0b10 => 256, + 0b11 => 1024, + _ => unreachable!(), + } + } + pub fn timer1_prescaler(&self) -> u16 { + match self.io_registers[0x106] & 3 { + 0b00 => 1, + 0b01 => 64, + 0b10 => 256, + 0b11 => 1024, + _ => unreachable!(), + } + } + pub fn timer2_prescaler(&self) -> u16 { + match self.io_registers[0x10A] & 3 { + 0b00 => 1, + 0b01 => 64, + 0b10 => 256, + 0b11 => 1024, + _ => unreachable!(), + } + } + pub fn timer3_prescaler(&self) -> u16 { + match self.io_registers[0x10E] & 3 { + 0b00 => 1, + 0b01 => 64, + 0b10 => 256, + 0b11 => 1024, + _ => unreachable!(), + } + } pub fn set_mem8(&mut self, addr: u32, val: u8) { debug_assert!((0x4000000..=0x4FFFFFF).contains(&addr)); @@ -271,10 +399,37 @@ impl IoRegisters { //println!("DMA: {:X} = {:X}", addr, val); self.io_registers[addr as usize] = val; } + 0x102 => { + if (self.io_registers[0x102] >> 7) == 0 && (val >> 7) == 1 { + self.timer0 = self.get_mem16(0x100); + } + self.io_registers[addr as usize] = val; + } + 0x106 => { + if (self.io_registers[0x106] >> 7) == 0 && (val >> 7) == 1 { + self.timer1 = self.get_mem16(0x104); + } + self.io_registers[addr as usize] = val; + } + 0x10A => { + if (self.io_registers[0x10A] >> 7) == 0 && (val >> 7) == 1 { + self.timer2 = self.get_mem16(0x108); + } + self.io_registers[addr as usize] = val; + } + 0x10E => { + if (self.io_registers[0x10E] >> 7) == 0 && (val >> 7) == 1 { + self.timer3 = self.get_mem16(0x10C); + } + self.io_registers[addr as usize] = val; + } 0x100..=0x110 => { - println!("TIMER: {:X} = {:X}", addr, val); self.io_registers[addr as usize] = val; } + // interupt clearing + 0x214..=0x217 => { + self.io_registers[addr as usize] &= !val; + } // TODO: 4000204h - WAITCNT - Waitstate Control (R/W) _ => { self.io_registers[addr as usize] = val; @@ -302,6 +457,35 @@ impl IoRegisters { self.set_mem16(lo_addr, lo_half_word); self.set_mem16(hi_addr, hi_half_word); } + pub fn get_mem8(&self, addr: u32) -> u8 { + debug_assert!((0x4000000..=0x4FFFFFF).contains(&addr)); + let addr = addr & 0x3FF; + match addr { + 0x100 => (self.timer0 & 0xFF) as u8, + 0x101 => (self.timer0 >> 8) as u8, + 0x104 => (self.timer1 & 0xFF) as u8, + 0x105 => (self.timer1 >> 8) as u8, + 0x108 => (self.timer2 & 0xFF) as u8, + 0x109 => (self.timer2 >> 8) as u8, + 0x10C => (self.timer3 & 0xFF) as u8, + 0x10D => (self.timer3 >> 8) as u8, + _ => self.io_registers[addr as usize], + } + } + pub fn get_mem16(&self, addr: u32) -> u16 { + debug_assert!((0x4000000..=0x4FFFFFF).contains(&addr)); + let lo_addr = addr & !1; + let hi_addr = addr | 1; + + (self.get_mem8(lo_addr) as u16) | ((self.get_mem8(hi_addr) as u16) << 8) + } + pub fn get_mem32(&self, addr: u32) -> u32 { + debug_assert!((0x4000000..=0x4FFFFFF).contains(&addr)); + let lo_addr = addr & !3; + let hi_addr = addr | 2; + + (self.get_mem16(lo_addr) as u32) | ((self.get_mem16(hi_addr) as u32) << 16) + } } impl std::ops::Index for IoRegisters { @@ -1076,6 +1260,15 @@ impl GameboyAdvance { self.io_registers[0x202] &= !(1 << 6); self.io_registers[0x202] |= (value as u8) << 6; } + pub fn set_timer_interrupt(&mut self, timer: u8, value: bool) { + match timer { + 0 => self.set_timer0_interrupt(value), + 1 => self.set_timer1_interrupt(value), + 2 => self.set_timer2_interrupt(value), + 3 => self.set_timer3_interrupt(value), + _ => panic!("Invalid timer number"), + } + } pub fn set_serial_interrupt(&mut self, value: bool) { self.io_registers[0x202] &= !(1 << 7); self.io_registers[0x202] |= (value as u8) << 7; @@ -1130,14 +1323,15 @@ impl GameboyAdvance { // HACK: (0, 0) } - 0x02000000..=0x0203FFFF => { + //0x02000000..=0x0203FFFF => { + 0x02000000..=0x02FFFFFF => { // on-board work ram (self.wram[(address & 0x3FFFF) as usize], 3) } //0x03000000..=0x03007FFF => (self.iw_ram[(address & 0x7FFF) as usize], 1), 0x03000000..=0x03FFFFFF => (self.iw_ram[(address & 0x7FFF) as usize], 1), //0x04000000..=0x040003FE => (self.io_registers[(address & 0x3FE) as usize], 1), - 0x04000000..=0x04FFFFFE => (self.io_registers[(address & 0x3FF) as usize], 1), + 0x04000000..=0x04FFFFFF => (self.io_registers.get_mem8(address), 1), //0x05000000..=0x050003FF => (self.obj_palette_ram[(address & 0x3FF) as usize], 1), 0x05000000..=0x05FFFFFF => (self.obj_palette_ram[(address & 0x3FF) as usize], 1), //0x06000000..=0x06017FFF => (self.vram[(address & 0x17FFF) as usize], 1), @@ -1147,7 +1341,8 @@ impl GameboyAdvance { 0x08000000..=0x09FFFFFF => (self.entire_rom[(address & 0x1FFFFFF) as usize], 5), 0x0A000000..=0x0BFFFFFF => todo!("Game Pak ROM/FlashROM (max 32MB) - Wait State 1"), 0x0C000000..=0x0DFFFFFF => todo!("Game Pak ROM/FlashROM (max 32MB) - Wait State 2"), - 0x0E000000..=0x0E00FFFF => todo!("Game Pak SRAM (max 64 KBytes) - 8bit Bus width"), + //0x0E000000..=0x0E00FFFF => todo!("Game Pak SRAM (max 64 KBytes) - 8bit Bus width"), + 0x0E000000..=0x0FFFFFFF => todo!("Game Pak SRAM (max 64 KBytes) - 8bit Bus width"), _ => (0, 0), } } @@ -1178,9 +1373,7 @@ impl GameboyAdvance { } 0x04000000..=0x04FFFFFF => { //0x04000000..=0x040003FF => { - let lo_bit = self.io_registers[(lo_bit_idx & 0x3FF) as usize] as u16; - let hi_bit = self.io_registers[(hi_bit_idx & 0x3FF) as usize] as u16; - ((hi_bit << 8) | lo_bit, 1) + (self.io_registers.get_mem16(address), 1) } 0x05000000..=0x05FFFFFF => { //0x05000000..=0x050003FF => { @@ -1224,11 +1417,13 @@ impl GameboyAdvance { let hi_bit = self.entire_rom[(hi_bit_idx & 0x1FFFFFF) as usize] as u16; ((hi_bit << 8) | lo_bit, 5) } - 0x0E000000..=0x0E00FFFF => todo!("Game Pak SRAM (max 64 KBytes) - 8bit Bus width"), + //0x0E000000..=0x0E00FFFF => todo!("Game Pak SRAM (max 64 KBytes) - 8bit Bus width"), + 0x0E000000..=0x0FFFFFFF => todo!("Game Pak SRAM (max 64 KBytes) - 8bit Bus width"), _ => (0, 0), } } pub fn get_mem32(&self, address: u32) -> (u32, u8) { + //address = address & 0x0FFF_FFFF; let bit1_idx = address & !0x3; let bit2_idx = (address & !0x3) | 0b01; let bit3_idx = (address & !0x3) | 0b10; @@ -1265,12 +1460,7 @@ impl GameboyAdvance { } 0x04000000..=0x04FFFFFE => { //0x04000000..=0x040003FE => { - let bit1 = self.io_registers[(bit1_idx & 0x3FF) as usize] as u32; - let bit2 = self.io_registers[(bit2_idx & 0x3FF) as usize] as u32; - let bit3 = self.io_registers[(bit3_idx & 0x3FF) as usize] as u32; - let bit4 = self.io_registers[(bit4_idx & 0x3FF) as usize] as u32; - let out = (bit4 << 24) | (bit3 << 16) | (bit2 << 8) | bit1; - (out, 1) + (self.io_registers.get_mem32(address), 1) } 0x05000000..=0x05FFFFFF => { //0x05000000..=0x050003FF => { @@ -1299,7 +1489,7 @@ impl GameboyAdvance { let out = (bit4 << 24) | (bit3 << 16) | (bit2 << 8) | bit1; (out, 1) } - 0x08000000..=0x09FFFFFF => { + 0x0A000000..=0x0BFFFFFF | 0x0C000000..=0x0DFFFFFF | 0x08000000..=0x09FFFFFF => { let bit1 = self.entire_rom[(bit1_idx & 0x1FFFFFF) as usize] as u32; let bit2 = self.entire_rom[(bit2_idx & 0x1FFFFFF) as usize] as u32; let bit3 = self.entire_rom[(bit3_idx & 0x1FFFFFF) as usize] as u32; @@ -1307,9 +1497,12 @@ impl GameboyAdvance { let out = (bit4 << 24) | (bit3 << 16) | (bit2 << 8) | bit1; (out, 8) } + /* 0x0A000000..=0x0BFFFFFF => todo!("Game Pak ROM/FlashROM (max 32MB) - Wait State 1"), 0x0C000000..=0x0DFFFFFF => todo!("Game Pak ROM/FlashROM (max 32MB) - Wait State 2"), - 0x0E000000..=0x0E00FFFF => todo!("Game Pak SRAM (max 64 KBytes) - 8bit Bus width"), + //0x0E000000..=0x0E00FFFF => todo!("Game Pak SRAM (max 64 KBytes) - 8bit Bus width"), + */ + 0x0E000000..=0x0FFFFFFF => todo!("Game Pak SRAM (max 64 KBytes) - 8bit Bus width"), _ => (0, 0), } } @@ -1349,7 +1542,8 @@ impl GameboyAdvance { 0x08000000..=0x09FFFFFF => todo!("Game Pak ROM/FlashROM (max 32MB) - Wait State 0"), 0x0A000000..=0x0BFFFFFF => todo!("Game Pak ROM/FlashROM (max 32MB) - Wait State 1"), 0x0C000000..=0x0DFFFFFF => todo!("Game Pak ROM/FlashROM (max 32MB) - Wait State 2"), - 0x0E000000..=0x0E00FFFF => todo!("Game Pak SRAM (max 64 KBytes) - 8bit Bus width"), + //0x0E000000..=0x0E00FFFF => todo!("Game Pak SRAM (max 64 KBytes) - 8bit Bus width"), + 0x0E000000..=0x0FFFFFFF => todo!("Game Pak SRAM (max 64 KBytes) - 8bit Bus width"), _ => 0, } } @@ -1402,10 +1596,11 @@ impl GameboyAdvance { self.oam[(hi_bit_idx & 0x3FF) as usize] = hi_val; 1 } - 0x08000000..=0x09FFFFFF => todo!("Game Pak ROM/FlashROM (max 32MB) - Wait State 0"), + 0x08000000..=0x09FFFFFF => 0, //todo!("Game Pak ROM/FlashROM (max 32MB) - Wait State 0"), 0x0A000000..=0x0BFFFFFF => todo!("Game Pak ROM/FlashROM (max 32MB) - Wait State 1"), 0x0C000000..=0x0DFFFFFF => todo!("Game Pak ROM/FlashROM (max 32MB) - Wait State 2"), - 0x0E000000..=0x0E00FFFF => todo!("Game Pak SRAM (max 64 KBytes) - 8bit Bus width"), + //0x0E000000..=0x0E00FFFF => todo!("Game Pak SRAM (max 64 KBytes) - 8bit Bus width"), + 0x0E000000..=0x0FFFFFFF => todo!("Game Pak SRAM (max 64 KBytes) - 8bit Bus width"), _ => 0, } } @@ -1480,7 +1675,8 @@ impl GameboyAdvance { ),*/ 0x0A000000..=0x0BFFFFFF => todo!("Game Pak ROM/FlashROM (max 32MB) - Wait State 1"), 0x0C000000..=0x0DFFFFFF => todo!("Game Pak ROM/FlashROM (max 32MB) - Wait State 2"), - 0x0E000000..=0x0E00FFFF => todo!("Game Pak SRAM (max 64 KBytes) - 8bit Bus width"), + //0x0E000000..=0x0E00FFFF => todo!("Game Pak SRAM (max 64 KBytes) - 8bit Bus width"), + 0x0E000000..=0x0FFFFFFF => todo!("Game Pak SRAM (max 64 KBytes) - 8bit Bus width"), _ => 0, } } @@ -1502,47 +1698,47 @@ impl GameboyAdvance { return; } if self.lcdc_vblank_interrupt_requested() && self.lcdc_hblank_interrupt_enabled() { - trace!("VBLANK interrupt started"); + debug!("VBLANK interrupt started"); self.irq_interrupt(); } else if self.lcdc_hblank_interrupt_requested() && self.lcdc_hblank_interrupt_enabled() { - trace!("HBLANK interrupt started"); + debug!("HBLANK interrupt started"); self.irq_interrupt(); } else if self.lcdc_vcounter_interrupt_requested() && self.lcdc_vcounter_interrupt_enabled() { - trace!("VCOUNTER interrupt started"); + debug!("VCOUNTER interrupt started"); self.irq_interrupt(); } else if self.timer0_interrupt_requested() && self.timer0_interrupt_enabled() { - trace!("TIMER0 interrupt started"); + debug!("TIMER0 interrupt started"); self.irq_interrupt(); } else if self.timer1_interrupt_requested() && self.timer1_interrupt_enabled() { - trace!("TIMER1 interrupt started"); + debug!("TIMER1 interrupt started"); self.irq_interrupt(); } else if self.timer2_interrupt_requested() && self.timer2_interrupt_enabled() { - trace!("TIMER2 interrupt started"); + debug!("TIMER2 interrupt started"); self.irq_interrupt(); } else if self.timer3_interrupt_requested() && self.timer3_interrupt_enabled() { - trace!("TIMER3 interrupt started"); + debug!("TIMER3 interrupt started"); self.irq_interrupt(); } else if self.serial_interrupt_requested() && self.serial_interrupt_enabled() { - trace!("SERIAL interrupt started"); + debug!("SERIAL interrupt started"); self.irq_interrupt(); } else if self.dma0_interrupt_requested() && self.dma0_interrupt_enabled() { - trace!("DMA0 interrupt started"); + debug!("DMA0 interrupt started"); self.irq_interrupt(); } else if self.dma1_interrupt_requested() && self.dma1_interrupt_enabled() { - trace!("DMA1 interrupt started"); + debug!("DMA1 interrupt started"); self.irq_interrupt(); } else if self.dma2_interrupt_requested() && self.dma2_interrupt_enabled() { - trace!("DMA2 interrupt started"); + debug!("DMA2 interrupt started"); self.irq_interrupt(); } else if self.dma3_interrupt_requested() && self.dma3_interrupt_enabled() { - trace!("DMA3 interrupt started"); + debug!("DMA3 interrupt started"); self.irq_interrupt(); } else if self.keypad_interrupt_requested() && self.keypad_interrupt_enabled() { - trace!("KEYPAD interrupt started"); + debug!("KEYPAD interrupt started"); self.irq_interrupt(); } else if self.game_pak_interrupt_requested() && self.game_pak_interrupt_enabled() { - trace!("GAMEPAK interrupt started"); + debug!("GAMEPAK interrupt started"); self.irq_interrupt(); } } @@ -1663,6 +1859,10 @@ impl GameboyAdvance { } pub fn dispatch(&mut self) -> u32 { + if self.r.pc >= 0x4000000 && self.r.pc <= 0x4FFFFFF { + std::process::exit(0); + } + //println!("R14 = 0x{:X}", self.r[14]); if self.io_registers.dma_waiting() { return self.handle_dma(); } @@ -1674,17 +1874,18 @@ impl GameboyAdvance { } let opcode = self.get_opcode(); let opcode_idx = (opcode >> 25) & 0x7; + + self.r.pc = self.r.pc.wrapping_add(4); + // TODO: some instructions can't be skipped, handle those if opcode == 0 { - self.r.pc += 4; //self.r.pc = self.r.pc.wrapping_add(4); return 4; } - //println!("opcode: {:032b} at 0x{:X}", opcode, self.r.pc); + trace!("opcode: {:032b} at 0x{:X}", opcode, self.r.pc - 4); let cond = Cond::from_u8(((opcode >> 28) & 0xF) as u8); if !self.cond_should_execute(cond) { - //println!("Skipped!"); - self.r.pc += 4; + trace!("Skipped!"); return 1; } @@ -1702,7 +1903,31 @@ impl GameboyAdvance { // TODO: add 000 to end of above and multiply // TODO: add 01 for mul long // |_Cond__|0_0_0_0_1|U|A|S|_RdHi__|_RdLo__|__Rs___|1_0_0_1|__Rm___| MulLong - 0b001 | 0b000 => self.dispatch_alu(opcode), + 0b000 => { + let multiply_end = ((opcode >> 4) & 0xF) == 0b1001; + let bits8to11 = ((opcode >> 8) & 0xF); + let multiply_next3 = (opcode >> 22) & 0x7; + let multiply_next2 = multiply_next3 >> 1; + match multiply_next2 { + 0b00 if multiply_next3 == 0 && multiply_end => self.dispatch_multiply(opcode), + 0b01 if multiply_end => todo!("mul long"), + 0b10 if multiply_end && bits8to11 == 0 && ((opcode >> 20) & 0x3) == 0 => { + todo!("transswp12") + } + _ => { + if (opcode >> 4) & 1 == 1 && (opcode >> 7) & 1 == 1 { + if (opcode >> 22) & 1 == 0 && (opcode >> 8) & 0xF == 0 { + todo!("TransReg10") + } else { + self.dispatch_data_trans_imm(opcode) + } + } else { + self.dispatch_alu(opcode) + } + } + } + } + 0b001 => self.dispatch_alu(opcode), 0b010 | 0b011 => self.dispatch_mem(opcode), 0b100 => self.dispatch_block_data(opcode), // TODO: 0b100 block trans @@ -1718,19 +1943,18 @@ impl GameboyAdvance { } }; - self.r.pc += 4; - cycles as u32 } pub fn dispatch_thumb(&mut self) -> u8 { let opcode = self.get_thumb_opcode(); let opcode_idx = (opcode >> 13) & 0x7; + self.r.pc += 2; + if opcode == 0 { - self.r.pc += 2; return 4; } - //println!("THUMB opcode: {:016b} at 0x{:X}", opcode, self.r.pc); + trace!("THUMB opcode: {:016b} at 0x{:X}", opcode, self.r.pc - 2); let cycles = match opcode_idx { 0b000 => self.dispatch_thumb_shift_add_sub(opcode), @@ -1794,8 +2018,6 @@ impl GameboyAdvance { ), }; - self.r.pc += 2; - cycles } @@ -1818,62 +2040,68 @@ impl GameboyAdvance { } 0b0010 => { trace!("LSL r{} = r{} << r{}", rd, rs, rd); - let result = self.r[rd] << (self.r[rs] & 0xFF); - self.r[rd] = result; + let result = self.r[rd] << ((self.r[rs] & 0xFF) % 32); if self.r[rs] & 0xFF != 0 { // TODO: REVIEW - self.r - .cpsr_set_carry_flag(self.r[rs] & (1 << (32 - (self.r[rs] & 0xFF))) != 0); + self.r.cpsr_set_carry_flag( + self.r[rs] & (1 << (32 - ((self.r[rs] & 0xFF) % 32))) != 0, + ); } + self.r[rd] = result; 2 } 0b0011 => { trace!("LSR r{} = r{} >> r{}", rd, rs, rd); - self.r[rd] = self.r[rd] >> (self.r[rs] & 0xFF); if self.r[rs] & 0xFF != 0 { // TODO: REVIEW self.r .cpsr_set_carry_flag(self.r[rs] & (1 << ((self.r[rs] & 0xFF) - 1)) != 0); } + self.r[rd] = self.r[rd] >> (self.r[rs] & 0xFF); 2 } 0b0100 => { trace!("ASR r{} = r{} >> r{}", rd, rs, rd); - self.r[rd] = ((self.r[rd] as i32) >> (self.r[rs] & 0xFF)) as u32; if self.r[rs] & 0xFF != 0 { // TODO: REVIEW self.r .cpsr_set_carry_flag(self.r[rs] & (1 << ((self.r[rs] & 0xFF) - 1)) != 0); } + self.r[rd] = ((self.r[rd] as i32) >> (self.r[rs] & 0xFF)) as u32; 2 } 0b0101 => { trace!("ADC r{} = r{} + r{} + C", rd, rs, rd); - let overflow_check = self.r[rd] - .checked_add(self.r[rs]) - .and_then(|v| v.checked_add(self.r.cpsr_carry_flag() as u32)); let old_val = self.r[rd]; + // TODO: this is wrong, if rd = rs, then the flag logic is wrong + // this applies to many instructions self.r[rd] = self.r[rs] .wrapping_add(self.r[rd]) .wrapping_add(self.r.cpsr_carry_flag() as u32); - self.r.cpsr_set_carry_flag(overflow_check.is_none()); - // TODO: what does overflow mean here? - // TODO: include carry in overflow check self.r.cpsr_set_overflow_flag( - ((old_val ^ self.r[rs]) & 0x8000_0000 == 0) - && ((old_val ^ self.r[rd]) & 0x8000_0000 != 0), + (!(self.r[rs] ^ old_val) & (old_val ^ self.r[rd])) >> 31 == 1, + ); + self.r.cpsr_set_carry_flag( + ((self.r[rs] as u64) + (old_val as u64) + (self.r.cpsr_carry_flag() as u64)) + > 0xFFFF_FFFF, ); + 1 } 0b0110 => { trace!("SBC r{} = r{} - r{} - C", rd, rs, rd); // TODO: review what not carry means, does it just mean add the carry? or is it 32bit negation? + let op1 = self.r[rs]; + let op2 = self.r[rd]; self.r[rd] = self.r[rd] .wrapping_sub(self.r[rs]) - .wrapping_sub(self.r.cpsr_carry_flag() as u32); - // TODO: review this - self.r.cpsr_set_overflow_flag(false); - self.r.cpsr_set_carry_flag(false); + .wrapping_add(1 - self.r.cpsr_carry_flag() as u32); + self.r + .cpsr_set_overflow_flag((!(op1 ^ !op2) & (!op2 ^ self.r[rd])) >> 31 == 1); + self.r.cpsr_set_carry_flag( + ((op1 as u64) + ((!op2) as u64) + (self.r.cpsr_carry_flag() as u64)) + > 0xFFFF_FFFF, + ); 1 } 0b0111 => { @@ -1906,12 +2134,14 @@ impl GameboyAdvance { } 0b1010 => { trace!("CMP r{} - r{}", rs, rd); + let op1 = self.r[rs]; + let op2 = self.r[rd]; let result = self.r[rd].wrapping_sub(self.r[rs]); self.r.cpsr_set_zero_flag(result == 0); self.r.cpsr_set_sign_flag(result & 0x8000_0000 != 0); - // TODO: review this - self.r.cpsr_set_overflow_flag(false); - self.r.cpsr_set_carry_flag(false); + self.r + .cpsr_set_overflow_flag((op1 as i32).overflowing_sub(op2 as i32).1); + self.r.cpsr_set_carry_flag(op2 <= op1); skip_end_flags = true; 1 } @@ -1963,7 +2193,7 @@ impl GameboyAdvance { pub fn dispatch_thumb_load_pc_relative(&mut self, opcode: u16) -> u8 { let rd = ((opcode >> 8) & 0x7) as u8; let nn = (opcode & 0xFF) << 2; - let pc = (self.r.pc + 4) & !2; + let pc = (self.r.pc + 2) & !2; trace!("LDR r{}, [PC, #{}]", rd, nn); let o = self.get_mem32(pc + nn as u32); @@ -1980,6 +2210,7 @@ impl GameboyAdvance { let hi_bit = (opcode >> 7) & 1 == 1; rd |= (hi_bit as u8) << 3; } + let old_thumb_enabled = self.r.thumb_enabled(); let cycles = match subop { 0b00 => { trace!("ADD r{}, r{}", rd, rs); @@ -1988,12 +2219,14 @@ impl GameboyAdvance { } 0b01 => { trace!("CMP r{}, r{}", rd, rs); + let op1 = self.r[rd]; + let op2 = self.r[rs]; let result = self.r[rd].wrapping_sub(self.r[rs]); self.r.cpsr_set_zero_flag(result == 0); self.r.cpsr_set_sign_flag(result & 0x8000_0000 != 0); - // TODO: review this - self.r.cpsr_set_overflow_flag(false); - self.r.cpsr_set_carry_flag(false); + self.r + .cpsr_set_overflow_flag((op1 as i32).overflowing_sub(op2 as i32).1); + self.r.cpsr_set_carry_flag(op2 <= op1); 1 } 0b10 => { @@ -2004,23 +2237,36 @@ impl GameboyAdvance { 0b11 => { let x_flag = (opcode >> 7) & 1 == 1; let thumb_mode = self.r[rs] & 1 == 1; - if self.r.thumb_enabled() != thumb_mode { - if thumb_mode { - info!("Enabling Thumb mode"); - } else { - info!("Enabling ARM mode!"); - } - } + let addr = if thumb_mode && rs == 15 { + self.r[rs] & !1 + } else { + self.r[rs] & !3 + }; self.r.set_thumb(thumb_mode); if x_flag { trace!("BLX r{}", rs); - let old_pc = self.r.pc; + let old_pc = self.r.pc + 4; + self.r.pc = addr; + *self.r.lr_mut() = old_pc + 1; + /* self.r.pc = (self.r[rs] + 4) & !2; - *self.r.lr_mut() = old_pc + 3; + // *self.r.lr_mut() = old_pc + 3; + *self.r.lr_mut() = old_pc + 1; + */ } else { - trace!("BX r{}", rs); - //self.r.pc = (self.r[rs] + 4) & !2; - self.r.pc = (self.r[rs] + 2) & !1; + trace!("BX r{} (0x{:X})", rs, addr); + self.r.pc = addr + 4; + /* + self.r.pc = (self.r[rs] + 4) & !2; + //self.r.pc = (self.r[rs] + 2) & !1; + */ + } + if old_thumb_enabled != thumb_mode { + if thumb_mode { + trace!("Enabling Thumb mode"); + } else { + trace!("Enabling ARM mode!"); + } } 3 } @@ -2160,6 +2406,8 @@ impl GameboyAdvance { // Execution Time: nS+1N+1I for LDM, or (n-1)S+2N for STM. let mut cycles = 0; + // TODO: figure out order + if subop { trace!("LDMIA r{}, {{{}}}", rb, r_list); for i in 0..8 { @@ -2186,7 +2434,7 @@ impl GameboyAdvance { pub fn dispatch_thumb_load_store_sp_relative(&mut self, opcode: u16) -> u8 { let subop = (opcode >> 11) & 0x1 == 1; - let rd = (opcode >> 8 & 0x7) as u8; + let rd = ((opcode >> 8) & 0x7) as u8; let nn = (opcode & 0xFF) as u32; if subop { @@ -2211,7 +2459,7 @@ impl GameboyAdvance { self.r[rd] = self.r.sp() + (nn * 4); } else { trace!("ADD r{}, PC, #{}", rd, nn); - self.r[rd] = ((self.r.pc + 4) & !2) + (nn * 4); + self.r[rd] = ((self.r.pc + 2) & !2) + (nn * 4); } 1 @@ -2224,16 +2472,9 @@ impl GameboyAdvance { let mut cycles = 0; if subop { - trace!("POP"); - if pc_lr { - let o = self.get_mem32(self.r.sp()); - *self.r.sp_mut() += 4; - self.r.pc = o.0 & !1; - cycles += o.1; - cycles += 2; - } - // is reverse correct? do we need to do it elsewhere? - for i in (0..8).rev() { + trace!("POP at 0x{:X}", self.r.sp()); + + for i in 0..8 { if r_list & (1 << i) != 0 { let o = self.get_mem32(self.r.sp()); *self.r.sp_mut() += 4; @@ -2242,22 +2483,32 @@ impl GameboyAdvance { cycles += 2; } } + if pc_lr { + let o = self.get_mem32(self.r.sp()); + *self.r.sp_mut() += 4; + self.r.pc = o.0 & !1; + cycles += o.1; + cycles += 2; + } // 0 1 2 3 4 // 4 3 2 1 0 } else { - trace!("PUSH"); - for i in 0..8 { + trace!("PUSH at 0x{:X}", self.r.sp()); + if pc_lr { + cycles += 1; + *self.r.sp_mut() -= 4; + //println!("pushing LR ({:X}) to 0x{:X}", self.r.lr(), self.r.sp()); + cycles += self.set_mem32(self.r.sp(), self.r.lr()); + } + for i in (0..8).rev() { if r_list & (1 << i) != 0 { cycles += 1; - cycles += self.set_mem32(self.r.sp(), self.r[i as u8]); + // REVIEW: docs suggest this happens first *self.r.sp_mut() -= 4; + //println!("pushing r{} ({:X}) to 0x{:X}", i, self.r[i as u8], self.r.sp()); + cycles += self.set_mem32(self.r.sp(), self.r[i as u8]); } } - if pc_lr { - cycles += 1; - cycles += self.set_mem32(self.r.sp(), self.r.lr()); - *self.r.sp_mut() -= 4; - } } cycles @@ -2296,9 +2547,9 @@ impl GameboyAdvance { let result = self.r[rd].wrapping_sub(imm); self.r.cpsr_set_zero_flag(result == 0); self.r.cpsr_set_sign_flag(result & 0x8000_0000 != 0); - // TODO: review this - self.r.cpsr_set_overflow_flag(false); - self.r.cpsr_set_carry_flag(false); + self.r + .cpsr_set_overflow_flag((self.r[rd] as i32).overflowing_sub(imm as i32).1); + self.r.cpsr_set_carry_flag(imm <= self.r[rd]); } // ADD 0b10 => { @@ -2306,12 +2557,10 @@ impl GameboyAdvance { self.r[rd] = self.r[rd].wrapping_add(imm); self.r.cpsr_set_zero_flag(self.r[rd] == 0); self.r.cpsr_set_sign_flag(self.r[rd] & 0x8000_0000 != 0); - self.r.cpsr_set_overflow_flag( - ((old_val ^ imm) & 0x8000_0000 == 0) - && ((old_val ^ self.r[rd]) & 0x8000_0000 != 0), - ); self.r - .cpsr_set_carry_flag(old_val.checked_add(imm).is_none()); + .cpsr_set_overflow_flag((self.r[rd] as i32).overflowing_add(imm as i32).1); + self.r + .cpsr_set_carry_flag(((self.r[rd] as u64) + (imm as u64)) > 0xFFFF_FFFF); } // SUB 0b11 => { @@ -2319,9 +2568,9 @@ impl GameboyAdvance { self.r[rd] = self.r[rd].wrapping_sub(imm); self.r.cpsr_set_zero_flag(self.r[rd] == 0); self.r.cpsr_set_sign_flag(self.r[rd] & 0x8000_0000 != 0); - // TODO: review this - self.r.cpsr_set_overflow_flag(false); - self.r.cpsr_set_carry_flag(false); + self.r + .cpsr_set_overflow_flag((self.r[rd] as i32).overflowing_sub(imm as i32).1); + self.r.cpsr_set_carry_flag(imm <= self.r[rd]); } _ => unreachable!(), } @@ -2331,25 +2580,25 @@ impl GameboyAdvance { pub fn dispatch_thumb_shift_add_sub(&mut self, opcode: u16) -> u8 { let sub_op_idx = (opcode >> 11) & 0x3; - let offset = opcode >> 6 & 0x1F; - let rs = (opcode >> 3 & 0x7) as u8; + let offset = (opcode >> 6) & 0x1F; + let rs = ((opcode >> 3) & 0x7) as u8; let rd = (opcode & 0x7) as u8; match sub_op_idx { // LSL 0b00 => { - trace!("LSL r{}, r{}, #{}", rd, rs, offset); - self.r[rd] = self.r[rs] << offset; + trace!("LSL r{}, r{}, #{:X}", rd, rs, offset); // shift of 0 = don't modify carry flag if offset != 0 { // TODO: review this self.r .cpsr_set_carry_flag(self.r[rs] & (1 << (32 - offset)) != 0); } + self.r[rd] = self.r[rs] << offset; self.r.cpsr_set_sign_flag(self.r[rd] & 0x8000_0000 != 0); self.r.cpsr_set_zero_flag(self.r[rd] == 0); } 0b01 => { - trace!("LSR r{}, r{}, #{}", rd, rs, offset); + trace!("LSR r{}, r{}, #{:X}", rd, rs, offset); self.r[rd] = self.r[rs] >> offset; // shift of 0 = don't modify carry flag if offset != 0 { @@ -2361,7 +2610,7 @@ impl GameboyAdvance { self.r.cpsr_set_zero_flag(self.r[rd] == 0); } 0b10 => { - trace!("ASR r{}, r{}, #{}", rd, rs, offset); + trace!("ASR r{}, r{}, #{:X}", rd, rs, offset); self.r[rd] = ((self.r[rs] as i32) >> offset) as u32; // shift of 0 = don't modify carry flag if offset != 0 { @@ -2377,39 +2626,48 @@ impl GameboyAdvance { let reg_or_imm = ((opcode >> 6) & 0x7) as u32; let result; let op2; - let old_value = self.r[rd]; match sub_sub_op_idx { 0b00 => { trace!("ADD r{}, r{}, r{}", rd, rs, reg_or_imm); op2 = self.r[reg_or_imm as u8]; result = self.r[rs].wrapping_add(op2); self.r[rd] = result; - self.r - .cpsr_set_carry_flag(self.r[rs].checked_add(op2).is_none()); + self.r.cpsr_set_overflow_flag( + (self.r[rs] as i32).overflowing_sub(op2 as i32).1, + ); + self.r.cpsr_set_carry_flag(op2 < self.r[rs]); } 0b01 => { trace!("SUB r{}, r{}, r{}", rd, rs, reg_or_imm); op2 = self.r[reg_or_imm as u8]; result = self.r[rs].wrapping_sub(op2); self.r[rd] = result; - // REVIEW: - self.r.cpsr_set_carry_flag(false); + self.r.cpsr_set_overflow_flag( + (self.r[rs] as i32).overflowing_sub(op2 as i32).1, + ); + self.r.cpsr_set_carry_flag(op2 <= self.r[rs]); } 0b10 => { trace!("ADD r{}, r{}, #{}", rd, rs, reg_or_imm); op2 = reg_or_imm; result = self.r[rs].wrapping_add(op2); self.r[rd] = result; - self.r - .cpsr_set_carry_flag(self.r[rs].checked_add(op2).is_none()); + self.r.cpsr_set_overflow_flag( + (self.r[rs] as i32).overflowing_add(op2 as i32).1, + ); + self.r.cpsr_set_carry_flag( + ((self.r[rs] as u64) + (op2 as u64)) > 0xFFFF_FFFF, + ); } 0b11 => { trace!("SUB r{}, r{}, #{}", rd, rs, reg_or_imm); op2 = reg_or_imm; result = self.r[rs].wrapping_sub(op2); self.r[rd] = result; - // REVIEW: - self.r.cpsr_set_carry_flag(false); + self.r.cpsr_set_overflow_flag( + (self.r[rs] as i32).overflowing_sub(op2 as i32).1, + ); + self.r.cpsr_set_carry_flag(op2 <= self.r[rs]); } _ => unreachable!(), } @@ -2429,6 +2687,7 @@ impl GameboyAdvance { pub fn dispatch_thumb_conditional_branch(&mut self, opcode: u16) -> u8 { let cond = Cond::from_u8(((opcode >> 8) & 0xF) as u8); + trace!("B{:?} #{:X}", cond, opcode & 0xFF); let nn = (opcode & 0xFF) as i8 as i32 * 2; if self.cond_should_execute(cond) { self.r.pc = ((self.r.pc as i32) + 2 + nn) as u32; @@ -2447,24 +2706,30 @@ impl GameboyAdvance { let signed_offset = (opcode & 0x7FF) as i16 | ((if sign { 0xF8 } else { 0 }) << 8); //println!("{} {:b} ({:b})", sign, signed_offset, signed_offset as i32); let signed_offset = signed_offset as i32 * 2; - trace!("B #{:X} + 2 + #{:X}", self.r.pc, signed_offset); - self.r.pc = (self.r.pc as i32 + 2 + signed_offset) as u32; + let new_pc = (self.r.pc as i32 + 2 + signed_offset) as u32; + trace!( + "B #{:X} + 2 + #{:X} = #{:X}", + self.r.pc, + signed_offset, + new_pc + ); + self.r.pc = new_pc; 3 } 0b10 => { trace!("BL (part 1)"); let n = (opcode & 0x7FF) as u32; - *self.r.lr_mut() = self.r.pc + 4 + /*2 +*/ (n << 12); + *self.r.lr_mut() = self.r.pc /*+ 4*/ + 2 + (n << 12); 1 } 0b11 | 0b01 => { let n = (opcode & 0x7FF) as u32; - let old_pc = self.r.pc; //+ 2; - // add 2 here? - // as this is the second half of the instruction, probably not... - self.r.pc = self.r.lr() + (n << 1); + let old_pc = self.r.pc; + //let new_pc = (self.r.lr() + (n << 1)) & 0x3F_FFFF; + let new_pc = (self.r.lr() + (n << 1)); + self.r.pc = new_pc; trace!("BL to 0x{:X}", self.r.pc); - *self.r.lr_mut() = old_pc; + *self.r.lr_mut() = old_pc | 1; 3 } // I think 0b01 is only on ARM9, so let's just route it to the same BL @@ -2510,14 +2775,16 @@ impl GameboyAdvance { */ if sub_opcode { - *self.r.lr_mut() = self.r.pc + 4; + //*self.r.lr_mut() = self.r.pc; + 4; + *self.r.lr_mut() = self.r.pc; } let new_pc = self.r.pc as i32 + 4 + (signed_offset * 4); + //let new_pc = self.r.pc as i32 + 8 + (signed_offset * 4); trace!( "Branching at 0x{:X} to 0x{:X} with offset {} {:b}", self.r.pc, new_pc, - signed_offset, + signed_offset * 4, signed_offset ); self.r.pc = new_pc as u32; @@ -2532,27 +2799,33 @@ impl GameboyAdvance { let s = (opcode >> 20) & 1 == 1; let imm = (opcode >> 25) & 1 == 1; - let op_reg = (opcode >> 16) & 0xF; - // if it's the PC, we do extra logic - let op_reg = if op_reg == 0xE { 0 } else { op_reg }; - let dest_reg = (opcode >> 12) & 0xF; - let mut op1 = self.r[op_reg as u8]; + let rn = ((opcode >> 16) & 0xF) as u8; + let rd = ((opcode >> 12) & 0xF) as u8; + let mut op1 = self.r[rn]; let mut op2; + // HACK for R15 case + let old_cpsr = self.r.cpsr; - if op_reg == 0xF { + if rn == 0xF { if !imm && (opcode >> 4) & 1 == 1 { - op1 += 12; + op1 += 12 - 4; } else { - op1 += 8; + op1 += 8 - 4; } } if imm { let ror_shift = (opcode >> 8) & 0xF; op2 = opcode & 0xFF; - if ror_shift != 0 { - op2 = op2.rotate_right(ror_shift * 2); - self.r.cpsr_set_carry_flag(op2 & 0x8000_0000 != 0); + let shift_amt = (ror_shift * 2) % 32; + if shift_amt != 0 { + op2 = op2.rotate_right(shift_amt); + self.r.cpsr_set_carry_flag((op2 & 0x8000_0000) != 0); + // TODO: apply this ROR logic everywhere else ROR is used + } else { + // TODO: this logic may only be for non-immediate mode + let carry_bit = self.r.cpsr_carry_flag() as u32; + op2 |= carry_bit << 31; } } else { let shift_by_register = (opcode >> 4) & 1 == 1; @@ -2591,7 +2864,10 @@ impl GameboyAdvance { op2 = self.r[rm as u8]; } // LSR - 1 => todo!("LSR zero shift amount handling"), + 1 => { + op2 = 0; + self.r.cpsr_set_carry_flag((self.r[rm as u8] >> 31) == 1); + } // ASR 2 => { op2 = ((self.r[rm as u8] as i32) >> 31) as u32; @@ -2613,7 +2889,9 @@ impl GameboyAdvance { op2 = self.r[rm as u8] >> shift_amt; } // ASR - 2 => todo!("ASR"), + 2 => { + op2 = ((self.r[rm as u8] as i32) >> shift_amt) as u32; + } // ROR 3 => todo!("ROR"), _ => unreachable!(), @@ -2622,7 +2900,7 @@ impl GameboyAdvance { } // detect if ALU instruction is actually an MRS/MSR: PSR transfer - // TODO: op_reg != 0xF = SWP + // TODO: rn != 0xF = SWP if (sub_opcode >> 2) == 0b10 && !s { let psr_src_dest = (opcode >> 22) & 1 == 1; let psr_subopcode = (opcode >> 21) & 1 == 1; @@ -2668,7 +2946,7 @@ impl GameboyAdvance { self.r.cpsr |= val & mask; } } else { - if op_reg != 0xF { + if rn != 0xF { let byte = (opcode >> 22) & 1 == 1; let rn = ((opcode >> 16) & 0xF) as u8; let rd = ((opcode >> 12) & 0xF) as u8; @@ -2694,7 +2972,7 @@ impl GameboyAdvance { trace!("MRS"); debug_assert_eq!(opcode & 0xFFF, 0); - debug_assert!(dest_reg < 15); + debug_assert!(rd < 15); debug_assert_eq!(imm, false); // HACK: if in user mode just force CPSR @@ -2705,16 +2983,16 @@ impl GameboyAdvance { self.r.cpsr }; - self.r[dest_reg as u8] = v; + self.r[rd] = v; } return 1; } match sub_opcode { // AND 0x0 => { - trace!("AND r{:X} = {:X} & {:X}", dest_reg, op1, op2); + trace!("AND r{} = {:X} & {:X}", rd, op1, op2); let result = op1 & op2; - self.r[dest_reg as u8] = result; + self.r[rd] = result; if s { self.r.cpsr_set_zero_flag(result == 0); self.r.cpsr_set_sign_flag((result >> 31) == 1); @@ -2722,9 +3000,9 @@ impl GameboyAdvance { } // EOR 0x1 => { - trace!("EOR r{:X} = {:X} ^ {:X}", dest_reg, op1, op2); + trace!("EOR r{} = {:X} ^ {:X}", rd, op1, op2); let result = op1 ^ op2; - self.r[dest_reg as u8] = result; + self.r[rd] = result; if s { self.r.cpsr_set_zero_flag(result == 0); self.r.cpsr_set_sign_flag((result >> 31) == 1); @@ -2732,86 +3010,103 @@ impl GameboyAdvance { } // SUB 0x2 => { - trace!("SUB r{:X} = {:X} - {:X}", dest_reg, op1, op2); + trace!("SUB r{} = {:X} - {:X}", rd, op1, op2); let result = op1.wrapping_sub(op2); - self.r[dest_reg as u8] = result; + self.r[rd] = result; if s { self.r.cpsr_set_zero_flag(result == 0); self.r.cpsr_set_sign_flag(result & 0x8000_0000 != 0); - // TODO: review this - self.r.cpsr_set_overflow_flag(false); - self.r.cpsr_set_carry_flag(false); + self.r + .cpsr_set_overflow_flag((op1 as i32).overflowing_sub(op2 as i32).1); + self.r.cpsr_set_carry_flag(op2 <= op1); + } + } + // RSB + 0x3 => { + trace!("RSB r{} = {:X} - {:X}", rd, op2, op1); + let result = op2.wrapping_sub(op1); + self.r[rd] = result; + if s { + self.r.cpsr_set_zero_flag(result == 0); + self.r.cpsr_set_sign_flag(result & 0x8000_0000 != 0); + self.r + .cpsr_set_overflow_flag((op2 as i32).overflowing_sub(op1 as i32).1); + self.r.cpsr_set_carry_flag(op1 <= op2); } } // ADD 0x4 => { - trace!("ADD r{:X} = {:X} + {:X}", dest_reg, op1, op2); + trace!("ADD r{} = 0x{:X} + 0x{:X}", rd, op1, op2); let result = op1.wrapping_add(op2); - self.r[dest_reg as u8] = result; + self.r[rd] = result; if s { self.r.cpsr_set_zero_flag(result == 0); self.r.cpsr_set_sign_flag((result >> 31) == 1); - self.r.cpsr_set_overflow_flag( - ((op1 ^ op2) & 0x8000_0000 == 0) && ((op1 ^ result) & 0x8000_0000 != 0), - ); + self.r + .cpsr_set_overflow_flag((op1 as i32).overflowing_add(op2 as i32).1); self.r .cpsr_set_carry_flag(((op1 as u64) + (op2 as u64)) > 0xFFFF_FFFF); } } // ADC 0x5 => { - trace!("ADC r{:X} = {:X} + {:X} + C", dest_reg, op1, op2); + trace!("ADC r{} = {:X} + {:X} + C", rd, op1, op2); let result = op1 .wrapping_add(op2) .wrapping_add(self.r.cpsr_carry_flag() as u32); - self.r[dest_reg as u8] = result; + self.r[rd] = result; if s { self.r.cpsr_set_zero_flag(result == 0); self.r.cpsr_set_sign_flag((result >> 31) == 1); - self.r.cpsr_set_overflow_flag( - ((op1 ^ op2) & 0x8000_0000 == 0) && ((op1 ^ result) & 0x8000_0000 != 0), - ); self.r - .cpsr_set_carry_flag(((op1 as u64) + (op2 as u64)) > 0xFFFF_FFFF); + .cpsr_set_overflow_flag((!(op1 ^ op2) & (op2 ^ result)) >> 31 == 1); + self.r.cpsr_set_carry_flag( + ((op1 as u64) + (op2 as u64) + (self.r.cpsr_carry_flag() as u64)) + > 0xFFFF_FFFF, + ); } } // SBC 0x6 => { - trace!("SBC r{:X} = {:X} - {:X} + C", dest_reg, op1, op2); + trace!("SBC r{:X} = {:X} - {:X} + C", rd, op1, op2); let result = op1 .wrapping_sub(op2) - .wrapping_add(self.r.cpsr_carry_flag() as u32) - .wrapping_sub(1); - self.r[dest_reg as u8] = result; + .wrapping_add(1 - self.r.cpsr_carry_flag() as u32); + self.r[rd] = result; if s { self.r.cpsr_set_zero_flag(result == 0); self.r.cpsr_set_sign_flag((result >> 31) == 1); - self.r.cpsr_set_overflow_flag( - ((op1 ^ op2) & 0x8000_0000 == 0) && ((op1 ^ result) & 0x8000_0000 != 0), + // TODO: check this in more detail + self.r + .cpsr_set_overflow_flag((!(op1 ^ !op2) & (!op2 ^ result)) >> 31 == 1); + self.r.cpsr_set_carry_flag( + ((op1 as u64) + ((!op2) as u64) + (self.r.cpsr_carry_flag() as u64)) + > 0xFFFF_FFFF, ); - // TODO: carry flag } } // RSC 0x7 => { - trace!("RSC r{:X} = {:X} - {:X} + C", dest_reg, op2, op1); + trace!("RSC r{:X} = {:X} - {:X} + C", rd, op2, op1); let result = op2 .wrapping_sub(op1) - .wrapping_add(self.r.cpsr_carry_flag() as u32) - .wrapping_sub(1); - self.r[dest_reg as u8] = result; + .wrapping_add(1 - self.r.cpsr_carry_flag() as u32); + self.r[rd] = result; if s { self.r.cpsr_set_zero_flag(result == 0); self.r.cpsr_set_sign_flag((result >> 31) == 1); - self.r.cpsr_set_overflow_flag( - ((op1 ^ op2) & 0x8000_0000 == 0) && ((op1 ^ result) & 0x8000_0000 != 0), + // TODO: check this in more detail + self.r + .cpsr_set_overflow_flag((!(op1 ^ !op2) & (!op2 ^ result)) >> 31 == 1); + self.r.cpsr_set_carry_flag( + ((op1 as u64) + ((!op2) as u64) + (self.r.cpsr_carry_flag() as u64)) + > 0xFFFF_FFFF, ); - // TODO: carry flag } } // TST 0x8 => { - trace!("TST r{:X} = {:X} & {:X}", dest_reg, op1, op2); + trace!("TST r{:X} = {:X} & {:X}", rd, op1, op2); let result = op1 & op2; if s { self.r.cpsr_set_zero_flag(result == 0); @@ -2820,7 +3115,7 @@ impl GameboyAdvance { } // TEQ 0x9 => { - trace!("TEQ r{:X} = {:X} ^ {:X}", dest_reg, op1, op2); + trace!("TEQ r{:X} = {:X} ^ {:X}", rd, op1, op2); let result = op1 ^ op2; if s { self.r.cpsr_set_zero_flag(result == 0); @@ -2829,20 +3124,21 @@ impl GameboyAdvance { } // CMP 0xA => { - trace!("CMP r{:X} = {:X} - {:X}", dest_reg, op1, op2); + trace!("CMP r{:X} = {:X} - {:X}", rd, op1, op2); let result = op1.wrapping_sub(op2); if s { self.r.cpsr_set_zero_flag(result == 0); - self.r.cpsr_set_sign_flag((result >> 31) == 1); - self.r.cpsr_set_overflow_flag(false); - self.r.cpsr_set_carry_flag(false); + self.r.cpsr_set_sign_flag(result & 0x8000_0000 != 0); + self.r + .cpsr_set_overflow_flag((op1 as i32).overflowing_sub(op2 as i32).1); + self.r.cpsr_set_carry_flag(op2 <= op1); } } // ORR 0xC => { - trace!("ORR r{:X} = {:X} | {:X}", dest_reg, op1, op2); + trace!("ORR r{:X} = {:X} | {:X}", rd, op1, op2); let result = op1 | op2; - self.r[dest_reg as u8] = result; + self.r[rd] = result; if s { self.r.cpsr_set_zero_flag(result == 0); self.r.cpsr_set_sign_flag((result >> 31) == 1); @@ -2850,8 +3146,8 @@ impl GameboyAdvance { } // MOV 0xD => { - trace!("MOV r{:X} = {:X}", dest_reg, op2); - self.r[dest_reg as u8] = op2; + trace!("MOV r{:X} = {:X}", rd, op2); + self.r[rd] = op2; if s { self.r.cpsr_set_zero_flag(op2 == 0); self.r.cpsr_set_sign_flag((op2 >> 31) == 1); @@ -2859,9 +3155,9 @@ impl GameboyAdvance { } // BIC 0xE => { - trace!("BIC r{:X} = {:X} & !{:X}", dest_reg, op1, op2); + trace!("BIC r{} = #{:X} & !#{:X}", rd, op1, op2); let result = op1 & !op2; - self.r[dest_reg as u8] = result; + self.r[rd] = result; if s { self.r.cpsr_set_zero_flag(result == 0); self.r.cpsr_set_sign_flag((result >> 31) == 1); @@ -2869,22 +3165,24 @@ impl GameboyAdvance { } // MVN 0xF => { - trace!("MVN r{:X} = !{:X}", dest_reg, op2); + trace!("MVN r{:X} = !{:X}", rd, op2); let result = !op2; - self.r[dest_reg as u8] = result; + self.r[rd] = result; if s { self.r.cpsr_set_zero_flag(result == 0); self.r.cpsr_set_sign_flag((result >> 31) == 1); - // carry flag from shift } } _ => unimplemented!("ALU instruction 0x{:X} at 0x{:X}", sub_opcode, self.r.pc), } // HACK: don't set CPSR if in user mode - // TODO: this is probably wrong, we should avoid modifying flags in user mode too - if s && dest_reg == 0xF && self.r.register_mode() != Some(RegisterMode::User) { - self.r.cpsr = self.r.get_spsr(); + if s && rd == 0xF { + if self.r.register_mode() != Some(RegisterMode::User) { + self.r.cpsr = self.r.get_spsr(); + } else { + self.r.cpsr = old_cpsr; + } } // TODO: writing to PC affects things @@ -2894,6 +3192,136 @@ impl GameboyAdvance { 4 } + pub fn dispatch_multiply(&mut self, opcode: u32) -> u8 { + todo!("Multiply") + } + + /* + + Opcode Format + + Bit Expl. + 31-28 Condition + 27-25 Must be 000b for this instruction + 24 P - Pre/Post (0=post; add offset after transfer, 1=pre; before trans.) + 23 U - Up/Down Bit (0=down; subtract offset from base, 1=up; add to base) + 22 I - Immediate Offset Flag (0=Register Offset, 1=Immediate Offset) + When above Bit 24 P=0 (Post-indexing, write-back is ALWAYS enabled): + 21 Not used, must be zero (0) + When above Bit 24 P=1 (Pre-indexing, write-back is optional): + 21 W - Write-back bit (0=no write-back, 1=write address into base) + 20 L - Load/Store bit (0=Store to memory, 1=Load from memory) + 19-16 Rn - Base register (R0-R15) (Including R15=PC+8) + 15-12 Rd - Source/Destination Register (R0-R15) (Including R15=PC+12) + 11-8 When above Bit 22 I=0 (Register as Offset): + Not used. Must be 0000b + When above Bit 22 I=1 (immediate as Offset): + Immediate Offset (upper 4bits) + 7 Reserved, must be set (1) + 6-5 Opcode (0-3) + When Bit 20 L=0 (Store) (and Doubleword Load/Store): + 0: Reserved for SWP instruction + 1: STR{cond}H Rd,
;Store halfword [a]=Rd + 2: LDR{cond}D Rd,
;Load Doubleword R(d)=[a], R(d+1)=[a+4] + 3: STR{cond}D Rd,
;Store Doubleword [a]=R(d), [a+4]=R(d+1) + When Bit 20 L=1 (Load): + 0: Reserved. + 1: LDR{cond}H Rd,
;Load Unsigned halfword (zero-extended) + 2: LDR{cond}SB Rd,
;Load Signed byte (sign extended) + 3: LDR{cond}SH Rd,
;Load Signed halfword (sign extended) + 4 Reserved, must be set (1) + 3-0 When above Bit 22 I=0: + Rm - Offset Register (R0-R14) (not including R15) + When above Bit 22 I=1: + Immediate Offset (lower 4bits) (0-255, together with upper bits) + */ + pub fn dispatch_data_trans_imm(&mut self, opcode: u32) -> u8 { + let p = (opcode >> 24) & 1 == 1; + let up = (opcode >> 23) & 1 == 1; + let write_back = !p || ((opcode >> 21) & 1 == 1); + let load = (opcode >> 20) & 1 == 1; + let rn = ((opcode >> 16) & 0xF) as u8; + let rd = ((opcode >> 12) & 0xF) as u8; + let mut base_val = self.r[rn]; + if rn == 15 { + base_val += 4; + } + let mut src_val = self.r[rd]; + if rd == 15 { + src_val += 8; + } + let offset = (((opcode >> 8) & 0xF) << 4) | (opcode & 0xF); + let opcode = (opcode >> 5) & 0x3; + + if p { + if up { + base_val += offset; + } else { + base_val -= offset; + } + } + let mut cycles = 0; + + match (load, opcode) { + (true, 0b00) => todo!("reserved"), + (true, 0b01) => { + trace!("LDRH r{} = [r{} + 0x{:X}]", rd, rn, offset); + let o = self.get_mem16(base_val); + self.r[rd] = o.0 as u32; + cycles = 2 + o.1; + } + (true, 0b10) => { + trace!("LDRSB r{} = [r{} + 0x{:X}]", rd, rn, offset); + let o = self.get_mem8(base_val); + self.r[rd] = (o.0 as i8) as i32 as u32; + cycles = 2 + o.1; + } + (true, 0b11) => { + trace!("LDRSH r{} = [r{} + 0x{:X}]", rd, rn, offset); + let o = self.get_mem16(base_val); + self.r[rd] = (o.0 as i16) as i32 as u32; + cycles = 2 + o.1; + } + (false, 0b00) => todo!("SWP"), + (false, 0b01) => { + trace!("STRH r{} = [r{} + 0x{:X}]", rd, rn, offset); + let o = self.set_mem16(base_val, src_val as u16); + cycles = 2 + o; + } + (false, 0b10) => { + trace!("LDRD r{} = [r{} + 0x{:X}]", rd, rn, offset); + let o1 = self.get_mem32(base_val); + let o2 = self.get_mem32(base_val + 4); + self.r[rd] = o1.0; + self.r[rd + 1] = o2.0; + cycles = 2 + o1.1 + o2.1; + } + (false, 0b11) => { + trace!("STRD r{} = [r{} + 0x{:X}]", rd, rn, offset); + let o1 = self.set_mem32(base_val, src_val); + let mut src_val2 = self.r[rd + 1]; + if rd + 1 == 15 { + src_val2 += 8; + } + let o2 = self.set_mem32(base_val + 4, src_val2); + cycles = 2 + o1 + o2; + } + _ => unreachable!(), + } + if !p { + if up { + base_val += offset; + } else { + base_val -= offset; + } + } + if write_back { + self.r[rd] = base_val; + // TODO: do we need to write back twice for double words? probably + } + cycles + } + // ARM Opcodes: Memory: Single Data Transfer (LDR, STR, PLD) pub fn dispatch_mem(&mut self, opcode: u32) -> u8 { let mut cycles = 0; @@ -2905,10 +3333,11 @@ impl GameboyAdvance { let force_nonpriviliged = (opcode >> 21) & 1 == 1; let write_back = !p || ((opcode >> 21) & 1 == 1); let load = (opcode >> 20) & 1 == 1; - // TODO: R15 may need special logic here let base_reg = (opcode >> 16) & 0xF; - // TODO: R15 may need special logic here let src_dest_reg = (opcode >> 12) & 0xF; + if force_nonpriviliged { + todo!("figure this out"); + } let offset = if imm { let shift_type = (opcode >> 5) & 3; @@ -2932,7 +3361,7 @@ impl GameboyAdvance { let mut val = self.r[base_reg as u8]; if base_reg == 15 { - val += 8; + val += 4; } if p { if up { @@ -2954,18 +3383,39 @@ impl GameboyAdvance { */ self.r[src_dest_reg as u8] = o.0 as u32; } else { - trace!("LDR r{:X} = mem[{:X}]", src_dest_reg, val); let o = self.get_mem32(val); + trace!( + "LDR r{} = mem[r{} (0x{:X})] (0x{:X})", + src_dest_reg, + base_reg, + val, + o.0 + ); cycles += o.1; self.r[src_dest_reg as u8] = o.0; } } else { + let write_val = if src_dest_reg == 15 { + self.r[src_dest_reg as u8] + 8 + } else { + self.r[src_dest_reg as u8] + }; if byte { - trace!("STRB mem[{:X}] = r{:X}", val, src_dest_reg); - cycles += self.set_mem8(val, self.r[src_dest_reg as u8] as u8); + trace!( + "STRB mem[{:X}] = r{:X} (0x{:X})", + val, + src_dest_reg, + write_val as u8 + ); + cycles += self.set_mem8(val, write_val as u8); } else { - trace!("STR mem[{:X}] = r{:X}", val, src_dest_reg); - cycles += self.set_mem32(val, self.r[src_dest_reg as u8]); + trace!( + "STR mem[{:X}] = r{:X} (0x{:X})", + val, + src_dest_reg, + write_val + ); + cycles += self.set_mem32(val, write_val); } } if !p { @@ -2979,10 +3429,12 @@ impl GameboyAdvance { if write_back || !p { self.r[base_reg as u8] = val; } + /* if !p && write_back { - warn!("Write back bit has special meaning in post-inc mode, figure this out"); - //todo!("Write back bit has special meaning in post-inc mode, figure this out"); + //warn!("Write back bit has special meaning in post-inc mode, figure this out"); + todo!("Write back bit has special meaning in post-inc mode, figure this out"); } + */ cycles } @@ -2991,7 +3443,7 @@ impl GameboyAdvance { let p = (opcode >> 24) & 1 == 1; let up = (opcode >> 23) & 1 == 1; let force_user_mode = (opcode >> 22) & 1 == 1; - let write_back = (opcode >> 21) & 1 == 1; + let mut write_back = (opcode >> 21) & 1 == 1; let load = (opcode >> 20) & 1 == 1; let rn = ((opcode >> 16) & 0xF) as u8; let r_list = opcode & 0xFFFF; @@ -3004,12 +3456,37 @@ impl GameboyAdvance { warn!("Figure out force user mode"); } + let debug_type = match (load, p, up) { + (true, true, true) => "LDMED", + (true, false, true) => "LDMFD", + (true, true, false) => "LDMEA", + (true, false, false) => "LDMFA", + (false, false, false) => "STMED", + (false, true, false) => "STMFD", + (false, false, true) => "STMEA", + (false, true, true) => "STMFA", + }; + + // LDM, STM + trace!( + "{} r{} (0x{:X}) - {:016b}: in {:?}", + debug_type, + rn, + base, + r_list, + self.r.register_mode() + ); + // TOOD: all accesses should be done lower to higher if load { - trace!("LDM"); - for i in 0..16 { + for i in 0..16 + /* .rev()*/ + { if r_list & (1 << i) == 0 { continue; } + if i == rn { + write_back = false; + } if p { base = if up { base.wrapping_add(4) @@ -3018,6 +3495,7 @@ impl GameboyAdvance { } } let o = self.get_mem32(base); + //println!("Loading r{} (0x{:X}) from 0x{:X}", i, o.0, base); self.r[i as u8] = o.0; cycles += o.1 + 2; if !p { @@ -3029,8 +3507,7 @@ impl GameboyAdvance { } } } else { - trace!("STM"); - for i in 0..16 { + for i in (0..16).rev() { if r_list & (1 << i) == 0 { continue; } @@ -3041,6 +3518,7 @@ impl GameboyAdvance { base.wrapping_sub(4) } } + //println!("pushing r{} ({:X}) to 0x{:X}", i, self.r[i as u8], base); cycles += self.set_mem32(base, self.r[i as u8]); cycles += 1; if !p { @@ -3062,23 +3540,19 @@ impl GameboyAdvance { let subopcode = (opcode >> 4) & 0xF; let op_reg = opcode & 0xF; debug_assert!(op_reg < 15); - let op = self.r[op_reg as u8]; - let thumb_mode = op & 1 == 1; - let new_pc = op - (thumb_mode as u32); - if self.r.thumb_enabled() != thumb_mode { - if thumb_mode { - info!( - "Enabling Thumb mode from 0x{:X} to 0x{:X}!", - self.r.pc, new_pc - ); - } else { - info!("Enabling ARM mode!"); - } + let mut op = self.r[op_reg as u8]; + if op_reg == 15 { + op += 4; } + let thumb_mode = op & 1 == 1; + let old_pc = self.r.pc; + let new_pc = if thumb_mode { op & !1 } else { op & !3 }; + let old_thumb_enabled = self.r.thumb_enabled(); + match subopcode { 0b0001 => { // BX - trace!("BX pc = r{} (0x{:X})", op_reg, op); + trace!("BX to r{} (0x{:X})", op_reg, new_pc); self.r.pc = new_pc; self.r.set_thumb(thumb_mode); } @@ -3087,20 +3561,67 @@ impl GameboyAdvance { } 0b0011 => { // BLX - trace!( - "BLX pc = r{} (0x{:X}), lr = 0x{:X}", - op_reg, - op, - self.r.pc + 4 - ); - let old_pc = self.r.pc; + trace!("BLX pc = r{} (0x{:X}), lr = 0x{:X}", op_reg, op, self.r.pc); self.r.set_thumb(thumb_mode); self.r.pc = new_pc; - *self.r.lr_mut() = old_pc + 4; + //*self.r.lr_mut() = old_pc + 4; + *self.r.lr_mut() = old_pc; } _ => panic!("Unknown branch and exchange subopcode 0x{:X}", subopcode), } + + if old_thumb_enabled != thumb_mode { + if thumb_mode { + trace!("Enabling Thumb mode from 0x{:X} to 0x{:X}!", old_pc, new_pc); + } else { + trace!("Enabling ARM mode!"); + } + } // 2S + 1N 3 } } + +use crate::io::graphics::renderer::Button; +fn button_to_bit(button: Button) -> u16 { + match button { + Button::A => 0x0001, + Button::B => 0x0002, + Button::Select => 0x0004, + Button::Start => 0x0008, + Button::Right => 0x0010, + Button::Left => 0x0020, + Button::Up => 0x0040, + Button::Down => 0x0080, + Button::R => 0x0100, + Button::L => 0x0200, + _ => 0, + } +} + +impl crate::io::graphics::renderer::InputReceiver for GameboyAdvance { + // TODO: 4000132h - KEYCNT - Key Interrupt Control (R/W) + fn press(&mut self, button: Button) { + let bit = button_to_bit(button); + if bit > 0xFF { + self.io_registers[0x131] &= !((bit >> 8) as u8); + } else { + self.io_registers[0x130] &= !(bit as u8); + } + if self.keypad_interrupt_enabled() { + dbg!("Button interrupt fired"); + self.keypad_interrupt_requested(); + } + } + fn unpress(&mut self, button: Button) { + let bit = button_to_bit(button); + if bit > 0xFF { + self.io_registers[0x131] |= (bit >> 8) as u8; + } else { + self.io_registers[0x130] |= bit as u8; + } + } + fn reset(&mut self) { + todo!("reset"); + } +} diff --git a/src/io/applicationstate.rs b/src/io/applicationstate.rs index a262b7f..91ce76c 100644 --- a/src/io/applicationstate.rs +++ b/src/io/applicationstate.rs @@ -32,6 +32,7 @@ pub struct ApplicationState { /// counts cycles since last sound update sound_cycles: u64, _screenshot_frame_num: Wrapping, + gba_timers: [u16; 4], pub renderer: Box, } @@ -54,6 +55,7 @@ impl ApplicationState { cycles_per_second: CPU_CYCLES_PER_SECOND, sound_cycles: 0, _screenshot_frame_num: Wrapping(0), + gba_timers: [0, 0, 0, 0], renderer, }) } @@ -72,6 +74,12 @@ impl ApplicationState { let mut cycles = 0; let mut frame = [[(0u8, 0u8, 0u8); GBA_SCREEN_WIDTH]; GBA_SCREEN_HEIGHT]; let mut y = 0; + let timer_prescalers = [ + self.gba.io_registers.timer0_prescaler(), + self.gba.io_registers.timer1_prescaler(), + self.gba.io_registers.timer2_prescaler(), + self.gba.io_registers.timer3_prescaler(), + ]; #[derive(Debug, Clone, Copy)] enum GameBoyAdvanceMode { @@ -86,6 +94,23 @@ impl ApplicationState { cycles += cycles_from_opcode; hblank_cycles += cycles_from_opcode; + for (i, timer) in self.gba_timers.iter_mut().enumerate() { + if !self.gba.io_registers.timer_enabled(i as u8) { + continue; + } + *timer += cycles_from_opcode as u16; + if *timer >= timer_prescalers[i] { + *timer -= timer_prescalers[i]; + if self.gba.io_registers.increment_timer(i as u8) + && self.gba.io_registers.timer_irq_enabled(i as u8) + && self.gba.master_interrupts_enabled() + { + dbg!("TIMER INTERRUPT!"); + self.gba.set_timer_interrupt(i as u8, true); + } + } + } + if in_hblank { if hblank_cycles >= 272 { in_hblank = false; diff --git a/src/io/deferred_renderer_gba.rs b/src/io/deferred_renderer_gba.rs index b92c652..6b61e52 100644 --- a/src/io/deferred_renderer_gba.rs +++ b/src/io/deferred_renderer_gba.rs @@ -39,25 +39,26 @@ pub fn deferred_renderer_draw_gba_scanline( nth_pixel = 7 - nth_pixel; } - // 16/16 mode - let tile_line = nth_line * 4; + if bg0_control.color_mode { + todo!() + } else { + // 16/16 mode + let tile_line = nth_line * 4; - let tile_start = tile_base_ptr as usize + (tile_num as usize * 32); - let tile_line_start = tile_start + tile_line as usize; - let tile_byte_start = tile_line_start + (nth_pixel >> 1) as usize; - let color_4bit = gba.vram[tile_byte_start] >> (nth_pixel & 0x1); + let tile_start = tile_base_ptr as usize + (tile_num as usize * 32); + let tile_line_start = tile_start + tile_line as usize; + let tile_byte_start = tile_line_start + (nth_pixel >> 1) as usize; + let color_4bit = gba.vram[tile_byte_start] >> (nth_pixel & 0x1); - let palette_start = palette_num as usize * 16; - let color_lo = gba.obj_palette_ram[palette_start + (color_4bit as usize * 2)]; - let color_hi = gba.obj_palette_ram[palette_start + (color_4bit as usize * 2) + 1]; - let red = color_lo & 0x1F; - let green = ((color_hi & 0x3) << 3) | (color_lo >> 5); - let blue = (color_hi >> 2) & 0x1F; - if (red | green | blue) != 0 { - panic!("COLOR!"); - } + let palette_start = palette_num as usize * 16; + let color_lo = gba.obj_palette_ram[palette_start + (color_4bit as usize * 2)]; + let color_hi = gba.obj_palette_ram[palette_start + (color_4bit as usize * 2) + 1]; + let red = color_lo & 0x1F; + let green = ((color_hi & 0x3) << 3) | (color_lo >> 5); + let blue = (color_hi >> 2) & 0x1F; - bg_pixels[x as usize] = (red << 3, green << 3, blue << 3); + bg_pixels[x as usize] = (red << 3, green << 3, blue << 3); + } } bg_pixels diff --git a/src/io/dr_sdl2.rs b/src/io/dr_sdl2.rs index e24fbd8..e7a856b 100644 --- a/src/io/dr_sdl2.rs +++ b/src/io/dr_sdl2.rs @@ -13,6 +13,8 @@ use crate::io::graphics::renderer::Renderer; use crate::io::graphics::sdl2::input::setup_controller_subsystem; use crate::io::sound::*; +use super::graphics::renderer::{Button, InputReceiver}; + pub struct Sdl2Renderer { sdl_context: Sdl, sound_system: sdl2::audio::AudioDevice, @@ -271,7 +273,7 @@ impl Renderer for Sdl2Renderer { self.canvas.present(); } - fn handle_events(&mut self, gameboy: &mut Cpu) -> Vec { + fn handle_events(&mut self, ir: &mut dyn InputReceiver) -> Vec { let mut ret_vec: Vec = vec![]; for event in self.sdl_context.event_pump().unwrap().poll_iter() { use sdl2::event::Event; @@ -285,29 +287,29 @@ impl Renderer for Sdl2Renderer { match axis { controller::Axis::LeftX if deadzone < (val as i32).abs() => { if val < 0 { - gameboy.press_left(); - gameboy.unpress_right(); + ir.press(Button::Left); + ir.unpress(Button::Right); } else { - gameboy.press_right(); - gameboy.unpress_left(); + ir.press(Button::Right); + ir.unpress(Button::Left); }; } controller::Axis::LeftX => { - gameboy.unpress_left(); - gameboy.unpress_right(); + ir.unpress(Button::Left); + ir.press(Button::Right); } controller::Axis::LeftY if deadzone < (val as i32).abs() => { if val < 0 { - gameboy.press_up(); - gameboy.unpress_down(); + ir.press(Button::Up); + ir.unpress(Button::Down); } else { - gameboy.press_down(); - gameboy.unpress_up(); + ir.press(Button::Down); + ir.unpress(Button::Up); } } controller::Axis::LeftY => { - gameboy.unpress_up(); - gameboy.unpress_down(); + ir.unpress(Button::Up); + ir.unpress(Button::Down); } _ => {} } @@ -316,13 +318,13 @@ impl Renderer for Sdl2Renderer { trace!("Button {:?} down", button); match button { controller::Button::A => { - gameboy.press_a(); + ir.press(Button::A); // TODO: sound // device.resume(); } - controller::Button::B => gameboy.press_b(), - controller::Button::Back => gameboy.press_select(), - controller::Button::Start => gameboy.press_start(), + controller::Button::B => ir.press(Button::B), + controller::Button::Back => ir.press(Button::Select), + controller::Button::Start => ir.press(Button::Start), _ => (), } } @@ -331,11 +333,11 @@ impl Renderer for Sdl2Renderer { trace!("Button {:?} up", button); match button { controller::Button::A => { - gameboy.unpress_a(); + ir.unpress(Button::A); } - controller::Button::B => gameboy.unpress_b(), - controller::Button::Back => gameboy.unpress_select(), - controller::Button::Start => gameboy.unpress_start(), + controller::Button::B => ir.unpress(Button::B), + controller::Button::Back => ir.unpress(Button::Select), + controller::Button::Start => ir.unpress(Button::Start), _ => (), } } @@ -379,15 +381,15 @@ impl Renderer for Sdl2Renderer { } => { if !repeat { match keycode { - Keycode::F3 => gameboy.toggle_logger(), + Keycode::F3 => ir.toggle_logger(), Keycode::R => { // Reset/reload emu // TODO Keep previous visualization settings - gameboy.reset(); + ir.reset(); ret_vec.push(EventResponse::Reset); //let gbcopy = self.initial_gameboy_state.clone(); //gameboy = gbcopy; - gameboy.reinit_logger(); + ir.reinit_logger(); // // This way makes it possible to edit rom // // with external editor and see changes @@ -395,14 +397,14 @@ impl Renderer for Sdl2Renderer { // gameboy = Cpu::new(); // gameboy.load_rom(rom_file); } - Keycode::A => gameboy.press_a(), - Keycode::S => gameboy.press_b(), - Keycode::D => gameboy.press_select(), - Keycode::F => gameboy.press_start(), - Keycode::Up => gameboy.press_up(), - Keycode::Down => gameboy.press_down(), - Keycode::Left => gameboy.press_left(), - Keycode::Right => gameboy.press_right(), + Keycode::A => ir.press(Button::A), + Keycode::S => ir.press(Button::B), + Keycode::D => ir.press(Button::Select), + Keycode::F => ir.press(Button::Start), + Keycode::Up => ir.press(Button::Up), + Keycode::Down => ir.press(Button::Down), + Keycode::Left => ir.press(Button::Left), + Keycode::Right => ir.press(Button::Right), _ => (), } } @@ -414,15 +416,14 @@ impl Renderer for Sdl2Renderer { } => { if !repeat { match keycode { - Keycode::A => gameboy.unpress_a(), - Keycode::S => gameboy.unpress_b(), - Keycode::D => gameboy.unpress_select(), - Keycode::F => gameboy.unpress_start(), - Keycode::Up => gameboy.unpress_up(), - Keycode::Down => gameboy.unpress_down(), - Keycode::Left => gameboy.unpress_left(), - Keycode::Right => gameboy.unpress_right(), - + Keycode::A => ir.unpress(Button::A), + Keycode::S => ir.unpress(Button::B), + Keycode::D => ir.unpress(Button::Select), + Keycode::F => ir.unpress(Button::Start), + Keycode::Up => ir.unpress(Button::Up), + Keycode::Down => ir.unpress(Button::Down), + Keycode::Left => ir.unpress(Button::Left), + Keycode::Right => ir.unpress(Button::Right), _ => (), } } diff --git a/src/io/graphics/renderer.rs b/src/io/graphics/renderer.rs index fdc87c2..a566773 100644 --- a/src/io/graphics/renderer.rs +++ b/src/io/graphics/renderer.rs @@ -9,6 +9,28 @@ pub enum EventResponse { Reset, } +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Button { + A, + B, + Start, + Select, + Up, + Down, + Left, + Right, + R, + L, +} + +pub trait InputReceiver { + fn press(&mut self, button: Button); + fn unpress(&mut self, button: Button); + fn reset(&mut self); + fn toggle_logger(&mut self) {} + fn reinit_logger(&mut self) {} +} + pub trait Renderer { fn draw_frame(&mut self, frame: &[[(u8, u8, u8); GB_SCREEN_WIDTH]; GB_SCREEN_HEIGHT]); fn draw_gba_frame(&mut self, frame: &[[(u8, u8, u8); GBA_SCREEN_WIDTH]; GBA_SCREEN_HEIGHT]) { @@ -18,7 +40,7 @@ pub trait Renderer { fn draw_memory_visualization(&mut self, _: &Cpu) { unimplemented!(); } - fn handle_events(&mut self, _: &mut Cpu) -> Vec; + fn handle_events(&mut self, _: &mut dyn InputReceiver) -> Vec; fn audio_step(&mut self, _gb: &Cpu) { unimplemented!(); diff --git a/src/io/graphics/sdl2/mod.rs b/src/io/graphics/sdl2/mod.rs index 073894f..65f2b17 100644 --- a/src/io/graphics/sdl2/mod.rs +++ b/src/io/graphics/sdl2/mod.rs @@ -15,10 +15,9 @@ use self::utility::PositionedFrame; use self::vidram::{VidRamBGDisplay, VidRamTileDisplay}; use super::renderer; use super::renderer::EventResponse; -use crate::cpu::Cpu; use crate::io::applicationsettings::ApplicationSettings; use crate::io::constants::*; -use crate::io::graphics::renderer::Renderer; +use crate::io::graphics::renderer::{Button, InputReceiver, Renderer}; use crate::io::sound::*; use self::utility::*; @@ -160,7 +159,7 @@ impl Renderer for Sdl2Renderer { todo!("do this later if we care") } - fn handle_events(&mut self, gameboy: &mut Cpu) -> Vec { + fn handle_events(&mut self, ir: &mut dyn InputReceiver) -> Vec { let mut ret_vec: Vec = vec![]; for event in self.sdl_context.event_pump().unwrap().poll_iter() { use sdl2::event::Event; @@ -174,29 +173,29 @@ impl Renderer for Sdl2Renderer { match axis { controller::Axis::LeftX if deadzone < (val as i32).abs() => { if val < 0 { - gameboy.press_left(); - gameboy.unpress_right(); + ir.press(Button::Left); + ir.unpress(Button::Right); } else { - gameboy.press_right(); - gameboy.unpress_left(); + ir.press(Button::Right); + ir.unpress(Button::Left); }; } controller::Axis::LeftX => { - gameboy.unpress_left(); - gameboy.unpress_right(); + ir.unpress(Button::Left); + ir.press(Button::Right); } controller::Axis::LeftY if deadzone < (val as i32).abs() => { if val < 0 { - gameboy.press_up(); - gameboy.unpress_down(); + ir.press(Button::Up); + ir.unpress(Button::Down); } else { - gameboy.press_down(); - gameboy.unpress_up(); + ir.press(Button::Down); + ir.unpress(Button::Up); } } controller::Axis::LeftY => { - gameboy.unpress_up(); - gameboy.unpress_down(); + ir.unpress(Button::Up); + ir.unpress(Button::Down); } _ => {} } @@ -205,13 +204,13 @@ impl Renderer for Sdl2Renderer { trace!("Button {:?} down", button); match button { controller::Button::A => { - gameboy.press_a(); + ir.press(Button::A); // TODO: sound // device.resume(); } - controller::Button::B => gameboy.press_b(), - controller::Button::Back => gameboy.press_select(), - controller::Button::Start => gameboy.press_start(), + controller::Button::B => ir.press(Button::B), + controller::Button::Back => ir.press(Button::Select), + controller::Button::Start => ir.press(Button::Start), _ => (), } } @@ -220,11 +219,11 @@ impl Renderer for Sdl2Renderer { trace!("Button {:?} up", button); match button { controller::Button::A => { - gameboy.unpress_a(); + ir.unpress(Button::A); } - controller::Button::B => gameboy.unpress_b(), - controller::Button::Back => gameboy.unpress_select(), - controller::Button::Start => gameboy.unpress_start(), + controller::Button::B => ir.unpress(Button::B), + controller::Button::Back => ir.unpress(Button::Select), + controller::Button::Start => ir.unpress(Button::Start), _ => (), } } @@ -268,15 +267,15 @@ impl Renderer for Sdl2Renderer { } => { if !repeat { match keycode { - Keycode::F3 => gameboy.toggle_logger(), + Keycode::F3 => ir.toggle_logger(), Keycode::R => { // Reset/reload emu // TODO Keep previous visualization settings - gameboy.reset(); + ir.reset(); ret_vec.push(EventResponse::Reset); //let gbcopy = self.initial_gameboy_state.clone(); //gameboy = gbcopy; - gameboy.reinit_logger(); + ir.reinit_logger(); // // This way makes it possible to edit rom // // with external editor and see changes @@ -284,14 +283,14 @@ impl Renderer for Sdl2Renderer { // gameboy = Cpu::new(); // gameboy.load_rom(rom_file); } - Keycode::A => gameboy.press_a(), - Keycode::S => gameboy.press_b(), - Keycode::D => gameboy.press_select(), - Keycode::F => gameboy.press_start(), - Keycode::Up => gameboy.press_up(), - Keycode::Down => gameboy.press_down(), - Keycode::Left => gameboy.press_left(), - Keycode::Right => gameboy.press_right(), + Keycode::A => ir.press(Button::A), + Keycode::S => ir.press(Button::B), + Keycode::D => ir.press(Button::Select), + Keycode::F => ir.press(Button::Start), + Keycode::Up => ir.press(Button::Up), + Keycode::Down => ir.press(Button::Down), + Keycode::Left => ir.press(Button::Left), + Keycode::Right => ir.press(Button::Right), _ => (), } } @@ -303,25 +302,26 @@ impl Renderer for Sdl2Renderer { } => { if !repeat { match keycode { - Keycode::A => gameboy.unpress_a(), - Keycode::S => gameboy.unpress_b(), - Keycode::D => gameboy.unpress_select(), - Keycode::F => gameboy.unpress_start(), - Keycode::Up => gameboy.unpress_up(), - Keycode::Down => gameboy.unpress_down(), - Keycode::Left => gameboy.unpress_left(), - Keycode::Right => gameboy.unpress_right(), - + Keycode::A => ir.unpress(Button::A), + Keycode::S => ir.unpress(Button::B), + Keycode::D => ir.unpress(Button::Select), + Keycode::F => ir.unpress(Button::Start), + Keycode::Up => ir.unpress(Button::Up), + Keycode::Down => ir.unpress(Button::Down), + Keycode::Left => ir.unpress(Button::Left), + Keycode::Right => ir.unpress(Button::Right), _ => (), } } } Event::MouseButtonDown { - x, y, mouse_btn, .. + x: _x, + y: _y, + mouse_btn: _mouse_btn, + .. } => { // Transform screen coordinates in UI coordinates - let click_point = - display_coords_to_ui_point(2. /*app_settings.ui_scale*/, x, y); + /* let click_point = display_coords_to_ui_point(app_settings.ui_scale, x, y); // Find clicked widget for widget in &mut self.widgets { @@ -329,7 +329,7 @@ impl Renderer for Sdl2Renderer { widget.click(mouse_btn, click_point, gameboy); break; } - } + }*/ } Event::MouseWheel { y: _y, .. } => { //self.ui_scale += y as f32; @@ -345,7 +345,7 @@ impl Renderer for Sdl2Renderer { } } - ret_vec + return ret_vec; } } diff --git a/src/main.rs b/src/main.rs index fb26dd7..b759649 100644 --- a/src/main.rs +++ b/src/main.rs @@ -120,6 +120,46 @@ fn main() { None }; + if is_gba { + loop { + let time_since_last_frame = std::time::Instant::now(); + for event in appstate + .renderer + .handle_events(&mut appstate.gba /* , &application_settings*/) + .iter() + { + match *event { + EventResponse::ProgramTerminated => { + info!("Program exiting!"); + if let Some(ref mut debugger) = debugger { + debugger.die(); + } + /* + appstate + .gameboy + .save_ram(application_settings.data_path.clone()); + */ + std::process::exit(0); + } + EventResponse::Reset => { + info!("Resetting gameboy advance"); + todo!("GBA reset"); + //appstate.gba.reset(); + } + } + } + + appstate.step_gba(); + /*//check for new controller every frame + self.load_controller_if_none_exist();*/ + + let time_diff = time_since_last_frame.elapsed(); + if time_diff < std::time::Duration::from_millis(16) { + std::thread::sleep(std::time::Duration::from_millis(16) - time_diff); + } + } + } + loop { let time_since_last_frame = std::time::Instant::now(); for event in appstate @@ -145,13 +185,9 @@ fn main() { } } - if is_gba { - appstate.step_gba(); - } else { - appstate.step(); - if let Some(ref mut dbg) = debugger { - dbg.step(&mut appstate.gameboy); - } + appstate.step(); + if let Some(ref mut dbg) = debugger { + dbg.step(&mut appstate.gameboy); } /*//check for new controller every frame From bd2e48e052a67f142ead6e725134f538ba6554c7 Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Wed, 26 Jul 2023 11:35:54 +0800 Subject: [PATCH 3/6] Fix many bugs, pass more tests, PPU mode 4 --- src/gba/mod.rs | 806 ++++++++++++++++++++++---------- src/io/applicationstate.rs | 39 +- src/io/deferred_renderer_gba.rs | 88 +++- src/main.rs | 2 + src/prelude.rs | 23 + 5 files changed, 686 insertions(+), 272 deletions(-) create mode 100644 src/prelude.rs diff --git a/src/gba/mod.rs b/src/gba/mod.rs index a3c750c..61aab5b 100644 --- a/src/gba/mod.rs +++ b/src/gba/mod.rs @@ -11,6 +11,9 @@ pub struct GameboyAdvance { pub obj_palette_ram: [u8; 0x400], pub vram: [u8; 0x18000], pub oam: [u8; 0x400], + sram: [u8; 0x10000], + // used for "break points" for counting loops, etc while I debug the basics + debug_counter: usize, } #[derive(Debug, Clone, Copy)] @@ -54,6 +57,71 @@ impl PpuBgControl { } } +/// Bit shifting rotation mechanism in the ARM7 CPU. +/// In ARM mode these are bundled into some instructions. +/// In Thumb mode this can be accessed with separate instructions. +pub struct BarrelShifter; + +impl BarrelShifter { + pub fn lsl(val: u32, shift_amt: u32, registers: Option<&mut Registers>) -> u32 { + if shift_amt == 0 { + return val; + } + if shift_amt > 32 { + registers.map(|r| r.cpsr_set_carry_flag(false)); + return 0; + } + if shift_amt == 32 { + registers.map(|r| r.cpsr_set_carry_flag(val & 1 == 1)); + return 0; + } + registers.map(|r| r.cpsr_set_carry_flag((val >> (32 - shift_amt)) & 1 == 1)); + val << shift_amt + } + pub fn lsr(val: u32, shift_amt: u32, registers: Option<&mut Registers>) -> u32 { + if shift_amt == 0 { + registers.map(|r| r.cpsr_set_carry_flag((val >> 31) & 1 == 1)); + return 0; + } + if shift_amt > 32 { + registers.map(|r| r.cpsr_set_carry_flag(false)); + return 0; + } + if shift_amt == 32 { + registers.map(|r| r.cpsr_set_carry_flag((val >> 31) & 1 == 1)); + return 0; + } + registers.map(|r| r.cpsr_set_carry_flag((val >> (shift_amt - 1)) & 1 == 1)); + val >> shift_amt + } + pub fn asr(val: u32, shift_amt: u32, registers: Option<&mut Registers>) -> u32 { + if shift_amt == 0 { + registers.map(|r| r.cpsr_set_carry_flag((val >> 31) & 1 == 1)); + return ((val as i32) >> 31) as u32; + } + if shift_amt >= 32 { + registers.map(|r| r.cpsr_set_carry_flag((val >> 31) & 1 == 1)); + return ((val as i32) >> 31) as u32; + } + registers.map(|r| r.cpsr_set_carry_flag((val >> (shift_amt - 1)) & 1 == 1)); + ((val as i32) >> shift_amt) as u32 + } + pub fn ror(val: u32, shift_amt: u32, registers: Option<&mut Registers>) -> u32 { + if shift_amt == 0 { + return val; + } + let out = val.rotate_right(shift_amt); + registers.map(|r| r.cpsr_set_carry_flag((out >> 31) & 1 == 1)); + out + } + // acts like rrx1, do we need a general RRX? + pub fn rrx(val: u32, registers: Option<&mut Registers>, carry: bool) -> u32 { + let out = (val >> 1) | ((carry as u32) << 31); + registers.map(|r| r.cpsr_set_carry_flag(val & 1 == 1)); + out + } +} + #[derive(Debug, Clone, Copy)] pub enum DmaAddrControl { Increment, @@ -610,6 +678,7 @@ impl std::ops::Index for Registers { RegisterMode::IRQ => &self.r13_irq, RegisterMode::Undefined => &self.r13_und, RegisterMode::User => &self.sp, + RegisterMode::System => &self.sp, }, 14 => match mode { RegisterMode::FIQ => &self.r14_fiq, @@ -618,6 +687,7 @@ impl std::ops::Index for Registers { RegisterMode::IRQ => &self.r14_irq, RegisterMode::Undefined => &self.r14_und, RegisterMode::User => &self.lr, + RegisterMode::System => &self.lr, }, 15 => &self.pc, _ => unimplemented!("invalid register read"), @@ -680,6 +750,7 @@ impl std::ops::IndexMut for Registers { RegisterMode::IRQ => &mut self.r13_irq, RegisterMode::Undefined => &mut self.r13_und, RegisterMode::User => &mut self.sp, + RegisterMode::System => &mut self.sp, }, 14 => match mode { RegisterMode::FIQ => &mut self.r14_fiq, @@ -688,6 +759,7 @@ impl std::ops::IndexMut for Registers { RegisterMode::IRQ => &mut self.r14_irq, RegisterMode::Undefined => &mut self.r14_und, RegisterMode::User => &mut self.lr, + RegisterMode::System => &mut self.lr, }, 15 => &mut self.pc, _ => unimplemented!("invalid register write"), @@ -723,6 +795,8 @@ pub enum RegisterMode { Supervisor, Abort, Undefined, + /// Not described in GBA docs, but this is a real ARMv7 mode... + System, } impl RegisterMode { @@ -736,7 +810,7 @@ impl RegisterMode { 0b10111 => Some(Self::Abort), 0b11011 => Some(Self::Undefined), // privileged user - 0b11111 => Some(Self::User), + 0b11111 => Some(Self::System), _ => None, } } @@ -802,9 +876,9 @@ impl Registers { r10: 0, r11: 0, r12: 0, - sp: 0, + sp: 0x03007F00, lr: 0, - pc: 0, //0x08000000, + pc: 0x08000000, r8_fiq: 0, r9_fiq: 0, r10_fiq: 0, @@ -812,11 +886,11 @@ impl Registers { r12_fiq: 0, r13_fiq: 0, r14_fiq: 0, - r13_svc: 0, + r13_svc: 0x03007FE0, r14_svc: 0, r13_abt: 0, r14_abt: 0, - r13_irq: 0, + r13_irq: 0x03007FA0, r14_irq: 0, r13_und: 0, r14_und: 0, @@ -885,7 +959,7 @@ impl Registers { pub fn get_spsr(&self) -> u32 { let mode = self.register_mode().unwrap(); match mode { - RegisterMode::User => unimplemented!("Is this possible?"), + RegisterMode::User | RegisterMode::System => unimplemented!("Is this possible?"), RegisterMode::FIQ => self.spsr_fiq, RegisterMode::Supervisor => self.spsr_svc, RegisterMode::Abort => self.spsr_abt, @@ -896,7 +970,7 @@ impl Registers { pub fn get_spsr_mut(&mut self) -> &mut u32 { let mode = self.register_mode().unwrap(); match mode { - RegisterMode::User => unimplemented!("Is this possible?"), + RegisterMode::User | RegisterMode::System => unimplemented!("Is this possible?"), RegisterMode::FIQ => &mut self.spsr_fiq, RegisterMode::Supervisor => &mut self.spsr_svc, RegisterMode::Abort => &mut self.spsr_abt, @@ -921,6 +995,7 @@ impl Registers { RegisterMode::Abort => self.r14_abt, RegisterMode::IRQ => self.r14_irq, RegisterMode::Undefined => self.r14_und, + RegisterMode::System => self.lr, } } pub fn lr_mut(&mut self) -> &mut u32 { @@ -932,6 +1007,7 @@ impl Registers { RegisterMode::Abort => &mut self.r14_abt, RegisterMode::IRQ => &mut self.r14_irq, RegisterMode::Undefined => &mut self.r14_und, + RegisterMode::System => &mut self.lr, } } pub fn sp(&self) -> u32 { @@ -943,6 +1019,7 @@ impl Registers { RegisterMode::Abort => self.r13_abt, RegisterMode::IRQ => self.r13_irq, RegisterMode::Undefined => self.r13_und, + RegisterMode::System => self.sp, } } pub fn sp_mut(&mut self) -> &mut u32 { @@ -954,6 +1031,7 @@ impl Registers { RegisterMode::Abort => &mut self.r13_abt, RegisterMode::IRQ => &mut self.r13_irq, RegisterMode::Undefined => &mut self.r13_und, + RegisterMode::System => &mut self.sp, } } pub fn split_pc(&self) -> (u8, (bool, bool), u32, Mode) { @@ -980,6 +1058,8 @@ impl GameboyAdvance { obj_palette_ram: [0u8; 0x400], vram: [0u8; 0x18000], oam: [0u8; 0x400], + sram: [0u8; 0x10000], + debug_counter: 0, } } @@ -1046,6 +1126,10 @@ impl GameboyAdvance { } } + // for modes 4 and 5 + pub fn ppu_frame_select(&self) -> bool { + (self.io_registers[0] >> 4) & 1 == 1 + } pub fn ppu_bg0_x_scroll(&self) -> u16 { ((self.io_registers[0x10] as u16) << 8) | (self.io_registers[0x11] as u16) & 0x1F } @@ -1071,19 +1155,19 @@ impl GameboyAdvance { ((self.io_registers[0x1E] as u16) << 8) | (self.io_registers[0x1F] as u16) & 0x1F } pub fn ppu_bg0_control(&self) -> PpuBgControl { - let bits = ((self.io_registers[0x8] as u16) << 8) | (self.io_registers[0x9] as u16) & 0x1F; + let bits = ((self.io_registers[0x8] as u16) << 8) | (self.io_registers[0x9] as u16); PpuBgControl::from_bits(bits) } pub fn ppu_bg1_control(&self) -> PpuBgControl { - let bits = ((self.io_registers[0xA] as u16) << 8) | (self.io_registers[0xB] as u16) & 0x1F; + let bits = ((self.io_registers[0xA] as u16) << 8) | (self.io_registers[0xB] as u16); PpuBgControl::from_bits(bits) } pub fn ppu_bg2_control(&self) -> PpuBgControl { - let bits = ((self.io_registers[0xC] as u16) << 8) | (self.io_registers[0xD] as u16) & 0x1F; + let bits = ((self.io_registers[0xC] as u16) << 8) | (self.io_registers[0xD] as u16); PpuBgControl::from_bits(bits) } pub fn ppu_bg3_control(&self) -> PpuBgControl { - let bits = ((self.io_registers[0xE] as u16) << 8) | (self.io_registers[0xF] as u16) & 0x1F; + let bits = ((self.io_registers[0xE] as u16) << 8) | (self.io_registers[0xF] as u16); PpuBgControl::from_bits(bits) } pub fn ppu_bg_mode(&self) -> u8 { @@ -1139,7 +1223,7 @@ impl GameboyAdvance { self.io_registers[0x5] } pub fn ppu_set_readonly_vcounter(&mut self, ly: u8) { - self.io_registers[0x6] = ly; + self.io_registers[0x6] = ly as u8; } pub fn master_interrupts_enabled(&self) -> bool { @@ -1338,11 +1422,16 @@ impl GameboyAdvance { 0x06000000..=0x06FFFFFF => (self.vram[(address & 0x17FFF) as usize], 1), //0x07000000..=0x070003FF => (self.oam[(address & 0x3FF) as usize], 1), 0x07000000..=0x07FFFFFF => (self.oam[(address & 0x3FF) as usize], 1), - 0x08000000..=0x09FFFFFF => (self.entire_rom[(address & 0x1FFFFFF) as usize], 5), + 0x08000000..=0x09FFFFFF => { + if (address - 0x0800_0000) > self.entire_rom.len() as u32 { + return (0, 5); + } + (self.entire_rom[(address & 0x1FFFFFF) as usize], 5) + } 0x0A000000..=0x0BFFFFFF => todo!("Game Pak ROM/FlashROM (max 32MB) - Wait State 1"), 0x0C000000..=0x0DFFFFFF => todo!("Game Pak ROM/FlashROM (max 32MB) - Wait State 2"), //0x0E000000..=0x0E00FFFF => todo!("Game Pak SRAM (max 64 KBytes) - 8bit Bus width"), - 0x0E000000..=0x0FFFFFFF => todo!("Game Pak SRAM (max 64 KBytes) - 8bit Bus width"), + 0x0E000000..=0x0FFFFFFF => (self.sram[(address & 0xFFFF) as usize], 5), _ => (0, 0), } } @@ -1350,7 +1439,7 @@ impl GameboyAdvance { let lo_bit_idx = address & !0x1; let hi_bit_idx = address | 0x1; match address { - 0x00000000..=0x01FF3FFF => { + 0x00000000..=0x01FFFFFF => { //0x00000000..=0x00003FFF => { // bios system ROM // TODO: separate opcoed reading logic from these getters @@ -1490,6 +1579,10 @@ impl GameboyAdvance { (out, 1) } 0x0A000000..=0x0BFFFFFF | 0x0C000000..=0x0DFFFFFF | 0x08000000..=0x09FFFFFF => { + // TODO: properly handle later bytes overflowing too + if bit1_idx & 0x1FFFFFF >= self.entire_rom.len() as u32 { + return (0, 8); + } let bit1 = self.entire_rom[(bit1_idx & 0x1FFFFFF) as usize] as u32; let bit2 = self.entire_rom[(bit2_idx & 0x1FFFFFF) as usize] as u32; let bit3 = self.entire_rom[(bit3_idx & 0x1FFFFFF) as usize] as u32; @@ -1533,8 +1626,11 @@ impl GameboyAdvance { 0x05000000..=0x050003FF => { todo!("OBJ Pallete ram") } - 0x06000000..=0x06017FFF => { - todo!("VRAM") + //0x06000000..=0x06017FFF => { + 0x06000000..=0x06FFFFFF => { + // INACCUARY: GBA can't do 8 bit writes here + self.vram[(address & 0x17FFF) as usize] = val; + 1 } 0x07000000..=0x070003FF => { todo!("OAM") @@ -1543,7 +1639,10 @@ impl GameboyAdvance { 0x0A000000..=0x0BFFFFFF => todo!("Game Pak ROM/FlashROM (max 32MB) - Wait State 1"), 0x0C000000..=0x0DFFFFFF => todo!("Game Pak ROM/FlashROM (max 32MB) - Wait State 2"), //0x0E000000..=0x0E00FFFF => todo!("Game Pak SRAM (max 64 KBytes) - 8bit Bus width"), - 0x0E000000..=0x0FFFFFFF => todo!("Game Pak SRAM (max 64 KBytes) - 8bit Bus width"), + 0x0E000000..=0x0FFFFFFF => { + self.sram[(address & 0xFFFF) as usize] = val; + 5 + } _ => 0, } } @@ -1859,6 +1958,15 @@ impl GameboyAdvance { } pub fn dispatch(&mut self) -> u32 { + /* + if self.r.pc > 0x3FFF && self.r.pc < 0x2000000 { + panic!("PC is maybe OOB in bios: 0x{:X}", self.r.pc); + } + */ + // HACK: ignore no-ops for this test ROM during dev + if self.r.pc == 0x8001EBC { + return 8; + } if self.r.pc >= 0x4000000 && self.r.pc <= 0x4FFFFFF { std::process::exit(0); } @@ -1905,7 +2013,7 @@ impl GameboyAdvance { // |_Cond__|0_0_0_0_1|U|A|S|_RdHi__|_RdLo__|__Rs___|1_0_0_1|__Rm___| MulLong 0b000 => { let multiply_end = ((opcode >> 4) & 0xF) == 0b1001; - let bits8to11 = ((opcode >> 8) & 0xF); + let bits8to11 = (opcode >> 8) & 0xF; let multiply_next3 = (opcode >> 22) & 0x7; let multiply_next2 = multiply_next3 >> 1; match multiply_next2 { @@ -1914,12 +2022,15 @@ impl GameboyAdvance { 0b10 if multiply_end && bits8to11 == 0 && ((opcode >> 20) & 0x3) == 0 => { todo!("transswp12") } + 0b10 if (opcode >> 20) & 1 == 0 && ((opcode >> 4) & 0xFF) == 0 => { + self.dispatch_psr(opcode) + } _ => { if (opcode >> 4) & 1 == 1 && (opcode >> 7) & 1 == 1 { if (opcode >> 22) & 1 == 0 && (opcode >> 8) & 0xF == 0 { - todo!("TransReg10") + self.dispatch_data_trans(opcode) } else { - self.dispatch_data_trans_imm(opcode) + self.dispatch_data_trans(opcode) } } else { self.dispatch_alu(opcode) @@ -1927,17 +2038,28 @@ impl GameboyAdvance { } } } - 0b001 => self.dispatch_alu(opcode), + 0b001 => { + let next2 = (opcode >> 23) & 0x3; + //if next2 == 0b10 && (opcode >> 20) & 3 == 0b10 { + if next2 == 0b10 && (opcode >> 20) & 1 == 0 { + self.dispatch_psr(opcode) + } else { + self.dispatch_alu(opcode) + } + } 0b010 | 0b011 => self.dispatch_mem(opcode), 0b100 => self.dispatch_block_data(opcode), // TODO: 0b100 block trans // TODO: 0b110 co data trans - 0b111 => self.dispatch_codata_op(opcode), - /* - |_Cond__|1_1_1_0|_CPopc_|__CRn__|__CRd__|__CP#__|_CP__|0|__CRm__| CoDataOp - |_Cond__|1_1_1_0|CPopc|L|__CRn__|__Rd___|__CP#__|_CP__|1|__CRm__| CoRegTrans - |_Cond__|1_1_1_1|_____________Ignored_by_Processor______________| SWI - */ + 0b111 => { + let next_bit = (opcode >> 24) & 1 == 1; + if next_bit { + self.dispatch_software_interrupt(opcode) + } else { + todo!("SWI with next bit not set"); + } + } + //0b111 => self.dispatch_codata_op(opcode), _ => { unimplemented!("0x{:X} ({:b}) at 0x{:X}", opcode, opcode_idx, self.r.pc); } @@ -2000,8 +2122,7 @@ impl GameboyAdvance { if next_bit { let cond = (opcode >> 8) & 0xF; if cond == 0b1111 { - todo!("THUMB SWI"); - //self.dispatch_thumb_software_interrupt(opcode) + self.dispatch_thumb_software_interrupt(opcode) } else { self.dispatch_thumb_conditional_branch(opcode) } @@ -2040,34 +2161,30 @@ impl GameboyAdvance { } 0b0010 => { trace!("LSL r{} = r{} << r{}", rd, rs, rd); - let result = self.r[rd] << ((self.r[rs] & 0xFF) % 32); - if self.r[rs] & 0xFF != 0 { - // TODO: REVIEW - self.r.cpsr_set_carry_flag( - self.r[rs] & (1 << (32 - ((self.r[rs] & 0xFF) % 32))) != 0, - ); - } - self.r[rd] = result; + let shift_amt = self.r[rs] & 0xFF; + let val = self.r[rd]; + self.r[rd] = BarrelShifter::lsl(val, shift_amt, Some(&mut self.r)); + 2 } 0b0011 => { trace!("LSR r{} = r{} >> r{}", rd, rs, rd); - if self.r[rs] & 0xFF != 0 { - // TODO: REVIEW - self.r - .cpsr_set_carry_flag(self.r[rs] & (1 << ((self.r[rs] & 0xFF) - 1)) != 0); + let shift_amt = self.r[rs] & 0xFF; + if shift_amt != 0 { + let val = self.r[rd]; + self.r[rd] = BarrelShifter::lsr(val, shift_amt, Some(&mut self.r)); } - self.r[rd] = self.r[rd] >> (self.r[rs] & 0xFF); + 2 } 0b0100 => { trace!("ASR r{} = r{} >> r{}", rd, rs, rd); - if self.r[rs] & 0xFF != 0 { - // TODO: REVIEW - self.r - .cpsr_set_carry_flag(self.r[rs] & (1 << ((self.r[rs] & 0xFF) - 1)) != 0); + let shift_amt = self.r[rs] & 0xFF; + if shift_amt != 0 { + let val = self.r[rd]; + self.r[rd] = BarrelShifter::asr(val, shift_amt, Some(&mut self.r)); } - self.r[rd] = ((self.r[rd] as i32) >> (self.r[rs] & 0xFF)) as u32; + 2 } 0b0101 => { @@ -2090,17 +2207,16 @@ impl GameboyAdvance { } 0b0110 => { trace!("SBC r{} = r{} - r{} - C", rd, rs, rd); - // TODO: review what not carry means, does it just mean add the carry? or is it 32bit negation? let op1 = self.r[rs]; let op2 = self.r[rd]; self.r[rd] = self.r[rd] - .wrapping_sub(self.r[rs]) - .wrapping_add(1 - self.r.cpsr_carry_flag() as u32); + .wrapping_sub(self.r[rs].wrapping_add(1 - self.r.cpsr_carry_flag() as u32)); + let a = op2; + let b = !op1; self.r - .cpsr_set_overflow_flag((!(op1 ^ !op2) & (!op2 ^ self.r[rd])) >> 31 == 1); + .cpsr_set_overflow_flag((!(a ^ b) & (b ^ self.r[rd])) >> 31 == 1); self.r.cpsr_set_carry_flag( - ((op1 as u64) + ((!op2) as u64) + (self.r.cpsr_carry_flag() as u64)) - > 0xFFFF_FFFF, + ((a as u64) + (b as u64) + (self.r.cpsr_carry_flag() as u64)) > 0xFFFF_FFFF, ); 1 } @@ -2108,8 +2224,7 @@ impl GameboyAdvance { trace!("ROR r{} = r{} ROR r{}", rd, rs, rd); self.r[rd] = self.r[rd].rotate_right(self.r[rs] & 0xFF); if self.r[rs] & 0xFF != 0 { - todo!("Carry flag for THUMB ROR"); - //self.r.cpsr_set_carry_flag() + self.r.cpsr_set_carry_flag(self.r[rd] >> 31 == 1); } 2 } @@ -2134,11 +2249,11 @@ impl GameboyAdvance { } 0b1010 => { trace!("CMP r{} - r{}", rs, rd); - let op1 = self.r[rs]; - let op2 = self.r[rd]; - let result = self.r[rd].wrapping_sub(self.r[rs]); + let op1 = self.r[rd]; + let op2 = self.r[rs]; + let result = op1.wrapping_sub(op2); self.r.cpsr_set_zero_flag(result == 0); - self.r.cpsr_set_sign_flag(result & 0x8000_0000 != 0); + self.r.cpsr_set_sign_flag((result >> 31) == 1); self.r .cpsr_set_overflow_flag((op1 as i32).overflowing_sub(op2 as i32).1); self.r.cpsr_set_carry_flag(op2 <= op1); @@ -2161,7 +2276,14 @@ impl GameboyAdvance { 1 } 0b1100 => { - trace!("ORR r{} = r{} | r{}", rd, rs, rd); + trace!( + "ORR r{} = r{} (0x{:X}) | r{} (0x{:X})", + rd, + rs, + self.r[rs], + rd, + self.r[rd] + ); self.r[rd] = self.r[rd] | self.r[rs]; 1 } @@ -2185,7 +2307,7 @@ impl GameboyAdvance { }; if !skip_end_flags { self.r.cpsr_set_zero_flag(self.r[rd] == 0); - self.r.cpsr_set_sign_flag(self.r[rd] & 0x8000_0000 != 0); + self.r.cpsr_set_sign_flag((self.r[rd] >> 31) & 1 == 1); } cycles } @@ -2194,9 +2316,10 @@ impl GameboyAdvance { let rd = ((opcode >> 8) & 0x7) as u8; let nn = (opcode & 0xFF) << 2; let pc = (self.r.pc + 2) & !2; - trace!("LDR r{}, [PC, #{}]", rd, nn); + let addr = pc + nn as u32; - let o = self.get_mem32(pc + nn as u32); + let o = self.get_mem32(addr); + trace!("LDR r{}, [PC, #{} (0x{:X})] (0x{:X})", rd, nn, addr, o.0); self.r[rd] = o.0; 2 + o.1 @@ -2237,10 +2360,14 @@ impl GameboyAdvance { 0b11 => { let x_flag = (opcode >> 7) & 1 == 1; let thumb_mode = self.r[rs] & 1 == 1; - let addr = if thumb_mode && rs == 15 { - self.r[rs] & !1 + let (thumb_mode, addr) = if rs == 15 { + (false, (self.r[rs] + 2) & !3) } else { - self.r[rs] & !3 + if thumb_mode { + (true, self.r[rs] & !1) + } else { + (false, (self.r[rs] + 2) & !3) + } }; self.r.set_thumb(thumb_mode); if x_flag { @@ -2255,11 +2382,12 @@ impl GameboyAdvance { */ } else { trace!("BX r{} (0x{:X})", rs, addr); - self.r.pc = addr + 4; - /* - self.r.pc = (self.r[rs] + 4) & !2; - //self.r.pc = (self.r[rs] + 2) & !1; - */ + // TODO(hello-world): check if this is correct + self.r.pc = addr; //+ 4; + /* + self.r.pc = (self.r[rs] + 4) & !2; + //self.r.pc = (self.r[rs] + 2) & !1; + */ } if old_thumb_enabled != thumb_mode { if thumb_mode { @@ -2424,6 +2552,7 @@ impl GameboyAdvance { for i in 0..8 { if r_list & (1 << i) != 0 { cycles += 1; + //println!("Storing value 0x{:X} to address 0x{:X}", self.r[i as u8], self.r[rb]); cycles += self.set_mem32(self.r[rb], self.r[i as u8]); self.r[rb] = self.r[rb].wrapping_add(4); } @@ -2479,6 +2608,7 @@ impl GameboyAdvance { let o = self.get_mem32(self.r.sp()); *self.r.sp_mut() += 4; self.r[i as u8] = o.0; + //println!("popping value 0x{:X} to r{}", o.0, i); cycles += o.1; cycles += 2; } @@ -2531,7 +2661,6 @@ impl GameboyAdvance { let sub_op_idx = (opcode >> 11) & 0x3; let rd = (opcode >> 8 & 0x7) as u8; let imm = (opcode & 0xFF) as u32; - let old_val = self.r[rd]; match sub_op_idx { // MOV @@ -2554,23 +2683,25 @@ impl GameboyAdvance { // ADD 0b10 => { trace!("ADD r{}, #{}", rd, imm); - self.r[rd] = self.r[rd].wrapping_add(imm); - self.r.cpsr_set_zero_flag(self.r[rd] == 0); - self.r.cpsr_set_sign_flag(self.r[rd] & 0x8000_0000 != 0); + let result = self.r[rd].wrapping_add(imm); + self.r.cpsr_set_zero_flag(result == 0); + self.r.cpsr_set_sign_flag(result & 0x8000_0000 != 0); self.r .cpsr_set_overflow_flag((self.r[rd] as i32).overflowing_add(imm as i32).1); self.r .cpsr_set_carry_flag(((self.r[rd] as u64) + (imm as u64)) > 0xFFFF_FFFF); + self.r[rd] = result; } // SUB 0b11 => { - trace!("SUB r{}, #{}", rd, imm); - self.r[rd] = self.r[rd].wrapping_sub(imm); - self.r.cpsr_set_zero_flag(self.r[rd] == 0); - self.r.cpsr_set_sign_flag(self.r[rd] & 0x8000_0000 != 0); + trace!("SUB r{} = r{} (0x{:X}) - #{}", rd, rd, self.r[rd], imm); + let result = self.r[rd].wrapping_sub(imm); + self.r.cpsr_set_zero_flag(result == 0); + self.r.cpsr_set_sign_flag(result & 0x8000_0000 != 0); self.r .cpsr_set_overflow_flag((self.r[rd] as i32).overflowing_sub(imm as i32).1); self.r.cpsr_set_carry_flag(imm <= self.r[rd]); + self.r[rd] = result; } _ => unreachable!(), } @@ -2587,37 +2718,31 @@ impl GameboyAdvance { // LSL 0b00 => { trace!("LSL r{}, r{}, #{:X}", rd, rs, offset); - // shift of 0 = don't modify carry flag - if offset != 0 { - // TODO: review this - self.r - .cpsr_set_carry_flag(self.r[rs] & (1 << (32 - offset)) != 0); - } - self.r[rd] = self.r[rs] << offset; + let val = self.r[rs]; + let shift_amt = offset as u32; + let registers = Some(&mut self.r); + self.r[rd] = BarrelShifter::lsl(val, shift_amt, registers); + self.r.cpsr_set_sign_flag(self.r[rd] & 0x8000_0000 != 0); self.r.cpsr_set_zero_flag(self.r[rd] == 0); } 0b01 => { trace!("LSR r{}, r{}, #{:X}", rd, rs, offset); - self.r[rd] = self.r[rs] >> offset; - // shift of 0 = don't modify carry flag - if offset != 0 { - // TODO: review this - self.r - .cpsr_set_carry_flag(self.r[rs] & (1 << (offset - 1)) != 0); - } + let val = self.r[rs]; + let shift_amt = if offset == 0 { 32 } else { offset as u32 }; + let registers = Some(&mut self.r); + self.r[rd] = BarrelShifter::lsr(val, shift_amt, registers); + self.r.cpsr_set_sign_flag(self.r[rd] & 0x8000_0000 != 0); self.r.cpsr_set_zero_flag(self.r[rd] == 0); } 0b10 => { trace!("ASR r{}, r{}, #{:X}", rd, rs, offset); - self.r[rd] = ((self.r[rs] as i32) >> offset) as u32; - // shift of 0 = don't modify carry flag - if offset != 0 { - // TODO: review this - self.r - .cpsr_set_carry_flag(self.r[rs] & (1 << (offset - 1)) != 0); - } + let val = self.r[rs]; + let shift_amt = if offset == 0 { 32 } else { offset as u32 }; + let registers = Some(&mut self.r); + self.r[rd] = BarrelShifter::asr(val, shift_amt, registers); + self.r.cpsr_set_sign_flag(self.r[rd] & 0x8000_0000 != 0); self.r.cpsr_set_zero_flag(self.r[rd] == 0); } @@ -2631,53 +2756,50 @@ impl GameboyAdvance { trace!("ADD r{}, r{}, r{}", rd, rs, reg_or_imm); op2 = self.r[reg_or_imm as u8]; result = self.r[rs].wrapping_add(op2); - self.r[rd] = result; self.r.cpsr_set_overflow_flag( - (self.r[rs] as i32).overflowing_sub(op2 as i32).1, + (self.r[rs] as i32).overflowing_add(op2 as i32).1, ); - self.r.cpsr_set_carry_flag(op2 < self.r[rs]); + self.r + .cpsr_set_carry_flag((self.r[rs] as u64) + (op2 as u64) > 0xFFFF_FFFF); + self.r[rd] = result; } 0b01 => { trace!("SUB r{}, r{}, r{}", rd, rs, reg_or_imm); op2 = self.r[reg_or_imm as u8]; result = self.r[rs].wrapping_sub(op2); - self.r[rd] = result; self.r.cpsr_set_overflow_flag( (self.r[rs] as i32).overflowing_sub(op2 as i32).1, ); self.r.cpsr_set_carry_flag(op2 <= self.r[rs]); + self.r[rd] = result; } 0b10 => { trace!("ADD r{}, r{}, #{}", rd, rs, reg_or_imm); op2 = reg_or_imm; result = self.r[rs].wrapping_add(op2); - self.r[rd] = result; self.r.cpsr_set_overflow_flag( (self.r[rs] as i32).overflowing_add(op2 as i32).1, ); self.r.cpsr_set_carry_flag( ((self.r[rs] as u64) + (op2 as u64)) > 0xFFFF_FFFF, ); + self.r[rd] = result; } 0b11 => { trace!("SUB r{}, r{}, #{}", rd, rs, reg_or_imm); op2 = reg_or_imm; result = self.r[rs].wrapping_sub(op2); - self.r[rd] = result; self.r.cpsr_set_overflow_flag( (self.r[rs] as i32).overflowing_sub(op2 as i32).1, ); self.r.cpsr_set_carry_flag(op2 <= self.r[rs]); + self.r[rd] = result; } _ => unreachable!(), } self.r.cpsr_set_zero_flag(self.r[rd] == 0); self.r.cpsr_set_sign_flag(self.r[rd] & 0x8000_0000 != 0); - self.r.cpsr_set_overflow_flag( - ((self.r[rs] ^ op2) & 0x8000_0000 == 0) - && ((self.r[rs] ^ result) & 0x8000_0000 != 0), - ); } _ => unreachable!(), } @@ -2685,6 +2807,32 @@ impl GameboyAdvance { 1 } + pub fn dispatch_software_interrupt(&mut self, _opcode: u32) -> u8 { + if self.master_interrupts_enabled() { + self.r.set_svc_mode(); + *self.r.lr_mut() = self.r.pc; + *self.r.get_spsr_mut() = self.r.cpsr; + self.r.set_thumb(false); + self.r.cpsr_disable_irq(); + self.r.pc = 0x08; + } + + 3 + } + + pub fn dispatch_thumb_software_interrupt(&mut self, _opcode: u16) -> u8 { + if self.master_interrupts_enabled() { + self.r.set_svc_mode(); + *self.r.lr_mut() = self.r.pc | 0; + *self.r.get_spsr_mut() = self.r.cpsr; + self.r.set_thumb(false); + self.r.cpsr_disable_irq(); + self.r.pc = 0x08; + } + + 3 + } + pub fn dispatch_thumb_conditional_branch(&mut self, opcode: u16) -> u8 { let cond = Cond::from_u8(((opcode >> 8) & 0xF) as u8); trace!("B{:?} #{:X}", cond, opcode & 0xFF); @@ -2717,27 +2865,44 @@ impl GameboyAdvance { 3 } 0b10 => { + let upper_n = (opcode & 0x7FF) as u32; + /* + let double_width_hack = false; + let next_op = self.get_thumb_opcode(); + if double_width_hack && next_op >> 11 == 0x1F { + // just do the entire jump here + let lower_n = (next_op & 0x7FF) as u32; + let signed_offset = (((upper_n as i32) << 21) >> 9) | ((lower_n as i32) << 1); + println!("{}, {:b}", signed_offset, signed_offset); + *self.r.lr_mut() = (self.r.pc + 2) | 1; + self.r.pc = ((self.r.pc as i32) + 2 + signed_offset) as u32; + + trace!("BL (32bit THUMB) to 0x{:X}", self.r.pc); + 4 + } else { + */ trace!("BL (part 1)"); - let n = (opcode & 0x7FF) as u32; - *self.r.lr_mut() = self.r.pc /*+ 4*/ + 2 + (n << 12); + // TODO: review all other sign extension logic, apply this trick to them + let offset = ((upper_n as i32) << 21) >> 9; + *self.r.lr_mut() = ((self.r.pc as i32) + 2 + offset) as u32; + 1 + //} } - 0b11 | 0b01 => { + 0b11 /*| 0b01*/ => { let n = (opcode & 0x7FF) as u32; let old_pc = self.r.pc; - //let new_pc = (self.r.lr() + (n << 1)) & 0x3F_FFFF; - let new_pc = (self.r.lr() + (n << 1)); + let new_pc = self.r.lr().wrapping_add(n << 1); self.r.pc = new_pc; trace!("BL to 0x{:X}", self.r.pc); *self.r.lr_mut() = old_pc | 1; + //println!("old_pc = 0x{:X}, n = {} (0x{:X})", old_pc, n, n); 3 } // I think 0b01 is only on ARM9, so let's just route it to the same BL - /* 0b01 => { todo!("Second opcode for THUMB branch long with link"); } - */ _ => unimplemented!("unknown sub-op-idx for THUMB branch: {:b}", sub_op_idx), } } @@ -2775,6 +2940,7 @@ impl GameboyAdvance { */ if sub_opcode { + trace!("Linking for branch"); //*self.r.lr_mut() = self.r.pc; + 4; *self.r.lr_mut() = self.r.pc; } @@ -2806,6 +2972,23 @@ impl GameboyAdvance { // HACK for R15 case let old_cpsr = self.r.cpsr; + //dbg!(sub_opcode, s, imm, rn, rd); + // valid programs shouldn't give us invalid instructions + // these checks are mostly for debugging, as hitting these in known good code suggests + // we're executing garbage data as code + debug_assert!(if matches!(sub_opcode, 0xD | 0xF) { + rn == 0 + } else { + true + }); + debug_assert!(if matches!(sub_opcode, 0xA | 0xB | 0x8 | 0x9) { + rd == 0xF || rd == 0 + } else { + true + }); + // opcodes that don't write back must set flags + //debug_assert!(if (0x8..=0xB).contains(&sub_opcode) { s } else { true }); + if rn == 0xF { if !imm && (opcode >> 4) & 1 == 1 { op1 += 12 - 4; @@ -2817,16 +3000,8 @@ impl GameboyAdvance { if imm { let ror_shift = (opcode >> 8) & 0xF; op2 = opcode & 0xFF; - let shift_amt = (ror_shift * 2) % 32; - if shift_amt != 0 { - op2 = op2.rotate_right(shift_amt); - self.r.cpsr_set_carry_flag((op2 & 0x8000_0000) != 0); - // TODO: apply this ROR logic everywhere else ROR is used - } else { - // TODO: this logic may only be for non-immediate mode - let carry_bit = self.r.cpsr_carry_flag() as u32; - op2 |= carry_bit << 31; - } + let shift_amt = ror_shift * 2; + op2 = op2.rotate_right(shift_amt); } else { let shift_by_register = (opcode >> 4) & 1 == 1; let rm = opcode & 0xF; @@ -2847,53 +3022,18 @@ impl GameboyAdvance { }; let shift_type = (opcode >> 5) & 0b11; - if shift_amt == 0 { - // shift amount of 0 is a special case - /* - Zero Shift Amount (Shift Register by Immediate, with Immediate=0) - - LSL#0: No shift performed, ie. directly Op2=Rm, the C flag is NOT affected. - LSR#0: Interpreted as LSR#32, ie. Op2 becomes zero, C becomes Bit 31 of Rm. - ASR#0: Interpreted as ASR#32, ie. Op2 and C are filled by Bit 31 of Rm. - ROR#0: Interpreted as RRX#1 (RCR), like ROR#1, but Op2 Bit 31 set to old C. - - */ - match shift_type { - // LSL - 0 => { - op2 = self.r[rm as u8]; - } - // LSR - 1 => { - op2 = 0; - self.r.cpsr_set_carry_flag((self.r[rm as u8] >> 31) == 1); - } - // ASR - 2 => { - op2 = ((self.r[rm as u8] as i32) >> 31) as u32; - self.r.cpsr_set_carry_flag(op2 & 1 == 1); - } - // ROR - 3 => todo!("ROR zero shift amount handling"), - _ => unreachable!(), - } + if shift_amt == 0 && shift_by_register { + op2 = self.r[rm as u8]; } else { + let carry = self.r.cpsr_carry_flag(); + let val = self.r[rm as u8]; + let registers = if s { Some(&mut self.r) } else { None }; match shift_type { - // LSL - 0 => { - // TODO: review overflowing here - op2 = self.r[rm as u8].overflowing_shl(shift_amt as _).0; - } - // LSR - 1 => { - op2 = self.r[rm as u8] >> shift_amt; - } - // ASR - 2 => { - op2 = ((self.r[rm as u8] as i32) >> shift_amt) as u32; - } - // ROR - 3 => todo!("ROR"), + 0 => op2 = BarrelShifter::lsl(val, shift_amt as u32, registers), + 1 => op2 = BarrelShifter::lsr(val, shift_amt as u32, registers), + 2 => op2 = BarrelShifter::asr(val, shift_amt as u32, registers), + 3 if shift_amt == 0 => op2 = BarrelShifter::rrx(val, registers, carry), + 3 => op2 = BarrelShifter::ror(val, shift_amt as u32, registers), _ => unreachable!(), } } @@ -2902,6 +3042,8 @@ impl GameboyAdvance { // detect if ALU instruction is actually an MRS/MSR: PSR transfer // TODO: rn != 0xF = SWP if (sub_opcode >> 2) == 0b10 && !s { + panic!("Refactored out, this should never trigger"); + // TOOD: delete this code let psr_src_dest = (opcode >> 22) & 1 == 1; let psr_subopcode = (opcode >> 21) & 1 == 1; if psr_subopcode { @@ -3068,45 +3210,43 @@ impl GameboyAdvance { } // SBC 0x6 => { - trace!("SBC r{:X} = {:X} - {:X} + C", rd, op1, op2); - let result = op1 - .wrapping_sub(op2) - .wrapping_add(1 - self.r.cpsr_carry_flag() as u32); + trace!("SBC r{} = {:X} - {:X} + C", rd, op1, op2); + let result = + op1.wrapping_sub(op2.wrapping_add(1 - self.r.cpsr_carry_flag() as u32)); self.r[rd] = result; if s { self.r.cpsr_set_zero_flag(result == 0); self.r.cpsr_set_sign_flag((result >> 31) == 1); - // TODO: check this in more detail + let a = op1; + let b = !op2; self.r - .cpsr_set_overflow_flag((!(op1 ^ !op2) & (!op2 ^ result)) >> 31 == 1); + .cpsr_set_overflow_flag((!(a ^ b) & (b ^ result)) >> 31 == 1); self.r.cpsr_set_carry_flag( - ((op1 as u64) + ((!op2) as u64) + (self.r.cpsr_carry_flag() as u64)) - > 0xFFFF_FFFF, + ((a as u64) + (b as u64) + (self.r.cpsr_carry_flag() as u64)) > 0xFFFF_FFFF, ); } } // RSC 0x7 => { - trace!("RSC r{:X} = {:X} - {:X} + C", rd, op2, op1); - let result = op2 - .wrapping_sub(op1) - .wrapping_add(1 - self.r.cpsr_carry_flag() as u32); + trace!("RSC r{} = {:X} - {:X} + C", rd, op2, op1); + let result = + op2.wrapping_sub(op1.wrapping_add(1 - self.r.cpsr_carry_flag() as u32)); self.r[rd] = result; + let a = op2; + let b = !op1; if s { self.r.cpsr_set_zero_flag(result == 0); self.r.cpsr_set_sign_flag((result >> 31) == 1); - // TODO: check this in more detail self.r - .cpsr_set_overflow_flag((!(op1 ^ !op2) & (!op2 ^ result)) >> 31 == 1); + .cpsr_set_overflow_flag((!(a ^ b) & (b ^ result)) >> 31 == 1); self.r.cpsr_set_carry_flag( - ((op1 as u64) + ((!op2) as u64) + (self.r.cpsr_carry_flag() as u64)) - > 0xFFFF_FFFF, + ((a as u64) + (b as u64) + (self.r.cpsr_carry_flag() as u64)) > 0xFFFF_FFFF, ); } } // TST 0x8 => { - trace!("TST r{:X} = {:X} & {:X}", rd, op1, op2); + trace!("TST {:X} & {:X}", op1, op2); let result = op1 & op2; if s { self.r.cpsr_set_zero_flag(result == 0); @@ -3115,7 +3255,7 @@ impl GameboyAdvance { } // TEQ 0x9 => { - trace!("TEQ r{:X} = {:X} ^ {:X}", rd, op1, op2); + trace!("TEQ {:X} ^ {:X}", op1, op2); let result = op1 ^ op2; if s { self.r.cpsr_set_zero_flag(result == 0); @@ -3124,7 +3264,8 @@ impl GameboyAdvance { } // CMP 0xA => { - trace!("CMP r{:X} = {:X} - {:X}", rd, op1, op2); + trace!("CMP {:X} - {:X}", op1, op2); + let result = op1.wrapping_sub(op2); if s { self.r.cpsr_set_zero_flag(result == 0); @@ -3134,9 +3275,21 @@ impl GameboyAdvance { self.r.cpsr_set_carry_flag(op2 <= op1); } } + // CMN + 0xB => { + trace!("CMN {:X} + {:X}", op1, op2); + let result = op1.wrapping_add(op2); + if s { + self.r.cpsr_set_zero_flag(result == 0); + self.r.cpsr_set_sign_flag((result >> 31) == 1); + self.r + .cpsr_set_overflow_flag((op1 as i32).overflowing_add(op2 as i32).1); + self.r.cpsr_set_carry_flag(op1.checked_add(op2).is_none()); + } + } // ORR 0xC => { - trace!("ORR r{:X} = {:X} | {:X}", rd, op1, op2); + trace!("ORR r{} = {:X} | {:X}", rd, op1, op2); let result = op1 | op2; self.r[rd] = result; if s { @@ -3146,7 +3299,7 @@ impl GameboyAdvance { } // MOV 0xD => { - trace!("MOV r{:X} = {:X}", rd, op2); + trace!("MOV r{} = {:X}", rd, op2); self.r[rd] = op2; if s { self.r.cpsr_set_zero_flag(op2 == 0); @@ -3165,7 +3318,7 @@ impl GameboyAdvance { } // MVN 0xF => { - trace!("MVN r{:X} = !{:X}", rd, op2); + trace!("MVN r{} = !{:X}", rd, op2); let result = !op2; self.r[rd] = result; if s { @@ -3177,10 +3330,12 @@ impl GameboyAdvance { } // HACK: don't set CPSR if in user mode + // TODO: make sure this doesn't break CMP, etc if s && rd == 0xF { if self.r.register_mode() != Some(RegisterMode::User) { self.r.cpsr = self.r.get_spsr(); } else { + panic!("should be reenabling thumb mode..."); self.r.cpsr = old_cpsr; } } @@ -3193,7 +3348,139 @@ impl GameboyAdvance { } pub fn dispatch_multiply(&mut self, opcode: u32) -> u8 { - todo!("Multiply") + let sub_opcode = (opcode >> 21) & 0xF; + let s = (opcode >> 20) & 1 == 1; + let rd = ((opcode >> 16) & 0xF) as u8; + let rn = ((opcode >> 12) & 0xF) as u8; + let rs = ((opcode >> 8) & 0xF) as u8; + let half_word_multiply = (opcode >> 4) & 1 == 1; + + match sub_opcode { + 0b0000 => { + trace!("MUL r{} = r{} * r{}", rd, rs, rn); + let result = self.r[rs].wrapping_mul(self.r[rn]); + self.r[rd] = result as u32; + if s { + self.r.cpsr_set_zero_flag(result == 0); + self.r.cpsr_set_sign_flag((result >> 31) == 1); + } + } + 0b0001 => { + trace!("MLA r{} = r{} * r{} + r{}", rd, rs, rn, self.r[rd]); + let result = self.r[rs].wrapping_mul(self.r[rn]).wrapping_add(self.r[rd]); + self.r[rd] = result as u32; + if s { + self.r.cpsr_set_zero_flag(result == 0); + self.r.cpsr_set_sign_flag((result >> 31) == 1); + } + } + 0b0010 => { + todo!("UMAAL") + } + 0b0100 => { + todo!("UMULL"); + } + 0b0101 => { + todo!("UMLAL"); + } + 0b0110 => { + todo!("SMULL"); + } + 0b0111 => { + todo!("SMLAL"); + } + 0b1000 => { + todo!("SMLAxy"); + } + 0b1001 => { + todo!("docs unclear, this could be either multiply, check more docs"); + } + 0b1010 => { + todo!("SMLALxy"); + } + 0b1011 => { + todo!("SMULxy"); + } + _ => unreachable!( + "multiply instruction 0x{:X} at 0x{:X}", + sub_opcode, self.r.pc + ), + } + // for armv4 we always clear this + self.r.cpsr_set_carry_flag(false); + + 3 + } + + pub fn dispatch_psr(&mut self, opcode: u32) -> u8 { + let psr_src_dest = (opcode >> 22) & 1 == 1; + let psr_subopcode = (opcode >> 21) & 1 == 1; + let imm = psr_subopcode && (opcode >> 25) & 1 == 1; + + if psr_subopcode { + let write_flags = (opcode >> 19) & 1 == 1; + let write_status = (opcode >> 18) & 1 == 1; + let write_extension = (opcode >> 17) & 1 == 1; + let write_control = (opcode >> 16) & 1 == 1; + let mask = { + let mut mask = 0; + if write_flags { + mask |= 0xFF << 24; + } + if write_status { + mask |= 0xFF << 16; + } + if write_extension { + mask |= 0xFF << 8; + } + if write_control { + mask |= 0xFF; + } + mask + }; + let val = if imm { + let shift_amt = (opcode >> 8) & 0xF; + (opcode & 0xFF).rotate_right(shift_amt * 2) + } else { + let rm = ((opcode >> 0) & 0xF) as u8; + debug_assert_ne!(rm, 0xF, "MSR from PC"); + self.r[rm as u8] + }; + trace!( + "MSR mode={:?}: 0x{:X} & 0x{:X}", + self.r.register_mode(), + mask, + val + ); + // HACK: if in user mode, just force CPSR + if psr_src_dest + /*&& self.r.register_mode() != Some(RegisterMode::User)*/ + { + debug_assert_ne!(self.r.register_mode(), Some(RegisterMode::User)); + *self.r.get_spsr_mut() &= !mask; + *self.r.get_spsr_mut() |= val & mask; + } else { + self.r.cpsr &= !mask; + self.r.cpsr |= val & mask; + } + } else { + trace!("MRS mode={:?}", self.r.register_mode()); + let rd = ((opcode >> 12) & 0xF) as u8; + debug_assert_ne!(rd, 0xF, "MRS to PC"); + debug_assert!(!imm); + + let v = if psr_src_dest + /*&& self.r.register_mode() != Some(RegisterMode::User)*/ + { + debug_assert_ne!(self.r.register_mode(), Some(RegisterMode::User)); + self.r.get_spsr() + } else { + self.r.cpsr + }; + + self.r[rd] = v; + } + 1 } /* @@ -3235,9 +3522,10 @@ impl GameboyAdvance { When above Bit 22 I=1: Immediate Offset (lower 4bits) (0-255, together with upper bits) */ - pub fn dispatch_data_trans_imm(&mut self, opcode: u32) -> u8 { + pub fn dispatch_data_trans(&mut self, opcode: u32) -> u8 { let p = (opcode >> 24) & 1 == 1; let up = (opcode >> 23) & 1 == 1; + let imm = (opcode >> 22) & 1 == 1; let write_back = !p || ((opcode >> 21) & 1 == 1); let load = (opcode >> 20) & 1 == 1; let rn = ((opcode >> 16) & 0xF) as u8; @@ -3250,7 +3538,13 @@ impl GameboyAdvance { if rd == 15 { src_val += 8; } - let offset = (((opcode >> 8) & 0xF) << 4) | (opcode & 0xF); + let offset = if imm { + (((opcode >> 8) & 0xF) << 4) | (opcode & 0xF) + } else { + let rm = (opcode & 0xF) as u8; + debug_assert_ne!(rm, 0xF, "Data transfer from PC"); + self.r[(opcode & 0xF) as u8] + }; let opcode = (opcode >> 5) & 0x3; if p { @@ -3260,7 +3554,7 @@ impl GameboyAdvance { base_val -= offset; } } - let mut cycles = 0; + let cycles; match (load, opcode) { (true, 0b00) => todo!("reserved"), @@ -3310,9 +3604,9 @@ impl GameboyAdvance { } if !p { if up { - base_val += offset; + base_val = base_val.wrapping_add(offset); } else { - base_val -= offset; + base_val = base_val.wrapping_sub(offset); } } if write_back { @@ -3335,7 +3629,7 @@ impl GameboyAdvance { let load = (opcode >> 20) & 1 == 1; let base_reg = (opcode >> 16) & 0xF; let src_dest_reg = (opcode >> 12) & 0xF; - if force_nonpriviliged { + if force_nonpriviliged && !p { todo!("figure this out"); } @@ -3344,16 +3638,25 @@ impl GameboyAdvance { let rm = (opcode & 0xF) as u8; let shift_amount = (opcode >> 7) & 0x1F; if shift_amount == 0 { - // TODO: - //panic!("Special logic here? docs don't describe it much, maybe we ignore it here"); - warn!("Special logic here? docs don't describe it much, maybe we ignore it here"); - } - match shift_type { - 0b00 => self.r[rm] << shift_amount, - 0b01 => self.r[rm] >> shift_amount, - 0b10 => ((self.r[rm] as i32) >> shift_amount) as u32, - 0b11 => self.r[rm].rotate_right(shift_amount), - _ => unreachable!(), + match shift_type { + // LSL + 0b00 => self.r[rm], + // LSR + 0b01 => 0, + // ASR + 0b10 => ((self.r[rm] as i32) >> 31) as u32, + // ROR + 0b11 => todo!("ROR!"), + _ => unreachable!(), + } + } else { + match shift_type { + 0b00 => self.r[rm] << shift_amount, + 0b01 => self.r[rm] >> shift_amount, + 0b10 => ((self.r[rm] as i32) >> shift_amount) as u32, + 0b11 => self.r[rm].rotate_right(shift_amount), + _ => unreachable!(), + } } } else { opcode & 0xFFF @@ -3373,14 +3676,9 @@ impl GameboyAdvance { if load { if byte { - trace!("LDRB r{:X} = mem[{:X}]", src_dest_reg, val); + trace!("LDRB r{} = mem[{:X}]", src_dest_reg, val); let o = self.get_mem8(val); cycles += o.1; - // TODO: does this zero the high bits? - /* - self.r[src_dest_reg as u8] &= !0xFF; - self.r[src_dest_reg as u8] |= o.0 as u32; - */ self.r[src_dest_reg as u8] = o.0 as u32; } else { let o = self.get_mem32(val); @@ -3402,19 +3700,14 @@ impl GameboyAdvance { }; if byte { trace!( - "STRB mem[{:X}] = r{:X} (0x{:X})", + "STRB mem[{:X}] = r{} (0x{:X})", val, src_dest_reg, write_val as u8 ); cycles += self.set_mem8(val, write_val as u8); } else { - trace!( - "STR mem[{:X}] = r{:X} (0x{:X})", - val, - src_dest_reg, - write_val - ); + trace!("STR mem[{:X}] = r{} (0x{:X})", val, src_dest_reg, write_val); cycles += self.set_mem32(val, write_val); } } @@ -3440,7 +3733,7 @@ impl GameboyAdvance { } pub fn dispatch_block_data(&mut self, opcode: u32) -> u8 { - let p = (opcode >> 24) & 1 == 1; + let mut p = (opcode >> 24) & 1 == 1; let up = (opcode >> 23) & 1 == 1; let force_user_mode = (opcode >> 22) & 1 == 1; let mut write_back = (opcode >> 21) & 1 == 1; @@ -3467,6 +3760,19 @@ impl GameboyAdvance { (false, true, true) => "STMFA", }; + let r_list_count = r_list.count_ones(); + + if r_list_count == 0 { + todo!("0 is a special case, handle it when it happens"); + } else if !up { + base = base.wrapping_sub(4 * r_list_count); + if write_back { + self.r[rn] = base; + write_back = false; + } + p = !p; + } + // LDM, STM trace!( "{} r{} (0x{:X}) - {:016b}: in {:?}", @@ -3478,9 +3784,7 @@ impl GameboyAdvance { ); // TOOD: all accesses should be done lower to higher if load { - for i in 0..16 - /* .rev()*/ - { + for i in 0..16 { if r_list & (1 << i) == 0 { continue; } @@ -3488,45 +3792,29 @@ impl GameboyAdvance { write_back = false; } if p { - base = if up { - base.wrapping_add(4) - } else { - base.wrapping_sub(4) - } + base = base.wrapping_add(4); } let o = self.get_mem32(base); //println!("Loading r{} (0x{:X}) from 0x{:X}", i, o.0, base); self.r[i as u8] = o.0; cycles += o.1 + 2; if !p { - base = if up { - base.wrapping_add(4) - } else { - base.wrapping_sub(4) - } + base = base.wrapping_add(4); } } } else { - for i in (0..16).rev() { + for i in 0..16 { if r_list & (1 << i) == 0 { continue; } if p { - base = if up { - base.wrapping_add(4) - } else { - base.wrapping_sub(4) - } + base = base.wrapping_add(4); } //println!("pushing r{} ({:X}) to 0x{:X}", i, self.r[i as u8], base); cycles += self.set_mem32(base, self.r[i as u8]); cycles += 1; if !p { - base = if up { - base.wrapping_add(4) - } else { - base.wrapping_sub(4) - } + base = base.wrapping_add(4); } } } @@ -3595,7 +3883,6 @@ fn button_to_bit(button: Button) -> u16 { Button::Down => 0x0080, Button::R => 0x0100, Button::L => 0x0200, - _ => 0, } } @@ -3608,9 +3895,20 @@ impl crate::io::graphics::renderer::InputReceiver for GameboyAdvance { } else { self.io_registers[0x130] &= !(bit as u8); } - if self.keypad_interrupt_enabled() { - dbg!("Button interrupt fired"); - self.keypad_interrupt_requested(); + let keycnt = self.io_registers.get_mem16(0x4000132); + if self.keypad_interrupt_enabled() && (keycnt >> 14) & 1 == 1 { + let buttons = self.io_registers.get_mem16(0x4000130) & 0x3F; + if (keycnt >> 15) == 1 { + // AND mode + if (keycnt & 0x3F) & buttons == (keycnt & 0x3F) { + self.keypad_interrupt_requested(); + } + } else { + // OR mode + if (keycnt & 0x3F) & buttons != 0 { + self.keypad_interrupt_requested(); + } + } } } fn unpress(&mut self, button: Button) { diff --git a/src/io/applicationstate.rs b/src/io/applicationstate.rs index 91ce76c..ec4fbc8 100644 --- a/src/io/applicationstate.rs +++ b/src/io/applicationstate.rs @@ -33,6 +33,7 @@ pub struct ApplicationState { sound_cycles: u64, _screenshot_frame_num: Wrapping, gba_timers: [u16; 4], + debug_gba_last_seen_ppu_bg_mode: Option, pub renderer: Box, } @@ -56,6 +57,7 @@ impl ApplicationState { sound_cycles: 0, _screenshot_frame_num: Wrapping(0), gba_timers: [0, 0, 0, 0], + debug_gba_last_seen_ppu_bg_mode: None, renderer, }) } @@ -146,18 +148,33 @@ impl ApplicationState { } } self.gba.ppu_set_vblank(false); + if Some(self.gba.ppu_bg_mode()) != self.debug_gba_last_seen_ppu_bg_mode { + debug!("PPU BG mode is {}", self.gba.ppu_bg_mode()); + self.debug_gba_last_seen_ppu_bg_mode = Some(self.gba.ppu_bg_mode()); + } /* - dbg!( - self.gba.ppu_bg_mode(), - self.gba.ppu_bg0_enabled(), - self.gba.ppu_bg1_enabled(), - self.gba.ppu_bg2_enabled(), - self.gba.ppu_bg3_enabled(), - self.gba.ppu_obj_enabled(), - self.gba.ppu_win0_enabled(), - self.gba.ppu_win1_enabled(), - self.gba.ppu_obj_win_enabled(), - ); + if + self.gba.ppu_bg_mode() != 0 || + self.gba.ppu_bg0_enabled() || + self.gba.ppu_bg1_enabled() || + self.gba.ppu_bg2_enabled() || + self.gba.ppu_bg3_enabled() || + self.gba.ppu_obj_enabled() || + self.gba.ppu_win0_enabled() || + self.gba.ppu_win1_enabled() || + self.gba.ppu_obj_win_enabled() { + dbg!( + self.gba.ppu_bg_mode(), + self.gba.ppu_bg0_enabled(), + self.gba.ppu_bg1_enabled(), + self.gba.ppu_bg2_enabled(), + self.gba.ppu_bg3_enabled(), + self.gba.ppu_obj_enabled(), + self.gba.ppu_win0_enabled(), + self.gba.ppu_win1_enabled(), + self.gba.ppu_obj_win_enabled(), + ); + } */ self.renderer.draw_gba_frame(&frame); } diff --git a/src/io/deferred_renderer_gba.rs b/src/io/deferred_renderer_gba.rs index 6b61e52..01b7e66 100644 --- a/src/io/deferred_renderer_gba.rs +++ b/src/io/deferred_renderer_gba.rs @@ -1,24 +1,73 @@ use crate::gba; use crate::io::constants::*; +pub fn deferred_renderer_draw_gba_bg4( + y: u8, + gba: &mut gba::GameboyAdvance, +) -> [(u8, u8, u8); GBA_SCREEN_WIDTH] { + let mut bg_pixels = [(0u8, 0u8, 0u8); GBA_SCREEN_WIDTH]; + let bg2_control = gba.ppu_bg2_control(); + + let base = if gba.ppu_frame_select() { 0xA000 } else { 0 }; + + let adj_y = y as usize; + for x in 0..GBA_SCREEN_WIDTH { + let idx = (adj_y * 240 + x) as usize; + let palette_idx = gba.vram[base + idx]; + let color_lo = gba.obj_palette_ram[palette_idx as usize]; + let color_hi = gba.obj_palette_ram[palette_idx as usize + 1]; + + let red = color_lo & 0x1F; + let green = ((color_hi & 0x3) << 3) | (color_lo >> 5); + let blue = (color_hi >> 2) & 0x1F; + + bg_pixels[x as usize] = (red << 3, green << 3, blue << 3); + } + + bg_pixels +} + pub fn deferred_renderer_draw_gba_scanline( y: u8, gba: &mut gba::GameboyAdvance, ) -> [(u8, u8, u8); GBA_SCREEN_WIDTH] { + match gba.ppu_bg_mode() { + 4 => return deferred_renderer_draw_gba_bg4(y, gba), + _ => (), + } let mut bg_pixels = [(0u8, 0u8, 0u8); GBA_SCREEN_WIDTH]; let scx = gba.ppu_bg0_x_scroll(); let scy = gba.ppu_bg0_y_scroll(); - let adj_y = (y as u16).wrapping_add(scy) as u16 & 0x1FF; let bg0_control = gba.ppu_bg0_control(); + let (screen_x_size_mask, screen_y_size_mask) = + // ASSUMES TEXT MODE DURING EARLY DEVELOPMENT + // TOOD: add support for other mode here + match bg0_control.screen_size { + 0 => (0xFF, 0xFF), + 1 => (0x1FF, 0xFF), + 2 => (0xFF, 0x1FF), + 3 => (0x1FF, 0x1FF), + _ => unreachable!(), + }; + + //dbg!(gba.ppu_bg0_control()); + //dbg!(gba.ppu_bg1_control()); + //dbg!(gba.ppu_bg2_control()); + //dbg!(gba.ppu_bg3_control()); + if bg0_control.mosaic { + todo!("Mosaic mode!"); + } + let adj_y = (y as u16).wrapping_add(scy) as u16 & screen_y_size_mask; + // this address is auto-incremented by 2kb for each background let map_base_ptr = bg0_control.screen_base_block as u32 * 0x800; let tile_base_ptr = bg0_control.character_base_block as u32 * 0x4000; - let row = (adj_y >> 3) as u32; + let tile_row = (adj_y >> 3) as u32; for x in 0..GBA_SCREEN_WIDTH { - let adj_x = (x as u16).wrapping_add(scx) as u16 & 0x1FF; - let col = (adj_x >> 3) as u32; - let idx_into_tile_idx_mem = map_base_ptr + (row << 5) + col; + let adj_x = (x as u16).wrapping_add(scx) as u16 & screen_x_size_mask; + let tile_col = (adj_x >> 3) as u32; + let idx_into_tile_idx_mem = map_base_ptr + (tile_row * 32 * 2) + (tile_col * 2); let tile_idx_lo = gba.vram[idx_into_tile_idx_mem as usize] as u16; let tile_idx_hi = gba.vram[idx_into_tile_idx_mem as usize + 1] as u16; let tile_num = ((tile_idx_hi & 0x3) << 8) | tile_idx_lo; @@ -39,23 +88,48 @@ pub fn deferred_renderer_draw_gba_scanline( nth_pixel = 7 - nth_pixel; } + /* + if tile_num != 0 || palette_num != 0 { + dbg!(adj_x, adj_y, tile_num, palette_num); + panic!("found!"); + } + */ + if bg0_control.color_mode { todo!() } else { // 16/16 mode + // 4bit palette index so 4 bytes per line = 8 palette indices per line + // 4 bytes per line * 8 lines = 32 bytes per tile let tile_line = nth_line * 4; let tile_start = tile_base_ptr as usize + (tile_num as usize * 32); let tile_line_start = tile_start + tile_line as usize; let tile_byte_start = tile_line_start + (nth_pixel >> 1) as usize; - let color_4bit = gba.vram[tile_byte_start] >> (nth_pixel & 0x1); + let color_4bit = (gba.vram[tile_byte_start] >> ((nth_pixel & 0x1) * 4)) & 0xF; + // HACK: hello world wants this + //let color_4bit = color_4bit + 1; - let palette_start = palette_num as usize * 16; + // 2 bytes per color, 16 colors per palette + let palette_start = palette_num as usize * 16 * 2; + /* + let mut found = false; + for i in (palette_start)..(0x400 -palette_start) { + if gba.obj_palette_ram[i] != 0 { + dbg!(i, gba.obj_palette_ram[i], color_4bit); + found = true; + } + } + if found { panic!("found "); } + */ let color_lo = gba.obj_palette_ram[palette_start + (color_4bit as usize * 2)]; let color_hi = gba.obj_palette_ram[palette_start + (color_4bit as usize * 2) + 1]; let red = color_lo & 0x1F; let green = ((color_hi & 0x3) << 3) | (color_lo >> 5); let blue = (color_hi >> 2) & 0x1F; + if red | green | blue != 0 { + //panic!("COLOR!"); + } bg_pixels[x as usize] = (red << 3, green << 3, blue << 3); } diff --git a/src/main.rs b/src/main.rs index b759649..66a0ef7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,6 +30,8 @@ pub mod io; pub mod gba; +pub mod prelude; + use crate::debugger::graphics::Debugger; use crate::io::applicationsettings::*; use crate::io::applicationstate::*; diff --git a/src/prelude.rs b/src/prelude.rs new file mode 100644 index 0000000..408778d --- /dev/null +++ b/src/prelude.rs @@ -0,0 +1,23 @@ +//! Misc helper functions. + +// TODO: when we benchmark and optimize, use these: + +#[inline] +#[cold] +pub fn cold() {} + +#[inline] +pub fn likely(b: bool) -> bool { + if !b { + cold() + } + b +} + +#[inline] +pub fn unlikely(b: bool) -> bool { + if b { + cold() + } + b +} From 287518e31511072fac36616f5048e9e8a6c51922 Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Wed, 26 Jul 2023 14:40:34 +0800 Subject: [PATCH 4/6] Pass all FuzzARM tests Thanks so much for making that test ROM -- my bugs were almost entirely related to flag setting but without a definitive test ROM to root them all out, those kinds of subtle bugs can lay hiding for a very long time. --- src/gba/mod.rs | 207 +++++++++++++++++++++---------------------------- 1 file changed, 90 insertions(+), 117 deletions(-) diff --git a/src/gba/mod.rs b/src/gba/mod.rs index 61aab5b..c641309 100644 --- a/src/gba/mod.rs +++ b/src/gba/mod.rs @@ -1958,19 +1958,6 @@ impl GameboyAdvance { } pub fn dispatch(&mut self) -> u32 { - /* - if self.r.pc > 0x3FFF && self.r.pc < 0x2000000 { - panic!("PC is maybe OOB in bios: 0x{:X}", self.r.pc); - } - */ - // HACK: ignore no-ops for this test ROM during dev - if self.r.pc == 0x8001EBC { - return 8; - } - if self.r.pc >= 0x4000000 && self.r.pc <= 0x4FFFFFF { - std::process::exit(0); - } - //println!("R14 = 0x{:X}", self.r[14]); if self.io_registers.dma_waiting() { return self.handle_dma(); } @@ -2007,10 +1994,6 @@ impl GameboyAdvance { let cycles = match opcode_idx { 0b101 => self.dispatch_branch(opcode), - // TODO: add 10 to end of above and PSR - // TODO: add 000 to end of above and multiply - // TODO: add 01 for mul long - // |_Cond__|0_0_0_0_1|U|A|S|_RdHi__|_RdLo__|__Rs___|1_0_0_1|__Rm___| MulLong 0b000 => { let multiply_end = ((opcode >> 4) & 0xF) == 0b1001; let bits8to11 = (opcode >> 8) & 0xF; @@ -2018,9 +2001,9 @@ impl GameboyAdvance { let multiply_next2 = multiply_next3 >> 1; match multiply_next2 { 0b00 if multiply_next3 == 0 && multiply_end => self.dispatch_multiply(opcode), - 0b01 if multiply_end => todo!("mul long"), + 0b01 if multiply_end => self.dispatch_multiply(opcode), 0b10 if multiply_end && bits8to11 == 0 && ((opcode >> 20) & 0x3) == 0 => { - todo!("transswp12") + self.dispatch_swap(opcode) } 0b10 if (opcode >> 20) & 1 == 0 && ((opcode >> 4) & 0xFF) == 0 => { self.dispatch_psr(opcode) @@ -3039,96 +3022,6 @@ impl GameboyAdvance { } } - // detect if ALU instruction is actually an MRS/MSR: PSR transfer - // TODO: rn != 0xF = SWP - if (sub_opcode >> 2) == 0b10 && !s { - panic!("Refactored out, this should never trigger"); - // TOOD: delete this code - let psr_src_dest = (opcode >> 22) & 1 == 1; - let psr_subopcode = (opcode >> 21) & 1 == 1; - if psr_subopcode { - trace!("MSR"); - // MSR: Psr[field] = Op - let write_flags = (opcode >> 19) & 1 == 1; - let write_status = (opcode >> 18) & 1 == 1; - let write_extension = (opcode >> 17) & 1 == 1; - let write_control = (opcode >> 16) & 1 == 1; - let mask = { - let mut mask = 0; - if write_flags { - mask |= 0xFF << 24; - } - if write_status { - mask |= 0xFF << 16; - } - if write_extension { - mask |= 0xFF << 8; - } - if write_control { - mask |= 0xFF; - } - mask - }; - let val = if imm { - let shift_amt = (opcode >> 8) & 0xF; - (opcode & 0xFF).rotate_right(shift_amt * 2) - // TODO: set flags? - } else { - debug_assert_eq!((opcode >> 4) & 0xFF, 0); - let reg_idx = opcode & 0xF; - debug_assert!(reg_idx < 15); - self.r[reg_idx as u8] - }; - // HACK: if in user mode, just force CPSR - if psr_src_dest && self.r.register_mode() != Some(RegisterMode::User) { - *self.r.get_spsr_mut() &= !mask; - *self.r.get_spsr_mut() |= val & mask; - } else { - self.r.cpsr &= !mask; - self.r.cpsr |= val & mask; - } - } else { - if rn != 0xF { - let byte = (opcode >> 22) & 1 == 1; - let rn = ((opcode >> 16) & 0xF) as u8; - let rd = ((opcode >> 12) & 0xF) as u8; - let rm = (opcode & 0xF) as u8; - let mut cycles = 0; - - trace!("SWP r{} r{} [r{}]", rd, rm, rn); - - if byte { - let o = self.get_mem8(self.r[rn]); - self.r[rd] = o.0 as u32; - cycles += o.1; - cycles += self.set_mem8(self.r[rn], self.r[rm] as u8); - } else { - let o = self.get_mem32(self.r[rn]); - self.r[rd] = o.0; - cycles += o.1; - cycles += self.set_mem32(self.r[rn], self.r[rm]); - } - - return 4 + cycles; - } - - trace!("MRS"); - debug_assert_eq!(opcode & 0xFFF, 0); - debug_assert!(rd < 15); - debug_assert_eq!(imm, false); - - // HACK: if in user mode just force CPSR - // MRS: Rd = Psr - let v = if psr_src_dest && self.r.register_mode() != Some(RegisterMode::User) { - self.r.get_spsr() - } else { - self.r.cpsr - }; - - self.r[rd] = v; - } - return 1; - } match sub_opcode { // AND 0x0 => { @@ -3353,12 +3246,14 @@ impl GameboyAdvance { let rd = ((opcode >> 16) & 0xF) as u8; let rn = ((opcode >> 12) & 0xF) as u8; let rs = ((opcode >> 8) & 0xF) as u8; + let rm = (opcode & 0xF) as u8; + // is this ARM9 only? maybe we don't need it let half_word_multiply = (opcode >> 4) & 1 == 1; match sub_opcode { 0b0000 => { - trace!("MUL r{} = r{} * r{}", rd, rs, rn); - let result = self.r[rs].wrapping_mul(self.r[rn]); + trace!("MUL r{} = r{} * r{}", rd, rm, rs); + let result = self.r[rm].wrapping_mul(self.r[rs]); self.r[rd] = result as u32; if s { self.r.cpsr_set_zero_flag(result == 0); @@ -3366,8 +3261,8 @@ impl GameboyAdvance { } } 0b0001 => { - trace!("MLA r{} = r{} * r{} + r{}", rd, rs, rn, self.r[rd]); - let result = self.r[rs].wrapping_mul(self.r[rn]).wrapping_add(self.r[rd]); + trace!("MLA r{} = r{} * r{} + r{}", rd, rm, rs, rn); + let result = self.r[rm].wrapping_mul(self.r[rs]).wrapping_add(self.r[rn]); self.r[rd] = result as u32; if s { self.r.cpsr_set_zero_flag(result == 0); @@ -3378,16 +3273,64 @@ impl GameboyAdvance { todo!("UMAAL") } 0b0100 => { - todo!("UMULL"); + trace!("UMULL r{},r{} = r{} * r{}", rd, rn, rs, rm); + let result = (self.r[rs] as u64).wrapping_mul(self.r[rm] as u64); + self.r[rd] = (result >> 32) as u32; + self.r[rn] = (result & 0xFFFF_FFFF) as u32; + if s { + self.r.cpsr_set_zero_flag(result == 0); + self.r.cpsr_set_sign_flag((result >> 63) == 1); + } } 0b0101 => { - todo!("UMLAL"); + trace!( + "UMLAL r{},r{} = r{} * r{} + r{},r{}", + rd, + rn, + rs, + rm, + rd, + rn + ); + let add_val = ((self.r[rd] as u64) << 32) | (self.r[rn] as u64); + let result = + ((self.r[rs] as u64).wrapping_mul(self.r[rm] as u64)).wrapping_add(add_val); + self.r[rd] = (result >> 32) as u32; + self.r[rn] = (result & 0xFFFF_FFFF) as u32; + if s { + self.r.cpsr_set_zero_flag(result == 0); + self.r.cpsr_set_sign_flag((result >> 63) == 1); + } } 0b0110 => { - todo!("SMULL"); + trace!("SMULL r{},r{} = r{} * r{}", rd, rn, rs, rm); + let result = (self.r[rs] as i32 as i64).wrapping_mul(self.r[rm] as i32 as i64); + self.r[rd] = (result >> 32) as u32; + self.r[rn] = (result & 0xFFFF_FFFF) as u32; + if s { + self.r.cpsr_set_zero_flag(result == 0); + self.r.cpsr_set_sign_flag((result >> 63) & 1 == 1); + } } 0b0111 => { - todo!("SMLAL"); + trace!( + "SMLAL r{},r{} = r{} * r{} + r{},r{}", + rd, + rn, + rs, + rm, + rd, + rn + ); + let add_val = ((self.r[rd] as u64) << 32) | (self.r[rn] as u64); + let mul_result = (self.r[rs] as i32 as i64).wrapping_mul(self.r[rm] as i32 as i64); + let result = (mul_result as u64).wrapping_add(add_val); + self.r[rd] = (result >> 32) as u32; + self.r[rn] = (result & 0xFFFF_FFFF) as u32; + if s { + self.r.cpsr_set_zero_flag(result == 0); + self.r.cpsr_set_sign_flag((result >> 63) == 1); + } } 0b1000 => { todo!("SMLAxy"); @@ -3408,10 +3351,40 @@ impl GameboyAdvance { } // for armv4 we always clear this self.r.cpsr_set_carry_flag(false); + //self.r.cpsr_set_overflow_flag(false); 3 } + pub fn dispatch_swap(&mut self, opcode: u32) -> u8 { + let byte = (opcode >> 22) & 1 == 1; + let rn = ((opcode >> 16) & 0xF) as u8; + let rd = ((opcode >> 12) & 0xF) as u8; + let rm = (opcode & 0xF) as u8; + debug_assert_ne!(rn, 0xF); + debug_assert_ne!(rd, 0xF); + debug_assert_ne!(rm, 0xF); + + trace!("SWP r{} = r{} <-> r{}", rd, rm, rn); + + let addr = self.r[rn]; + let rm_val = self.r[rm]; + let mut cycles = 0; + if byte { + let o = self.get_mem8(addr); + cycles += o.1; + self.r[rd] = o.0 as u32; + cycles += self.set_mem8(addr, rm_val as u8); + } else { + let o = self.get_mem32(addr); + cycles += o.1; + self.r[rd] = o.0; + cycles += self.set_mem32(addr, rm_val); + } + + cycles + 1 + } + pub fn dispatch_psr(&mut self, opcode: u32) -> u8 { let psr_src_dest = (opcode >> 22) & 1 == 1; let psr_subopcode = (opcode >> 21) & 1 == 1; From 691afb3bcf4e6c6cf04a1c008b0fa626f8d6c8fa Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Sun, 30 Jul 2023 13:46:58 +0800 Subject: [PATCH 5/6] Pass more tests, get through boot process --- src/cpu/memory.rs | 2 +- src/gba/mod.rs | 787 +++++++++++++++++++++++++------- src/io/applicationstate.rs | 68 ++- src/io/deferred_renderer_gba.rs | 130 +++++- src/io/dr_sdl2.rs | 44 +- src/io/graphics/renderer.rs | 4 +- 6 files changed, 812 insertions(+), 223 deletions(-) diff --git a/src/cpu/memory.rs b/src/cpu/memory.rs index 98ec0dd..a95aa0e 100644 --- a/src/cpu/memory.rs +++ b/src/cpu/memory.rs @@ -252,7 +252,7 @@ impl IndexMut for Memory { 0xFF02 => { //self.io_ports[0x02] = 0x7E; // interrupt handler should be called, but let's try not doing it for now - print!("{}", self.io_ports[0x01] as char); + //print!("{}", self.io_ports[0x01] as char); &mut self.io_ports[0x02] } _ => &mut self.io_ports[index - 0xFF00], diff --git a/src/gba/mod.rs b/src/gba/mod.rs index c641309..bab0b83 100644 --- a/src/gba/mod.rs +++ b/src/gba/mod.rs @@ -191,39 +191,111 @@ impl DmaControl { } } +#[derive(Debug, Clone, Default)] +pub struct BitMapBgRotationScale { + pub x: i32, + pub y: i32, + pub cached_x: i32, + pub cached_y: i32, + pub pa: i16, + pub pb: i16, + pub pc: i16, + pub pd: i16, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +enum DmaDelay {} + +const CGB_APU_BASE: u16 = 0xFF10; + pub struct IoRegisters { io_registers: [u8; 0x400], dma0_enabled: bool, dma1_enabled: bool, dma2_enabled: bool, dma3_enabled: bool, + dma0_delay_counter: u8, + dma1_delay_counter: u8, + dma2_delay_counter: u8, + dma3_delay_counter: u8, timer0: u16, timer1: u16, timer2: u16, timer3: u16, + pub bg2_rotation: BitMapBgRotationScale, + pub bg3_rotation: BitMapBgRotationScale, + pub apu: crate::cpu::apu::Apu, } impl IoRegisters { - pub fn new() -> Self { + pub fn new(direct_boot: bool) -> Self { let mut io_registers = [0; 0x400]; - io_registers[0x130] = 0xFF; - io_registers[0x131] = 0x3; + if direct_boot { + io_registers[0x130] = 0xFF; + io_registers[0x131] = 0x3; + } + io_registers[0x88] = 0; + io_registers[0x89] = 0x2; IoRegisters { io_registers, dma0_enabled: false, dma1_enabled: false, dma2_enabled: false, dma3_enabled: false, + dma0_delay_counter: 0, + dma1_delay_counter: 0, + dma2_delay_counter: 0, + dma3_delay_counter: 0, timer0: 0, timer1: 0, timer2: 0, timer3: 0, + bg2_rotation: BitMapBgRotationScale { + pa: if direct_boot { 0x100 } else { 0 }, + pd: if direct_boot { 0x100 } else { 0 }, + ..BitMapBgRotationScale::default() + }, + bg3_rotation: BitMapBgRotationScale { + pa: if direct_boot { 0x100 } else { 0 }, + pd: if direct_boot { 0x100 } else { 0 }, + ..BitMapBgRotationScale::default() + }, + apu: crate::cpu::apu::Apu::new(), } } - pub fn dma_waiting(&self) -> bool { + pub const fn dma_waiting(&self) -> bool { self.dma0_enabled || self.dma1_enabled || self.dma2_enabled || self.dma3_enabled } + pub const fn dma0_ready(&self) -> bool { + self.dma0_enabled && self.dma0_delay_counter > 2 + } + pub const fn dma1_ready(&self) -> bool { + self.dma1_enabled && self.dma1_delay_counter > 2 + } + pub const fn dma2_ready(&self) -> bool { + self.dma2_enabled && self.dma2_delay_counter > 2 + } + pub const fn dma3_ready(&self) -> bool { + self.dma3_enabled && self.dma3_delay_counter > 2 + } + pub const fn dma_ready(&self) -> bool { + self.dma0_ready() || self.dma1_ready() || self.dma2_ready() || self.dma3_ready() + } + pub fn dma_inc_delay_counter(&mut self, cycles: u8) { + if self.dma0_enabled { + self.dma0_delay_counter += cycles; + } + if self.dma1_enabled { + self.dma1_delay_counter += cycles; + } + if self.dma2_enabled { + self.dma2_delay_counter += cycles; + } + if self.dma3_enabled { + self.dma3_delay_counter += cycles; + } + } pub fn dma0(&self) -> DmaControl { DmaControl::from_bits( @@ -437,11 +509,167 @@ impl IoRegisters { pub fn set_mem8(&mut self, addr: u32, val: u8) { debug_assert!((0x4000000..=0x4FFFFFF).contains(&addr)); + let addr = addr & 0x3FF; match addr { + 0x4 => { + let writeable_bits = val & !0x47; + self.io_registers[0x4] &= !writeable_bits; + self.io_registers[0x4] |= !writeable_bits; + } + 0x6 | 0x7 => (), + 0x20..=0x21 => { + let offset = addr - 0x20; + self.bg2_rotation.pa &= !(0xFF << (offset * 8)); + self.bg2_rotation.pa |= ((val as u16) << (offset * 8)) as i16; + } + 0x22..=0x23 => { + let offset = addr - 0x22; + self.bg2_rotation.pb &= !(0xFF << (offset * 8)); + self.bg2_rotation.pb |= ((val as u16) << (offset * 8)) as i16; + } + 0x24..=0x25 => { + let offset = addr - 0x24; + self.bg2_rotation.pc &= !(0xFF << (offset * 8)); + self.bg2_rotation.pc |= ((val as u16) << (offset * 8)) as i16; + } + 0x26..=0x27 => { + let offset = addr - 0x26; + self.bg2_rotation.pd &= !(0xFF << (offset * 8)); + self.bg2_rotation.pd |= ((val as u16) << (offset * 8)) as i16; + } + 0x28..=0x2B => { + let offset = addr - 0x28; + self.bg2_rotation.x &= !(0xFF << (offset * 8)); + self.bg2_rotation.x |= ((val as u32) << (offset * 8)) as i32; + if offset == 3 { + // sign extend + self.bg2_rotation.x <<= 4; + self.bg2_rotation.x >>= 4; + } + } + 0x2C..=0x2F => { + let offset = addr - 0x2C; + self.bg2_rotation.y &= !(0xFF << (offset * 8)); + self.bg2_rotation.y |= ((val as u32) << (offset * 8)) as i32; + if offset == 3 { + // sign extend + self.bg2_rotation.y <<= 4; + self.bg2_rotation.y >>= 4; + } + } + 0x30..=0x31 => { + let offset = addr - 0x30; + self.bg3_rotation.pa &= !(0xFF << (offset * 8)); + self.bg3_rotation.pa |= ((val as u16) << (offset * 8)) as i16; + } + 0x32..=0x33 => { + let offset = addr - 0x32; + self.bg3_rotation.pb &= !(0xFF << (offset * 8)); + self.bg3_rotation.pb |= ((val as u16) << (offset * 8)) as i16; + } + 0x34..=0x35 => { + let offset = addr - 0x34; + self.bg3_rotation.pc &= !(0xFF << (offset * 8)); + self.bg3_rotation.pc |= ((val as u16) << (offset * 8)) as i16; + } + 0x36..=0x37 => { + let offset = addr - 0x36; + self.bg3_rotation.pd &= !(0xFF << (offset * 8)); + self.bg3_rotation.pd |= ((val as u16) << (offset * 8)) as i16; + } + 0x38..=0x3B => { + let offset = addr - 0x38; + self.bg3_rotation.x &= !(0xFF << (offset * 8)); + self.bg3_rotation.x |= ((val as u32) << (offset * 8)) as i32; + if offset == 3 { + // sign extend + self.bg3_rotation.x <<= 4; + self.bg3_rotation.x >>= 4; + } + } + 0x3C..=0x3F => { + let offset = addr - 0x3C; + self.bg3_rotation.y &= !(0xFF << (offset * 8)); + self.bg3_rotation.y |= ((val as u32) << (offset * 8)) as i32; + if offset == 3 { + // sign extend + self.bg3_rotation.y <<= 4; + self.bg3_rotation.y >>= 4; + } + } + // NR10 + 0x60 => self.apu.set_mem(CGB_APU_BASE, val), + 0x61 => (), + 0x62 => self.apu.set_mem(CGB_APU_BASE + 1, val), + 0x63 => self.apu.set_mem(CGB_APU_BASE + 2, val), + 0x64 => self.apu.set_mem(CGB_APU_BASE + 3, val), + 0x65 => self.apu.set_mem(CGB_APU_BASE + 4, val), + 0x66 | 0x67 => (), + // NR 21 + 0x68 => self.apu.set_mem(CGB_APU_BASE + 6, val), + 0x69 => self.apu.set_mem(CGB_APU_BASE + 7, val), + 0x6A | 0x6B => (), + 0x6C => self.apu.set_mem(CGB_APU_BASE + 8, val), + 0x6D => self.apu.set_mem(CGB_APU_BASE + 9, val), + // TODO: verify these 2 addresses are unused + 0x6E | 0x6F => (), + // NR30 + // TODO: NR30 has an important GBA only bit + // ALSO: we have 2 wave RAM buffers and need to figure out how to + // squuze that into our CGB APU + 0x70 => self.apu.set_mem(CGB_APU_BASE + 0xA, val), + 0x71 => (), + 0x72 => self.apu.set_mem(CGB_APU_BASE + 0xB, val), + 0x73 => self.apu.set_mem(CGB_APU_BASE + 0xC, val), + 0x74 => self.apu.set_mem(CGB_APU_BASE + 0xD, val), + 0x75 => self.apu.set_mem(CGB_APU_BASE + 0xE, val), + 0x76 | 0x77 => (), + // NR41 + 0x78 => self.apu.set_mem(CGB_APU_BASE + 0x10, val), + 0x79 => self.apu.set_mem(CGB_APU_BASE + 0x11, val), + 0x7A | 0x7B => (), + 0x7C => self.apu.set_mem(CGB_APU_BASE + 0x12, val), + 0x7D => self.apu.set_mem(CGB_APU_BASE + 0x13, val), + 0x7E | 0x7F => (), + // Wave RAM + 0x90..=0x9F => { + self.apu.set_mem(CGB_APU_BASE + (addr as u16 - 0x90), val); + } + 0xA0..=0xA3 => { + // TODO: + /* + Initializing DMA-Sound Playback + - Select Timer 0 or 1 in SOUNDCNT_H control register. + - Clear the FIFO. + - Manually write a sample byte to the FIFO. + - Initialize transfer mode for DMA 1 or 2. + - Initialize DMA Sound settings in sound control register. + - Start the timer. + + DMA-Sound Playback Procedure + The pseudo-procedure below is automatically repeated. + + If Timer overflows then + Move 8bit data from FIFO to sound circuit. + If FIFO contains only 4 x 32bits (16 bytes) then + Request more data per DMA + Receive 4 x 32bit (16 bytes) per DMA + Endif + Endif + + This playback mechanism will be repeated forever, regardless of the actual length of the sample buffer. + */ + warn!("Writing to Sound A FIFO but discarding the data!"); + self.io_registers[addr as usize] = val; + } + 0xA4..=0xA7 => { + warn!("Writing to Sound B FIFO but discarding the data!"); + self.io_registers[addr as usize] = val; + } // IF: interrupt request flags // writes to this register clear set bits to aknowledge interrupts - 0x200..=0x203 => { + 0x202..=0x203 => { self.io_registers[addr as usize] &= !val; } 0x0B0..=0x0E0 => { @@ -449,51 +677,58 @@ impl IoRegisters { 0xBB if val & 0x80 != 0 => { // DMA 0 start self.dma0_enabled = true; + self.dma0_delay_counter = 0; } 0xC7 if val & 0x80 != 0 => { // DMA 1 start self.dma1_enabled = true; + self.dma1_delay_counter = 0; } 0xD3 if val & 0x80 != 0 => { // DMA 2 start self.dma2_enabled = true; + self.dma2_delay_counter = 0; } 0xDF if val & 0x80 != 0 => { // DMA3 start self.dma3_enabled = true; + self.dma3_delay_counter = 0; } _ => (), } - //println!("DMA: {:X} = {:X}", addr, val); self.io_registers[addr as usize] = val; } 0x102 => { if (self.io_registers[0x102] >> 7) == 0 && (val >> 7) == 1 { - self.timer0 = self.get_mem16(0x100); + self.timer0 = self.get_mem16(0x4000100); } self.io_registers[addr as usize] = val; } 0x106 => { if (self.io_registers[0x106] >> 7) == 0 && (val >> 7) == 1 { - self.timer1 = self.get_mem16(0x104); + self.timer1 = self.get_mem16(0x4000104); } self.io_registers[addr as usize] = val; } 0x10A => { if (self.io_registers[0x10A] >> 7) == 0 && (val >> 7) == 1 { - self.timer2 = self.get_mem16(0x108); + self.timer2 = self.get_mem16(0x4000108); } self.io_registers[addr as usize] = val; } 0x10E => { if (self.io_registers[0x10E] >> 7) == 0 && (val >> 7) == 1 { - self.timer3 = self.get_mem16(0x10C); + self.timer3 = self.get_mem16(0x400010C); } self.io_registers[addr as usize] = val; } 0x100..=0x110 => { self.io_registers[addr as usize] = val; } + 0x208 => { + self.io_registers[addr as usize] = val & 1; + } + 0x209..=0x20B => (), // interupt clearing 0x214..=0x217 => { self.io_registers[addr as usize] &= !val; @@ -529,6 +764,43 @@ impl IoRegisters { debug_assert!((0x4000000..=0x4FFFFFF).contains(&addr)); let addr = addr & 0x3FF; match addr { + // NR10 + 0x60 => self.apu.get_mem(CGB_APU_BASE), + 0x61 => 0, + 0x62 => self.apu.get_mem(CGB_APU_BASE + 1), + 0x63 => self.apu.get_mem(CGB_APU_BASE + 2), + 0x64 => self.apu.get_mem(CGB_APU_BASE + 3), + 0x65 => self.apu.get_mem(CGB_APU_BASE + 4), + 0x66 | 0x67 => 0, + // NR 21 + 0x68 => self.apu.get_mem(CGB_APU_BASE + 6), + 0x69 => self.apu.get_mem(CGB_APU_BASE + 7), + 0x6A | 0x6B => 0, + 0x6C => self.apu.get_mem(CGB_APU_BASE + 8), + 0x6D => self.apu.get_mem(CGB_APU_BASE + 9), + // TODO: verify these 2 addresses are unused + 0x6E | 0x6F => 0, + // NR30 + // TODO: NR30 has an important GBA only bit + // ALSO: we have 2 wave RAM buffers and need to figure out how to + // squuze that into our CGB APU + 0x70 => self.apu.get_mem(CGB_APU_BASE + 0xA), + 0x71 => 0, + 0x72 => self.apu.get_mem(CGB_APU_BASE + 0xB), + 0x73 => self.apu.get_mem(CGB_APU_BASE + 0xC), + 0x74 => self.apu.get_mem(CGB_APU_BASE + 0xD), + 0x75 => self.apu.get_mem(CGB_APU_BASE + 0xE), + 0x76 | 0x77 => 0, + // NR41 + 0x78 => self.apu.get_mem(CGB_APU_BASE + 0x10), + 0x79 => self.apu.get_mem(CGB_APU_BASE + 0x11), + 0x7A | 0x7B => 0, + 0x7C => self.apu.get_mem(CGB_APU_BASE + 0x12), + 0x7D => self.apu.get_mem(CGB_APU_BASE + 0x13), + 0x7E | 0x7F => 0, + // Wave RAM + 0x90..=0x9F => self.apu.get_mem(CGB_APU_BASE + (addr as u16 - 0x90)), + // TODO: 0xA0... GBA register stuff 0x100 => (self.timer0 & 0xFF) as u8, 0x101 => (self.timer0 >> 8) as u8, 0x104 => (self.timer1 & 0xFF) as u8, @@ -537,6 +809,8 @@ impl IoRegisters { 0x109 => (self.timer2 >> 8) as u8, 0x10C => (self.timer3 & 0xFF) as u8, 0x10D => (self.timer3 >> 8) as u8, + 0x208 => self.io_registers[addr as usize] & 1, + 0x209..=0x20B => 0, _ => self.io_registers[addr as usize], } } @@ -570,6 +844,7 @@ impl std::ops::IndexMut for IoRegisters { } } +#[derive(Debug)] pub struct Registers { pub r0: u32, pub r1: u32, @@ -814,6 +1089,17 @@ impl RegisterMode { _ => None, } } + pub fn to_u8(self) -> u8 { + match self { + Self::User => 0b10000, + Self::FIQ => 0b10001, + Self::IRQ => 0b10010, + Self::Supervisor => 0b10011, + Self::Abort => 0b10111, + Self::Undefined => 0b11011, + Self::System => 0b11111, + } + } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -861,7 +1147,7 @@ impl Cond { } impl Registers { - pub fn new() -> Self { + pub fn new(direct_boot: bool) -> Self { Self { r0: 0, r1: 0, @@ -876,26 +1162,26 @@ impl Registers { r10: 0, r11: 0, r12: 0, - sp: 0x03007F00, + sp: if direct_boot { 0x03007F00 } else { 0 }, lr: 0, - pc: 0x08000000, + pc: if direct_boot { 0x08000000 } else { 0 }, r8_fiq: 0, r9_fiq: 0, r10_fiq: 0, r11_fiq: 0, r12_fiq: 0, - r13_fiq: 0, + r13_fiq: if direct_boot { 0x03007F00 } else { 0 }, r14_fiq: 0, - r13_svc: 0x03007FE0, + r13_svc: if direct_boot { 0x03007FE0 } else { 0 }, r14_svc: 0, - r13_abt: 0, + r13_abt: if direct_boot { 0x03007F00 } else { 0 }, r14_abt: 0, - r13_irq: 0x03007FA0, + r13_irq: if direct_boot { 0x03007FA0 } else { 0 }, r14_irq: 0, - r13_und: 0, + r13_und: if direct_boot { 0x03007F00 } else { 0 }, r14_und: 0, // Disable IRQ, FIQ, and set supervisor mode - cpsr: 0b11010011, + cpsr: if direct_boot { 0x5F } else { 0b11010011 }, spsr_fiq: 0, spsr_svc: 0, spsr_abt: 0, @@ -978,11 +1264,23 @@ impl Registers { RegisterMode::Undefined => &mut self.spsr_und, } } + pub fn set_mode(&mut self, mode: RegisterMode) { + if !matches!( + self.register_mode(), + Some(RegisterMode::User | RegisterMode::System) + ) { + *self.get_spsr_mut() = self.cpsr; + } + self.cpsr &= !0x1F; + self.cpsr |= mode.to_u8() as u32; + } pub fn set_svc_mode(&mut self) { + self.spsr_svc = self.cpsr; self.cpsr &= !0x1F; self.cpsr |= 0b10011; } pub fn set_irq_mode(&mut self) { + self.spsr_irq = self.cpsr; self.cpsr &= !0x1F; self.cpsr |= 0b10010; } @@ -1047,14 +1345,14 @@ impl Registers { } impl GameboyAdvance { - pub fn new() -> GameboyAdvance { + pub fn new(direct_boot: bool) -> GameboyAdvance { GameboyAdvance { - r: Registers::new(), + r: Registers::new(direct_boot), entire_rom: vec![], bios: [0u8; 0x4000], iw_ram: [0u8; 0x8000], wram: [0u8; 0x40000], - io_registers: IoRegisters::new(), + io_registers: IoRegisters::new(direct_boot), obj_palette_ram: [0u8; 0x400], vram: [0u8; 0x18000], oam: [0u8; 0x400], @@ -1400,12 +1698,21 @@ impl GameboyAdvance { pub fn get_mem8(&self, address: u32) -> (u8, u8) { match address { - //0x00000000..=0x00003FFF => { - 0x00000000..=0x01FFFFFF => { - // bios system ROM - // todo!("bios system ROM") - // HACK: - (0, 0) + 0x00000000..=0x00003FFF => { + if self.r.pc < 0x4000 { + // bios + (self.bios[address as usize], 3) + } else { + todo!("Reading from bios while PC is not in the bios"); + } + } + 0x00004000..=0x01FFFFFF => { + if self.r.thumb_enabled() { + // TODO: handle all cases + self.get_mem8(self.r.pc + 2) + } else { + self.get_mem8(self.r.pc + 4) + } } //0x02000000..=0x0203FFFF => { 0x02000000..=0x02FFFFFF => { @@ -1414,8 +1721,8 @@ impl GameboyAdvance { } //0x03000000..=0x03007FFF => (self.iw_ram[(address & 0x7FFF) as usize], 1), 0x03000000..=0x03FFFFFF => (self.iw_ram[(address & 0x7FFF) as usize], 1), - //0x04000000..=0x040003FE => (self.io_registers[(address & 0x3FE) as usize], 1), - 0x04000000..=0x04FFFFFF => (self.io_registers.get_mem8(address), 1), + 0x04000000..=0x040003FF => (self.io_registers.get_mem8(address), 1), + //0x04000000..=0x04FFFFFF => (self.io_registers.get_mem8(address), 1), //0x05000000..=0x050003FF => (self.obj_palette_ram[(address & 0x3FF) as usize], 1), 0x05000000..=0x05FFFFFF => (self.obj_palette_ram[(address & 0x3FF) as usize], 1), //0x06000000..=0x06017FFF => (self.vram[(address & 0x17FFF) as usize], 1), @@ -1432,21 +1739,28 @@ impl GameboyAdvance { 0x0C000000..=0x0DFFFFFF => todo!("Game Pak ROM/FlashROM (max 32MB) - Wait State 2"), //0x0E000000..=0x0E00FFFF => todo!("Game Pak SRAM (max 64 KBytes) - 8bit Bus width"), 0x0E000000..=0x0FFFFFFF => (self.sram[(address & 0xFFFF) as usize], 5), - _ => (0, 0), + _ => (0, 1), } } pub fn get_mem16(&self, address: u32) -> (u16, u8) { let lo_bit_idx = address & !0x1; let hi_bit_idx = address | 0x1; match address { - 0x00000000..=0x01FFFFFF => { - //0x00000000..=0x00003FFF => { + 0x00000000..=0x00003FFF => { // bios system ROM // TODO: separate opcoed reading logic from these getters let lo_bit = self.bios[(lo_bit_idx & 0x3FFF) as usize] as u16; let hi_bit = self.bios[(hi_bit_idx & 0x3FFF) as usize] as u16; ((hi_bit << 8) | lo_bit, 1) } + 0x00004000..=0x01FFFFFF => { + if self.r.thumb_enabled() { + // TODO: handle all cases + self.get_mem16(self.r.pc + 2) + } else { + self.get_mem16(self.r.pc + 4) + } + } 0x02000000..=0x02FFFFFF => { //0x02000000..=0x0203FFFF => { // on-board work ram @@ -1460,10 +1774,8 @@ impl GameboyAdvance { let hi_bit = self.iw_ram[(hi_bit_idx & 0x7FFF) as usize] as u16; ((hi_bit << 8) | lo_bit, 1) } - 0x04000000..=0x04FFFFFF => { - //0x04000000..=0x040003FF => { - (self.io_registers.get_mem16(address), 1) - } + //0x04000000..=0x04FFFFFF => { + 0x04000000..=0x040003FF => (self.io_registers.get_mem16(address), 1), 0x05000000..=0x05FFFFFF => { //0x05000000..=0x050003FF => { let lo_bit = self.obj_palette_ram[(lo_bit_idx & 0x3FF) as usize] as u16; @@ -1484,7 +1796,7 @@ impl GameboyAdvance { } 0x08000000..=0x09FFFFFF => { if (hi_bit_idx - 0x0800_0000) > self.entire_rom.len() as u32 { - return (0, 5); + return (((address >> 1) & 0xFFFF) as u16, 5); } let lo_bit = self.entire_rom[(lo_bit_idx & 0x1FFFFFF) as usize] as u16; let hi_bit = self.entire_rom[(hi_bit_idx & 0x1FFFFFF) as usize] as u16; @@ -1492,7 +1804,7 @@ impl GameboyAdvance { } 0x0A000000..=0x0BFFFFFF => { if (hi_bit_idx - 0x0A00_0000) > self.entire_rom.len() as u32 { - return (0, 5); + return (((address >> 1) & 0xFFFF) as u16, 5); } let lo_bit = self.entire_rom[(lo_bit_idx & 0x1FFFFFF) as usize] as u16; let hi_bit = self.entire_rom[(hi_bit_idx & 0x1FFFFFF) as usize] as u16; @@ -1500,7 +1812,7 @@ impl GameboyAdvance { } 0x0C000000..=0x0DFFFFFF => { if (hi_bit_idx - 0x0C00_0000) > self.entire_rom.len() as u32 { - return (0, 5); + return (((address >> 1) & 0xFFFF) as u16, 5); } let lo_bit = self.entire_rom[(lo_bit_idx & 0x1FFFFFF) as usize] as u16; let hi_bit = self.entire_rom[(hi_bit_idx & 0x1FFFFFF) as usize] as u16; @@ -1508,18 +1820,22 @@ impl GameboyAdvance { } //0x0E000000..=0x0E00FFFF => todo!("Game Pak SRAM (max 64 KBytes) - 8bit Bus width"), 0x0E000000..=0x0FFFFFFF => todo!("Game Pak SRAM (max 64 KBytes) - 8bit Bus width"), - _ => (0, 0), + _ => (0, 1), } } pub fn get_mem32(&self, address: u32) -> (u32, u8) { + if address != address & !3 { + let lo = self.get_mem16(address); + let hi = self.get_mem16(address - 2); + return ((hi.0 as u32) << 16 | lo.0 as u32, lo.1); + } //address = address & 0x0FFF_FFFF; let bit1_idx = address & !0x3; let bit2_idx = (address & !0x3) | 0b01; let bit3_idx = (address & !0x3) | 0b10; let bit4_idx = (address & !0x3) | 0b11; match address { - 0x00000000..=0x01FFFFFF => { - //0x00000000..=0x00003FFF => { + 0x00000000..=0x00003FFF => { // bios system ROM let bit1 = self.bios[(bit1_idx & 0x3FFF) as usize] as u32; let bit2 = self.bios[(bit2_idx & 0x3FFF) as usize] as u32; @@ -1528,6 +1844,14 @@ impl GameboyAdvance { let out = (bit4 << 24) | (bit3 << 16) | (bit2 << 8) | bit1; (out, 1) } + 0x00004000..=0x01FFFFFF => { + if self.r.thumb_enabled() { + // TODO: handle all cases + self.get_mem32(self.r.pc + 2) + } else { + self.get_mem32(self.r.pc + 4) + } + } 0x02000000..=0x02FFFFFF => { //0x02000000..=0x0203FFFF => { // on-board work ram @@ -1547,10 +1871,8 @@ impl GameboyAdvance { let out = (bit4 << 24) | (bit3 << 16) | (bit2 << 8) | bit1; (out, 1) } - 0x04000000..=0x04FFFFFE => { - //0x04000000..=0x040003FE => { - (self.io_registers.get_mem32(address), 1) - } + //0x04000000..=0x04FFFFFF => { + 0x04000000..=0x040003FF => (self.io_registers.get_mem32(address), 1), 0x05000000..=0x05FFFFFF => { //0x05000000..=0x050003FF => { let bit1 = self.obj_palette_ram[(bit1_idx & 0x3FF) as usize] as u32; @@ -1581,7 +1903,7 @@ impl GameboyAdvance { 0x0A000000..=0x0BFFFFFF | 0x0C000000..=0x0DFFFFFF | 0x08000000..=0x09FFFFFF => { // TODO: properly handle later bytes overflowing too if bit1_idx & 0x1FFFFFF >= self.entire_rom.len() as u32 { - return (0, 8); + return ((address >> 1) & 0xFFFF, 8); } let bit1 = self.entire_rom[(bit1_idx & 0x1FFFFFF) as usize] as u32; let bit2 = self.entire_rom[(bit2_idx & 0x1FFFFFF) as usize] as u32; @@ -1596,7 +1918,7 @@ impl GameboyAdvance { //0x0E000000..=0x0E00FFFF => todo!("Game Pak SRAM (max 64 KBytes) - 8bit Bus width"), */ 0x0E000000..=0x0FFFFFFF => todo!("Game Pak SRAM (max 64 KBytes) - 8bit Bus width"), - _ => (0, 0), + _ => (0, 1), } } @@ -1604,8 +1926,7 @@ impl GameboyAdvance { match address { 0x00000000..=0x00003FFF => { // bios system ROM - //todo!("bios system ROM") - 1 + todo!("bios system ROM") } 0x02000000..=0x02FFFFFF => { //0x02000000..=0x0203FFFF => { @@ -1618,8 +1939,8 @@ impl GameboyAdvance { self.iw_ram[(address & 0x7FFF) as usize] = val; 1 } - 0x04000000..=0x04FFFFFF => { - //0x04000000..=0x040003FE => { + //0x04000000..=0x04FFFFFF => { + 0x04000000..=0x040003FF => { self.io_registers.set_mem8(address, val); 1 } @@ -1643,7 +1964,7 @@ impl GameboyAdvance { self.sram[(address & 0xFFFF) as usize] = val; 5 } - _ => 0, + _ => 1, } } @@ -1655,9 +1976,7 @@ impl GameboyAdvance { match address { 0x00000000..=0x00003FFF => { // bios system ROM - // HACK: disable - //todo!("bios system ROM") - 0 + todo!("bios system ROM") } 0x02000000..=0x02FFFFFF => { //0x02000000..=0x0203FFFF => { @@ -1672,8 +1991,8 @@ impl GameboyAdvance { self.iw_ram[(hi_bit_idx & 0x7FFF) as usize] = hi_val; 1 } - 0x04000000..=0x04FFFFFE => { - //0x04000000..=0x040003FE => { + // 0x04000000..=0x04FFFFFF => { + 0x04000000..=0x040003FF => { self.io_registers.set_mem16(address, val); 1 } @@ -1700,7 +2019,7 @@ impl GameboyAdvance { 0x0C000000..=0x0DFFFFFF => todo!("Game Pak ROM/FlashROM (max 32MB) - Wait State 2"), //0x0E000000..=0x0E00FFFF => todo!("Game Pak SRAM (max 64 KBytes) - 8bit Bus width"), 0x0E000000..=0x0FFFFFFF => todo!("Game Pak SRAM (max 64 KBytes) - 8bit Bus width"), - _ => 0, + _ => 1, } } @@ -1716,9 +2035,7 @@ impl GameboyAdvance { match address { 0x00000000..=0x00003FFF => { // bios system ROM - // HACK: disable this for now, we don't want to write but something is trying to - //todo!("bios system ROM") - 0 + todo!("bios system ROM") } 0x02000000..=0x02FFFFFF => { //0x02000000..=0x0203FFFF => { @@ -1737,8 +2054,8 @@ impl GameboyAdvance { self.iw_ram[(bit4_idx & 0x7FFF) as usize] = val4; 1 } - 0x04000000..=0x04FFFFFE => { - //0x04000000..=0x040003FE => { + //0x04000000..=0x04FFFFFF => { + 0x04000000..=0x040003FF => { self.io_registers.set_mem32(address, val); 1 } @@ -1775,8 +2092,15 @@ impl GameboyAdvance { 0x0A000000..=0x0BFFFFFF => todo!("Game Pak ROM/FlashROM (max 32MB) - Wait State 1"), 0x0C000000..=0x0DFFFFFF => todo!("Game Pak ROM/FlashROM (max 32MB) - Wait State 2"), //0x0E000000..=0x0E00FFFF => todo!("Game Pak SRAM (max 64 KBytes) - 8bit Bus width"), - 0x0E000000..=0x0FFFFFFF => todo!("Game Pak SRAM (max 64 KBytes) - 8bit Bus width"), - _ => 0, + // 0x0E000000..=0x0FFFFFFF => todo!("Game Pak SRAM (max 64 KBytes) - 8bit Bus width"), + 0x0E000000..=0x0FFFFFFF => { + self.sram[(bit1_idx & 0xFFFF) as usize] = val1; + self.sram[(bit2_idx & 0xFFFF) as usize] = val2; + self.sram[(bit3_idx & 0xFFFF) as usize] = val3; + self.sram[(bit4_idx & 0xFFFF) as usize] = val4; + 5 + } + _ => 1, } } @@ -1785,7 +2109,6 @@ impl GameboyAdvance { // Docs say `SUBS PC,R14,4 ;both PC=R14_irq-4, and CPSR=SPSR_irq' // to return from IRQ, so we add 4 self.r[14] = self.r.pc + 4; - *self.r.get_spsr_mut() = self.r.cpsr; self.r.set_thumb(false); self.r.cpsr_disable_irq(); self.r.pc = 0x18; @@ -1796,53 +2119,60 @@ impl GameboyAdvance { if self.r.irq_disabled() { return; } - if self.lcdc_vblank_interrupt_requested() && self.lcdc_hblank_interrupt_enabled() { - debug!("VBLANK interrupt started"); + if self.lcdc_vblank_interrupt_requested() && self.lcdc_vblank_interrupt_enabled() { + trace!("VBLANK interrupt started"); self.irq_interrupt(); } else if self.lcdc_hblank_interrupt_requested() && self.lcdc_hblank_interrupt_enabled() { - debug!("HBLANK interrupt started"); + trace!("HBLANK interrupt started"); self.irq_interrupt(); } else if self.lcdc_vcounter_interrupt_requested() && self.lcdc_vcounter_interrupt_enabled() { - debug!("VCOUNTER interrupt started"); + trace!("VCOUNTER interrupt started"); self.irq_interrupt(); } else if self.timer0_interrupt_requested() && self.timer0_interrupt_enabled() { - debug!("TIMER0 interrupt started"); + trace!("TIMER0 interrupt started"); self.irq_interrupt(); } else if self.timer1_interrupt_requested() && self.timer1_interrupt_enabled() { - debug!("TIMER1 interrupt started"); + trace!("TIMER1 interrupt started"); self.irq_interrupt(); } else if self.timer2_interrupt_requested() && self.timer2_interrupt_enabled() { - debug!("TIMER2 interrupt started"); + trace!("TIMER2 interrupt started"); self.irq_interrupt(); } else if self.timer3_interrupt_requested() && self.timer3_interrupt_enabled() { - debug!("TIMER3 interrupt started"); + trace!("TIMER3 interrupt started"); self.irq_interrupt(); } else if self.serial_interrupt_requested() && self.serial_interrupt_enabled() { - debug!("SERIAL interrupt started"); + trace!("SERIAL interrupt started"); self.irq_interrupt(); } else if self.dma0_interrupt_requested() && self.dma0_interrupt_enabled() { - debug!("DMA0 interrupt started"); + trace!("DMA0 interrupt started"); self.irq_interrupt(); } else if self.dma1_interrupt_requested() && self.dma1_interrupt_enabled() { - debug!("DMA1 interrupt started"); + trace!("DMA1 interrupt started"); self.irq_interrupt(); } else if self.dma2_interrupt_requested() && self.dma2_interrupt_enabled() { - debug!("DMA2 interrupt started"); + trace!("DMA2 interrupt started"); self.irq_interrupt(); } else if self.dma3_interrupt_requested() && self.dma3_interrupt_enabled() { - debug!("DMA3 interrupt started"); + trace!("DMA3 interrupt started"); self.irq_interrupt(); } else if self.keypad_interrupt_requested() && self.keypad_interrupt_enabled() { - debug!("KEYPAD interrupt started"); + trace!("KEYPAD interrupt started"); self.irq_interrupt(); } else if self.game_pak_interrupt_requested() && self.game_pak_interrupt_enabled() { - debug!("GAMEPAK interrupt started"); + trace!("GAMEPAK interrupt started"); self.irq_interrupt(); } } - pub fn run_dma(&mut self, control: DmaControl, src: u32, dest: u32, count: u32) -> u32 { + pub fn run_dma( + &mut self, + control: DmaControl, + src: u32, + dest: u32, + count: u32, + dma_id: u8, + ) -> u32 { let mut cycles = 0; match control.start_timing { DmaStartTiming::Immediately => { @@ -1874,14 +2204,37 @@ impl GameboyAdvance { todo!("HBlank DMA timing not implemented") } DmaStartTiming::Special => { - todo!("Special DMA timing not implemented"); + match dma_id { + 0 => panic!("DMA0 does not have special timing"), + 1 => { + /* + debug_assert!(control.repeat); + debug_assert!(matches!(dest, 0x40000A0 | 0x40000A4), "0x{:X}", dest); + // TODO: transfer 16 bytes when sound channel requests it + */ + warn!("DMA1 Sound FIFO"); + } + 2 => { + /* + debug_assert!(control.repeat); + debug_assert!(matches!(dest, 0x40000A0 | 0x40000A4), "0x{:X}", dest); + // TODO: transfer 16 bytes when sound channel requests it + */ + warn!("DMA2 Sound FIFO"); + } + 3 => todo!("DMA3 Video capture"), + _ => unreachable!(), + } } } cycles } pub fn handle_dma(&mut self) -> u32 { - if self.io_registers.dma0_enabled { + // TODO: only process Immediate DMA here + // TODO: structure the code so that the tight dispatch loop doesn't need + // to do a lot of work to check which DMA is ready: do the logic in IoRegisters + if self.io_registers.dma0_ready() { self.io_registers.dma0_enabled = false; let dma = self.io_registers.dma0(); let src = self.io_registers.dma0_source_addr(); @@ -1890,13 +2243,13 @@ impl GameboyAdvance { if count == 0 { count = 0x4000; } - let out = self.run_dma(dma, src, dest, count as u32); + let out = self.run_dma(dma, src, dest, count as u32, 0); if dma.irq_at_end && self.dma0_interrupt_enabled() { self.set_dma0_interrupt(true); } self.io_registers.disable_dma0(); out - } else if self.io_registers.dma1_enabled { + } else if self.io_registers.dma1_ready() { self.io_registers.dma1_enabled = false; let dma = self.io_registers.dma1(); let src = self.io_registers.dma1_source_addr(); @@ -1905,13 +2258,13 @@ impl GameboyAdvance { if count == 0 { count = 0x4000; } - let out = self.run_dma(dma, src, dest, count as u32); + let out = self.run_dma(dma, src, dest, count as u32, 1); if dma.irq_at_end && self.dma1_interrupt_enabled() { self.set_dma1_interrupt(true); } self.io_registers.disable_dma1(); out - } else if self.io_registers.dma2_enabled { + } else if self.io_registers.dma2_ready() { self.io_registers.dma2_enabled = false; let dma = self.io_registers.dma2(); let src = self.io_registers.dma2_source_addr(); @@ -1920,13 +2273,13 @@ impl GameboyAdvance { if count == 0 { count = 0x4000; } - let out = self.run_dma(dma, src, dest, count as u32); + let out = self.run_dma(dma, src, dest, count as u32, 2); if dma.irq_at_end && self.dma2_interrupt_enabled() { self.set_dma2_interrupt(true); } self.io_registers.disable_dma2(); out - } else if self.io_registers.dma3_enabled { + } else if self.io_registers.dma3_ready() { self.io_registers.dma3_enabled = false; let dma = self.io_registers.dma3(); let src = self.io_registers.dma3_source_addr(); @@ -1935,7 +2288,7 @@ impl GameboyAdvance { if count == 0 { count = 0x10000; } - let out = self.run_dma(dma, src, dest, count); + let out = self.run_dma(dma, src, dest, count, 3); if dma.irq_at_end && self.dma3_interrupt_enabled() { self.set_dma3_interrupt(true); } @@ -1958,15 +2311,41 @@ impl GameboyAdvance { } pub fn dispatch(&mut self) -> u32 { - if self.io_registers.dma_waiting() { + /* + if self.r.pc >= 0x08000000 && self.r.pc <= 0x09FFFFFF { + println!("{:?}", &self.r); + std::process::exit(0); + if self.debug_counter == 1 { + std::process::exit(0); + } + self.debug_counter += 1; + } + */ + let mut cycles = 0; + let dma_waiting = self.io_registers.dma_waiting(); + let dma_ready = self.io_registers.dma_ready(); + if dma_waiting && dma_ready { return self.handle_dma(); } if self.get_mem16(0x4000202).0 != 0 && self.master_interrupts_enabled() { self.maybe_handle_interrupts(); } if self.r.thumb_enabled() { - return self.dispatch_thumb() as u32; + cycles = self.dispatch_thumb() as u32; + } else { + cycles = self.dispatch_arm() as u32; + } + + // TODO: this probably needs to be tracked per DMA channel + // TODO: do we need to recheck DMA to see if it's been disabled by this last instruction? + if dma_waiting && !dma_ready { + self.io_registers.dma_inc_delay_counter(cycles as u8); } + + cycles + } + + pub fn dispatch_arm(&mut self) -> u8 { let opcode = self.get_opcode(); let opcode_idx = (opcode >> 25) & 0x7; @@ -1983,13 +2362,22 @@ impl GameboyAdvance { trace!("Skipped!"); return 1; } + //if self.r.pc != (0x08001CF0 - 4){ + /* + let mut reg_str = String::new(); + for i in 0..15 { + reg_str += &format!("r{}=0x{:X}, ", i, self.r[i as u8]); + } + println!("pc=0x{:X}: regs={}", self.r.pc + 4, reg_str); + */ + /* + } else { + std::process::exit(0); + } + */ if (opcode >> 8) & 0xF_FFFF == 0b0001_0010_1111_1111_1111 { - let cycles = self.dispatch_branch_and_exchange(opcode); - // don't increment PC when switching execution modes - // TODO: this might be more complicated than this - //self.r.pc += 4; - return cycles as u32; + return self.dispatch_branch_and_exchange(opcode); } let cycles = match opcode_idx { @@ -2047,8 +2435,7 @@ impl GameboyAdvance { unimplemented!("0x{:X} ({:b}) at 0x{:X}", opcode, opcode_idx, self.r.pc); } }; - - cycles as u32 + cycles } pub fn dispatch_thumb(&mut self) -> u8 { @@ -2056,6 +2443,14 @@ impl GameboyAdvance { let opcode_idx = (opcode >> 13) & 0x7; self.r.pc += 2; + /* + let mut reg_str = String::new(); + for i in 0..15 { + reg_str += &format!("r{}=0x{:X}, ", i, self.r[i as u8]); + } + println!("THUMB pc=0x{:X}: regs={}", self.r.pc + 2, reg_str); + */ + if opcode == 0 { return 4; } @@ -2316,18 +2711,21 @@ impl GameboyAdvance { let hi_bit = (opcode >> 7) & 1 == 1; rd |= (hi_bit as u8) << 3; } + let mut op2 = self.r[rs]; + if rs == 15 { + op2 += 2; + } let old_thumb_enabled = self.r.thumb_enabled(); let cycles = match subop { 0b00 => { trace!("ADD r{}, r{}", rd, rs); - self.r[rd] = self.r[rd].wrapping_add(self.r[rs]); + self.r[rd] = self.r[rd].wrapping_add(op2); 1 } 0b01 => { trace!("CMP r{}, r{}", rd, rs); let op1 = self.r[rd]; - let op2 = self.r[rs]; - let result = self.r[rd].wrapping_sub(self.r[rs]); + let result = self.r[rd].wrapping_sub(op2); self.r.cpsr_set_zero_flag(result == 0); self.r.cpsr_set_sign_flag(result & 0x8000_0000 != 0); self.r @@ -2337,12 +2735,12 @@ impl GameboyAdvance { } 0b10 => { trace!("MOV r{}, r{}", rd, rs); - self.r[rd] = self.r[rs]; + self.r[rd] = op2; 1 } 0b11 => { let x_flag = (opcode >> 7) & 1 == 1; - let thumb_mode = self.r[rs] & 1 == 1; + let thumb_mode = op2 & 1 == 1; let (thumb_mode, addr) = if rs == 15 { (false, (self.r[rs] + 2) & !3) } else { @@ -2387,20 +2785,20 @@ impl GameboyAdvance { } pub fn dispatch_thumb_load_store_reg(&mut self, opcode: u16) -> u8 { - let sub_op_idx = (opcode >> 11) & 0x3; - let ro = (opcode >> 6 & 0x7) as u8; - let rb = (opcode >> 3 & 0x7) as u8; + let sub_op_idx = (opcode >> 10) & 0x3; + let ro = ((opcode >> 6) & 0x7) as u8; + let rb = ((opcode >> 3) & 0x7) as u8; let rd = (opcode & 0x7) as u8; match sub_op_idx { 0b00 => { trace!("STR r{}, [r{}, r{}]", rd, rb, ro); - let o = self.set_mem32(self.r[rb] + self.r[ro], self.r[rd]); + let o = self.set_mem32(self.r[rb].wrapping_add(self.r[ro]), self.r[rd]); 1 + o } 0b01 => { trace!("STRB r{}, [r{}, r{}]", rd, rb, ro); - let o = self.set_mem8(self.r[rb] + self.r[ro], self.r[rd] as u8); + let o = self.set_mem8(self.r[rb].wrapping_add(self.r[ro]), self.r[rd] as u8); 1 + o } 0b10 => { @@ -2421,8 +2819,8 @@ impl GameboyAdvance { pub fn dispatch_thumb_load_store_halfword_sign_extend(&mut self, opcode: u16) -> u8 { let sub_opcode = (opcode >> 10) & 0x3; - let ro = (opcode >> 6 & 0x7) as u8; - let rb = (opcode >> 3 & 0x7) as u8; + let ro = ((opcode >> 6) & 0x7) as u8; + let rb = ((opcode >> 3) & 0x7) as u8; let rd = (opcode & 0x7) as u8; match sub_opcode { @@ -2476,8 +2874,8 @@ impl GameboyAdvance { pub fn dispatch_thumb_load_store_imm(&mut self, opcode: u16) -> u8 { let sub_op_idx = (opcode >> 11) & 0x3; - let offset = (opcode >> 6 & 0x1F) as u32; - let rb = (opcode >> 3 & 0x7) as u8; + let offset = ((opcode >> 6) & 0x1F) as u32; + let rb = ((opcode >> 3) & 0x7) as u8; let rd = (opcode & 0x7) as u8; match sub_op_idx { @@ -2563,7 +2961,7 @@ impl GameboyAdvance { pub fn dispatch_thumb_get_relative_address(&mut self, opcode: u16) -> u8 { let subop = (opcode >> 11) & 0x1 == 1; - let rd = (opcode >> 8 & 0x7) as u8; + let rd = ((opcode >> 8) & 0x7) as u8; let nn = (opcode & 0xFF) as u32; if subop { @@ -2598,6 +2996,7 @@ impl GameboyAdvance { } if pc_lr { let o = self.get_mem32(self.r.sp()); + //println!("popping PC ({:X}) to 0x{:X}", self.r.sp(), o.0 & !1); *self.r.sp_mut() += 4; self.r.pc = o.0 & !1; cycles += o.1; @@ -2872,7 +3271,7 @@ impl GameboyAdvance { 1 //} } - 0b11 /*| 0b01*/ => { + 0b11 | 0b01 => { let n = (opcode & 0x7FF) as u32; let old_pc = self.r.pc; let new_pc = self.r.lr().wrapping_add(n << 1); @@ -2883,9 +3282,11 @@ impl GameboyAdvance { 3 } // I think 0b01 is only on ARM9, so let's just route it to the same BL + /* 0b01 => { todo!("Second opcode for THUMB branch long with link"); } + */ _ => unimplemented!("unknown sub-op-idx for THUMB branch: {:b}", sub_op_idx), } } @@ -2984,10 +3385,11 @@ impl GameboyAdvance { let ror_shift = (opcode >> 8) & 0xF; op2 = opcode & 0xFF; let shift_amt = ror_shift * 2; - op2 = op2.rotate_right(shift_amt); + let registers = if s { Some(&mut self.r) } else { None }; + op2 = BarrelShifter::ror(op2, shift_amt, registers); } else { let shift_by_register = (opcode >> 4) & 1 == 1; - let rm = opcode & 0xF; + let rm = (opcode & 0xF) as u8; let shift_amt = if shift_by_register { let shift_reg_idx = (opcode >> 8) & 0xF; // docs say this must be true, TODO: deal with this later @@ -3005,18 +3407,26 @@ impl GameboyAdvance { }; let shift_type = (opcode >> 5) & 0b11; + op2 = self.r[rm]; + if rm == 0xF { + if !imm && (opcode >> 4) & 1 == 1 { + op2 += 12 - 4; + } else { + op2 += 8 - 4; + } + } + if shift_amt == 0 && shift_by_register { - op2 = self.r[rm as u8]; + // nop } else { let carry = self.r.cpsr_carry_flag(); - let val = self.r[rm as u8]; let registers = if s { Some(&mut self.r) } else { None }; match shift_type { - 0 => op2 = BarrelShifter::lsl(val, shift_amt as u32, registers), - 1 => op2 = BarrelShifter::lsr(val, shift_amt as u32, registers), - 2 => op2 = BarrelShifter::asr(val, shift_amt as u32, registers), - 3 if shift_amt == 0 => op2 = BarrelShifter::rrx(val, registers, carry), - 3 => op2 = BarrelShifter::ror(val, shift_amt as u32, registers), + 0 => op2 = BarrelShifter::lsl(op2, shift_amt as u32, registers), + 1 => op2 = BarrelShifter::lsr(op2, shift_amt as u32, registers), + 2 => op2 = BarrelShifter::asr(op2, shift_amt as u32, registers), + 3 if shift_amt == 0 => op2 = BarrelShifter::rrx(op2, registers, carry), + 3 => op2 = BarrelShifter::ror(op2, shift_amt as u32, registers), _ => unreachable!(), } } @@ -3413,7 +3823,7 @@ impl GameboyAdvance { }; let val = if imm { let shift_amt = (opcode >> 8) & 0xF; - (opcode & 0xFF).rotate_right(shift_amt * 2) + BarrelShifter::ror(opcode & 0xFF, shift_amt * 2, Some(&mut self.r)) } else { let rm = ((opcode >> 0) & 0xF) as u8; debug_assert_ne!(rm, 0xF, "MSR from PC"); @@ -3430,8 +3840,9 @@ impl GameboyAdvance { /*&& self.r.register_mode() != Some(RegisterMode::User)*/ { debug_assert_ne!(self.r.register_mode(), Some(RegisterMode::User)); - *self.r.get_spsr_mut() &= !mask; - *self.r.get_spsr_mut() |= val & mask; + let spsr = self.r.get_spsr_mut(); + *spsr &= !mask; + *spsr |= val & mask; } else { self.r.cpsr &= !mask; self.r.cpsr |= val & mask; @@ -3499,7 +3910,7 @@ impl GameboyAdvance { let p = (opcode >> 24) & 1 == 1; let up = (opcode >> 23) & 1 == 1; let imm = (opcode >> 22) & 1 == 1; - let write_back = !p || ((opcode >> 21) & 1 == 1); + let write_back = (opcode >> 21) & 1 == 1; let load = (opcode >> 20) & 1 == 1; let rn = ((opcode >> 16) & 0xF) as u8; let rd = ((opcode >> 12) & 0xF) as u8; @@ -3518,7 +3929,7 @@ impl GameboyAdvance { debug_assert_ne!(rm, 0xF, "Data transfer from PC"); self.r[(opcode & 0xF) as u8] }; - let opcode = (opcode >> 5) & 0x3; + let sub_opcode = (opcode >> 5) & 0x3; if p { if up { @@ -3526,10 +3937,13 @@ impl GameboyAdvance { } else { base_val -= offset; } + if write_back { + self.r[rn] = base_val; + } } let cycles; - match (load, opcode) { + match (load, sub_opcode) { (true, 0b00) => todo!("reserved"), (true, 0b01) => { trace!("LDRH r{} = [r{} + 0x{:X}]", rd, rn, offset); @@ -3581,9 +3995,9 @@ impl GameboyAdvance { } else { base_val = base_val.wrapping_sub(offset); } - } - if write_back { - self.r[rd] = base_val; + if rn != rd { + self.r[rn] = base_val; + } // TODO: do we need to write back twice for double words? probably } cycles @@ -3598,38 +4012,32 @@ impl GameboyAdvance { let byte = (opcode >> 22) & 1 == 1; // only when P is false let force_nonpriviliged = (opcode >> 21) & 1 == 1; - let write_back = !p || ((opcode >> 21) & 1 == 1); + //let write_back = !p || ((opcode >> 21) & 1 == 1); + let write_back = (opcode >> 21) & 1 == 1; let load = (opcode >> 20) & 1 == 1; let base_reg = (opcode >> 16) & 0xF; let src_dest_reg = (opcode >> 12) & 0xF; + + let mut old_register_mode = None; if force_nonpriviliged && !p { - todo!("figure this out"); + old_register_mode = self.r.register_mode(); + self.r.set_mode(RegisterMode::User); } let offset = if imm { let shift_type = (opcode >> 5) & 3; let rm = (opcode & 0xF) as u8; let shift_amount = (opcode >> 7) & 0x1F; - if shift_amount == 0 { - match shift_type { - // LSL - 0b00 => self.r[rm], - // LSR - 0b01 => 0, - // ASR - 0b10 => ((self.r[rm] as i32) >> 31) as u32, - // ROR - 0b11 => todo!("ROR!"), - _ => unreachable!(), - } - } else { - match shift_type { - 0b00 => self.r[rm] << shift_amount, - 0b01 => self.r[rm] >> shift_amount, - 0b10 => ((self.r[rm] as i32) >> shift_amount) as u32, - 0b11 => self.r[rm].rotate_right(shift_amount), - _ => unreachable!(), - } + let offset = self.r[rm]; + let carry = self.r.cpsr_carry_flag(); + let registers = None; + match shift_type { + 0 => BarrelShifter::lsl(offset, shift_amount, registers), + 1 => BarrelShifter::lsr(offset, shift_amount, registers), + 2 => BarrelShifter::asr(offset, shift_amount, registers), + 3 if shift_amount == 0 => BarrelShifter::rrx(offset, registers, carry), + 3 => BarrelShifter::ror(offset, shift_amount, registers), + _ => unreachable!(), } } else { opcode & 0xFFF @@ -3654,7 +4062,14 @@ impl GameboyAdvance { cycles += o.1; self.r[src_dest_reg as u8] = o.0 as u32; } else { - let o = self.get_mem32(val); + let o = if val & 0x3 != 0 { + let shift_amt = (val & 0x3) << 3; + let o = self.get_mem32(val & !0x3); + let v = BarrelShifter::ror(o.0, shift_amt, Some(&mut self.r)); + (v, o.1) + } else { + self.get_mem32(val) + }; trace!( "LDR r{} = mem[r{} (0x{:X})] (0x{:X})", src_dest_reg, @@ -3681,9 +4096,10 @@ impl GameboyAdvance { cycles += self.set_mem8(val, write_val as u8); } else { trace!("STR mem[{:X}] = r{} (0x{:X})", val, src_dest_reg, write_val); - cycles += self.set_mem32(val, write_val); + cycles += self.set_mem32(val & !3, write_val); } } + if !p { if up { val = val.wrapping_add(offset); @@ -3692,15 +4108,13 @@ impl GameboyAdvance { } } - if write_back || !p { - self.r[base_reg as u8] = val; + if let Some(old_mode) = old_register_mode { + self.r.set_mode(old_mode); } - /* - if !p && write_back { - //warn!("Write back bit has special meaning in post-inc mode, figure this out"); - todo!("Write back bit has special meaning in post-inc mode, figure this out"); + + if (!load || base_reg != src_dest_reg) && (!p || write_back) { + self.r[base_reg as u8] = val; } - */ cycles } @@ -3717,9 +4131,12 @@ impl GameboyAdvance { let mut base = self.r[rn]; + let load_psr = force_user_mode && load && (r_list >> 15) & 1 == 1; + let force_user_mode = force_user_mode && load && (r_list >> 15) & 1 != 1; + let mut old_register_mode = None; if force_user_mode { - //todo!("Figure out force user mode"); - warn!("Figure out force user mode"); + old_register_mode = self.r.register_mode(); + self.r.set_mode(RegisterMode::User); } let debug_type = match (load, p, up) { @@ -3735,6 +4152,7 @@ impl GameboyAdvance { let r_list_count = r_list.count_ones(); + let original_base = base; if r_list_count == 0 { todo!("0 is a special case, handle it when it happens"); } else if !up { @@ -3755,7 +4173,6 @@ impl GameboyAdvance { r_list, self.r.register_mode() ); - // TOOD: all accesses should be done lower to higher if load { for i in 0..16 { if r_list & (1 << i) == 0 { @@ -3770,30 +4187,58 @@ impl GameboyAdvance { let o = self.get_mem32(base); //println!("Loading r{} (0x{:X}) from 0x{:X}", i, o.0, base); self.r[i as u8] = o.0; + if i == 15 && load_psr { + let spsr = self.r.get_spsr(); + self.r.cpsr = spsr; + } cycles += o.1 + 2; if !p { base = base.wrapping_add(4); } } } else { + let mut first = true; for i in 0..16 { if r_list & (1 << i) == 0 { continue; } + let val = if i == rn { + if first { + original_base + } else { + if up { + original_base.wrapping_add(4 * r_list_count) + } else { + original_base.wrapping_sub(4 * r_list_count) + } + } + } else { + if i == 15 { + self.r[i as u8] + 8 + } else { + self.r[i as u8] + } + }; + first = false; if p { base = base.wrapping_add(4); } //println!("pushing r{} ({:X}) to 0x{:X}", i, self.r[i as u8], base); - cycles += self.set_mem32(base, self.r[i as u8]); + cycles += self.set_mem32(base, val); cycles += 1; if !p { base = base.wrapping_add(4); } } } + if let Some(old_mode) = old_register_mode { + self.r.set_mode(old_mode); + } + if write_back { self.r[rn] = base; } + cycles } diff --git a/src/io/applicationstate.rs b/src/io/applicationstate.rs index ec4fbc8..78e4511 100644 --- a/src/io/applicationstate.rs +++ b/src/io/applicationstate.rs @@ -42,7 +42,7 @@ impl ApplicationState { pub fn new(renderer: Box) -> Result { // Set up gameboy and other state let gameboy = cpu::Cpu::new(); - let gba = crate::gba::GameboyAdvance::new(); + let gba = crate::gba::GameboyAdvance::new(false); Ok(ApplicationState { gameboy, @@ -73,6 +73,7 @@ impl ApplicationState { pub fn step_gba(&mut self) { let cycles_per_frame = 83776 + (160 * /*1232*/ 960); + let audio_timing_cycles = cycles_per_frame / 512; let mut cycles = 0; let mut frame = [[(0u8, 0u8, 0u8); GBA_SCREEN_WIDTH]; GBA_SCREEN_HEIGHT]; let mut y = 0; @@ -90,24 +91,26 @@ impl ApplicationState { } let mut in_hblank = false; let mut hblank_cycles = 0; + // we need this? + self.gba.ppu_set_vblank(false); while y < 227 { let cycles_from_opcode = self.gba.dispatch() as u64; - cycles += cycles_from_opcode; - hblank_cycles += cycles_from_opcode; + cycles += cycles_from_opcode * 3; + hblank_cycles += cycles_from_opcode * 3; for (i, timer) in self.gba_timers.iter_mut().enumerate() { if !self.gba.io_registers.timer_enabled(i as u8) { continue; } - *timer += cycles_from_opcode as u16; + *timer = timer.saturating_add(cycles_from_opcode as u16); if *timer >= timer_prescalers[i] { *timer -= timer_prescalers[i]; if self.gba.io_registers.increment_timer(i as u8) && self.gba.io_registers.timer_irq_enabled(i as u8) && self.gba.master_interrupts_enabled() { - dbg!("TIMER INTERRUPT!"); + //dbg!("TIMER INTERRUPT!"); self.gba.set_timer_interrupt(i as u8, true); } } @@ -118,8 +121,9 @@ impl ApplicationState { in_hblank = false; hblank_cycles -= 272; self.gba.ppu_set_hblank(false); + y += 1; + self.gba.ppu_set_readonly_vcounter(y); } - continue; } if hblank_cycles >= 960 { @@ -127,15 +131,21 @@ impl ApplicationState { if y < 160 { let scanline = deferred_renderer_draw_gba_scanline(y, &mut self.gba); frame[y as usize] = scanline; + self.gba.io_registers.bg2_rotation.cached_x += + self.gba.io_registers.bg2_rotation.pb as i32; + self.gba.io_registers.bg2_rotation.cached_y += + self.gba.io_registers.bg2_rotation.pd as i32; + self.gba.io_registers.bg3_rotation.cached_x += + self.gba.io_registers.bg3_rotation.pb as i32; + self.gba.io_registers.bg3_rotation.cached_y += + self.gba.io_registers.bg3_rotation.pd as i32; } - y += 1; in_hblank = true; self.gba.ppu_set_hblank(true); if self.gba.ppu_hblank_irq_enabled() { self.gba.set_lcdc_hblank_interrupt(true); } - self.gba.ppu_set_readonly_vcounter(y); if y == self.gba.ppu_vcounter_setting() && self.gba.ppu_vcounter_irq_enabled() { self.gba.set_lcdc_vcounter_interrupt(true); } @@ -144,7 +154,21 @@ impl ApplicationState { if self.gba.ppu_vblank_irq_enabled() { self.gba.set_lcdc_vblank_interrupt(true); } + self.gba.io_registers.bg2_rotation.cached_x = + self.gba.io_registers.bg2_rotation.x; + self.gba.io_registers.bg2_rotation.cached_y = + self.gba.io_registers.bg2_rotation.y; + self.gba.io_registers.bg3_rotation.cached_x = + self.gba.io_registers.bg3_rotation.x; + self.gba.io_registers.bg3_rotation.cached_y = + self.gba.io_registers.bg3_rotation.y; } + //y += 1; + } + self.sound_cycles += cycles_from_opcode as u64; + if self.sound_cycles >= audio_timing_cycles as u64 { + self.renderer.audio_step(&self.gba.io_registers.apu); + self.sound_cycles -= audio_timing_cycles as u64; } } self.gba.ppu_set_vblank(false); @@ -153,19 +177,19 @@ impl ApplicationState { self.debug_gba_last_seen_ppu_bg_mode = Some(self.gba.ppu_bg_mode()); } /* - if - self.gba.ppu_bg_mode() != 0 || - self.gba.ppu_bg0_enabled() || - self.gba.ppu_bg1_enabled() || - self.gba.ppu_bg2_enabled() || - self.gba.ppu_bg3_enabled() || - self.gba.ppu_obj_enabled() || - self.gba.ppu_win0_enabled() || - self.gba.ppu_win1_enabled() || - self.gba.ppu_obj_win_enabled() { - dbg!( - self.gba.ppu_bg_mode(), - self.gba.ppu_bg0_enabled(), + if self.gba.ppu_bg_mode() != 0 + || self.gba.ppu_bg0_enabled() + || self.gba.ppu_bg1_enabled() + || self.gba.ppu_bg2_enabled() + || self.gba.ppu_bg3_enabled() + || self.gba.ppu_obj_enabled() + || self.gba.ppu_win0_enabled() + || self.gba.ppu_win1_enabled() + || self.gba.ppu_obj_win_enabled() + { + dbg!( + self.gba.ppu_bg_mode(), + self.gba.ppu_bg0_enabled(), self.gba.ppu_bg1_enabled(), self.gba.ppu_bg2_enabled(), self.gba.ppu_bg3_enabled(), @@ -366,7 +390,7 @@ impl ApplicationState { // and APU state (i.e. not here, somewhere CPU accessible) // HACK: we just update it randomly //self.update_channel_vars(); - self.renderer.audio_step(&self.gameboy); + self.renderer.audio_step(&self.gameboy.apu); self.sound_cycles -= audio_timing_cycles as u64; } diff --git a/src/io/deferred_renderer_gba.rs b/src/io/deferred_renderer_gba.rs index 01b7e66..bb2f367 100644 --- a/src/io/deferred_renderer_gba.rs +++ b/src/io/deferred_renderer_gba.rs @@ -1,6 +1,74 @@ -use crate::gba; +use crate::gba::{self, PpuBgControl}; use crate::io::constants::*; +pub fn deferred_renderer_draw_rotated_bg( + y: u8, + gba: &mut gba::GameboyAdvance, + bg_control: PpuBgControl, + pa: i16, + pc: i16, + bg_x: i32, + bg_y: i32, +) -> [(u8, u8, u8); GBA_SCREEN_WIDTH] { + let mut bg_pixels = [(0u8, 0u8, 0u8); GBA_SCREEN_WIDTH]; + + // TODO: for bg3 this should be + 0x600 + let map_base_ptr = bg_control.screen_base_block as u32 * 0x800; //+ 0x400; + let tile_base_ptr = bg_control.character_base_block as u32 * 0x4000; + for x in 0..GBA_SCREEN_WIDTH { + let adj_y = (bg_y + (x as i32) * pc as i32) >> 8; + let adj_x = (bg_x + (x as i32) * pa as i32) >> 8; + + if adj_x < 0 + || adj_x >= GBA_SCREEN_WIDTH as i32 + || adj_y < 0 + || adj_y >= GBA_SCREEN_HEIGHT as i32 + { + continue; + } + + let tile_col = (adj_x >> 3) as u32; + let tile_row = (adj_y >> 3) as u32; + let idx_into_tile_idx_mem = map_base_ptr + (tile_row * 32 * 2) + (tile_col * 2); + let tile_idx_lo = gba.vram[idx_into_tile_idx_mem as usize] as u16; + let tile_idx_hi = gba.vram[idx_into_tile_idx_mem as usize + 1] as u16; + let tile_num = ((tile_idx_hi & 0x3) << 8) | tile_idx_lo; + let horizontal_flip = (tile_idx_hi & 0x4) != 0; + let vertical_flip = (tile_idx_hi & 0x8) != 0; + //let palette_num = tile_idx_hi >> 4; + + // Lower 3 bits determine which line of the tile we're on + let mut nth_line = adj_y & 0x7; + // 8 choices for which pixel on the line we're on, so we take 3 bits here + let tile_pixel = adj_x & 0x7; + // pixels go from MSB to LSB within a tile + let mut nth_pixel = 7 - tile_pixel; + if vertical_flip { + nth_line = 7 - nth_line; + } + if horizontal_flip { + nth_pixel = 7 - nth_pixel; + } + + let tile_line = nth_line * 8; + + let tile_start = tile_base_ptr as usize + (tile_num as usize * 8 * 8); + let tile_line_start = tile_start + tile_line as usize; + let tile_byte_start = tile_line_start + (nth_pixel >> 1) as usize; + let color_8bit = gba.vram[tile_byte_start]; + + let color_lo = gba.obj_palette_ram[color_8bit as usize * 2]; + let color_hi = gba.obj_palette_ram[(color_8bit as usize * 2) + 1]; + let red = color_lo & 0x1F; + let green = ((color_hi & 0x3) << 3) | (color_lo >> 5); + let blue = (color_hi >> 2) & 0x1F; + + bg_pixels[x as usize] = (red << 3, green << 3, blue << 3); + } + + bg_pixels +} + pub fn deferred_renderer_draw_gba_bg4( y: u8, gba: &mut gba::GameboyAdvance, @@ -9,13 +77,45 @@ pub fn deferred_renderer_draw_gba_bg4( let bg2_control = gba.ppu_bg2_control(); let base = if gba.ppu_frame_select() { 0xA000 } else { 0 }; + /* + for i in 0x4C..=(0x4C + 4) { + if gba.io_registers[i] != 0 { + panic!("mode 4 is using mosaic! at {} 0x{:X}", i, gba.io_registers[i]) + } + } + */ + /* + + for i in 0x14000..=0x17FFF { + if gba.vram[i] != 0 { + panic!("mode 4 is using objects! at {} 0x{:X}", i, gba.vram[i]) + } + } + */ + let pa = gba.io_registers.bg2_rotation.pa; + let pc = gba.io_registers.bg2_rotation.pc; + let bg_x = gba.io_registers.bg2_rotation.cached_x; + let bg_y = gba.io_registers.bg2_rotation.cached_y; - let adj_y = y as usize; for x in 0..GBA_SCREEN_WIDTH { - let idx = (adj_y * 240 + x) as usize; - let palette_idx = gba.vram[base + idx]; - let color_lo = gba.obj_palette_ram[palette_idx as usize]; - let color_hi = gba.obj_palette_ram[palette_idx as usize + 1]; + let adj_y = (bg_y + (x as i32) * pc as i32) >> 8; + let adj_x = (bg_x + (x as i32) * pa as i32) >> 8; + + if adj_x < 0 + || adj_x >= GBA_SCREEN_WIDTH as i32 + || adj_y < 0 + || adj_y >= GBA_SCREEN_HEIGHT as i32 + { + continue; + } + + let idx = (adj_y * 240 + adj_x) as usize; + let palette_idx = gba.vram[base + idx] as usize; + if palette_idx == 0 { + continue; + } + let color_lo = gba.obj_palette_ram[palette_idx * 2]; + let color_hi = gba.obj_palette_ram[palette_idx * 2 + 1]; let red = color_lo & 0x1F; let green = ((color_hi & 0x3) << 3) | (color_lo >> 5); @@ -32,7 +132,25 @@ pub fn deferred_renderer_draw_gba_scanline( gba: &mut gba::GameboyAdvance, ) -> [(u8, u8, u8); GBA_SCREEN_WIDTH] { match gba.ppu_bg_mode() { + 2 => { + let pa = gba.io_registers.bg2_rotation.pa; + let pc = gba.io_registers.bg2_rotation.pc; + let bg_x = gba.io_registers.bg2_rotation.cached_x; + let bg_y = gba.io_registers.bg2_rotation.cached_y; + // TODO: also blend with bg 3 + return deferred_renderer_draw_rotated_bg( + y, + gba, + gba.ppu_bg2_control(), + pa, + pc, + bg_x, + bg_y, + ); + } + 3 => todo!("bg mode 3"), 4 => return deferred_renderer_draw_gba_bg4(y, gba), + 5 => todo!("bg mode 5"), _ => (), } let mut bg_pixels = [(0u8, 0u8, 0u8); GBA_SCREEN_WIDTH]; diff --git a/src/io/dr_sdl2.rs b/src/io/dr_sdl2.rs index e7a856b..5e0730c 100644 --- a/src/io/dr_sdl2.rs +++ b/src/io/dr_sdl2.rs @@ -4,7 +4,7 @@ use sdl2::rect::{Point, Rect}; use sdl2::surface::Surface; use sdl2::*; -use crate::cpu::Cpu; +use crate::cpu::apu::Apu; use crate::io::applicationsettings::ApplicationSettings; use crate::io::constants::*; use crate::io::graphics::renderer; @@ -462,7 +462,7 @@ impl Renderer for Sdl2Renderer { return ret_vec; } - fn audio_step(&mut self, gb: &Cpu) { + fn audio_step(&mut self, gb_apu: &Apu) { // TODO: /* if gb.get_sound_all() && (gb.get_sound1() || gb.get_sound2() || gb.get_sound3() || gb.get_sound4()) { @@ -476,46 +476,46 @@ impl Renderer for Sdl2Renderer { self.sound_system.resume(); let mut sound_system = self.sound_system.lock(); // TODO move this to channel.update() or something - sound_system.channel1.enabled = gb.apu.get_sound1(); - sound_system.channel2.enabled = gb.apu.get_sound2(); - sound_system.channel3.enabled = gb.apu.get_sound3(); - sound_system.channel4.enabled = gb.apu.get_sound4(); + sound_system.channel1.enabled = gb_apu.get_sound1(); + sound_system.channel2.enabled = gb_apu.get_sound2(); + sound_system.channel3.enabled = gb_apu.get_sound3(); + sound_system.channel4.enabled = gb_apu.get_sound4(); //if gb.apu.get_sound1() { - sound_system.channel1.volume = gb.apu.channel1_envelope_volume as f32 / 15.0; - sound_system.channel1.wave_duty = gb.apu.channel1_wave_pattern_duty(); - let channel1_freq = 4194304.0 / (4.0 * 8.0 * (2048.0 - gb.apu.channel1_frequency() as f32)); + sound_system.channel1.volume = gb_apu.channel1_envelope_volume as f32 / 15.0; + sound_system.channel1.wave_duty = gb_apu.channel1_wave_pattern_duty(); + let channel1_freq = 4194304.0 / (4.0 * 8.0 * (2048.0 - gb_apu.channel1_frequency() as f32)); sound_system.channel1.phase_inc = channel1_freq / sound_system.out_freq; //} //if gb.apu.get_sound2() { - sound_system.channel2.wave_duty = gb.apu.channel2_wave_pattern_duty(); - sound_system.channel2.volume = gb.apu.channel2_envelope_volume as f32 / 15.0; - let channel2_freq = 4194304.0 / (4.0 * 8.0 * (2048.0 - gb.apu.channel2_frequency() as f32)); + sound_system.channel2.wave_duty = gb_apu.channel2_wave_pattern_duty(); + sound_system.channel2.volume = gb_apu.channel2_envelope_volume as f32 / 15.0; + let channel2_freq = 4194304.0 / (4.0 * 8.0 * (2048.0 - gb_apu.channel2_frequency() as f32)); sound_system.channel2.phase_inc = channel2_freq / sound_system.out_freq; //} //if gb.apu.get_sound3() { - let channel3_freq = 2097152.0 / (2048.0 - gb.apu.channel3_frequency() as f32); - sound_system.channel3.volume = gb.apu.channel3_output_level() as f32; - sound_system.channel3.shift_amount = gb.apu.channel3_shift_amount(); + let channel3_freq = 2097152.0 / (2048.0 - gb_apu.channel3_frequency() as f32); + sound_system.channel3.volume = gb_apu.channel3_output_level() as f32; + sound_system.channel3.shift_amount = gb_apu.channel3_shift_amount(); sound_system.channel3.phase_inc = channel3_freq / sound_system.out_freq; - sound_system.channel3.wave_ram = gb.apu.channel3_wave_pattern_ram(); + sound_system.channel3.wave_ram = gb_apu.channel3_wave_pattern_ram(); //} else { - if !gb.apu.get_sound3() { + if !gb_apu.get_sound3() { // HACK: retrigger logic sound_system.channel3.wave_ram_index = 0; } //if gb.apu.get_sound4() { - sound_system.channel4.volume = gb.apu.channel4_envelope_volume as f32 / 15.0; - let clock_div = gb.apu.channel4_clock_divider(); - let clock_shift = gb.apu.channel4_clock_shift(); + sound_system.channel4.volume = gb_apu.channel4_envelope_volume as f32 / 15.0; + let clock_div = gb_apu.channel4_clock_divider(); + let clock_shift = gb_apu.channel4_clock_shift(); //let channel4_freq = 262144. / (clock_div * (2 << clock_shift) as f32); let channel4_freq = 262144. / (clock_div * (2_u32.pow(clock_shift as _)) as f32); sound_system.channel4.phase_inc = channel4_freq / sound_system.out_freq; - sound_system.channel4.lfsr_width = gb.apu.channel4_lfsr_width(); + sound_system.channel4.lfsr_width = gb_apu.channel4_lfsr_width(); //} else { - if !gb.apu.get_sound4() { + if !gb_apu.get_sound4() { // HACK: this is because we can't do it on trigger until we refactor APU sound_system.channel4.lfsr = 0x7FFF; } diff --git a/src/io/graphics/renderer.rs b/src/io/graphics/renderer.rs index a566773..b0ff3eb 100644 --- a/src/io/graphics/renderer.rs +++ b/src/io/graphics/renderer.rs @@ -1,3 +1,4 @@ +use crate::cpu::apu::Apu; use crate::cpu::Cpu; use crate::io::constants::{ GBA_SCREEN_HEIGHT, GBA_SCREEN_WIDTH, GB_SCREEN_HEIGHT, GB_SCREEN_WIDTH, @@ -42,7 +43,8 @@ pub trait Renderer { } fn handle_events(&mut self, _: &mut dyn InputReceiver) -> Vec; - fn audio_step(&mut self, _gb: &Cpu) { + #[allow(unused_variables)] + fn audio_step(&mut self, apu: &Apu) { unimplemented!(); } } From 124bb7156f8cb49c21532f95a8e26afef8b3a3b6 Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Sun, 13 Aug 2023 11:48:44 +0800 Subject: [PATCH 6/6] Add a lot of GBA progress --- src/cpu/apu.rs | 12 + src/cpu/fifo.rs | 60 ++ src/cpu/mod.rs | 1 + src/gba/mod.rs | 1248 +++++++++++++++++++++++++------ src/io/applicationstate.rs | 189 ++++- src/io/deferred_renderer_gba.rs | 954 ++++++++++++++++++++--- src/io/dr_sdl2.rs | 65 ++ src/io/graphics/renderer.rs | 9 + src/io/sound.rs | 129 ++++ src/main.rs | 10 + 10 files changed, 2318 insertions(+), 359 deletions(-) create mode 100644 src/cpu/fifo.rs diff --git a/src/cpu/apu.rs b/src/cpu/apu.rs index f258d3f..09c2960 100644 --- a/src/cpu/apu.rs +++ b/src/cpu/apu.rs @@ -23,6 +23,12 @@ pub struct Apu { pub div_apu: u8, /// 0xFF10..=0xFF3F pub apu_mem: [u8; 0x30], + pub gba_fifo_a: super::fifo::Fifo, + pub gba_fifo_b: super::fifo::Fifo, + pub gba_fifo_a_sample_rate: f32, + pub gba_fifo_b_sample_rate: f32, + pub gba_sound_a_enabled: (bool, bool), + pub gba_sound_b_enabled: (bool, bool), } impl Apu { @@ -44,6 +50,12 @@ impl Apu { // We default to 7 as the next tick wraps us back to 0 div_apu: 7, apu_mem: [0; 0x30], + gba_fifo_a: super::fifo::Fifo::new(), + gba_fifo_b: super::fifo::Fifo::new(), + gba_fifo_a_sample_rate: 0., + gba_fifo_b_sample_rate: 0., + gba_sound_a_enabled: (false, false), + gba_sound_b_enabled: (false, false), } } diff --git a/src/cpu/fifo.rs b/src/cpu/fifo.rs new file mode 100644 index 0000000..bcde682 --- /dev/null +++ b/src/cpu/fifo.rs @@ -0,0 +1,60 @@ +//! GBA speciifc FIFO + +use std::collections::VecDeque; + +#[derive(Debug, Clone)] +pub struct Fifo { + data: VecDeque, + sound_buffer: Vec, + pub reset_flag: bool, +} + +impl Fifo { + pub fn new() -> Self { + Self { + data: VecDeque::with_capacity(32), + sound_buffer: vec![], + reset_flag: false, + } + } + + pub fn push(&mut self, data: i8) { + if self.data.len() >= 32 { + self.data.pop_back(); + } + self.data.push_front(data) + } + + /* + /// Buffer uncircularized, copied into a linear array + pub fn get_buffer(&self) -> [i8; 32] { + let mut out = [0; 32]; + let n_elements = 32 - self.idx; + (&mut out[..n_elements]).copy_from_slice(&self.data[self.idx..]); + (&mut out[n_elements..]).copy_from_slice(&self.data[..self.idx]); + out + } + */ + + pub fn reset(&mut self) { + self.reset_flag = true; + self.data.clear(); + self.sound_buffer.clear(); + } + + pub fn get_current_data(&self) -> &[i8] { + &self.sound_buffer + } + pub fn inc_note(&mut self) { + if let Some(d) = self.data.pop_back() { + self.sound_buffer.push(d); + } + } + pub fn ready_for_more_data(&self) -> bool { + self.data.len() <= 16 + //self.sound_buffer.len() <= 16 + } + pub fn clear_sound_buffer(&mut self) { + self.sound_buffer.clear(); + } +} diff --git a/src/cpu/mod.rs b/src/cpu/mod.rs index 371ef8a..b0917d1 100644 --- a/src/cpu/mod.rs +++ b/src/cpu/mod.rs @@ -7,6 +7,7 @@ mod macros; pub mod apu; pub mod cartridge; pub mod constants; +pub mod fifo; pub mod memory; pub mod memvis; mod tests; diff --git a/src/gba/mod.rs b/src/gba/mod.rs index bab0b83..9f03a94 100644 --- a/src/gba/mod.rs +++ b/src/gba/mod.rs @@ -11,7 +11,7 @@ pub struct GameboyAdvance { pub obj_palette_ram: [u8; 0x400], pub vram: [u8; 0x18000], pub oam: [u8; 0x400], - sram: [u8; 0x10000], + sram: [u8; 0x80000], // used for "break points" for counting loops, etc while I debug the basics debug_counter: usize, } @@ -50,7 +50,7 @@ impl PpuBgControl { character_base_block: ((bits >> 2) & 0b11) as u8, mosaic: (bits & 0x40) != 0, color_mode: (bits & 0x80) != 0, - screen_base_block: ((bits >> 8) & 0b11111) as u8, + screen_base_block: ((bits >> 8) & 0b1_1111) as u8, display_area_overflow: (bits & 0x2000) != 0, screen_size: ((bits >> 14) & 0b11) as u8, } @@ -122,7 +122,7 @@ impl BarrelShifter { } } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum DmaAddrControl { Increment, Decrement, @@ -142,7 +142,7 @@ impl DmaAddrControl { } } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum DmaStartTiming { Immediately, VBlank, @@ -211,13 +211,21 @@ const CGB_APU_BASE: u16 = 0xFF10; pub struct IoRegisters { io_registers: [u8; 0x400], dma0_enabled: bool, - dma1_enabled: bool, - dma2_enabled: bool, - dma3_enabled: bool, + pub dma1_enabled: bool, + pub dma2_enabled: bool, + pub dma3_enabled: bool, + pub dma_triggered: [bool; 4], + pub dma_just_enabled: [bool; 4], dma0_delay_counter: u8, dma1_delay_counter: u8, dma2_delay_counter: u8, dma3_delay_counter: u8, + /// cached values + dma_source: [u32; 4], + /// cached values + dma_dest: [u32; 4], + /// cached values + dma_count: [u16; 4], timer0: u16, timer1: u16, timer2: u16, @@ -225,15 +233,21 @@ pub struct IoRegisters { pub bg2_rotation: BitMapBgRotationScale, pub bg3_rotation: BitMapBgRotationScale, pub apu: crate::cpu::apu::Apu, + pub sound_a_timer1: bool, + pub sound_b_timer1: bool, + pub sram_wait: u8, + pub wait_state0: (u8, u8), + pub wait_state1: (u8, u8), + pub wait_state2: (u8, u8), } impl IoRegisters { pub fn new(direct_boot: bool) -> Self { let mut io_registers = [0; 0x400]; - if direct_boot { - io_registers[0x130] = 0xFF; - io_registers[0x131] = 0x3; - } + //if direct_boot { + io_registers[0x130] = 0xFF; + io_registers[0x131] = 0x3; + //} io_registers[0x88] = 0; io_registers[0x89] = 0x2; IoRegisters { @@ -242,10 +256,16 @@ impl IoRegisters { dma1_enabled: false, dma2_enabled: false, dma3_enabled: false, + dma_triggered: [false; 4], + dma_just_enabled: [false; 4], dma0_delay_counter: 0, dma1_delay_counter: 0, dma2_delay_counter: 0, dma3_delay_counter: 0, + // TODO: we must also cache the length + dma_source: [0; 4], + dma_dest: [0; 4], + dma_count: [0; 4], timer0: 0, timer1: 0, timer2: 0, @@ -261,39 +281,79 @@ impl IoRegisters { ..BitMapBgRotationScale::default() }, apu: crate::cpu::apu::Apu::new(), + sound_a_timer1: false, + sound_b_timer1: false, + sram_wait: 4, + wait_state0: (4, 2), + wait_state1: (4, 4), + wait_state2: (4, 8), } } pub const fn dma_waiting(&self) -> bool { - self.dma0_enabled || self.dma1_enabled || self.dma2_enabled || self.dma3_enabled + (self.dma0_enabled && self.dma_triggered[0]) + || (self.dma1_enabled && self.dma_triggered[1]) + || (self.dma2_enabled && self.dma_triggered[2]) + || (self.dma3_enabled && self.dma_triggered[3]) + /* + (self.dma0_enabled || self.dma1_enabled || self.dma2_enabled || self.dma3_enabled) + && + ((self.dma_triggered[0] || self.dma_triggered[1] || self.dma_triggered[2] || self.dma_triggered[3]) + || (self.dma_just_enabled[0] || self.dma_just_enabled[1] || self.dma_just_enabled[2] || self.dma_just_enabled[3]) + ) + */ } pub const fn dma0_ready(&self) -> bool { - self.dma0_enabled && self.dma0_delay_counter > 2 + self.dma0_enabled && self.dma_just_enabled[0] && self.dma0_delay_counter > 4 } pub const fn dma1_ready(&self) -> bool { - self.dma1_enabled && self.dma1_delay_counter > 2 + self.dma1_enabled && self.dma_just_enabled[1] && self.dma1_delay_counter > 4 } pub const fn dma2_ready(&self) -> bool { - self.dma2_enabled && self.dma2_delay_counter > 2 + self.dma2_enabled && self.dma_just_enabled[2] && self.dma2_delay_counter > 4 } pub const fn dma3_ready(&self) -> bool { - self.dma3_enabled && self.dma3_delay_counter > 2 + self.dma3_enabled && self.dma_just_enabled[3] && self.dma3_delay_counter > 4 } pub const fn dma_ready(&self) -> bool { self.dma0_ready() || self.dma1_ready() || self.dma2_ready() || self.dma3_ready() } - pub fn dma_inc_delay_counter(&mut self, cycles: u8) { - if self.dma0_enabled { - self.dma0_delay_counter += cycles; - } - if self.dma1_enabled { - self.dma1_delay_counter += cycles; - } - if self.dma2_enabled { - self.dma2_delay_counter += cycles; - } - if self.dma3_enabled { - self.dma3_delay_counter += cycles; + pub fn load_internal_dma_values(&mut self, dma: u8) { + self.dma_source[dma as usize] = match dma { + 0 => self.dma0_source_addr_raw(), + 1 => self.dma1_source_addr_raw(), + 2 => self.dma2_source_addr_raw(), + 3 => self.dma3_source_addr_raw(), + _ => unreachable!(), + }; + self.dma_dest[dma as usize] = match dma { + 0 => self.dma0_dest_addr_raw(), + 1 => self.dma1_dest_addr_raw(), + 2 => self.dma2_dest_addr_raw(), + 3 => self.dma3_dest_addr_raw(), + _ => unreachable!(), + }; + self.dma_count[dma as usize] = match dma { + 0 => self.dma0_word_count_raw(), + 1 => self.dma1_word_count_raw(), + 2 => self.dma2_word_count_raw(), + 3 => self.dma3_word_count_raw(), + _ => unreachable!(), + }; + } + pub fn trigger_dma(&mut self, dma: u8) { + self.dma_triggered[dma as usize] = true; + } + pub fn dma_inc_delay_counter(&mut self, dma_id: u8, cycles: u8) { + // TODO: maybe also inc only if dma is enabled + if self.dma_just_enabled[dma_id as usize] { + match dma_id { + 0 => self.dma0_delay_counter += cycles, + 1 => self.dma1_delay_counter += cycles, + 2 => self.dma2_delay_counter += cycles, + 3 => self.dma3_delay_counter += cycles, + _ => unreachable!(), + } } } @@ -317,64 +377,165 @@ impl IoRegisters { self.io_registers[0xDE] as u16 | ((self.io_registers[0xDF] as u16) << 8), ) } + pub fn dma1_trigger(&mut self) { + self.dma_triggered[1] = true; + //self.dma1_delay_counter = 4; + } + pub fn dma2_trigger(&mut self) { + self.dma_triggered[2] = true; + /* + self.dma2_enabled = true; + self.dma2_delay_counter = 4; + */ + } + pub fn trigger_sound_a_dma(&mut self) { + let dma1 = self.dma1(); + let dma2 = self.dma2(); + let dma1_dest = self.dma1_dest_addr(); + let dma2_dest = self.dma2_dest_addr(); + if dma1_dest == 0x40000A0 + && dma1.start_timing == DmaStartTiming::Special + && self.dma1_enabled + { + self.dma1_trigger(); + } + if dma2_dest == 0x40000A0 + && dma2.start_timing == DmaStartTiming::Special + && self.dma2_enabled + { + self.dma2_trigger(); + } + } + pub fn trigger_sound_b_dma(&mut self) { + let dma1 = self.dma1(); + let dma2 = self.dma2(); + let dma1_dest = self.dma1_dest_addr(); + let dma2_dest = self.dma2_dest_addr(); + if dma1_dest == 0x40000A4 + && dma1.start_timing == DmaStartTiming::Special + && self.dma1_enabled + { + self.dma1_trigger(); + } + if dma2_dest == 0x40000A4 + && dma2.start_timing == DmaStartTiming::Special + && self.dma2_enabled + { + self.dma2_trigger(); + } + } pub fn dma0_source_addr(&self) -> u32 { + self.dma_source[0] + } + pub fn dma1_source_addr(&self) -> u32 { + self.dma_source[1] + } + pub fn dma2_source_addr(&self) -> u32 { + self.dma_source[2] + } + pub fn dma3_source_addr(&self) -> u32 { + self.dma_source[3] + } + pub fn dma0_dest_addr(&self) -> u32 { + self.dma_dest[0] + } + pub fn dma1_dest_addr(&self) -> u32 { + self.dma_dest[1] + } + pub fn dma2_dest_addr(&self) -> u32 { + self.dma_dest[2] + } + pub fn dma3_dest_addr(&self) -> u32 { + self.dma_dest[3] + } + const fn dma0_source_addr_raw(&self) -> u32 { self.io_registers[0xB0] as u32 | ((self.io_registers[0xB1] as u32) << 8) | ((self.io_registers[0xB2] as u32) << 16) | ((self.io_registers[0xB3] as u32) << 24) } - pub fn dma0_dest_addr(&self) -> u32 { + const fn dma0_dest_addr_raw(&self) -> u32 { self.io_registers[0xB4] as u32 | ((self.io_registers[0xB5] as u32) << 8) | ((self.io_registers[0xB6] as u32) << 16) | ((self.io_registers[0xB7] as u32) << 24) } + pub fn reload_dma_word_count(&mut self, dma_id: u8) { + self.dma_count[dma_id as usize] = match dma_id { + 0 => self.dma0_word_count_raw(), + 1 => self.dma1_word_count_raw(), + 2 => self.dma2_word_count_raw(), + 3 => self.dma3_word_count_raw(), + _ => unreachable!(), + }; + } + pub fn reload_dma_dest(&mut self, dma_id: u8) { + self.dma_dest[dma_id as usize] = match dma_id { + 0 => self.dma0_dest_addr_raw(), + 1 => self.dma1_dest_addr_raw(), + 2 => self.dma2_dest_addr_raw(), + 3 => self.dma3_dest_addr_raw(), + _ => unreachable!(), + }; + } pub fn dma0_word_count(&self) -> u16 { - self.io_registers[0xB8] as u16 | ((self.io_registers[0xB9] as u16) << 8) + self.dma_count[0] } - pub fn dma1_source_addr(&self) -> u32 { + fn dma0_word_count_raw(&self) -> u16 { + self.io_registers[0xB8] as u16 | (((self.io_registers[0xB9] & 0x3F) as u16) << 8) + } + const fn dma1_source_addr_raw(&self) -> u32 { self.io_registers[0xBC] as u32 | ((self.io_registers[0xBD] as u32) << 8) | ((self.io_registers[0xBE] as u32) << 16) | ((self.io_registers[0xBF] as u32) << 24) } - pub fn dma1_dest_addr(&self) -> u32 { - self.io_registers[0xC2] as u32 - | ((self.io_registers[0xC3] as u32) << 8) - | ((self.io_registers[0xC4] as u32) << 16) - | ((self.io_registers[0xC5] as u32) << 24) + const fn dma1_dest_addr_raw(&self) -> u32 { + self.io_registers[0xC0] as u32 + | ((self.io_registers[0xC1] as u32) << 8) + | ((self.io_registers[0xC2] as u32) << 16) + | ((self.io_registers[0xC3] as u32) << 24) } pub fn dma1_word_count(&self) -> u16 { - self.io_registers[0xC4] as u16 | ((self.io_registers[0xC5] as u16) << 8) + self.dma_count[1] } - pub fn dma2_source_addr(&self) -> u32 { + fn dma1_word_count_raw(&self) -> u16 { + self.io_registers[0xC4] as u16 | (((self.io_registers[0xC5] & 0x3F) as u16) << 8) + } + const fn dma2_source_addr_raw(&self) -> u32 { self.io_registers[0xC8] as u32 | ((self.io_registers[0xC9] as u32) << 8) | ((self.io_registers[0xCA] as u32) << 16) | ((self.io_registers[0xCB] as u32) << 24) } - pub fn dma2_dest_addr(&self) -> u32 { + const fn dma2_dest_addr_raw(&self) -> u32 { self.io_registers[0xCC] as u32 | ((self.io_registers[0xCD] as u32) << 8) | ((self.io_registers[0xCE] as u32) << 16) | ((self.io_registers[0xCF] as u32) << 24) } pub fn dma2_word_count(&self) -> u16 { - self.io_registers[0xD0] as u16 | ((self.io_registers[0xD1] as u16) << 8) + self.dma_count[2] } - pub fn dma3_source_addr(&self) -> u32 { + fn dma2_word_count_raw(&self) -> u16 { + self.io_registers[0xD0] as u16 | (((self.io_registers[0xD1] & 0x3F) as u16) << 8) + } + const fn dma3_source_addr_raw(&self) -> u32 { self.io_registers[0xD4] as u32 | ((self.io_registers[0xD5] as u32) << 8) | ((self.io_registers[0xD6] as u32) << 16) | ((self.io_registers[0xD7] as u32) << 24) } - pub fn dma3_dest_addr(&self) -> u32 { + const fn dma3_dest_addr_raw(&self) -> u32 { self.io_registers[0xD8] as u32 | ((self.io_registers[0xD9] as u32) << 8) | ((self.io_registers[0xDA] as u32) << 16) | ((self.io_registers[0xDB] as u32) << 24) } pub fn dma3_word_count(&self) -> u16 { + self.dma_count[3] + } + fn dma3_word_count_raw(&self) -> u16 { self.io_registers[0xDC] as u16 | ((self.io_registers[0xDD] as u16) << 8) } pub fn disable_dma0(&mut self) { @@ -437,7 +598,9 @@ impl IoRegisters { if let Some(v) = self.timer0.checked_add(1) { self.timer0 = v; } else { - self.timer0 = self.get_mem16(0x4000100); + self.timer0 = (self.io_registers[0x100] as u16) + | ((self.io_registers[0x101] as u16) << 8); + //println!("timer 0 overflow 0x{:X}", self.timer0); return true; } } @@ -445,7 +608,8 @@ impl IoRegisters { if let Some(v) = self.timer1.checked_add(1) { self.timer1 = v; } else { - self.timer1 = self.get_mem16(0x4000104); + self.timer1 = (self.io_registers[0x104] as u16) + | ((self.io_registers[0x105] as u16) << 8); return true; } } @@ -453,7 +617,8 @@ impl IoRegisters { if let Some(v) = self.timer2.checked_add(1) { self.timer2 = v; } else { - self.timer2 = self.get_mem16(0x4000108); + self.timer2 = (self.io_registers[0x108] as u16) + | ((self.io_registers[0x109] as u16) << 8); return true; } } @@ -461,7 +626,8 @@ impl IoRegisters { if let Some(v) = self.timer3.checked_add(1) { self.timer3 = v; } else { - self.timer3 = self.get_mem16(0x400010C); + self.timer3 = (self.io_registers[0x10C] as u16) + | ((self.io_registers[0x10D] as u16) << 8); return true; } } @@ -506,10 +672,31 @@ impl IoRegisters { _ => unreachable!(), } } + // TODO: inaccessible to the sound player, need to move this to the APU + pub fn sound_a_enabled(&self) -> (bool, bool) { + ( + (self.io_registers[0x83] >> 1) & 1 == 1, + (self.io_registers[0x83] & 1) == 1, + ) + } + pub fn sound_b_enabled(&self) -> (bool, bool) { + ( + (self.io_registers[0x83] >> 5) & 1 == 1, + ((self.io_registers[0x83] >> 4) & 1) == 1, + ) + } pub fn set_mem8(&mut self, addr: u32, val: u8) { debug_assert!((0x4000000..=0x4FFFFFF).contains(&addr)); + // possibly useful for emulating on-board work RAM wait state configuration + /* + let undocumented_reg_check = addr & 0x10000; + if undocumented_reg_check >= 0x800 && undocumented_reg_check < 0x804 { + panic!("found write to undocumented register 0x04000800"); + } + */ + let addr = addr & 0x3FF; match addr { 0x4 => { @@ -632,6 +819,24 @@ impl IoRegisters { 0x7C => self.apu.set_mem(CGB_APU_BASE + 0x12, val), 0x7D => self.apu.set_mem(CGB_APU_BASE + 0x13, val), 0x7E | 0x7F => (), + // SOUNDCNT_H + // just write data as bits for now here + // 0x82 => {} + 0x83 => { + if (val >> 3) & 1 == 1 { + self.apu.gba_fifo_a.reset(); + } + if (val >> 7) & 1 == 1 { + self.apu.gba_fifo_b.reset(); + } + let sound_a_enabled = ((val >> 1) & 1 == 1, (val & 1) == 1); + let sound_b_enabled = ((val >> 5) & 1 == 1, ((val >> 4) & 1) == 1); + self.apu.gba_sound_a_enabled = sound_a_enabled; + self.apu.gba_sound_b_enabled = sound_b_enabled; + self.sound_a_timer1 = (val >> 2) & 1 == 1; + self.sound_b_timer1 = (val >> 6) & 1 == 1; + self.io_registers[addr as usize] = val; + } // Wave RAM 0x90..=0x9F => { self.apu.set_mem(CGB_APU_BASE + (addr as u16 - 0x90), val); @@ -662,10 +867,12 @@ impl IoRegisters { */ warn!("Writing to Sound A FIFO but discarding the data!"); self.io_registers[addr as usize] = val; + todo!("8 bit writes to sound FIFO not implemented. How do we do this?"); } 0xA4..=0xA7 => { warn!("Writing to Sound B FIFO but discarding the data!"); self.io_registers[addr as usize] = val; + todo!("8 bit writes to sound FIFO not implemented. How do we do this?"); } // IF: interrupt request flags // writes to this register clear set bits to aknowledge interrupts @@ -676,23 +883,63 @@ impl IoRegisters { match addr { 0xBB if val & 0x80 != 0 => { // DMA 0 start + if !self.dma0_enabled { + self.dma_just_enabled[0] = true; + } self.dma0_enabled = true; self.dma0_delay_counter = 0; + self.load_internal_dma_values(0); + /* + self.dma_source[0] = self.dma0_source_addr_raw(); + self.dma_dest[0] = self.dma0_dest_addr_raw(); + */ } - 0xC7 if val & 0x80 != 0 => { - // DMA 1 start - self.dma1_enabled = true; - self.dma1_delay_counter = 0; + 0xC7 => { + if val & 0x80 != 0 { + // DMA 1 start + if !self.dma1_enabled { + self.dma_just_enabled[1] = true; + } + self.dma1_enabled = true; + self.dma1_delay_counter = 0; + self.load_internal_dma_values(1); + /* + self.dma_source[1] = self.dma1_source_addr_raw(); + self.dma_dest[1] = self.dma1_dest_addr_raw(); + */ + } else { + self.dma1_enabled = false; + } } - 0xD3 if val & 0x80 != 0 => { - // DMA 2 start - self.dma2_enabled = true; - self.dma2_delay_counter = 0; + 0xD3 => { + if val & 0x80 != 0 { + // DMA 2 start + if !self.dma2_enabled { + self.dma_just_enabled[2] = true; + } + self.dma2_enabled = true; + self.dma2_delay_counter = 0; + self.load_internal_dma_values(2); + /* + self.dma_source[2] = self.dma2_source_addr_raw(); + self.dma_dest[2] = self.dma2_dest_addr_raw(); + */ + } else { + self.dma2_enabled = false; + } } 0xDF if val & 0x80 != 0 => { // DMA3 start + if !self.dma3_enabled { + self.dma_just_enabled[3] = true; + } self.dma3_enabled = true; self.dma3_delay_counter = 0; + self.load_internal_dma_values(3); + /* + self.dma_source[3] = self.dma3_source_addr_raw(); + self.dma_dest[3] = self.dma3_dest_addr_raw(); + */ } _ => (), } @@ -700,31 +947,70 @@ impl IoRegisters { } 0x102 => { if (self.io_registers[0x102] >> 7) == 0 && (val >> 7) == 1 { - self.timer0 = self.get_mem16(0x4000100); + self.timer0 = (self.io_registers[0x100] as u16) + | ((self.io_registers[0x101] as u16) << 8); } self.io_registers[addr as usize] = val; } 0x106 => { + if (val >> 2) & 1 == 1 { + todo!("Count up timing!"); + } if (self.io_registers[0x106] >> 7) == 0 && (val >> 7) == 1 { - self.timer1 = self.get_mem16(0x4000104); + self.timer1 = (self.io_registers[0x104] as u16) + | ((self.io_registers[0x105] as u16) << 8); } self.io_registers[addr as usize] = val; } 0x10A => { + if (val >> 2) & 1 == 1 { + todo!("Count up timing!"); + } if (self.io_registers[0x10A] >> 7) == 0 && (val >> 7) == 1 { - self.timer2 = self.get_mem16(0x4000108); + self.timer2 = (self.io_registers[0x108] as u16) + | ((self.io_registers[0x109] as u16) << 8); } self.io_registers[addr as usize] = val; } 0x10E => { + if (val >> 2) & 1 == 1 { + todo!("Count up timing!"); + } if (self.io_registers[0x10E] >> 7) == 0 && (val >> 7) == 1 { - self.timer3 = self.get_mem16(0x400010C); + self.timer3 = (self.io_registers[0x10C] as u16) + | ((self.io_registers[0x10D] as u16) << 8); } self.io_registers[addr as usize] = val; } - 0x100..=0x110 => { + 0x100..=0x11D => { + self.io_registers[addr as usize] = val; + } + 0x130 | 0x131 => { + // writes to button registetrs are ignored + // TODO: why does the bios try to write here at 0xA0C? + } + 0x204 => { + const DEFAULT_WAITSTATES: [u8; 4] = [4, 3, 2, 8]; + const SRAM_WAITSTATES: [u8; 4] = DEFAULT_WAITSTATES; + self.sram_wait = SRAM_WAITSTATES[(val & 0x3) as usize]; + self.wait_state0.0 = DEFAULT_WAITSTATES[((val >> 2) & 0x3) as usize]; + self.wait_state0.1 = if (val >> 4) & 1 == 1 { 1 } else { 2 }; + self.wait_state1.0 = DEFAULT_WAITSTATES[((val >> 5) & 0x3) as usize]; + self.wait_state1.1 = if (val >> 7) & 1 == 1 { 1 } else { 4 }; + self.io_registers[addr as usize] = val; + } + 0x205 => { + const DEFAULT_WAITSTATES: [u8; 4] = [4, 3, 2, 8]; + self.wait_state2.0 = DEFAULT_WAITSTATES[(val & 0x3) as usize]; + self.wait_state2.1 = if val >> 2 & 1 == 1 { 1 } else { 8 }; + // TODO: GBA ROM prefetch + if (self.io_registers[addr as usize] >> 6) & 1 == 0 && (val >> 6) & 1 == 1 { + warn!("prefetch toggled on but it's not implemented!"); + } self.io_registers[addr as usize] = val; } + // unused + 0x206 | 0x207 => {} 0x208 => { self.io_registers[addr as usize] = val & 1; } @@ -755,6 +1041,21 @@ impl IoRegisters { let lo_addr = addr & !3; let hi_addr = addr | 2; + if addr == 0x40000A0 { + //println!("FIFO A: 0x{:x}", val); + u32::to_le_bytes(val) + .iter() + .for_each(|&b| self.apu.gba_fifo_a.push(b as i8)); + return; + } + if addr == 0x40000A4 { + //println!("FIFO B: 0x{:x}", val); + u32::to_le_bytes(val) + .iter() + .for_each(|&b| self.apu.gba_fifo_b.push(b as i8)); + return; + } + let lo_half_word = val as u16; let hi_half_word = (val >> 16) as u16; self.set_mem16(lo_addr, lo_half_word); @@ -798,6 +1099,28 @@ impl IoRegisters { 0x7C => self.apu.get_mem(CGB_APU_BASE + 0x12), 0x7D => self.apu.get_mem(CGB_APU_BASE + 0x13), 0x7E | 0x7F => 0, + // TODO: implement proper read handling here like other + // + 0xA0..=0xDD => 0, + /* + 0xA0..=0xA3 => panic!("Can't read from DMA0 FIFO buffer"), + 0xA4..=0xA7 => panic!("Can't read from DMA1 FIFO buffer"), + 0xB0..=0xB3 => panic!("Can't read from DMA0 source"), + 0xB4..=0xB7 => panic!("Can't read from DMA0 dest"), + 0xB8..=0xB9 => panic!("Can't read from DMA0 word count"), + 0xBC..=0xBF => panic!("Can't read from DMA1 source"), + 0xC0..=0xC3 => panic!("Can't read from DMA1 dest"), + //0xC4..=0xC5 => panic!("Can't read from DMA1 word count"), + // TODO: + 0xC4..=0xC5 => 0, + 0xC8..=0xCB => panic!("Can't read from DMA2 source"), + 0xCC..=0xCF => panic!("Can't read from DMA2 dest"), + //0xD0..=0xD1 => panic!("Can't read from DMA2 word count"), + 0xD0..=0xD1 => 0, + //0xD4..=0xD7 => panic!("Can't read from DMA3 source"), + 0xD8..=0xDB => panic!("Can't read from DMA3 dest"), + 0xDC..=0xDD => 0,//panic!("Can't read from DMA3 word count"), + */ // Wave RAM 0x90..=0x9F => self.apu.get_mem(CGB_APU_BASE + (addr as u16 - 0x90)), // TODO: 0xA0... GBA register stuff @@ -1245,7 +1568,9 @@ impl Registers { pub fn get_spsr(&self) -> u32 { let mode = self.register_mode().unwrap(); match mode { - RegisterMode::User | RegisterMode::System => unimplemented!("Is this possible?"), + RegisterMode::User | RegisterMode::System => { + unimplemented!("Is this possible? at pc={:X}", self.pc) + } RegisterMode::FIQ => self.spsr_fiq, RegisterMode::Supervisor => self.spsr_svc, RegisterMode::Abort => self.spsr_abt, @@ -1256,7 +1581,9 @@ impl Registers { pub fn get_spsr_mut(&mut self) -> &mut u32 { let mode = self.register_mode().unwrap(); match mode { - RegisterMode::User | RegisterMode::System => unimplemented!("Is this possible?"), + RegisterMode::User | RegisterMode::System => { + unimplemented!("Is this possible? at pc={:X}", self.pc) + } RegisterMode::FIQ => &mut self.spsr_fiq, RegisterMode::Supervisor => &mut self.spsr_svc, RegisterMode::Abort => &mut self.spsr_abt, @@ -1344,6 +1671,18 @@ impl Registers { } } +#[derive(Debug, Clone, PartialEq, Eq)] +enum SramType { + // 64kb + Flash64, + // 128kb + Flash128, + // 512 bytes or 8kb + Eeprom, + // 32kb + Sram(Box<[u8; 0x8000]>), +} + impl GameboyAdvance { pub fn new(direct_boot: bool) -> GameboyAdvance { GameboyAdvance { @@ -1356,11 +1695,49 @@ impl GameboyAdvance { obj_palette_ram: [0u8; 0x400], vram: [0u8; 0x18000], oam: [0u8; 0x400], - sram: [0u8; 0x10000], + sram: [0u8; 0x80000], debug_counter: 0, } } + // not perfect but good enough. + fn detect_sram(rom_bytes: &[u8]) -> Option { + const EEPROM: (u8, u8, u8, u8, u8, u8) = ( + 'E' as u8, 'E' as u8, 'P' as u8, 'R' as u8, 'O' as u8, 'M' as u8, + ); + const SRAM_V: (u8, u8, u8, u8, u8, u8) = ( + 'S' as u8, 'R' as u8, 'A' as u8, 'M' as u8, '_' as u8, 'V' as u8, + ); + const FLASH_: (u8, u8, u8, u8, u8, u8) = ( + 'F' as u8, 'L' as u8, 'A' as u8, 'S' as u8, 'H' as u8, '_' as u8, + ); + const FLASH5: (u8, u8, u8, u8, u8, u8) = ( + 'F' as u8, 'L' as u8, 'A' as u8, 'S' as u8, 'H' as u8, '5' as u8, + ); + const FLASH1: (u8, u8, u8, u8, u8, u8) = ( + 'F' as u8, 'L' as u8, 'A' as u8, 'S' as u8, 'H' as u8, '1' as u8, + ); + for i in 0..((rom_bytes.len() / 4) - 1) { + let i = i * 4; + match ( + rom_bytes[i], + rom_bytes[i + 1], + rom_bytes[i + 2], + rom_bytes[i + 3], + rom_bytes[i + 4], + rom_bytes[i + 5], + ) { + EEPROM => return Some(SramType::Eeprom), + SRAM_V => return Some(SramType::Sram(Box::new([0u8; 0x8000]))), + FLASH_ => return Some(SramType::Flash64), + FLASH5 => return Some(SramType::Flash64), + FLASH1 => return Some(SramType::Flash128), + _ => continue, + } + } + None + } + /// Very useful link: /// https://problemkaputt.de/gbatek.htm#gbacartridgeheader pub fn load_rom(&mut self, bytes: Vec) { @@ -1379,6 +1756,14 @@ impl GameboyAdvance { }); info!("Found GBA ROM: `{}` ({})", title, game_code); + let sram_type = GameboyAdvance::detect_sram(&bytes); + match &sram_type { + Some(SramType::Eeprom) => info!("Found EEPROM"), + Some(SramType::Sram(_)) => info!("Found SRAM"), + Some(SramType::Flash64) => info!("Found 64kb Flash"), + Some(SramType::Flash128) => info!("Found 128kb Flash"), + None => info!("No SRAM found"), + } let fixed_value = bytes[0xB2]; if fixed_value != 0x96 { @@ -1390,8 +1775,6 @@ impl GameboyAdvance { warn!("Non-zero main unit code: {} found!", main_unit_code); } - // TODO: 0xBD is the complement check, check header for validity - let first_opcode = bytes[0]; self.entire_rom = bytes; } @@ -1428,49 +1811,64 @@ impl GameboyAdvance { pub fn ppu_frame_select(&self) -> bool { (self.io_registers[0] >> 4) & 1 == 1 } + pub fn ppu_obj_mapping_1d(&self) -> bool { + (self.io_registers[0] >> 6) & 1 == 1 + } pub fn ppu_bg0_x_scroll(&self) -> u16 { - ((self.io_registers[0x10] as u16) << 8) | (self.io_registers[0x11] as u16) & 0x1F + (((self.io_registers[0x11] as u16) << 8) | (self.io_registers[0x10] as u16)) & 0x1FF } pub fn ppu_bg0_y_scroll(&self) -> u16 { - ((self.io_registers[0x12] as u16) << 8) | (self.io_registers[0x13] as u16) & 0x1F + (((self.io_registers[0x13] as u16) << 8) | (self.io_registers[0x12] as u16)) & 0x1FF } pub fn ppu_bg1_x_scroll(&self) -> u16 { - ((self.io_registers[0x14] as u16) << 8) | (self.io_registers[0x15] as u16) & 0x1F + (((self.io_registers[0x15] as u16) << 8) | (self.io_registers[0x14] as u16)) & 0x1FF } pub fn ppu_bg1_y_scroll(&self) -> u16 { - ((self.io_registers[0x16] as u16) << 8) | (self.io_registers[0x17] as u16) & 0x1F + (((self.io_registers[0x17] as u16) << 8) | (self.io_registers[0x16] as u16)) & 0x1FF } pub fn ppu_bg2_x_scroll(&self) -> u16 { - ((self.io_registers[0x18] as u16) << 8) | (self.io_registers[0x19] as u16) & 0x1F + (((self.io_registers[0x19] as u16) << 8) | (self.io_registers[0x18] as u16)) & 0x1FF } pub fn ppu_bg2_y_scroll(&self) -> u16 { - ((self.io_registers[0x1A] as u16) << 8) | (self.io_registers[0x1B] as u16) & 0x1F + (((self.io_registers[0x1B] as u16) << 8) | (self.io_registers[0x1A] as u16)) & 0x1FF } pub fn ppu_bg3_x_scroll(&self) -> u16 { - ((self.io_registers[0x1C] as u16) << 8) | (self.io_registers[0x1D] as u16) & 0x1F + (((self.io_registers[0x1D] as u16) << 8) | (self.io_registers[0x1C] as u16)) & 0x1FF } pub fn ppu_bg3_y_scroll(&self) -> u16 { - ((self.io_registers[0x1E] as u16) << 8) | (self.io_registers[0x1F] as u16) & 0x1F + (((self.io_registers[0x1F] as u16) << 8) | (self.io_registers[0x1E] as u16)) & 0x1FF + } + pub fn ppu_bg_scroll(&self, bg: u8) -> (u16, u16) { + match bg { + 0 => (self.ppu_bg0_x_scroll(), self.ppu_bg0_y_scroll()), + 1 => (self.ppu_bg1_x_scroll(), self.ppu_bg1_y_scroll()), + 2 => (self.ppu_bg2_x_scroll(), self.ppu_bg2_y_scroll()), + 3 => (self.ppu_bg3_x_scroll(), self.ppu_bg3_y_scroll()), + _ => unreachable!(), + } } pub fn ppu_bg0_control(&self) -> PpuBgControl { - let bits = ((self.io_registers[0x8] as u16) << 8) | (self.io_registers[0x9] as u16); + let bits = ((self.io_registers[0x9] as u16) << 8) | (self.io_registers[0x8] as u16); PpuBgControl::from_bits(bits) } pub fn ppu_bg1_control(&self) -> PpuBgControl { - let bits = ((self.io_registers[0xA] as u16) << 8) | (self.io_registers[0xB] as u16); + let bits = ((self.io_registers[0xB] as u16) << 8) | (self.io_registers[0xA] as u16); PpuBgControl::from_bits(bits) } pub fn ppu_bg2_control(&self) -> PpuBgControl { - let bits = ((self.io_registers[0xC] as u16) << 8) | (self.io_registers[0xD] as u16); + let bits = ((self.io_registers[0xD] as u16) << 8) | (self.io_registers[0xC] as u16); PpuBgControl::from_bits(bits) } pub fn ppu_bg3_control(&self) -> PpuBgControl { - let bits = ((self.io_registers[0xE] as u16) << 8) | (self.io_registers[0xF] as u16); + let bits = ((self.io_registers[0xF] as u16) << 8) | (self.io_registers[0xE] as u16); PpuBgControl::from_bits(bits) } pub fn ppu_bg_mode(&self) -> u8 { self.io_registers[0x0] & 0x7 } + pub fn ppu_force_blank(&self) -> bool { + (self.io_registers[0x0] >> 7) & 1 == 1 + } pub fn ppu_bg0_enabled(&self) -> bool { self.io_registers[0x1] & 1 == 1 } @@ -1495,6 +1893,18 @@ impl GameboyAdvance { pub fn ppu_obj_win_enabled(&self) -> bool { (self.io_registers[0x1] >> 7) & 1 == 1 } + pub fn ppu_bg_mosaic_h(&self) -> u8 { + (self.io_registers[0x4C] & 0xF) + 1 + } + pub fn ppu_bg_mosaic_v(&self) -> u8 { + ((self.io_registers[0x4C] >> 4) & 0xF) + 1 + } + pub fn ppu_obj_mosaic_h(&self) -> u8 { + (self.io_registers[0x4D] & 0xF) + 1 + } + pub fn ppu_obj_mosaic_v(&self) -> u8 { + ((self.io_registers[0x4D] >> 4) & 0xF) + 1 + } pub fn ppu_set_vblank(&mut self, v: bool) { self.io_registers[0x4] &= !1; @@ -1521,7 +1931,7 @@ impl GameboyAdvance { self.io_registers[0x5] } pub fn ppu_set_readonly_vcounter(&mut self, ly: u8) { - self.io_registers[0x6] = ly as u8; + self.io_registers[0x6] = ly; } pub fn master_interrupts_enabled(&self) -> bool { @@ -1680,22 +2090,6 @@ impl GameboyAdvance { self.io_registers[0x203] |= (value as u8) << 5; } - pub fn dma3_source_address(&self) -> u32 { - (self.io_registers[0x0D7] as u32) << 24 - | (self.io_registers[0x0D6] as u32) << 16 - | (self.io_registers[0x0D5] as u32) << 8 - | (self.io_registers[0x0D4] as u32) - } - pub fn dma3_dest_address(&self) -> u32 { - (self.io_registers[0x0DE] as u32) << 24 - | (self.io_registers[0x0DC] as u32) << 16 - | (self.io_registers[0x0DB] as u32) << 8 - | (self.io_registers[0x0DA] as u32) - } - pub fn dma3_count(&self) -> u16 { - (self.io_registers[0x0DD] as u16) << 8 | (self.io_registers[0x0DC] as u16) - } - pub fn get_mem8(&self, address: u32) -> (u8, u8) { match address { 0x00000000..=0x00003FFF => { @@ -1703,7 +2097,9 @@ impl GameboyAdvance { // bios (self.bios[address as usize], 3) } else { - todo!("Reading from bios while PC is not in the bios"); + // implementing this properly requires keeping track of the last executed BIOS instruction + // TODO: implement this properly + (self.bios[0xDC + 8], 1) } } 0x00004000..=0x01FFFFFF => { @@ -1726,23 +2122,45 @@ impl GameboyAdvance { //0x05000000..=0x050003FF => (self.obj_palette_ram[(address & 0x3FF) as usize], 1), 0x05000000..=0x05FFFFFF => (self.obj_palette_ram[(address & 0x3FF) as usize], 1), //0x06000000..=0x06017FFF => (self.vram[(address & 0x17FFF) as usize], 1), - 0x06000000..=0x06FFFFFF => (self.vram[(address & 0x17FFF) as usize], 1), + 0x06000000..=0x06FFFFFF => { + let mut i = address & 0x1FFFF; + if i > 0x17FFF { + i &= !0x08000 + }; + (self.vram[i as usize], 1) + } //0x07000000..=0x070003FF => (self.oam[(address & 0x3FF) as usize], 1), 0x07000000..=0x07FFFFFF => (self.oam[(address & 0x3FF) as usize], 1), 0x08000000..=0x09FFFFFF => { + let timing = self.io_registers.wait_state0.0 + 1; if (address - 0x0800_0000) > self.entire_rom.len() as u32 { - return (0, 5); + return (0, timing); } - (self.entire_rom[(address & 0x1FFFFFF) as usize], 5) + (self.entire_rom[(address & 0x1FFFFFF) as usize], timing) + } + 0x0A000000..=0x0BFFFFFF => { + let timing = self.io_registers.wait_state1.0 + 1; + if (address - 0x0A00_0000) > self.entire_rom.len() as u32 { + return (0, timing); + } + (self.entire_rom[(address & 0x1FFFFFF) as usize], timing) + } + 0x0C000000..=0x0DFFFFFF => { + let timing = self.io_registers.wait_state2.0 + 1; + if (address - 0x0C00_0000) > self.entire_rom.len() as u32 { + return (0, timing); + } + (self.entire_rom[(address & 0x1FFFFFF) as usize], timing) } - 0x0A000000..=0x0BFFFFFF => todo!("Game Pak ROM/FlashROM (max 32MB) - Wait State 1"), - 0x0C000000..=0x0DFFFFFF => todo!("Game Pak ROM/FlashROM (max 32MB) - Wait State 2"), //0x0E000000..=0x0E00FFFF => todo!("Game Pak SRAM (max 64 KBytes) - 8bit Bus width"), - 0x0E000000..=0x0FFFFFFF => (self.sram[(address & 0xFFFF) as usize], 5), + 0x0E000000..=0x0FFFFFFF => ( + self.sram[(address & 0x7FFFF) as usize], + self.io_registers.sram_wait + 1, + ), _ => (0, 1), } } - pub fn get_mem16(&self, address: u32) -> (u16, u8) { + pub fn get_mem16(&self, address: u32, sequential: bool) -> (u16, u8) { let lo_bit_idx = address & !0x1; let hi_bit_idx = address | 0x1; match address { @@ -1756,9 +2174,9 @@ impl GameboyAdvance { 0x00004000..=0x01FFFFFF => { if self.r.thumb_enabled() { // TODO: handle all cases - self.get_mem16(self.r.pc + 2) + self.get_mem16(self.r.pc + 2, true) } else { - self.get_mem16(self.r.pc + 4) + self.get_mem16(self.r.pc + 4, true) } } 0x02000000..=0x02FFFFFF => { @@ -1784,8 +2202,16 @@ impl GameboyAdvance { } 0x06000000..=0x06FFFFFF => { //0x06000000..=0x06017FFF => { - let lo_bit = self.vram[(lo_bit_idx & 0x17FFF) as usize] as u16; - let hi_bit = self.vram[(hi_bit_idx & 0x17FFF) as usize] as u16; + let mut i1 = lo_bit_idx & 0x1FFFF; + let mut i2 = hi_bit_idx & 0x1FFFF; + if i1 > 0x17FFF { + i1 &= !0x08000 + }; + if i2 > 0x17FFF { + i2 &= !0x08000 + }; + let lo_bit = self.vram[i1 as usize] as u16; + let hi_bit = self.vram[i2 as usize] as u16; ((hi_bit << 8) | lo_bit, 1) } 0x07000000..=0x07FFFFFF => { @@ -1795,38 +2221,53 @@ impl GameboyAdvance { ((hi_bit << 8) | lo_bit, 1) } 0x08000000..=0x09FFFFFF => { + let timing = if sequential { + self.io_registers.wait_state0.1 + 1 + } else { + self.io_registers.wait_state0.0 + 1 + }; if (hi_bit_idx - 0x0800_0000) > self.entire_rom.len() as u32 { - return (((address >> 1) & 0xFFFF) as u16, 5); + return (((address >> 1) & 0xFFFF) as u16, timing); } let lo_bit = self.entire_rom[(lo_bit_idx & 0x1FFFFFF) as usize] as u16; let hi_bit = self.entire_rom[(hi_bit_idx & 0x1FFFFFF) as usize] as u16; - ((hi_bit << 8) | lo_bit, 5) + ((hi_bit << 8) | lo_bit, timing) } 0x0A000000..=0x0BFFFFFF => { + let timing = if sequential { + self.io_registers.wait_state1.1 + 1 + } else { + self.io_registers.wait_state1.0 + 1 + }; if (hi_bit_idx - 0x0A00_0000) > self.entire_rom.len() as u32 { - return (((address >> 1) & 0xFFFF) as u16, 5); + return (((address >> 1) & 0xFFFF) as u16, timing); } let lo_bit = self.entire_rom[(lo_bit_idx & 0x1FFFFFF) as usize] as u16; let hi_bit = self.entire_rom[(hi_bit_idx & 0x1FFFFFF) as usize] as u16; - ((hi_bit << 8) | lo_bit, 5) + ((hi_bit << 8) | lo_bit, timing) } 0x0C000000..=0x0DFFFFFF => { + let timing = if sequential { + self.io_registers.wait_state2.1 + 1 + } else { + self.io_registers.wait_state2.0 + 1 + }; if (hi_bit_idx - 0x0C00_0000) > self.entire_rom.len() as u32 { - return (((address >> 1) & 0xFFFF) as u16, 5); + return (((address >> 1) & 0xFFFF) as u16, timing); } let lo_bit = self.entire_rom[(lo_bit_idx & 0x1FFFFFF) as usize] as u16; let hi_bit = self.entire_rom[(hi_bit_idx & 0x1FFFFFF) as usize] as u16; - ((hi_bit << 8) | lo_bit, 5) + ((hi_bit << 8) | lo_bit, timing) } //0x0E000000..=0x0E00FFFF => todo!("Game Pak SRAM (max 64 KBytes) - 8bit Bus width"), 0x0E000000..=0x0FFFFFFF => todo!("Game Pak SRAM (max 64 KBytes) - 8bit Bus width"), _ => (0, 1), } } - pub fn get_mem32(&self, address: u32) -> (u32, u8) { + pub fn get_mem32(&self, address: u32, sequential: bool) -> (u32, u8) { if address != address & !3 { - let lo = self.get_mem16(address); - let hi = self.get_mem16(address - 2); + let lo = self.get_mem16(address, sequential); + let hi = self.get_mem16(address - 2, true); return ((hi.0 as u32) << 16 | lo.0 as u32, lo.1); } //address = address & 0x0FFF_FFFF; @@ -1847,9 +2288,9 @@ impl GameboyAdvance { 0x00004000..=0x01FFFFFF => { if self.r.thumb_enabled() { // TODO: handle all cases - self.get_mem32(self.r.pc + 2) + self.get_mem32(self.r.pc + 2, true) } else { - self.get_mem32(self.r.pc + 4) + self.get_mem32(self.r.pc + 4, true) } } 0x02000000..=0x02FFFFFF => { @@ -1884,10 +2325,26 @@ impl GameboyAdvance { } 0x06000000..=0x06FFFFFF => { //0x06000000..=0x06017FFF => { - let bit1 = self.vram[(bit1_idx & 0x17FFF) as usize] as u32; - let bit2 = self.vram[(bit2_idx & 0x17FFF) as usize] as u32; - let bit3 = self.vram[(bit3_idx & 0x17FFF) as usize] as u32; - let bit4 = self.vram[(bit4_idx & 0x17FFF) as usize] as u32; + let mut i1 = bit1_idx & 0x1FFFF; + let mut i2 = bit2_idx & 0x1FFFF; + let mut i3 = bit3_idx & 0x1FFFF; + let mut i4 = bit4_idx & 0x1FFFF; + if i1 > 0x17FFF { + i1 &= !0x08000 + }; + if i2 > 0x17FFF { + i2 &= !0x08000 + }; + if i3 > 0x17FFF { + i3 &= !0x08000 + }; + if i4 > 0x17FFF { + i4 &= !0x08000 + }; + let bit1 = self.vram[i1 as usize] as u32; + let bit2 = self.vram[i2 as usize] as u32; + let bit3 = self.vram[i3 as usize] as u32; + let bit4 = self.vram[i4 as usize] as u32; let out = (bit4 << 24) | (bit3 << 16) | (bit2 << 8) | bit1; (out, 2) } @@ -1901,16 +2358,31 @@ impl GameboyAdvance { (out, 1) } 0x0A000000..=0x0BFFFFFF | 0x0C000000..=0x0DFFFFFF | 0x08000000..=0x09FFFFFF => { + let timing = match (sequential, address) { + (false, 0x08000000..=0x09FFFFFF) => { + self.io_registers.wait_state0.0 + 1 + self.io_registers.wait_state0.1 + 1 + } + (false, 0x0A000000..=0x0BFFFFFF) => { + self.io_registers.wait_state1.0 + 1 + self.io_registers.wait_state1.1 + 1 + } + (false, 0x0C000000..=0x0DFFFFFF) => { + self.io_registers.wait_state2.0 + 1 + self.io_registers.wait_state2.1 + 1 + } + (true, 0x08000000..=0x09FFFFFF) => self.io_registers.wait_state0.1 * 2 + 2, + (true, 0x0A000000..=0x0BFFFFFF) => self.io_registers.wait_state1.1 * 2 + 2, + (true, 0x0C000000..=0x0DFFFFFF) => self.io_registers.wait_state2.1 * 2 + 2, + _ => unreachable!(), + }; // TODO: properly handle later bytes overflowing too if bit1_idx & 0x1FFFFFF >= self.entire_rom.len() as u32 { - return ((address >> 1) & 0xFFFF, 8); + return ((address >> 1) & 0xFFFF, timing); } let bit1 = self.entire_rom[(bit1_idx & 0x1FFFFFF) as usize] as u32; let bit2 = self.entire_rom[(bit2_idx & 0x1FFFFFF) as usize] as u32; let bit3 = self.entire_rom[(bit3_idx & 0x1FFFFFF) as usize] as u32; let bit4 = self.entire_rom[(bit4_idx & 0x1FFFFFF) as usize] as u32; let out = (bit4 << 24) | (bit3 << 16) | (bit2 << 8) | bit1; - (out, 8) + (out, timing) } /* 0x0A000000..=0x0BFFFFFF => todo!("Game Pak ROM/FlashROM (max 32MB) - Wait State 1"), @@ -1950,18 +2422,28 @@ impl GameboyAdvance { //0x06000000..=0x06017FFF => { 0x06000000..=0x06FFFFFF => { // INACCUARY: GBA can't do 8 bit writes here - self.vram[(address & 0x17FFF) as usize] = val; + let mut i = address & 0x1FFFF; + if i > 0x17FFF { + i &= !0x08000 + }; + self.vram[i as usize] = val; 1 } 0x07000000..=0x070003FF => { - todo!("OAM") + // 8 bit writes to OAM are ignored + 1 } - 0x08000000..=0x09FFFFFF => todo!("Game Pak ROM/FlashROM (max 32MB) - Wait State 0"), + 0x08000000..=0x09FFFFFF => todo!( + "Game Pak ROM/FlashROM (max 32MB) - Wait State 0, addr={:X}, val ={:X}, pc={:X}", + address, + val, + self.r.pc + ), 0x0A000000..=0x0BFFFFFF => todo!("Game Pak ROM/FlashROM (max 32MB) - Wait State 1"), 0x0C000000..=0x0DFFFFFF => todo!("Game Pak ROM/FlashROM (max 32MB) - Wait State 2"), //0x0E000000..=0x0E00FFFF => todo!("Game Pak SRAM (max 64 KBytes) - 8bit Bus width"), 0x0E000000..=0x0FFFFFFF => { - self.sram[(address & 0xFFFF) as usize] = val; + self.sram[(address & 0x7FFFF) as usize] = val; 5 } _ => 1, @@ -1976,7 +2458,8 @@ impl GameboyAdvance { match address { 0x00000000..=0x00003FFF => { // bios system ROM - todo!("bios system ROM") + //todo!("bios system ROM") + 1 } 0x02000000..=0x02FFFFFF => { //0x02000000..=0x0203FFFF => { @@ -2004,8 +2487,16 @@ impl GameboyAdvance { } 0x06000000..=0x06FFFFFF => { //0x06000000..=0x06017FFF => { - self.vram[(lo_bit_idx & 0x17FFF) as usize] = lo_val; - self.vram[(hi_bit_idx & 0x17FFF) as usize] = hi_val; + let mut i1 = lo_bit_idx & 0x1FFFF; + let mut i2 = hi_bit_idx & 0x1FFFF; + if i1 > 0x17FFF { + i1 &= !0x08000 + }; + if i2 > 0x17FFF { + i2 &= !0x08000 + }; + self.vram[i1 as usize] = lo_val; + self.vram[i2 as usize] = hi_val; 1 } 0x07000000..=0x07FFFFFF => { @@ -2016,7 +2507,14 @@ impl GameboyAdvance { } 0x08000000..=0x09FFFFFF => 0, //todo!("Game Pak ROM/FlashROM (max 32MB) - Wait State 0"), 0x0A000000..=0x0BFFFFFF => todo!("Game Pak ROM/FlashROM (max 32MB) - Wait State 1"), - 0x0C000000..=0x0DFFFFFF => todo!("Game Pak ROM/FlashROM (max 32MB) - Wait State 2"), + //0x0C000000..=0x0DFFFFFF => todo!("Game Pak ROM/FlashROM (max 32MB) - Wait State 2 0x{:X} = 0x{:X}", address, val), + // TODO: proper flashROM support + 0x0C000000..=0x0DFFFFFF => { + self.entire_rom[(lo_bit_idx & 0x1FFFFF) as usize] = lo_val; + self.entire_rom[(hi_bit_idx & 0x1FFFFF) as usize] = hi_val; + // TODO: + 5 + } //0x0E000000..=0x0E00FFFF => todo!("Game Pak SRAM (max 64 KBytes) - 8bit Bus width"), 0x0E000000..=0x0FFFFFFF => todo!("Game Pak SRAM (max 64 KBytes) - 8bit Bus width"), _ => 1, @@ -2035,7 +2533,11 @@ impl GameboyAdvance { match address { 0x00000000..=0x00003FFF => { // bios system ROM - todo!("bios system ROM") + todo!( + "bios system ROM (addr 0x{:X}) caused by instr at 0x{:X}", + address, + self.r.pc + ); } 0x02000000..=0x02FFFFFF => { //0x02000000..=0x0203FFFF => { @@ -2069,10 +2571,26 @@ impl GameboyAdvance { } 0x06000000..=0x06FFFFFF => { //0x06000000..=0x06017FFF => { - self.vram[(bit1_idx & 0x17FFF) as usize] = val1; - self.vram[(bit2_idx & 0x17FFF) as usize] = val2; - self.vram[(bit3_idx & 0x17FFF) as usize] = val3; - self.vram[(bit4_idx & 0x17FFF) as usize] = val4; + let mut i1 = bit1_idx & 0x1FFFF; + let mut i2 = bit2_idx & 0x1FFFF; + let mut i3 = bit3_idx & 0x1FFFF; + let mut i4 = bit4_idx & 0x1FFFF; + if i1 > 0x17FFF { + i1 &= !0x08000 + }; + if i2 > 0x17FFF { + i2 &= !0x08000 + }; + if i3 > 0x17FFF { + i3 &= !0x08000 + }; + if i4 > 0x17FFF { + i4 &= !0x08000 + }; + self.vram[i1 as usize] = val1; + self.vram[i2 as usize] = val2; + self.vram[i3 as usize] = val3; + self.vram[i4 as usize] = val4; 2 } 0x07000000..=0x07FFFFFF => { @@ -2165,6 +2683,78 @@ impl GameboyAdvance { } } + fn do_dma(&mut self, control: DmaControl, src: u32, dest: u32, count: u32, dma_id: u8) -> u32 { + let mut cycles = 0; + + println!( + "DMA{} Transfering data from 0x{:X} to 0x{:X}: 0x{:X} bytes at pc=0x{:X}", + dma_id, src, dest, count, self.r.pc + ); + let adj_src = |i: u32| -> u32 { + match control.source_addr_control { + DmaAddrControl::Increment | DmaAddrControl::IncrementReload => src + i, + DmaAddrControl::Decrement => src - i, + DmaAddrControl::Fixed => src, + } + }; + let adj_dest = |i: u32| -> u32 { + match control.dest_addr_control { + DmaAddrControl::Increment | DmaAddrControl::IncrementReload => dest + i, + DmaAddrControl::Decrement => dest - i, + DmaAddrControl::Fixed => dest, + } + }; + let mut seq = false; + if control.transfer_type { + for i in 0..count { + let src_addr = adj_src(i * 4); + let dest_addr = adj_dest(i * 4); + let val = self.get_mem32(src_addr, seq); + let o = self.set_mem32(dest_addr, val.0); + cycles += val.1 as u32 + o as u32; + seq = true; + } + } else { + for i in 0..count { + let src_addr = adj_src(i * 2); + let dest_addr = adj_dest(i * 2); + let val = self.get_mem16(src_addr, seq); + let o = self.set_mem16(dest_addr, val.0); + cycles += val.1 as u32 + o as u32; + seq = true; + } + } + let bytes_transferred = count * if control.transfer_type { 4 } else { 2 }; + match control.source_addr_control { + DmaAddrControl::Increment => { + self.io_registers.dma_source[dma_id as usize] = src + bytes_transferred; + } + DmaAddrControl::Decrement => { + self.io_registers.dma_source[dma_id as usize] = src - bytes_transferred; + } + DmaAddrControl::Fixed => {} + // TODO: what does increment reload mean? + // operating under the assumption it means increment but reload old value + // DAD is reloaded on repeat + _ => unreachable!(""), + } + match control.dest_addr_control { + DmaAddrControl::Increment => { + self.io_registers.dma_dest[dma_id as usize] = src + bytes_transferred; + } + DmaAddrControl::Decrement => { + self.io_registers.dma_dest[dma_id as usize] = src - bytes_transferred; + } + // TODO: what does increment reload mean? + // operating under the assumption it means increment but reload old value + // DAD is reloaded on repeat + DmaAddrControl::IncrementReload => (), //todo!("DMA increment reload"), + DmaAddrControl::Fixed => {} + } + + cycles + } + pub fn run_dma( &mut self, control: DmaControl, @@ -2176,23 +2766,7 @@ impl GameboyAdvance { let mut cycles = 0; match control.start_timing { DmaStartTiming::Immediately => { - println!( - "Transfering data from 0x{:X} to 0x{:X}: 0x{:X} bytes", - src, dest, count - ); - if control.transfer_type { - for i in 0..count { - let val = self.get_mem32(src + (i * 4) as u32); - let o = self.set_mem32(dest + (i * 4) as u32, val.0); - cycles += val.1 as u32 + o as u32; - } - } else { - for i in 0..count { - let val = self.get_mem16(src + (i * 2) as u32); - let o = self.set_mem16(dest + (i * 2) as u32, val.0); - cycles += val.1 as u32 + o as u32; - } - } + cycles += self.do_dma(control, src, dest, count, dma_id); if control.repeat { todo!("repeat dma"); } @@ -2212,7 +2786,45 @@ impl GameboyAdvance { debug_assert!(matches!(dest, 0x40000A0 | 0x40000A4), "0x{:X}", dest); // TODO: transfer 16 bytes when sound channel requests it */ - warn!("DMA1 Sound FIFO"); + // sound channel has to request this? + //warn!("DMA1 Sound FIFO 0x{:X}, {}, {}", src, count, control.transfer_type); + let adj_src = |i: u32| -> u32 { + match control.source_addr_control { + DmaAddrControl::Increment | DmaAddrControl::IncrementReload => { + src + i + } + DmaAddrControl::Decrement => src - i, + DmaAddrControl::Fixed => src, + } + }; + // TODO: 16 byte transfers? + if dest == 0x40000A0 || dest == 0x40000A4 { + let mut seq = false; + for i in 0..4 { + let src_addr = adj_src(i * 4); + //println!("src_addr: 0x{:X}", src_addr); + let val = self.get_mem32(src_addr, seq); + cycles += self.set_mem32(dest, val.0) as u32; + cycles += val.1 as u32; + seq = true; + } + } + let bytes_transferred = 16; + match control.source_addr_control { + DmaAddrControl::Increment => { + self.io_registers.dma_source[dma_id as usize] = + src + bytes_transferred; + } + DmaAddrControl::Decrement => { + self.io_registers.dma_source[dma_id as usize] = + src - bytes_transferred; + } + DmaAddrControl::Fixed => {} + // TODO: what does increment reload mean? + // operating under the assumption it means increment but reload old value + // DAD is reloaded on repeat + _ => unreachable!(""), + } } 2 => { /* @@ -2220,9 +2832,47 @@ impl GameboyAdvance { debug_assert!(matches!(dest, 0x40000A0 | 0x40000A4), "0x{:X}", dest); // TODO: transfer 16 bytes when sound channel requests it */ - warn!("DMA2 Sound FIFO"); + //warn!("DMA2 Sound FIFO"); + let adj_src = |i: u32| -> u32 { + match control.source_addr_control { + DmaAddrControl::Increment | DmaAddrControl::IncrementReload => { + src + i + } + DmaAddrControl::Decrement => src - i, + DmaAddrControl::Fixed => src, + } + }; + if dest == 0x40000A0 || dest == 0x40000A4 { + let mut seq = false; + for i in 0..4 { + let src_addr = adj_src(i * 4); + //println!("src_addr: 0x{:X}", src_addr); + let val = self.get_mem32(src_addr, seq); + cycles += self.set_mem32(dest, val.0) as u32; + cycles += val.1 as u32; + seq = true; + } + } + let bytes_transferred = 16; + match control.source_addr_control { + DmaAddrControl::Increment => { + self.io_registers.dma_source[dma_id as usize] = + src + bytes_transferred; + } + DmaAddrControl::Decrement => { + self.io_registers.dma_source[dma_id as usize] = + src - bytes_transferred; + } + DmaAddrControl::Fixed => {} + // TODO: what does increment reload mean? + // operating under the assumption it means increment but reload old value + // DAD is reloaded on repeat + _ => unreachable!(""), + } + } + 3 => { + cycles += self.do_dma(control, src, dest, count, dma_id); } - 3 => todo!("DMA3 Video capture"), _ => unreachable!(), } } @@ -2230,13 +2880,84 @@ impl GameboyAdvance { cycles } + pub fn handle_dma_delay(&mut self) { + if self.io_registers.dma0_ready() && self.io_registers.dma_just_enabled[0] { + self.io_registers.dma_just_enabled[0] = false; + // TODO: implement this when something needs it + // NOTE: special does not exist for dma0 + debug_assert_ne!( + self.io_registers.dma0().start_timing, + DmaStartTiming::Special + ); + //debug_assert_ne!(self.io_registers.dma0().start_timing, DmaStartTiming::HBlank); + debug_assert_ne!( + self.io_registers.dma0().start_timing, + DmaStartTiming::VBlank + ); + //self.io_registers.load_internal_dma_values(0); + if self.io_registers.dma0().start_timing == DmaStartTiming::Immediately { + self.io_registers.trigger_dma(0); + } + } + if self.io_registers.dma1_ready() && self.io_registers.dma_just_enabled[1] { + // TODO: implement this when something needs it + debug_assert!(!matches!( + self.io_registers.dma1().start_timing, + DmaStartTiming::VBlank | DmaStartTiming::HBlank + )); + self.io_registers.dma_just_enabled[1] = false; + //self.io_registers.load_internal_dma_values(1); + if self.io_registers.dma1().start_timing == DmaStartTiming::Immediately { + self.io_registers.trigger_dma(1); + } + } + if self.io_registers.dma2_ready() && self.io_registers.dma_just_enabled[2] { + // TODO: implement this when something needs it + debug_assert!(!matches!( + self.io_registers.dma2().start_timing, + DmaStartTiming::VBlank | DmaStartTiming::HBlank + )); + self.io_registers.dma_just_enabled[2] = false; + //self.io_registers.load_internal_dma_values(2); + if self.io_registers.dma2().start_timing == DmaStartTiming::Immediately { + self.io_registers.trigger_dma(2); + } + } + if self.io_registers.dma3_ready() && self.io_registers.dma_just_enabled[3] { + // TODO: implement this when something needs it + debug_assert_ne!( + self.io_registers.dma3().start_timing, + DmaStartTiming::VBlank + ); + debug_assert_ne!( + self.io_registers.dma3().start_timing, + DmaStartTiming::HBlank + ); + debug_assert_ne!( + self.io_registers.dma3().start_timing, + DmaStartTiming::Special + ); + self.io_registers.dma_just_enabled[3] = false; + //self.io_registers.load_internal_dma_values(3); + if self.io_registers.dma3().start_timing == DmaStartTiming::Immediately { + self.io_registers.trigger_dma(3); + } + } + } + pub fn handle_dma(&mut self) -> u32 { // TODO: only process Immediate DMA here // TODO: structure the code so that the tight dispatch loop doesn't need // to do a lot of work to check which DMA is ready: do the logic in IoRegisters - if self.io_registers.dma0_ready() { - self.io_registers.dma0_enabled = false; + if self.io_registers.dma0_enabled && self.io_registers.dma_triggered[0] { + self.io_registers.dma_triggered[0] = false; let dma = self.io_registers.dma0(); + if dma.repeat { + self.io_registers.reload_dma_word_count(0); + if dma.dest_addr_control == DmaAddrControl::IncrementReload { + self.io_registers.reload_dma_dest(0); + } + } let src = self.io_registers.dma0_source_addr(); let dest = self.io_registers.dma0_dest_addr(); let mut count = self.io_registers.dma0_word_count(); @@ -2247,11 +2968,20 @@ impl GameboyAdvance { if dma.irq_at_end && self.dma0_interrupt_enabled() { self.set_dma0_interrupt(true); } - self.io_registers.disable_dma0(); + if !dma.repeat { + self.io_registers.dma0_enabled = false; + self.io_registers.disable_dma0(); + } out - } else if self.io_registers.dma1_ready() { - self.io_registers.dma1_enabled = false; + } else if self.io_registers.dma1_enabled && self.io_registers.dma_triggered[1] { + self.io_registers.dma_triggered[1] = false; let dma = self.io_registers.dma1(); + if dma.repeat { + self.io_registers.reload_dma_word_count(1); + if dma.dest_addr_control == DmaAddrControl::IncrementReload { + self.io_registers.reload_dma_dest(1); + } + } let src = self.io_registers.dma1_source_addr(); let dest = self.io_registers.dma1_dest_addr(); let mut count = self.io_registers.dma1_word_count(); @@ -2262,11 +2992,20 @@ impl GameboyAdvance { if dma.irq_at_end && self.dma1_interrupt_enabled() { self.set_dma1_interrupt(true); } - self.io_registers.disable_dma1(); + if !dma.repeat { + self.io_registers.dma1_enabled = false; + self.io_registers.disable_dma1(); + } out - } else if self.io_registers.dma2_ready() { - self.io_registers.dma2_enabled = false; + } else if self.io_registers.dma2_enabled && self.io_registers.dma_triggered[2] { + self.io_registers.dma_triggered[2] = false; let dma = self.io_registers.dma2(); + if dma.repeat { + self.io_registers.reload_dma_word_count(2); + if dma.dest_addr_control == DmaAddrControl::IncrementReload { + self.io_registers.reload_dma_dest(2); + } + } let src = self.io_registers.dma2_source_addr(); let dest = self.io_registers.dma2_dest_addr(); let mut count = self.io_registers.dma2_word_count(); @@ -2277,11 +3016,20 @@ impl GameboyAdvance { if dma.irq_at_end && self.dma2_interrupt_enabled() { self.set_dma2_interrupt(true); } - self.io_registers.disable_dma2(); + if !dma.repeat { + self.io_registers.dma2_enabled = false; + self.io_registers.disable_dma2(); + } out - } else if self.io_registers.dma3_ready() { - self.io_registers.dma3_enabled = false; + } else if self.io_registers.dma3_enabled && self.io_registers.dma_triggered[3] { let dma = self.io_registers.dma3(); + if dma.repeat { + self.io_registers.reload_dma_word_count(3); + if dma.dest_addr_control == DmaAddrControl::IncrementReload { + self.io_registers.reload_dma_dest(3); + } + } + self.io_registers.dma_triggered[3] = false; let src = self.io_registers.dma3_source_addr(); let dest = self.io_registers.dma3_dest_addr(); let mut count = self.io_registers.dma3_word_count() as u32; @@ -2292,7 +3040,10 @@ impl GameboyAdvance { if dma.irq_at_end && self.dma3_interrupt_enabled() { self.set_dma3_interrupt(true); } - self.io_registers.disable_dma3(); + if !dma.repeat { + self.io_registers.dma3_enabled = false; + self.io_registers.disable_dma3(); + } out } else { 0 @@ -2300,14 +3051,12 @@ impl GameboyAdvance { } // TODO: aligned reads - pub fn get_opcode(&self) -> u32 { - // TODO: the timing of reading from mem for PC should be handled? - self.get_mem32(self.r.pc).0 + pub fn get_opcode(&self) -> (u32, u8) { + self.get_mem32(self.r.pc, true) } - pub fn get_thumb_opcode(&self) -> u16 { - // TODO: the timing of reading from mem for PC should be handled? - self.get_mem16(self.r.pc).0 + pub fn get_thumb_opcode(&self) -> (u16, u8) { + self.get_mem16(self.r.pc, true) } pub fn dispatch(&mut self) -> u32 { @@ -2322,12 +3071,20 @@ impl GameboyAdvance { } */ let mut cycles = 0; - let dma_waiting = self.io_registers.dma_waiting(); let dma_ready = self.io_registers.dma_ready(); - if dma_waiting && dma_ready { - return self.handle_dma(); + let dma_just_enabled_old = self.io_registers.dma_just_enabled.clone(); + if dma_ready { + self.handle_dma_delay(); + } + let dma_waiting = self.io_registers.dma_waiting(); + if dma_waiting { + let dma_cycles = self.handle_dma(); + // TODO: 0 should never be returned, the fact that is means our checking logic is wrong + if dma_cycles != 0 { + return dma_cycles; + } } - if self.get_mem16(0x4000202).0 != 0 && self.master_interrupts_enabled() { + if self.get_mem16(0x4000202, false).0 != 0 && self.master_interrupts_enabled() { self.maybe_handle_interrupts(); } if self.r.thumb_enabled() { @@ -2338,15 +3095,24 @@ impl GameboyAdvance { // TODO: this probably needs to be tracked per DMA channel // TODO: do we need to recheck DMA to see if it's been disabled by this last instruction? - if dma_waiting && !dma_ready { - self.io_registers.dma_inc_delay_counter(cycles as u8); + // TODO: make new condition + for i in 0..4 { + /* + if !dma_just_enabled_old[i] && self.io_registers.dma_just_enabled[i] { + continue; + }*/ + if dma_just_enabled_old[i] && self.io_registers.dma_just_enabled[i] { + self.io_registers.dma_inc_delay_counter(i as u8, 1); + } } + //if dma_waiting && !dma_ready { + //} cycles } pub fn dispatch_arm(&mut self) -> u8 { - let opcode = self.get_opcode(); + let (opcode, mut cycles) = self.get_opcode(); let opcode_idx = (opcode >> 25) & 0x7; self.r.pc = self.r.pc.wrapping_add(4); @@ -2354,22 +3120,28 @@ impl GameboyAdvance { // TODO: some instructions can't be skipped, handle those if opcode == 0 { //self.r.pc = self.r.pc.wrapping_add(4); - return 4; + return 4 + cycles; } trace!("opcode: {:032b} at 0x{:X}", opcode, self.r.pc - 4); let cond = Cond::from_u8(((opcode >> 28) & 0xF) as u8); if !self.cond_should_execute(cond) { trace!("Skipped!"); - return 1; + return 1 + cycles; } - //if self.r.pc != (0x08001CF0 - 4){ /* - let mut reg_str = String::new(); - for i in 0..15 { - reg_str += &format!("r{}=0x{:X}, ", i, self.r[i as u8]); + //if self.r.pc != (0x08001CF0 - 4){ + let mut reg_str = String::new(); + for i in 0..15 { + reg_str += &format!("r{}=0x{:X}, ", i, self.r[i as u8]); + } + use std::hash::Hasher; + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + for i in 0..0x18000 { + hasher.write_u8(self.vram[i]); } - println!("pc=0x{:X}: regs={}", self.r.pc + 4, reg_str); - */ + let hash = hasher.finish(); + println!("pc=0x{:X}: regs={} vram_hash={:X}", self.r.pc + 4, reg_str, hash); + */ /* } else { std::process::exit(0); @@ -2377,10 +3149,11 @@ impl GameboyAdvance { */ if (opcode >> 8) & 0xF_FFFF == 0b0001_0010_1111_1111_1111 { - return self.dispatch_branch_and_exchange(opcode); + cycles += self.dispatch_branch_and_exchange(opcode); + return cycles; } - let cycles = match opcode_idx { + cycles += match opcode_idx { 0b101 => self.dispatch_branch(opcode), 0b000 => { let multiply_end = ((opcode >> 4) & 0xF) == 0b1001; @@ -2427,7 +3200,8 @@ impl GameboyAdvance { if next_bit { self.dispatch_software_interrupt(opcode) } else { - todo!("SWI with next bit not set"); + self.dispatch_software_interrupt(opcode) + //todo!("SWI with next bit not set"); } } //0b111 => self.dispatch_codata_op(opcode), @@ -2439,24 +3213,29 @@ impl GameboyAdvance { } pub fn dispatch_thumb(&mut self) -> u8 { - let opcode = self.get_thumb_opcode(); + let (opcode, mut cycles) = self.get_thumb_opcode(); let opcode_idx = (opcode >> 13) & 0x7; self.r.pc += 2; /* - let mut reg_str = String::new(); - for i in 0..15 { - reg_str += &format!("r{}=0x{:X}, ", i, self.r[i as u8]); + let mut reg_str = String::new(); + for i in 0..15 { + reg_str += &format!("r{}=0x{:X}, ", i, self.r[i as u8]); + } + use std::hash::Hasher; + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + for i in 0..0x18000 { + hasher.write_u8(self.vram[i]); } - println!("THUMB pc=0x{:X}: regs={}", self.r.pc + 2, reg_str); - */ - + let hash = hasher.finish(); + println!("THUMB pc=0x{:X}: regs={} vram_hash={:X}", self.r.pc + 2, reg_str, hash); + */ if opcode == 0 { return 4; } trace!("THUMB opcode: {:016b} at 0x{:X}", opcode, self.r.pc - 2); - let cycles = match opcode_idx { + cycles += match opcode_idx { 0b000 => self.dispatch_thumb_shift_add_sub(opcode), 0b001 => self.dispatch_thumb_imm(opcode), 0b010 => { @@ -2696,7 +3475,7 @@ impl GameboyAdvance { let pc = (self.r.pc + 2) & !2; let addr = pc + nn as u32; - let o = self.get_mem32(addr); + let o = self.get_mem32(addr, false); trace!("LDR r{}, [PC, #{} (0x{:X})] (0x{:X})", rd, nn, addr, o.0); self.r[rd] = o.0; @@ -2792,7 +3571,7 @@ impl GameboyAdvance { match sub_op_idx { 0b00 => { - trace!("STR r{}, [r{}, r{}]", rd, rb, ro); + trace!("STR r{} (0x{:X}), [r{}, r{}]", rd, self.r[rd], rb, ro); let o = self.set_mem32(self.r[rb].wrapping_add(self.r[ro]), self.r[rd]); 1 + o } @@ -2803,7 +3582,7 @@ impl GameboyAdvance { } 0b10 => { trace!("LDR r{}, [r{}, r{}]", rd, rb, ro); - let o = self.get_mem32(self.r[rb].wrapping_add(self.r[ro])); + let o = self.get_mem32(self.r[rb].wrapping_add(self.r[ro]), false); self.r[rd] = o.0; 2 + o.1 } @@ -2838,13 +3617,13 @@ impl GameboyAdvance { } 0b10 => { trace!("LDRH r{}, [r{}, r{}]", rd, rb, ro); - let o = self.get_mem16(self.r[rb].wrapping_add(self.r[ro])); + let o = self.get_mem16(self.r[rb].wrapping_add(self.r[ro]), false); self.r[rd] = o.0 as u32; 2 + o.1 } 0b11 => { trace!("LDSH r{}, [r{}, r{}]", rd, rb, ro); - let o = self.get_mem16(self.r[rb].wrapping_add(self.r[ro])); + let o = self.get_mem16(self.r[rb].wrapping_add(self.r[ro]), false); // TODO: double check that this sign extends properly self.r[rd] = (o.0 as i16) as i32 as u32; 2 + o.1 @@ -2861,7 +3640,7 @@ impl GameboyAdvance { if sub_opcode { // LDRH trace!("LDRH r{}, [r{}, r{}]", rd, rb, offset); - let o = self.get_mem16(self.r[rb] + (offset * 2)); + let o = self.get_mem16(self.r[rb] + (offset * 2), false); self.r[rd] = o.0 as u32; 2 + o.1 } else { @@ -2880,25 +3659,32 @@ impl GameboyAdvance { match sub_op_idx { 0b00 => { - trace!("STR r{}, [r{}, #{}]", rd, rb, offset); + trace!( + "STR r{} (0x{:X}), [r{} (0x{:X}), #{:X}]", + rd, + self.r[rd], + rb, + self.r[rb], + offset + ); let o = self.set_mem32(self.r[rb].wrapping_add(offset * 4), self.r[rd]); // 2N? 1 + o } 0b01 => { - trace!("LDR r{}, [r{}, #{}]", rd, rb, offset); - let o = self.get_mem32(self.r[rb].wrapping_add(offset * 4)); + trace!("LDR r{}, [r{}, #{:X}]", rd, rb, offset); + let o = self.get_mem32(self.r[rb].wrapping_add(offset * 4), false); self.r[rd] = o.0; 2 + o.1 } 0b10 => { - trace!("STRB r{}, [r{}, #{}]", rd, rb, offset); + trace!("STRB r{}, [r{}, #{:X}]", rd, rb, offset); let o = self.set_mem8(self.r[rb].wrapping_add(offset), self.r[rd] as u8); // 2N? 1 + o } 0b11 => { - trace!("LDRB r{}, [r{}, #{}]", rd, rb, offset); + trace!("LDRB r{}, [r{}, #{:X}]", rd, rb, offset); // TODO: do we clear the upper 24 bits here? let o = self.get_mem8(self.r[rb].wrapping_add(offset)); self.r[rd] = o.0 as u32; @@ -2922,7 +3708,7 @@ impl GameboyAdvance { for i in 0..8 { if r_list & (1 << i) != 0 { cycles += 2; - let o = self.get_mem32(self.r[rb]); + let o = self.get_mem32(self.r[rb], true); self.r[rb] = self.r[rb].wrapping_add(4); self.r[i as u8] = o.0; cycles += o.1; @@ -2949,7 +3735,7 @@ impl GameboyAdvance { if subop { trace!("LDR r{}, [SP, #{}]", rd, nn); - let o = self.get_mem32(self.r.sp() + (nn * 4)); + let o = self.get_mem32(self.r.sp() + (nn * 4), false); self.r[rd] = o.0; o.1 + 2 } else { @@ -2986,7 +3772,7 @@ impl GameboyAdvance { for i in 0..8 { if r_list & (1 << i) != 0 { - let o = self.get_mem32(self.r.sp()); + let o = self.get_mem32(self.r.sp(), true); *self.r.sp_mut() += 4; self.r[i as u8] = o.0; //println!("popping value 0x{:X} to r{}", o.0, i); @@ -2995,7 +3781,7 @@ impl GameboyAdvance { } } if pc_lr { - let o = self.get_mem32(self.r.sp()); + let o = self.get_mem32(self.r.sp(), true); //println!("popping PC ({:X}) to 0x{:X}", self.r.sp(), o.0 & !1); *self.r.sp_mut() += 4; self.r.pc = o.0 & !1; @@ -3193,7 +3979,7 @@ impl GameboyAdvance { if self.master_interrupts_enabled() { self.r.set_svc_mode(); *self.r.lr_mut() = self.r.pc; - *self.r.get_spsr_mut() = self.r.cpsr; + //*self.r.get_spsr_mut() = self.r.cpsr; self.r.set_thumb(false); self.r.cpsr_disable_irq(); self.r.pc = 0x08; @@ -3206,7 +3992,7 @@ impl GameboyAdvance { if self.master_interrupts_enabled() { self.r.set_svc_mode(); *self.r.lr_mut() = self.r.pc | 0; - *self.r.get_spsr_mut() = self.r.cpsr; + //*self.r.get_spsr_mut() = self.r.cpsr; self.r.set_thumb(false); self.r.cpsr_disable_irq(); self.r.pc = 0x08; @@ -3365,6 +4151,12 @@ impl GameboyAdvance { } else { true }); + if matches!(sub_opcode, 0xA | 0xB | 0x8 | 0x9) && (rd != 0xF && rd != 0) { + println!("{:b}", opcode); + println!("pc={:X}", self.r.pc); + dbg!(sub_opcode, s, imm, rn, rd); + } + debug_assert!(if matches!(sub_opcode, 0xA | 0xB | 0x8 | 0x9) { rd == 0xF || rd == 0 } else { @@ -3786,7 +4578,7 @@ impl GameboyAdvance { self.r[rd] = o.0 as u32; cycles += self.set_mem8(addr, rm_val as u8); } else { - let o = self.get_mem32(addr); + let o = self.get_mem32(addr, false); cycles += o.1; self.r[rd] = o.0; cycles += self.set_mem32(addr, rm_val); @@ -3926,7 +4718,7 @@ impl GameboyAdvance { (((opcode >> 8) & 0xF) << 4) | (opcode & 0xF) } else { let rm = (opcode & 0xF) as u8; - debug_assert_ne!(rm, 0xF, "Data transfer from PC"); + debug_assert_ne!(rm, 0xF, "Data transfer from PC at pc={:X}", self.r.pc); self.r[(opcode & 0xF) as u8] }; let sub_opcode = (opcode >> 5) & 0x3; @@ -3947,7 +4739,7 @@ impl GameboyAdvance { (true, 0b00) => todo!("reserved"), (true, 0b01) => { trace!("LDRH r{} = [r{} + 0x{:X}]", rd, rn, offset); - let o = self.get_mem16(base_val); + let o = self.get_mem16(base_val, false); self.r[rd] = o.0 as u32; cycles = 2 + o.1; } @@ -3959,7 +4751,7 @@ impl GameboyAdvance { } (true, 0b11) => { trace!("LDRSH r{} = [r{} + 0x{:X}]", rd, rn, offset); - let o = self.get_mem16(base_val); + let o = self.get_mem16(base_val, false); self.r[rd] = (o.0 as i16) as i32 as u32; cycles = 2 + o.1; } @@ -3971,8 +4763,8 @@ impl GameboyAdvance { } (false, 0b10) => { trace!("LDRD r{} = [r{} + 0x{:X}]", rd, rn, offset); - let o1 = self.get_mem32(base_val); - let o2 = self.get_mem32(base_val + 4); + let o1 = self.get_mem32(base_val, false); + let o2 = self.get_mem32(base_val + 4, true); self.r[rd] = o1.0; self.r[rd + 1] = o2.0; cycles = 2 + o1.1 + o2.1; @@ -4064,11 +4856,11 @@ impl GameboyAdvance { } else { let o = if val & 0x3 != 0 { let shift_amt = (val & 0x3) << 3; - let o = self.get_mem32(val & !0x3); + let o = self.get_mem32(val & !0x3, true); let v = BarrelShifter::ror(o.0, shift_amt, Some(&mut self.r)); (v, o.1) } else { - self.get_mem32(val) + self.get_mem32(val, true) }; trace!( "LDR r{} = mem[r{} (0x{:X})] (0x{:X})", @@ -4184,7 +4976,7 @@ impl GameboyAdvance { if p { base = base.wrapping_add(4); } - let o = self.get_mem32(base); + let o = self.get_mem32(base, true); //println!("Loading r{} (0x{:X}) from 0x{:X}", i, o.0, base); self.r[i as u8] = o.0; if i == 15 && load_psr { @@ -4245,7 +5037,6 @@ impl GameboyAdvance { pub fn dispatch_branch_and_exchange(&mut self, opcode: u32) -> u8 { let subopcode = (opcode >> 4) & 0xF; let op_reg = opcode & 0xF; - debug_assert!(op_reg < 15); let mut op = self.r[op_reg as u8]; if op_reg == 15 { op += 4; @@ -4314,7 +5105,10 @@ impl crate::io::graphics::renderer::InputReceiver for GameboyAdvance { self.io_registers[0x130] &= !(bit as u8); } let keycnt = self.io_registers.get_mem16(0x4000132); - if self.keypad_interrupt_enabled() && (keycnt >> 14) & 1 == 1 { + if self.keypad_interrupt_enabled() + && self.master_interrupts_enabled() + && (keycnt >> 14) & 1 == 1 + { let buttons = self.io_registers.get_mem16(0x4000130) & 0x3F; if (keycnt >> 15) == 1 { // AND mode diff --git a/src/io/applicationstate.rs b/src/io/applicationstate.rs index 78e4511..bc5c4c7 100644 --- a/src/io/applicationstate.rs +++ b/src/io/applicationstate.rs @@ -6,6 +6,7 @@ use std; use crate::cpu; +use crate::gba::DmaStartTiming; use crate::io::constants::*; use crate::io::deferred_renderer::deferred_renderer_draw_scanline; @@ -31,8 +32,10 @@ pub struct ApplicationState { cycles_per_second: u64, /// counts cycles since last sound update sound_cycles: u64, + /// counts cycles since last GBA sound update + gba_sound_cycles: u64, _screenshot_frame_num: Wrapping, - gba_timers: [u16; 4], + gba_timers: [u32; 4], debug_gba_last_seen_ppu_bg_mode: Option, pub renderer: Box, } @@ -55,6 +58,7 @@ impl ApplicationState { div_timer_cycles: 0, cycles_per_second: CPU_CYCLES_PER_SECOND, sound_cycles: 0, + gba_sound_cycles: 0, _screenshot_frame_num: Wrapping(0), gba_timers: [0, 0, 0, 0], debug_gba_last_seen_ppu_bg_mode: None, @@ -72,17 +76,18 @@ impl ApplicationState { */ pub fn step_gba(&mut self) { - let cycles_per_frame = 83776 + (160 * /*1232*/ 960); - let audio_timing_cycles = cycles_per_frame / 512; + //let cycles_per_frame = 83776 + (160 * /*1232*/ 960); + // TODO: derive this more properly + let cycles_per_frame = 279709; + let audio_timing_cycles = (cycles_per_frame * 60) / 512; + let gba_audio_timing_cycles = 512; //(cycles_per_frame * 60) / 32768; + //let audio_timing_cycles = (cycles_per_frame * 60) / 32768; + let mut fifo_a_sample_counter = 0; + let mut fifo_b_sample_counter = 0; + //let audio_timing_cycles = (cycles_per_frame * 4) / 32768; let mut cycles = 0; let mut frame = [[(0u8, 0u8, 0u8); GBA_SCREEN_WIDTH]; GBA_SCREEN_HEIGHT]; let mut y = 0; - let timer_prescalers = [ - self.gba.io_registers.timer0_prescaler(), - self.gba.io_registers.timer1_prescaler(), - self.gba.io_registers.timer2_prescaler(), - self.gba.io_registers.timer3_prescaler(), - ]; #[derive(Debug, Clone, Copy)] enum GameBoyAdvanceMode { @@ -93,25 +98,80 @@ impl ApplicationState { let mut hblank_cycles = 0; // we need this? self.gba.ppu_set_vblank(false); + self.gba.ppu_set_readonly_vcounter(0); + if y == self.gba.ppu_vcounter_setting() + && self.gba.ppu_vcounter_irq_enabled() + && self.gba.master_interrupts_enabled() + { + self.gba.set_lcdc_vcounter_interrupt(true); + } while y < 227 { let cycles_from_opcode = self.gba.dispatch() as u64; - cycles += cycles_from_opcode * 3; - hblank_cycles += cycles_from_opcode * 3; + cycles += cycles_from_opcode; // * 2; + hblank_cycles += cycles_from_opcode; // * 2; for (i, timer) in self.gba_timers.iter_mut().enumerate() { if !self.gba.io_registers.timer_enabled(i as u8) { continue; } - *timer = timer.saturating_add(cycles_from_opcode as u16); - if *timer >= timer_prescalers[i] { - *timer -= timer_prescalers[i]; - if self.gba.io_registers.increment_timer(i as u8) - && self.gba.io_registers.timer_irq_enabled(i as u8) - && self.gba.master_interrupts_enabled() - { - //dbg!("TIMER INTERRUPT!"); - self.gba.set_timer_interrupt(i as u8, true); + let timer_prescaler = match i { + 0 => self.gba.io_registers.timer0_prescaler() as u32, + 1 => self.gba.io_registers.timer1_prescaler() as u32, + 2 => self.gba.io_registers.timer2_prescaler() as u32, + 3 => self.gba.io_registers.timer3_prescaler() as u32, + _ => unreachable!(), + }; + //*timer = timer.saturating_add(cycles_from_opcode as u32); + *timer += cycles_from_opcode as u32; + while *timer >= timer_prescaler { + *timer -= timer_prescaler; + if self.gba.io_registers.increment_timer(i as u8) { + //dbg!(i, timer_prescalers[i]); + // NOTE: this condition may be incorrect, + // we may want to still `inc_note` even if DMA is enabled. + // This should be a very small edge case though and if this is, in fact, + // incorerct then we would just expect to drop at most 32 samples which would + // be hard to notice. + if self.gba.io_registers.dma1_enabled || self.gba.io_registers.dma2_enabled + { + if i == 1 && self.gba.io_registers.sound_a_timer1 { + //debug_assert!(self.gba.io_registers.apu.gba_sound_a_enabled.0 || self.gba.io_registers.apu.gba_sound_a_enabled.1); + self.gba.io_registers.apu.gba_fifo_a.inc_note(); + fifo_a_sample_counter += 1; + if self.gba.io_registers.apu.gba_fifo_a.ready_for_more_data() { + self.gba.io_registers.trigger_sound_a_dma(); + } + } else if i == 0 && !self.gba.io_registers.sound_a_timer1 { + //debug_assert!(self.gba.io_registers.apu.gba_sound_a_enabled.0 || self.gba.io_registers.apu.gba_sound_a_enabled.1); + self.gba.io_registers.apu.gba_fifo_a.inc_note(); + fifo_a_sample_counter += 1; + if self.gba.io_registers.apu.gba_fifo_a.ready_for_more_data() { + self.gba.io_registers.trigger_sound_a_dma(); + } + } + if i == 1 && self.gba.io_registers.sound_b_timer1 { + //debug_assert!(self.gba.io_registers.apu.gba_sound_b_enabled.0 || self.gba.io_registers.apu.gba_sound_b_enabled.1); + self.gba.io_registers.apu.gba_fifo_b.inc_note(); + fifo_b_sample_counter += 1; + if self.gba.io_registers.apu.gba_fifo_b.ready_for_more_data() { + self.gba.io_registers.trigger_sound_b_dma(); + } + } else if i == 0 && !self.gba.io_registers.sound_b_timer1 { + //debug_assert!(self.gba.io_registers.apu.gba_sound_b_enabled.0 || self.gba.io_registers.apu.gba_sound_b_enabled.1); + self.gba.io_registers.apu.gba_fifo_b.inc_note(); + fifo_b_sample_counter += 1; + if self.gba.io_registers.apu.gba_fifo_b.ready_for_more_data() { + self.gba.io_registers.trigger_sound_b_dma(); + } + } + } + if self.gba.io_registers.timer_irq_enabled(i as u8) + && self.gba.master_interrupts_enabled() + { + //dbg!("TIMER INTERRUPT!"); + self.gba.set_timer_interrupt(i as u8, true); + } } } } @@ -123,6 +183,27 @@ impl ApplicationState { self.gba.ppu_set_hblank(false); y += 1; self.gba.ppu_set_readonly_vcounter(y); + if y == self.gba.ppu_vcounter_setting() + && self.gba.ppu_vcounter_irq_enabled() + && self.gba.master_interrupts_enabled() + { + self.gba.set_lcdc_vcounter_interrupt(true); + } + if y == 160 { + self.gba.ppu_set_vblank(true); + if self.gba.ppu_vblank_irq_enabled() && self.gba.master_interrupts_enabled() + { + self.gba.set_lcdc_vblank_interrupt(true); + } + self.gba.io_registers.bg2_rotation.cached_x = + self.gba.io_registers.bg2_rotation.x; + self.gba.io_registers.bg2_rotation.cached_y = + self.gba.io_registers.bg2_rotation.y; + self.gba.io_registers.bg3_rotation.cached_x = + self.gba.io_registers.bg3_rotation.x; + self.gba.io_registers.bg3_rotation.cached_y = + self.gba.io_registers.bg3_rotation.y; + } } } @@ -143,15 +224,14 @@ impl ApplicationState { in_hblank = true; self.gba.ppu_set_hblank(true); - if self.gba.ppu_hblank_irq_enabled() { + if self.gba.ppu_hblank_irq_enabled() && self.gba.master_interrupts_enabled() { self.gba.set_lcdc_hblank_interrupt(true); } - if y == self.gba.ppu_vcounter_setting() && self.gba.ppu_vcounter_irq_enabled() { - self.gba.set_lcdc_vcounter_interrupt(true); - } + + /* if y == 160 { self.gba.ppu_set_vblank(true); - if self.gba.ppu_vblank_irq_enabled() { + if self.gba.ppu_vblank_irq_enabled() && self.gba.master_interrupts_enabled() { self.gba.set_lcdc_vblank_interrupt(true); } self.gba.io_registers.bg2_rotation.cached_x = @@ -163,13 +243,70 @@ impl ApplicationState { self.gba.io_registers.bg3_rotation.cached_y = self.gba.io_registers.bg3_rotation.y; } - //y += 1; + */ + + if y >= 2 && y <= 163 { + if self.gba.io_registers.dma3_enabled { + let dma3 = self.gba.io_registers.dma3(); + if dma3.start_timing == DmaStartTiming::Special { + self.gba.io_registers.trigger_dma(3); + } + } + } + if self.gba.io_registers.dma1_enabled { + let dma1 = self.gba.io_registers.dma1(); + if dma1.start_timing == DmaStartTiming::HBlank { + self.gba.io_registers.trigger_dma(1); + } + } } self.sound_cycles += cycles_from_opcode as u64; if self.sound_cycles >= audio_timing_cycles as u64 { self.renderer.audio_step(&self.gba.io_registers.apu); self.sound_cycles -= audio_timing_cycles as u64; + + //self.gba_sound_cycles -= gba_audio_timing_cycles as u64; + //let rate = 4000. / 512.; + let rate = 64. * 8.; // * 18.;//2000.;// / 512.; + self.gba.io_registers.apu.gba_fifo_a_sample_rate = + fifo_a_sample_counter as f32 * rate; // / 0.00025; + self.gba.io_registers.apu.gba_fifo_b_sample_rate = + fifo_b_sample_counter as f32 * rate; // / 0.00025; + if fifo_b_sample_counter != 0 + || self.gba.io_registers.apu.gba_fifo_b_sample_rate != 0. + { + /* + dbg!( + fifo_b_sample_counter, + self.gba.io_registers.apu.gba_fifo_b_sample_rate + ); + */ + } + self.renderer.gba_audio_step(&self.gba.io_registers.apu); + self.gba.io_registers.apu.gba_fifo_a.clear_sound_buffer(); + fifo_a_sample_counter = 0; + fifo_b_sample_counter = 0; + self.gba.io_registers.apu.gba_fifo_a.reset_flag = false; + self.gba.io_registers.apu.gba_fifo_b.reset_flag = false; + } + /* + self.gba_sound_cycles += cycles_from_opcode as u64; + if self.gba_sound_cycles >= gba_audio_timing_cycles as u64 { + self.gba_sound_cycles -= gba_audio_timing_cycles as u64; + //let rate = 4000. / 512.; + let rate = 64.;//4000.;// / 512.; + self.gba.io_registers.apu.gba_fifo_a_sample_rate = fifo_a_sample_counter as f32 * rate; // / 0.00025; + self.gba.io_registers.apu.gba_fifo_b_sample_rate = fifo_b_sample_counter as f32 * rate; // / 0.00025; + if fifo_b_sample_counter != 0 || self.gba.io_registers.apu.gba_fifo_b_sample_rate != 0. { + //dbg!(fifo_b_sample_counter, self.gba.io_registers.apu.gba_fifo_b_sample_rate); + } + fifo_a_sample_counter = 0; + fifo_b_sample_counter = 0; + self.renderer.gba_audio_step(&self.gba.io_registers.apu); + self.gba.io_registers.apu.gba_fifo_a.clear_sound_buffer(); + self.gba.io_registers.apu.gba_fifo_b.clear_sound_buffer(); } + */ } self.gba.ppu_set_vblank(false); if Some(self.gba.ppu_bg_mode()) != self.debug_gba_last_seen_ppu_bg_mode { diff --git a/src/io/deferred_renderer_gba.rs b/src/io/deferred_renderer_gba.rs index bb2f367..bfcc252 100644 --- a/src/io/deferred_renderer_gba.rs +++ b/src/io/deferred_renderer_gba.rs @@ -1,6 +1,221 @@ use crate::gba::{self, PpuBgControl}; use crate::io::constants::*; +#[derive(Debug, Copy, Clone)] +#[repr(transparent)] +pub struct Rgb15(u16); + +impl Rgb15 { + pub const fn new(red: u8, green: u8, blue: u8) -> Self { + let red = red >> 3; + let green = green >> 3; + let blue = blue >> 3; + let color = (blue as u16) << 10 | (green as u16) << 5 | red as u16; + Rgb15(color) + } + pub const fn from_bits(bits: u16) -> Self { + Rgb15(bits & 0x7FFF) + } + pub const fn from_lo_hi(lo: u8, hi: u8) -> Self { + let color = ((hi as u16) << 8) | lo as u16; + Rgb15(color & 0x7FFF) + } + pub const fn transparent() -> Self { + Rgb15(0x8000) + } + pub const fn black() -> Self { + Rgb15(0) + } + pub const fn white() -> Self { + Rgb15(0x7FFF) + } + pub const fn is_transparent(self) -> bool { + self.0 & 0x8000 != 0 + } + pub const fn to_rgb(self) -> (u8, u8, u8) { + let red = (self.0 & 0x1F) as u8; + let green = ((self.0 >> 5) & 0x1F) as u8; + let blue = ((self.0 >> 10) & 0x1F) as u8; + (red << 3, green << 3, blue << 3) + } + // TODO: to_rgba with specific alpha values determined by blending mode +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum ObjectMode { + Normal, + SemiTransparent, + ObjectWindow, + Prohibited, +} + +impl ObjectMode { + pub fn from_bits(bits: u8) -> Self { + match bits { + 0 => ObjectMode::Normal, + 1 => ObjectMode::SemiTransparent, + 2 => ObjectMode::ObjectWindow, + 3 => ObjectMode::Prohibited, + _ => panic!("Invalid object mode: {}", bits), + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum ObjectShape { + Square, + Horizontal, + Vertical, + Prohibited, +} + +impl ObjectShape { + pub fn from_bits(bits: u8) -> Self { + match bits { + 0 => ObjectShape::Square, + 1 => ObjectShape::Horizontal, + 2 => ObjectShape::Vertical, + 3 => ObjectShape::Prohibited, + _ => panic!("Invalid object shape: {}", bits), + } + } +} + +#[derive(Debug, Copy, Clone)] +#[repr(transparent)] +pub struct Object(u64); + +impl Object { + pub fn from_bits(bits: u64) -> Self { + Object(bits) + } + pub fn from_bytes(bytes: [u8; 8]) -> Self { + Object(u64::from_le_bytes(bytes)) + } + pub fn y(self) -> i32 { + let mut val = (self.0 & 0xFF) as i32; + if val >= (GBA_SCREEN_HEIGHT as i32) { + val -= 1 << 8; + } + val + } + pub fn x(self) -> i32 { + let mut val = ((self.0 >> 16) & 0x1FF) as i32; + if val >= (GBA_SCREEN_WIDTH as i32) { + val -= 1 << 9; + } + val + } + pub fn rotation_enabled(self) -> bool { + self.0 & 0x100 != 0 + } + pub fn double_size(self) -> bool { + self.0 & 0x200 != 0 + } + pub fn object_disabled(self) -> bool { + (self.0 >> 8) & 1 == 0 && (self.0 >> 9) & 1 == 1 + } + pub fn object_mode(self) -> ObjectMode { + ObjectMode::from_bits(((self.0 >> 10) & 0x3) as u8) + } + pub fn mosaic(self) -> bool { + (self.0 >> 12) & 1 == 1 + } + pub fn full_color(self) -> bool { + (self.0 >> 13) & 1 == 1 + } + pub fn shape(self) -> ObjectShape { + ObjectShape::from_bits(((self.0 >> 14) & 0x3) as u8) + } + pub fn rotation_scaling_param_selection(self) -> u8 { + ((self.0 >> (16 + 9)) & 0x1F) as u8 + } + pub fn horizontal_flip(self) -> bool { + (self.0 >> (16 + 12)) & 1 == 1 + } + pub fn vertical_flip(self) -> bool { + (self.0 >> (16 + 13)) & 1 == 1 + } + pub fn size(self) -> (u8, u8) { + let shape = self.shape(); + let size_idx = ((self.0 >> (16 + 14)) & 0x3) as u8; + match (size_idx, shape) { + (0, ObjectShape::Square) => (8, 8), + (0, ObjectShape::Horizontal) => (16, 8), + (0, ObjectShape::Vertical) => (8, 16), + (1, ObjectShape::Square) => (16, 16), + (1, ObjectShape::Horizontal) => (32, 8), + (1, ObjectShape::Vertical) => (8, 32), + (2, ObjectShape::Square) => (32, 32), + (2, ObjectShape::Horizontal) => (32, 16), + (2, ObjectShape::Vertical) => (16, 32), + (3, ObjectShape::Square) => (64, 64), + (3, ObjectShape::Horizontal) => (64, 32), + (3, ObjectShape::Vertical) => (32, 64), + (_, ObjectShape::Prohibited) => panic!("Invalid object shape: {:?}", shape), + _ => unreachable!(), + } + } + pub fn character_name(self) -> u16 { + ((self.0 >> 32) & 0x3FF) as u16 + } + pub fn priority(self) -> u8 { + ((self.0 >> (32 + 10)) & 0x3) as u8 + } + pub fn palette_number(self) -> u8 { + ((self.0 >> (32 + 12)) & 0xF) as u8 + } +} + +pub struct ObjectIter<'a> { + gba: &'a gba::GameboyAdvance, + idx: usize, +} + +impl<'a> ObjectIter<'a> { + pub fn new(gba: &'a gba::GameboyAdvance) -> Self { + ObjectIter { gba, idx: 0 } + } +} + +impl<'a> std::iter::Iterator for ObjectIter<'a> { + type Item = Object; + + fn next(&mut self) -> Option { + if self.idx >= 128 { + None + } else { + let obj = Object::from_bytes([ + self.gba.oam[(self.idx * 8) + 0], + self.gba.oam[(self.idx * 8) + 1], + self.gba.oam[(self.idx * 8) + 2], + self.gba.oam[(self.idx * 8) + 3], + self.gba.oam[(self.idx * 8) + 4], + self.gba.oam[(self.idx * 8) + 5], + 0, + 0, + ]); + self.idx += 1; + Some(obj) + } + } + + fn size_hint(&self) -> (usize, Option) { + (128 - self.idx, Some(128 - self.idx)) + } +} + +impl<'a> std::iter::ExactSizeIterator for ObjectIter<'a> {} + +fn get_object_rotation_data(gba: &gba::GameboyAdvance, index: u8) -> (i32, i32, i32, i32) { + let index = 0x6 + (index as usize * 32); + let pa = (((gba.oam[index + 1] as i16) << 8) | gba.oam[index] as i16) as i32; + let pb = (((gba.oam[index + 1 + 8] as i16) << 8) | gba.oam[index + 8] as i16) as i32; + let pc = (((gba.oam[index + 1 + 16] as i16) << 8) | gba.oam[index + 16] as i16) as i32; + let pd = (((gba.oam[index + 1 + 24] as i16) << 8) | gba.oam[index + 24] as i16) as i32; + (pa, pb, pc, pd) +} + pub fn deferred_renderer_draw_rotated_bg( y: u8, gba: &mut gba::GameboyAdvance, @@ -9,13 +224,14 @@ pub fn deferred_renderer_draw_rotated_bg( pc: i16, bg_x: i32, bg_y: i32, -) -> [(u8, u8, u8); GBA_SCREEN_WIDTH] { - let mut bg_pixels = [(0u8, 0u8, 0u8); GBA_SCREEN_WIDTH]; +) -> [Rgb15; GBA_SCREEN_WIDTH] { + let mut bg_pixels = [Rgb15::transparent(); GBA_SCREEN_WIDTH]; // TODO: for bg3 this should be + 0x600 let map_base_ptr = bg_control.screen_base_block as u32 * 0x800; //+ 0x400; let tile_base_ptr = bg_control.character_base_block as u32 * 0x4000; for x in 0..GBA_SCREEN_WIDTH { + // TODO: review use of x/y here let adj_y = (bg_y + (x as i32) * pc as i32) >> 8; let adj_x = (bg_x + (x as i32) * pa as i32) >> 8; @@ -23,7 +239,9 @@ pub fn deferred_renderer_draw_rotated_bg( || adj_x >= GBA_SCREEN_WIDTH as i32 || adj_y < 0 || adj_y >= GBA_SCREEN_HEIGHT as i32 + //|| y != adj_y as u8 { + bg_pixels[x as usize] = Rgb15::transparent(); continue; } @@ -57,13 +275,15 @@ pub fn deferred_renderer_draw_rotated_bg( let tile_byte_start = tile_line_start + (nth_pixel >> 1) as usize; let color_8bit = gba.vram[tile_byte_start]; + if color_8bit == 0 { + bg_pixels[x as usize] = Rgb15::transparent(); + continue; + } + let color_lo = gba.obj_palette_ram[color_8bit as usize * 2]; let color_hi = gba.obj_palette_ram[(color_8bit as usize * 2) + 1]; - let red = color_lo & 0x1F; - let green = ((color_hi & 0x3) << 3) | (color_lo >> 5); - let blue = (color_hi >> 2) & 0x1F; - bg_pixels[x as usize] = (red << 3, green << 3, blue << 3); + bg_pixels[x as usize] = Rgb15::from_lo_hi(color_lo, color_hi); } bg_pixels @@ -72,8 +292,8 @@ pub fn deferred_renderer_draw_rotated_bg( pub fn deferred_renderer_draw_gba_bg4( y: u8, gba: &mut gba::GameboyAdvance, -) -> [(u8, u8, u8); GBA_SCREEN_WIDTH] { - let mut bg_pixels = [(0u8, 0u8, 0u8); GBA_SCREEN_WIDTH]; +) -> [Rgb15; GBA_SCREEN_WIDTH] { + let mut bg_pixels = [Rgb15::transparent(); GBA_SCREEN_WIDTH]; let bg2_control = gba.ppu_bg2_control(); let base = if gba.ppu_frame_select() { 0xA000 } else { 0 }; @@ -84,20 +304,23 @@ pub fn deferred_renderer_draw_gba_bg4( } } */ - /* - - for i in 0x14000..=0x17FFF { - if gba.vram[i] != 0 { - panic!("mode 4 is using objects! at {} 0x{:X}", i, gba.vram[i]) - } - } - */ let pa = gba.io_registers.bg2_rotation.pa; let pc = gba.io_registers.bg2_rotation.pc; let bg_x = gba.io_registers.bg2_rotation.cached_x; let bg_y = gba.io_registers.bg2_rotation.cached_y; + /* + use std::hash::Hasher; + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + for i in 0..0x02000 { + hasher.write_u8(gba.vram[i]); + } + dbg!(hasher.finish()); + */ + for x in 0..GBA_SCREEN_WIDTH { + // TODO: review this formula + // found evidence that adj_y uses x as well here let adj_y = (bg_y + (x as i32) * pc as i32) >> 8; let adj_x = (bg_x + (x as i32) * pa as i32) >> 8; @@ -106,99 +329,246 @@ pub fn deferred_renderer_draw_gba_bg4( || adj_y < 0 || adj_y >= GBA_SCREEN_HEIGHT as i32 { + bg_pixels[x as usize] = Rgb15::transparent(); continue; } let idx = (adj_y * 240 + adj_x) as usize; + let palette_idx = gba.vram[base + idx] as usize; if palette_idx == 0 { + bg_pixels[x as usize] = Rgb15::transparent(); continue; } let color_lo = gba.obj_palette_ram[palette_idx * 2]; let color_hi = gba.obj_palette_ram[palette_idx * 2 + 1]; - let red = color_lo & 0x1F; - let green = ((color_hi & 0x3) << 3) | (color_lo >> 5); - let blue = (color_hi >> 2) & 0x1F; + bg_pixels[x as usize] = Rgb15::from_lo_hi(color_lo, color_hi); + } + + bg_pixels +} - bg_pixels[x as usize] = (red << 3, green << 3, blue << 3); +pub fn deferred_renderer_draw_gba_mode_3( + y: u8, + gba: &mut gba::GameboyAdvance, +) -> [Rgb15; GBA_SCREEN_WIDTH] { + let mut bg_pixels = [Rgb15::transparent(); GBA_SCREEN_WIDTH]; + //let bg2_control = gba.ppu_bg2_control(); + + let pa = gba.io_registers.bg2_rotation.pa; + let pc = gba.io_registers.bg2_rotation.pc; + let bg_x = gba.io_registers.bg2_rotation.cached_x; + let bg_y = gba.io_registers.bg2_rotation.cached_y; + + for x in 0..GBA_SCREEN_WIDTH { + // TODO: review this formula + let adj_y = (bg_y + (x as i32) * pc as i32) >> 8; + let adj_x = (bg_x + (x as i32) * pa as i32) >> 8; + + if adj_x < 0 + || adj_x >= GBA_SCREEN_WIDTH as i32 + || adj_y < 0 + || adj_y >= GBA_SCREEN_HEIGHT as i32 + { + bg_pixels[x as usize] = Rgb15::transparent(); + continue; + } + + let idx = (adj_y * 240 + adj_x) as usize; + let color_lo = gba.vram[idx * 2]; + let color_hi = gba.vram[idx * 2 + 1]; + + bg_pixels[x as usize] = Rgb15::from_lo_hi(color_lo, color_hi); } bg_pixels } -pub fn deferred_renderer_draw_gba_scanline( +pub fn deferred_renderer_draw_gba_bg3( y: u8, gba: &mut gba::GameboyAdvance, -) -> [(u8, u8, u8); GBA_SCREEN_WIDTH] { - match gba.ppu_bg_mode() { - 2 => { - let pa = gba.io_registers.bg2_rotation.pa; - let pc = gba.io_registers.bg2_rotation.pc; - let bg_x = gba.io_registers.bg2_rotation.cached_x; - let bg_y = gba.io_registers.bg2_rotation.cached_y; - // TODO: also blend with bg 3 - return deferred_renderer_draw_rotated_bg( - y, - gba, - gba.ppu_bg2_control(), - pa, - pc, - bg_x, - bg_y, - ); +) -> [Rgb15; GBA_SCREEN_WIDTH] { + // bg3 doesn't support transparency, use base color here? + let mut bg_pixels = [Rgb15::transparent(); GBA_SCREEN_WIDTH]; + + let pa = gba.io_registers.bg3_rotation.pa; + let pc = gba.io_registers.bg3_rotation.pc; + let bg_x = gba.io_registers.bg3_rotation.cached_x; + let bg_y = gba.io_registers.bg3_rotation.cached_y; + + for x in 0..GBA_SCREEN_WIDTH { + let adj_y = (bg_y + (y as i32) * pc as i32) >> 8; + let adj_x = (bg_x + (x as i32) * pa as i32) >> 8; + + if adj_x < 0 + || adj_x >= GBA_SCREEN_WIDTH as i32 + || adj_y < 0 + || adj_y >= GBA_SCREEN_HEIGHT as i32 + { + bg_pixels[x as usize] = Rgb15::transparent(); + continue; } - 3 => todo!("bg mode 3"), - 4 => return deferred_renderer_draw_gba_bg4(y, gba), - 5 => todo!("bg mode 5"), - _ => (), - } - let mut bg_pixels = [(0u8, 0u8, 0u8); GBA_SCREEN_WIDTH]; - - let scx = gba.ppu_bg0_x_scroll(); - let scy = gba.ppu_bg0_y_scroll(); - let bg0_control = gba.ppu_bg0_control(); - let (screen_x_size_mask, screen_y_size_mask) = - // ASSUMES TEXT MODE DURING EARLY DEVELOPMENT - // TOOD: add support for other mode here - match bg0_control.screen_size { - 0 => (0xFF, 0xFF), - 1 => (0x1FF, 0xFF), - 2 => (0xFF, 0x1FF), - 3 => (0x1FF, 0x1FF), - _ => unreachable!(), - }; - //dbg!(gba.ppu_bg0_control()); - //dbg!(gba.ppu_bg1_control()); - //dbg!(gba.ppu_bg2_control()); - //dbg!(gba.ppu_bg3_control()); - if bg0_control.mosaic { - todo!("Mosaic mode!"); + let idx = (adj_y * 240 + adj_x) as usize; + let color_lo = gba.vram[idx * 2]; + let color_hi = gba.vram[idx * 2 + 1]; + + bg_pixels[x as usize] = Rgb15::from_lo_hi(color_lo, color_hi); + } + + bg_pixels +} + +fn get_full_color_pixel( + gba: &gba::GameboyAdvance, + base_ptr: usize, + tile_num: u16, + (tile_row, tile_col): (u32, u32), + (nth_line, nth_pixel): (u16, u16), + tile_array_width: usize, + obj_palette: bool, +) -> Rgb15 { + let tile_line = nth_line * 8; + let tile_size = 64; + + let tile_start = base_ptr + (tile_num as usize * 32); + let offset = (tile_row as usize * tile_array_width) + tile_col as usize; + let tile_addr = tile_start + (offset * tile_size); + let pixel_addr = tile_addr + tile_line as usize + nth_pixel as usize; + let color_8bit = gba.vram[pixel_addr]; + + if color_8bit == 0 { + return Rgb15::transparent(); } + + let palette_offset = if obj_palette { 0x200 } else { 0 }; + + let color_lo = gba.obj_palette_ram[palette_offset + color_8bit as usize * 2]; + let color_hi = gba.obj_palette_ram[palette_offset + (color_8bit as usize * 2) + 1]; + + Rgb15::from_lo_hi(color_lo, color_hi) +} + +fn get_4bit_color_pixel( + gba: &gba::GameboyAdvance, + base_ptr: usize, + tile_num: u16, + (tile_row, tile_col): (u32, u32), + (nth_line, nth_pixel): (u16, u16), + tile_array_width: usize, + palette_num: u8, + obj_palette: bool, +) -> Rgb15 { + // 16/16 mode + // 4bit palette index so 4 bytes per line = 8 palette indices per line + // 4 bytes per line * 8 lines = 32 bytes per tile + let tile_line = nth_line * 4; + let tile_size = 32; + + let tile_start = base_ptr + (tile_num as usize * 32); + let offset = (tile_row as usize * tile_array_width) + tile_col as usize; + let tile_addr = tile_start + (offset * tile_size); + let pixel_addr = tile_addr + tile_line as usize + (nth_pixel as usize >> 1); + /* + if pixel_addr >= gba.vram.len() { + dbg!(pixel_addr, base_ptr, tile_num, tile_row, tile_col, nth_line, nth_pixel); + return Rgb15::transparent(); + } + */ + let color_4bit = (gba.vram[pixel_addr] >> ((nth_pixel & 0x1) * 4)) & 0xF; + + if color_4bit == 0 { + return Rgb15::transparent(); + } + + let palette_offset = if obj_palette { 0x200 } else { 0 }; + // 2 bytes per color, 16 colors per palette + let palette_start = palette_offset + (palette_num as usize * 16 * 2); + + let color_lo = gba.obj_palette_ram[palette_start + (color_4bit as usize * 2)]; + let color_hi = gba.obj_palette_ram[palette_start + (color_4bit as usize * 2) + 1]; + + Rgb15::from_lo_hi(color_lo, color_hi) +} + +pub fn deferred_renderer_draw_gba_bg( + y: u8, + bg: u8, + bg_control: PpuBgControl, + gba: &mut gba::GameboyAdvance, +) -> [Rgb15; GBA_SCREEN_WIDTH] { + let mut bg_pixels = [Rgb15::transparent(); GBA_SCREEN_WIDTH]; + + let (scx, scy) = gba.ppu_bg_scroll(bg); + let (screen_x_size_mask, screen_y_size_mask) = match bg_control.screen_size { + 0 => (0xFF, 0xFF), + 1 => (0x1FF, 0xFF), + 2 => (0xFF, 0x1FF), + 3 => (0x1FF, 0x1FF), + _ => unreachable!(), + }; + + /* + let mut sbb = match (bg_width, bg_height) { + (256, 256) => 0, + (512, 256) => bg_x / 256, + (256, 512) => bg_y / 256, + (512, 512) => index2d!(u32, bg_x / 256, bg_y / 256, 2), + _ => unreachable!(), + } as u32; + */ + + let mosaic_x = gba.ppu_bg_mosaic_h(); + let mosaic_y = gba.ppu_bg_mosaic_v(); + let adj_y = (y as u16).wrapping_add(scy) as u16 & screen_y_size_mask; + let adj_y = if bg_control.mosaic { + adj_y - (adj_y % mosaic_y as u16) + } else { + adj_y + }; // this address is auto-incremented by 2kb for each background - let map_base_ptr = bg0_control.screen_base_block as u32 * 0x800; - let tile_base_ptr = bg0_control.character_base_block as u32 * 0x4000; + let tile_base_ptr = bg_control.character_base_block as usize * 0x4000; - let tile_row = (adj_y >> 3) as u32; + let tile_row = (adj_y >> 3) as u32 & 0x1F; for x in 0..GBA_SCREEN_WIDTH { let adj_x = (x as u16).wrapping_add(scx) as u16 & screen_x_size_mask; - let tile_col = (adj_x >> 3) as u32; - let idx_into_tile_idx_mem = map_base_ptr + (tile_row * 32 * 2) + (tile_col * 2); + let adj_x = if bg_control.mosaic { + adj_x - (adj_x % mosaic_x as u16) + } else { + adj_x + }; + + let screen_base_block = match bg_control.screen_size { + 0 => 0, + 1 => adj_x >> 8, + 2 => adj_y >> 8, + 3 => ((adj_y >> 8) * 2) + (adj_x >> 8), + _ => unreachable!(), + }; + let sbb = bg_control.screen_base_block as u32 + screen_base_block as u32; + // if width is 512, there's an extra edge case here with SBB selection + let map_base_ptr = sbb * 0x800; + + let tile_col = (adj_x >> 3) as u32 & 0x1F; + let tile_map_idx = (tile_row * 32) + tile_col; + let idx_into_tile_idx_mem = map_base_ptr + (tile_map_idx * 2); let tile_idx_lo = gba.vram[idx_into_tile_idx_mem as usize] as u16; let tile_idx_hi = gba.vram[idx_into_tile_idx_mem as usize + 1] as u16; let tile_num = ((tile_idx_hi & 0x3) << 8) | tile_idx_lo; + let horizontal_flip = (tile_idx_hi & 0x4) != 0; let vertical_flip = (tile_idx_hi & 0x8) != 0; - let palette_num = tile_idx_hi >> 4; + let palette_num = (tile_idx_hi >> 4) as u8; // Lower 3 bits determine which line of the tile we're on let mut nth_line = adj_y & 0x7; // 8 choices for which pixel on the line we're on, so we take 3 bits here let tile_pixel = adj_x & 0x7; // pixels go from MSB to LSB within a tile - let mut nth_pixel = 7 - tile_pixel; + //let mut nth_pixel = 7 - tile_pixel; + let mut nth_pixel = tile_pixel; if vertical_flip { nth_line = 7 - nth_line; } @@ -206,52 +576,424 @@ pub fn deferred_renderer_draw_gba_scanline( nth_pixel = 7 - nth_pixel; } - /* - if tile_num != 0 || palette_num != 0 { - dbg!(adj_x, adj_y, tile_num, palette_num); - panic!("found!"); - } - */ + if bg_control.color_mode { + // TODO: figure out this value + let tile_array_width = 0; + let color = get_full_color_pixel( + gba, + tile_base_ptr, + tile_num, + //(tile_row, tile_col), + (0, 0), + (nth_line, nth_pixel), + tile_array_width, + false, + ); - if bg0_control.color_mode { - todo!() + bg_pixels[x] = color; } else { - // 16/16 mode - // 4bit palette index so 4 bytes per line = 8 palette indices per line - // 4 bytes per line * 8 lines = 32 bytes per tile - let tile_line = nth_line * 4; - - let tile_start = tile_base_ptr as usize + (tile_num as usize * 32); - let tile_line_start = tile_start + tile_line as usize; - let tile_byte_start = tile_line_start + (nth_pixel >> 1) as usize; - let color_4bit = (gba.vram[tile_byte_start] >> ((nth_pixel & 0x1) * 4)) & 0xF; - // HACK: hello world wants this - //let color_4bit = color_4bit + 1; + let tile_ptr = tile_base_ptr + (tile_num as usize * 32); + //let tile_addr = ((gba.vram[tile_ptr] as u16) | ((gba.vram[tile_ptr + 1] as u16) << 8)) as usize; + //let tile_addr = ((gba.vram[tile_ptr] as u16) | ((gba.vram[tile_ptr + 1] as u16) << 8)) as usize; + let pixel_addr = tile_ptr + (nth_line as usize * 4) + (nth_pixel as usize >> 1); + let color_4bit = (gba.vram[pixel_addr] >> ((nth_pixel & 0x1) * 4)) & 0xF; + + if color_4bit == 0 { + continue; + } // 2 bytes per color, 16 colors per palette let palette_start = palette_num as usize * 16 * 2; + + let color_lo = gba.obj_palette_ram[palette_start + (color_4bit as usize * 2)]; + let color_hi = gba.obj_palette_ram[palette_start + (color_4bit as usize * 2) + 1]; + + let color = Rgb15::from_lo_hi(color_lo, color_hi); + /* - let mut found = false; - for i in (palette_start)..(0x400 -palette_start) { - if gba.obj_palette_ram[i] != 0 { - dbg!(i, gba.obj_palette_ram[i], color_4bit); - found = true; + let tile_array_width = 0; + let color = get_4bit_color_pixel( + gba, + tile_base_ptr, + tile_num, + //(tile_row, tile_col), + (0,0), + (nth_line, nth_pixel), + tile_array_width, + palette_num, + false, + ); + */ + + bg_pixels[x as usize] = color; + } + } + + bg_pixels +} + +fn naive_object_layer( + y: u8, + gba: &gba::GameboyAdvance, + obj_base_ptr: usize, +) -> [Rgb15; GBA_SCREEN_WIDTH] { + let mut pixels = [Rgb15::transparent(); GBA_SCREEN_WIDTH]; + let obj_iter = ObjectIter::new(&gba); + let obj_order = obj_iter + .filter(|obj| !obj.object_disabled()) + .filter(|obj| !(obj.x() == 0 || obj.y() == 0)) + .filter(|obj| { + let (_, y_size) = obj.size(); + if obj.rotation_enabled() { + let bbox_y = if obj.double_size() { + y_size as i32 * 2 + } else { + y_size as i32 + }; + (y as i32) >= obj.y() && (y as i32) < obj.y() + bbox_y + } else { + obj.y() as u16 <= y as u16 && (y as u16) < (obj.y() as u16) + y_size as u16 + } + }) + .filter(|obj| obj.object_mode() != ObjectMode::Prohibited) + .collect::>(); + // TODO: sort by priority + + for x in 0..GBA_SCREEN_WIDTH { + for obj in obj_order.iter() { + let (x_size, y_size) = obj.size(); + + let tile_array_width = if gba.ppu_obj_mapping_1d() { + x_size as usize >> 3 + } else { + if obj.full_color() { + 16 + } else { + 32 } + }; + let tile_num = obj.character_name(); + let palette_num = obj.palette_number(); + + // TODO: review this + if 0x10000 + (tile_num as usize * 32) < obj_base_ptr { + continue; } - if found { panic!("found "); } - */ - let color_lo = gba.obj_palette_ram[palette_start + (color_4bit as usize * 2)]; - let color_hi = gba.obj_palette_ram[palette_start + (color_4bit as usize * 2) + 1]; - let red = color_lo & 0x1F; - let green = ((color_hi & 0x3) << 3) | (color_lo >> 5); - let blue = (color_hi >> 2) & 0x1F; - if red | green | blue != 0 { - //panic!("COLOR!"); + + if obj.rotation_enabled() { + let (pa, pb, pc, pd) = + get_object_rotation_data(gba, obj.rotation_scaling_param_selection()); + let obj_x = obj.x(); + let obj_y = obj.y(); + + let (bbox_x, bbox_y) = if obj.double_size() { + (x_size as i32 * 2, y_size as i32 * 2) + } else { + (x_size as i32, y_size as i32) + }; + + if !((x as i32) >= obj_x && (x as i32) < obj_x + bbox_x) { + continue; + } + + let ix = x as i32 - (obj_x + bbox_x / 2); + let iy = y as i32 - (obj_y + bbox_y / 2); + + let transformed_x = (pa * ix + pb * iy) >> 8; + let transformed_y = (pc * ix + pd * iy) >> 8; + let texture_x = transformed_x + x_size as i32 / 2; + let texture_y = transformed_y + y_size as i32 / 2; + + if !(texture_x >= 0 + && texture_x < x_size as i32 + && texture_y >= 0 + && texture_y < y_size as i32) + { + continue; + } + + // TODO: review this + let (texture_x, texture_y) = if obj.mosaic() { + let mosaic_x = texture_x - (texture_x % gba.ppu_obj_mosaic_h() as i32); + let mosaic_y = texture_y - (texture_y % gba.ppu_obj_mosaic_v() as i32); + (mosaic_x, mosaic_y) + } else { + (texture_x, texture_y) + }; + + let tile_col = (texture_x >> 3) as u32; + let tile_row = (texture_y >> 3) as u32; + let nth_line = (texture_y & 0x7) as u16; + let tile_pixel = (texture_x & 0x7) as u16; + + if obj.full_color() { + let color = get_full_color_pixel( + gba, + obj_base_ptr, + tile_num, + (tile_row, tile_col), + (nth_line, tile_pixel), + tile_array_width, + true, + ); + if color.is_transparent() { + continue; + } + + pixels[x] = color; + break; + } else { + let color = get_4bit_color_pixel( + gba, + obj_base_ptr, + tile_num, + (tile_row, tile_col), + (nth_line, tile_pixel), + tile_array_width, + palette_num, + true, + ); + if color.is_transparent() { + continue; + } + + pixels[x] = color; + break; + } + } else { + if !(obj.x() as usize <= x && x < obj.x() as usize + x_size as usize) { + continue; + } + let adj_x = x as u16 - obj.x() as u16; + let adj_y = y as u16 - obj.y() as u16; + let adj_x = if obj.horizontal_flip() { + x_size as u16 - adj_x - 1 + } else { + adj_x + }; + let adj_y = if obj.vertical_flip() { + y_size as u16 - adj_y - 1 + } else { + adj_y + }; + let (adj_x, adj_y) = if obj.mosaic() { + let mosaic_x = adj_x - (adj_x % gba.ppu_obj_mosaic_h() as u16); + let mosaic_y = adj_y - (adj_y % gba.ppu_obj_mosaic_v() as u16); + (mosaic_x, mosaic_y) + } else { + (adj_x, adj_y) + }; + let tile_col = (adj_x >> 3) as u32; + let tile_row = (adj_y >> 3) as u32; + + // TOOD: handle non 8x8 objects + // Lower 3 bits determine which line of the tile we're on + let nth_line = adj_y & 0x7; + // 8 choices for which pixel on the line we're on, so we take 3 bits here + let tile_pixel = adj_x & 0x7; + let nth_pixel = tile_pixel; + if obj.full_color() { + let color = get_full_color_pixel( + gba, + obj_base_ptr, + tile_num, + (tile_row, tile_col), + (nth_line, nth_pixel), + tile_array_width, + true, + ); + if color.is_transparent() { + continue; + } + + pixels[x] = color; + break; + } else { + let color = get_4bit_color_pixel( + gba, + obj_base_ptr, + tile_num, + (tile_row, tile_col), + (nth_line, nth_pixel), + tile_array_width, + palette_num, + true, + ); + if color.is_transparent() { + continue; + } + + pixels[x] = color; + break; + } } + } + } + + pixels +} + +fn layer_order(gba: &gba::GameboyAdvance) -> Vec { + let mut bg_order = vec![]; + if gba.ppu_bg0_enabled() { + let bg0_priority = gba.ppu_bg0_control().priority; + bg_order.push((0, bg0_priority)); + } + if gba.ppu_bg1_enabled() { + let bg1_priority = gba.ppu_bg1_control().priority; + bg_order.push((1, bg1_priority)); + } + if gba.ppu_bg2_enabled() { + let bg2_priority = gba.ppu_bg2_control().priority; + bg_order.push((2, bg2_priority)); + } + if gba.ppu_bg3_enabled() { + let bg3_priority = gba.ppu_bg3_control().priority; + bg_order.push((3, bg3_priority)); + } + bg_order.sort_by_key(|(_, priority)| *priority); + // HACK: always put objects on top for now + std::iter::once(4) + .chain(bg_order.into_iter().map(|(bg_num, _)| bg_num)) + .collect() +} - bg_pixels[x as usize] = (red << 3, green << 3, blue << 3); +// HACK: just do it here... +// later we probably want to do this on the GPU and pass this data back for debug/visualization +fn scanline_blend( + gba: &gba::GameboyAdvance, + pixels: [[Rgb15; GBA_SCREEN_WIDTH]; 5], +) -> [(u8, u8, u8); GBA_SCREEN_WIDTH] { + let backdrop_color = Rgb15::from_lo_hi(gba.obj_palette_ram[0], gba.obj_palette_ram[1]); + let mut blended_pixels = [backdrop_color.to_rgb(); GBA_SCREEN_WIDTH]; + let mut pixel_written = [false; GBA_SCREEN_WIDTH]; + let bg_order = layer_order(gba); + + for x in 0..GBA_SCREEN_WIDTH { + for &layer in bg_order.iter() { + if !pixel_written[x as usize] && !pixels[layer as usize][x].is_transparent() { + blended_pixels[x as usize] = pixels[layer as usize][x].to_rgb(); + pixel_written[x as usize] = true; + break; + } } } + blended_pixels +} - bg_pixels +/* + Mode Rot/Scal Layers Size Tiles Colors Features + 0 No 0123 256x256..512x515 1024 16/16..256/1 SFMABP + 1 Mixed 012- (BG0,BG1 as above Mode 0, BG2 as below Mode 2) + 2 Yes --23 128x128..1024x1024 256 256/1 S-MABP + 3 Yes --2- 240x160 1 32768 --MABP + 4 Yes --2- 240x160 2 256/1 --MABP + 5 Yes --2- 160x128 2 32768 --MABP +*/ + +pub fn deferred_renderer_draw_gba_scanline( + y: u8, + gba: &mut gba::GameboyAdvance, +) -> [(u8, u8, u8); GBA_SCREEN_WIDTH] { + let mut out = [[Rgb15::transparent(); GBA_SCREEN_WIDTH]; 5]; + if gba.ppu_force_blank() { + return [Rgb15::white().to_rgb(); GBA_SCREEN_WIDTH]; + } + match gba.ppu_bg_mode() { + 0 => { + if gba.ppu_bg0_enabled() { + out[0] = deferred_renderer_draw_gba_bg(y, 0, gba.ppu_bg0_control(), gba); + } + if gba.ppu_bg1_enabled() { + out[1] = deferred_renderer_draw_gba_bg(y, 1, gba.ppu_bg1_control(), gba); + } + if gba.ppu_bg2_enabled() { + out[2] = deferred_renderer_draw_gba_bg(y, 2, gba.ppu_bg2_control(), gba); + } + if gba.ppu_bg3_enabled() { + out[3] = deferred_renderer_draw_gba_bg(y, 3, gba.ppu_bg3_control(), gba); + //out[3] = deferred_renderer_draw_gba_bg3(y, gba); + } + } + 1 => { + if gba.ppu_bg0_enabled() { + out[0] = deferred_renderer_draw_gba_bg(y, 0, gba.ppu_bg0_control(), gba); + } + if gba.ppu_bg1_enabled() { + out[1] = deferred_renderer_draw_gba_bg(y, 1, gba.ppu_bg1_control(), gba); + } + if gba.ppu_bg2_enabled() { + let pa = gba.io_registers.bg2_rotation.pa; + let pc = gba.io_registers.bg2_rotation.pc; + let bg_x = gba.io_registers.bg2_rotation.cached_x; + let bg_y = gba.io_registers.bg2_rotation.cached_y; + out[2] = deferred_renderer_draw_rotated_bg( + y, + gba, + gba.ppu_bg2_control(), + pa, + pc, + bg_x, + bg_y, + ); + } + } + 2 => { + if gba.ppu_bg2_enabled() { + let pa = gba.io_registers.bg2_rotation.pa; + let pc = gba.io_registers.bg2_rotation.pc; + let bg_x = gba.io_registers.bg2_rotation.cached_x; + let bg_y = gba.io_registers.bg2_rotation.cached_y; + out[2] = deferred_renderer_draw_rotated_bg( + y, + gba, + gba.ppu_bg2_control(), + pa, + pc, + bg_x, + bg_y, + ); + } + if gba.ppu_bg3_enabled() { + let pa = gba.io_registers.bg3_rotation.pa; + let pc = gba.io_registers.bg3_rotation.pc; + let bg_x = gba.io_registers.bg3_rotation.cached_x; + let bg_y = gba.io_registers.bg3_rotation.cached_y; + out[3] = deferred_renderer_draw_rotated_bg( + y, + gba, + gba.ppu_bg3_control(), + pa, + pc, + bg_x, + bg_y, + ); + } + } + 3 => { + if gba.ppu_bg3_enabled() { + out[3] = deferred_renderer_draw_gba_mode_3(y, gba); + } + } + 4 => { + if gba.ppu_bg2_enabled() { + out[2] = deferred_renderer_draw_gba_bg4(y, gba); + } + } + 5 => todo!("bg mode 5"), + _ => panic!("unknown PPU mode! {}", gba.ppu_bg_mode()), + } + + if gba.ppu_obj_enabled() { + let obj_base_ptr = match gba.ppu_bg_mode() { + 0 | 1 | 2 => 0x10000, + // TODO: review this + //3 | 4 | 5 => 0x14000, + 3 | 4 | 5 => 0x10000, + _ => panic!("unknown PPU mode! {}", gba.ppu_bg_mode()), + }; + out[4] = naive_object_layer(y, gba, obj_base_ptr); + } + + scanline_blend(gba, out) } diff --git a/src/io/dr_sdl2.rs b/src/io/dr_sdl2.rs index 5e0730c..0e805a1 100644 --- a/src/io/dr_sdl2.rs +++ b/src/io/dr_sdl2.rs @@ -20,6 +20,7 @@ pub struct Sdl2Renderer { sound_system: sdl2::audio::AudioDevice, canvas: render::Canvas, controller: Option, // storing to keep alive + base_title: String, _sound_cycles: u64, } @@ -112,6 +113,7 @@ impl Sdl2Renderer { Ok(Sdl2Renderer { sdl_context, sound_system, + base_title: app_settings.rom_file_name.clone(), canvas: renderer, controller, _sound_cycles: 0, @@ -207,6 +209,13 @@ impl Renderer for Sdl2Renderer { self.canvas.present(); } + fn update_fps(&mut self, fps: f32) { + self.canvas + .window_mut() + .set_title(format!("{} ({:.1} FPS)", self.base_title, fps).as_str()) + .unwrap(); + } + fn draw_gba_frame(&mut self, frame: &[[(u8, u8, u8); GBA_SCREEN_WIDTH]; GBA_SCREEN_HEIGHT]) { let scale = 3.0; //app_settings.ui_scale; @@ -405,6 +414,14 @@ impl Renderer for Sdl2Renderer { Keycode::Down => ir.press(Button::Down), Keycode::Left => ir.press(Button::Left), Keycode::Right => ir.press(Button::Right), + /* + Keycode::L => { + let env = env_logger::Env::default() + .filter_or("GAMEBOY_LOG_LEVEL", "info") + .write_style_or("GAMEBOY_LOG_STYLE", "always"); + let _handle = env_logger::init_from_env(env); + } + */ _ => (), } } @@ -520,4 +537,52 @@ impl Renderer for Sdl2Renderer { sound_system.channel4.lfsr = 0x7FFF; } } + + fn gba_audio_step(&mut self, gb_apu: &Apu) { + //self.sound_system.resume(); + // TODO: this function gets called a lot, we can definitely track information about + // whether it's worth getting this lock or not. + // AKA, has the enabled state changed? or is there new sound data? + let mut sound_system = self.sound_system.lock(); + + // if gba + let gba_fifo_freq = 32768.0 / 2.; + //sound_system.gba_fifo_a.wave_ram = gb_apu.gba_fifo_a.get_buffer(); + sound_system.gba_fifo_a.enabled = + gb_apu.gba_sound_a_enabled.0 || gb_apu.gba_sound_a_enabled.1; + if gb_apu.gba_fifo_a.get_current_data().len() + gb_apu.gba_fifo_b.get_current_data().len() + != 0 + { + //dbg!(gb_apu.gba_fifo_a.get_current_data().len(), gb_apu.gba_fifo_b.get_current_data().len()); + } + + if gb_apu.gba_fifo_a.reset_flag { + sound_system.gba_fifo_a.wave_ram.clear(); + sound_system.gba_fifo_a.wave_ram.push_back(0); + } + for &data in gb_apu.gba_fifo_a.get_current_data() { + if sound_system.gba_fifo_a.wave_ram.len() >= 1024 { + sound_system.gba_fifo_a.wave_ram.pop_back(); + } + sound_system.gba_fifo_a.wave_ram.push_front(data); + } + sound_system.gba_fifo_a.phase_inc = gb_apu.gba_fifo_a_sample_rate / sound_system.out_freq; + //sound_system.gba_fifo_a.phase_inc = gba_fifo_freq / sound_system.out_freq; + //sound_system.gba_fifo_b.wave_ram = gb_apu.gba_fifo_b.get_buffer(); + + if gb_apu.gba_fifo_b.reset_flag { + sound_system.gba_fifo_b.wave_ram.clear(); + sound_system.gba_fifo_b.wave_ram.push_back(0); + } + sound_system.gba_fifo_b.enabled = + gb_apu.gba_sound_b_enabled.0 || gb_apu.gba_sound_b_enabled.1; + for &data in gb_apu.gba_fifo_b.get_current_data() { + if sound_system.gba_fifo_b.wave_ram.len() >= 1024 { + sound_system.gba_fifo_b.wave_ram.pop_back(); + } + sound_system.gba_fifo_b.wave_ram.push_front(data); + } + sound_system.gba_fifo_b.phase_inc = gb_apu.gba_fifo_b_sample_rate / sound_system.out_freq; + //sound_system.gba_fifo_b.phase_inc = gba_fifo_freq / sound_system.out_freq; + } } diff --git a/src/io/graphics/renderer.rs b/src/io/graphics/renderer.rs index b0ff3eb..051ea00 100644 --- a/src/io/graphics/renderer.rs +++ b/src/io/graphics/renderer.rs @@ -47,4 +47,13 @@ pub trait Renderer { fn audio_step(&mut self, apu: &Apu) { unimplemented!(); } + // just for DMA sound A and B. + // testing to see if this works better. + fn gba_audio_step(&mut self, _: &Apu) { + unimplemented!(); + } + + fn update_fps(&mut self, _fps: f32) { + // NOP + } } diff --git a/src/io/sound.rs b/src/io/sound.rs index da55d21..854cc36 100644 --- a/src/io/sound.rs +++ b/src/io/sound.rs @@ -1,4 +1,6 @@ //! Everything for making sound play +use std::collections::VecDeque; + use sdl2; use sdl2::audio::{AudioCallback, AudioDevice, AudioSpecDesired}; @@ -9,6 +11,8 @@ pub struct GBSound { pub channel2: SquareWave, pub channel3: SquareWaveRam, pub channel4: Noise, + pub gba_fifo_a: SquareWaveRamGba, + pub gba_fifo_b: SquareWaveRamGba, } /// Contains information for a single channel of audio @@ -57,6 +61,27 @@ pub struct SquareWaveRam { pub shift_amount: u8, } +pub struct SquareWaveRamGba { + /// Whether or not the channel is enabled + pub enabled: bool, + /// The amount by which the `phase` is changed at each callback + pub phase_inc: f32, + + /// The "current" value of the wave + pub phase: f32, + + pub volume: f32, + /// Multiplier for wave between 0 and 1 (functions as volume (0 is off)) + base_volume: f32, + + /// A flag indicating the direction the phase will be changed + pub add: bool, + + pub wave_ram: VecDeque, + //pub wave_ram_index: usize, + //pub val: i8, +} + pub struct Noise { pub enabled: bool, pub phase_inc: f32, @@ -70,6 +95,9 @@ pub struct Noise { trait SoundChannel { fn generate_sample(&mut self) -> f32; + fn generate_sample_i8(&mut self) -> i8 { + unimplemented!(); + } } impl SoundChannel for SquareWave { @@ -131,10 +159,75 @@ impl SoundChannel for Noise { } } +impl SoundChannel for SquareWaveRamGba { + fn generate_sample(&mut self) -> f32 { + if !self.enabled { + return 0.; + } + //let adj = self.wave_ram[self.wave_ram_index] as f32; + let adj = self.wave_ram[0] as f32; + let out = adj / 128. * 4.; //* self.base_volume * self.volume; + //let out = self.base_volume * out; + + let v = self.phase + self.phase_inc; + self.phase = (self.phase + self.phase_inc) % 1.0; + + if v >= 1.0 { + self.wave_ram.rotate_left(1); + //self.wave_ram_index = (self.wave_ram_index + 1) % 512; + } + out + } + fn generate_sample_i8(&mut self) -> i8 { + if !self.enabled { + return 0; + } + //let adj = self.wave_ram[self.wave_ram_index] as f32; + let adj = self.wave_ram[0]; // as f32; + let out = adj; + //let out = self.base_volume * out; + + let v = self.phase + self.phase_inc; + self.phase = (self.phase + self.phase_inc) % 1.0; + + if v >= 1.0 { + self.wave_ram.rotate_left(1); + //self.wave_ram_index = (self.wave_ram_index + 1) % 512; + } + out + } +} + impl AudioCallback for GBSound { type Channel = f32; fn callback(&mut self, out: &mut [f32]) { + /* + for x in out.iter_mut() { + let val = self.channel1.generate_sample() + + self.channel2.generate_sample() + + self.channel3.generate_sample() + + self.channel4.generate_sample(); + *x = val / 4.; + } + */ + for x in out.iter_mut() { + let val = self.channel1.generate_sample() + + self.channel2.generate_sample() + + self.channel3.generate_sample() + + self.channel4.generate_sample() + + self.gba_fifo_a.generate_sample() + + self.gba_fifo_b.generate_sample(); + *x = val / 6.; + } + } +} +/* +impl AudioCallback for GBSound { + type Channel = i8; + + fn callback(&mut self, out: &mut [i8]) { + /* for x in out.iter_mut() { let val = self.channel1.generate_sample() + self.channel2.generate_sample() @@ -142,8 +235,15 @@ impl AudioCallback for GBSound { + self.channel4.generate_sample(); *x = val / 4.; } + */ + for x in out.iter_mut() { + let val = self.gba_fifo_a.generate_sample_i8() as i16 + + self.gba_fifo_b.generate_sample_i8() as i16; + *x = (val / 2) as i8; + } } } +*/ /// Creates a device from a context /// May have to be changed to allow each GB channel to have its own `Wave` @@ -151,11 +251,20 @@ pub fn setup_audio(sdl_context: &sdl2::Sdl) -> Result, Stri // set up audio let audio_subsystem = sdl_context.audio()?; + /* let desired_spec = AudioSpecDesired { freq: Some(44100), channels: Some(1), samples: Some(64), }; + */ + let desired_spec = AudioSpecDesired { + //freq: Some(44100), + //freq: Some(16000), + freq: Some(32768), + channels: Some(1), + samples: Some(64), + }; audio_subsystem.open_playback(None, &desired_spec, |spec| { // Show obtained AudioSpec @@ -203,6 +312,26 @@ pub fn setup_audio(sdl_context: &sdl2::Sdl) -> Result, Stri lfsr_width: false, lfsr: 0x7FFF, }, + gba_fifo_a: SquareWaveRamGba { + enabled: true, + phase_inc: 0.0, + phase: 0.0, + base_volume: 0.100, + volume: 1.0, + add: true, + wave_ram: VecDeque::from([0; 1]), + //wave_ram_index: 0, + }, + gba_fifo_b: SquareWaveRamGba { + enabled: true, + phase_inc: 0.0, + phase: 0.0, + base_volume: 0.100, + volume: 1.0, + add: true, + wave_ram: VecDeque::from([0; 1]), + //wave_ram_index: 0, + }, } }) } diff --git a/src/main.rs b/src/main.rs index 66a0ef7..b67b4b6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -122,6 +122,8 @@ fn main() { None }; + let mut running_frame_counter = 0; + let mut running_frame_time = std::time::Instant::now(); if is_gba { loop { let time_since_last_frame = std::time::Instant::now(); @@ -154,6 +156,14 @@ fn main() { appstate.step_gba(); /*//check for new controller every frame self.load_controller_if_none_exist();*/ + running_frame_counter += 1; + if running_frame_counter > (60 * 1) { + let time_diff = running_frame_time.elapsed(); + let fps = (running_frame_counter as f32) / time_diff.as_secs_f32(); + running_frame_counter = 0; + running_frame_time = std::time::Instant::now(); + appstate.renderer.update_fps(fps); + } let time_diff = time_since_last_frame.elapsed(); if time_diff < std::time::Duration::from_millis(16) {