diff --git a/emu/src/bus.rs b/emu/src/bus.rs index 81a6159d..dc6e72ad 100644 --- a/emu/src/bus.rs +++ b/emu/src/bus.rs @@ -4,13 +4,14 @@ use logger::log; use crate::bitwise::Bits; use crate::cpu::hardware::dma::{Dma, DmaRegisters}; +use crate::cpu::hardware::get_unmasked_address; +use crate::cpu::hardware::internal_memory::InternalMemory; use crate::cpu::hardware::interrupt_control::InterruptControl; use crate::cpu::hardware::keypad::Keypad; use crate::cpu::hardware::lcd::Lcd; use crate::cpu::hardware::serial::Serial; use crate::cpu::hardware::sound::Sound; use crate::cpu::hardware::timers::Timers; -use crate::memory::{internal_memory::InternalMemory, io_device::IoDevice}; #[derive(Default)] pub struct Bus { @@ -632,8 +633,11 @@ impl Bus { } } - fn read_raw(&self, address: usize) -> u8 { + pub fn read_raw(&self, address: usize) -> u8 { match address { + 0x0000000..=0x0003FFF | 0x2000000..=0x03FFFFFF | 0x08000000..=0x0E00FFFF => { + self.internal_memory.read_at(address) + } 0x4000000..=0x400005F => self.read_lcd_raw(address), 0x4000060..=0x40000AF => self.read_sound_raw(address), 0x40000B0..=0x40000FF => self.read_dma_raw(address), @@ -641,12 +645,49 @@ impl Bus { 0x4000120..=0x400012F | 0x4000134..=0x40001FF => self.read_serial_raw(address), 0x4000130..=0x4000133 => self.read_keypad_raw(address), 0x4000200..=0x4FFFFFF => self.read_interrupt_control_raw(address), - _ => self.internal_memory.read_at(address), + 0x5000000..=0x5FFFFFF => { + let unmasked_address = get_unmasked_address(address, 0x00FFFF00, 0xFF0000FF, 8, 4); + + match unmasked_address { + 0x05000000..=0x050001FF => { + self.lcd.bg_palette_ram[unmasked_address - 0x05000000] + } + 0x05000200..=0x050003FF => { + self.lcd.obj_palette_ram[unmasked_address - 0x05000200] + } + _ => unreachable!(), + } + } + 0x6000000..=0x6FFFFFF => { + let unmasked_address = get_unmasked_address(address, 0x00FF0000, 0xFF00FFFF, 16, 2); + + // VRAM is 64k+32k+32k with the last two 32k being one mirrors of each other + match unmasked_address { + 0x06000000..=0x06017FFF => self.lcd.video_ram[unmasked_address - 0x06000000], + 0x06018000..=0x0601FFFF => { + self.lcd.video_ram[unmasked_address - 0x06000000 - 0x8000] + } + _ => unreachable!(), + } + } + 0x7000000..=0x7FFFFFF => { + let unmasked_address = get_unmasked_address(address, 0x00FFFF00, 0xFF0000FF, 8, 4); + + self.lcd.obj_attributes[unmasked_address - 0x07000000] + } + 0x0004000..=0x1FFFFFF | 0xE010000..=0xFFFFFFF | 0x10000000..=0xFFFFFFFF => { + log(format!("read on unused memory {address:x}")); + *self.unused_region.get(&address).unwrap_or(&0) + } + _ => unimplemented!(), } } - fn write_raw(&mut self, address: usize, value: u8) { + pub fn write_raw(&mut self, address: usize, value: u8) { match address { + 0x0000000..=0x0003FFF | 0x2000000..=0x03FFFFFF | 0x08000000..=0x0E00FFFF => { + self.internal_memory.write_at(address, value) + } 0x4000000..=0x400005F => self.write_lcd_raw(address, value), 0x4000060..=0x40000AF => self.write_sound_raw(address, value), 0x40000B0..=0x40000FF => self.write_dma_raw(address, value), @@ -654,7 +695,43 @@ impl Bus { 0x4000120..=0x400012F | 0x4000134..=0x40001FF => self.write_serial_raw(address, value), 0x4000130..=0x4000133 => self.write_keypad_raw(address, value), 0x4000200..=0x4FFFFFF => self.write_interrupt_control_raw(address, value), - _ => self.internal_memory.write_at(address, value), + 0x5000000..=0x5FFFFFF => { + let unmasked_address = get_unmasked_address(address, 0x00FFFF00, 0xFF0000FF, 8, 4); + + match unmasked_address { + 0x05000000..=0x050001FF => { + self.lcd.bg_palette_ram[unmasked_address - 0x05000000] = value + } + 0x05000200..=0x050003FF => { + self.lcd.obj_palette_ram[unmasked_address - 0x05000200] = value + } + _ => unreachable!(), + }; + } + 0x6000000..=0x6FFFFFF => { + let unmasked_address = get_unmasked_address(address, 0x00FF0000, 0xFF00FFFF, 16, 2); + + // VRAM is 64k+32k+32k with the last two 32k being one mirrors of each other + match unmasked_address { + 0x06000000..=0x06017FFF => { + self.lcd.video_ram[unmasked_address - 0x06000000] = value + } + 0x06018000..=0x0601FFFF => { + self.lcd.video_ram[unmasked_address - 0x06000000 - 0x8000] = value + } + _ => unreachable!(), + } + } + 0x7000000..=0x7FFFFFF => { + let unmasked_address = get_unmasked_address(address, 0x00FFFF00, 0xFF0000FF, 8, 4); + + self.lcd.obj_attributes[unmasked_address - 0x07000000] = value + } + 0x0004000..=0x1FFFFFF | 0xE010000..=0xFFFFFFF | 0x10000000..=0xFFFFFFFF => { + log(format!("write on unused memory {address:x}")); + self.unused_region.insert(address, value); + } + _ => unimplemented!(), } } @@ -730,7 +807,7 @@ impl Bus { match address { // Bios 0x0..=0x3FFF => 1, - _ => 0, + _ => 1, } } @@ -881,4 +958,191 @@ mod tests { assert_eq!(bus.read_raw(address), 10); } + + #[test] + fn write_bg_palette_ram() { + let mut bus = Bus::default(); + let address = 0x05000008; + + bus.write_raw(address, 10); + assert_eq!(bus.lcd.bg_palette_ram[8], 10); + } + + #[test] + fn read_bg_palette_ram() { + let mut bus = Bus::default(); + bus.lcd.bg_palette_ram[8] = 15; + + let address = 0x05000008; + let value = bus.read_raw(address); + + assert_eq!(value, 15); + } + + #[test] + fn test_last_byte_bg_palette_ram() { + let mut bus = Bus::default(); + + let address = 0x050001FF; + bus.write_raw(address, 5); + + assert_eq!(bus.lcd.bg_palette_ram[0x1FF], 5); + } + + #[test] + fn write_obj_palette_ram() { + let mut bus = Bus::default(); + let address = 0x05000208; + + bus.write_raw(address, 10); + assert_eq!(bus.lcd.obj_palette_ram[8], 10); + } + + #[test] + fn read_obj_palette_ram() { + let mut bus = Bus::default(); + bus.lcd.obj_palette_ram[8] = 15; + + let address = 0x05000208; + + let value = bus.read_raw(address); + + assert_eq!(value, 15); + } + + #[test] + fn test_last_byte_obj_palette_ram() { + let mut bus = Bus::default(); + + let address = 0x050003FF; + bus.write_raw(address, 5); + + assert_eq!(bus.lcd.obj_palette_ram[0x1FF], 5); + } + + #[test] + fn write_vram() { + let mut bus = Bus::default(); + let address = 0x06000004; + + bus.write_raw(address, 23); + assert_eq!(bus.lcd.video_ram[4], 23); + } + + #[test] + fn read_vram() { + let mut bus = Bus::default(); + bus.lcd.video_ram[4] = 15; + + let address = 0x06000004; + let value = bus.read_raw(address); + + assert_eq!(value, 15); + } + + #[test] + fn test_last_byte_vram() { + let mut bus = Bus::default(); + + let address = 0x06017FFF; + bus.write_raw(address, 5); + + assert_eq!(bus.lcd.video_ram[0x17FFF], 5); + } + + #[test] + fn test_mirror_bg_palette() { + let mut bus = Bus::default(); + bus.lcd.bg_palette_ram[0x134] = 5; + + assert_eq!(bus.read_raw(0x05000134), 5); + assert_eq!(bus.read_raw(0x05000534), 5); + assert_eq!(bus.read_raw(0x05012534), 5); + assert_eq!(bus.read_raw(0x05FFFD34), 5); + + bus.write_raw(0x05000134, 10); + assert_eq!(bus.lcd.bg_palette_ram[0x134], 10); + + bus.write_raw(0x05000534, 11); + assert_eq!(bus.lcd.bg_palette_ram[0x134], 11); + + bus.write_raw(0x05012534, 12); + assert_eq!(bus.lcd.bg_palette_ram[0x134], 12); + + bus.write_raw(0x05FFFD34, 13); + assert_eq!(bus.lcd.bg_palette_ram[0x134], 13); + } + + #[test] + fn test_mirror_obj_palette() { + let mut bus = Bus::default(); + bus.lcd.obj_palette_ram[0x134] = 5; + + assert_eq!(bus.read_raw(0x05000334), 5); + assert_eq!(bus.read_raw(0x05000734), 5); + assert_eq!(bus.read_raw(0x05012734), 5); + assert_eq!(bus.read_raw(0x05FFFF34), 5); + + bus.write_raw(0x05000334, 10); + assert_eq!(bus.lcd.obj_palette_ram[0x134], 10); + + bus.write_raw(0x05000734, 11); + assert_eq!(bus.lcd.obj_palette_ram[0x134], 11); + + bus.write_raw(0x05012734, 12); + assert_eq!(bus.lcd.obj_palette_ram[0x134], 12); + + bus.write_raw(0x05FFFF34, 13); + assert_eq!(bus.lcd.obj_palette_ram[0x134], 13); + } + + #[test] + fn test_mirror_vram() { + let mut bus = Bus::default(); + bus.lcd.video_ram[0x09345] = 5; + + assert_eq!(bus.read_raw(0x06009345), 5); + assert_eq!(bus.read_raw(0x06029345), 5); + assert_eq!(bus.read_raw(0x06129345), 5); + assert_eq!(bus.read_raw(0x06FE9345), 5); + + bus.write_raw(0x06009345, 1); + assert_eq!(bus.lcd.video_ram[0x09345], 1); + + bus.write_raw(0x06029345, 2); + assert_eq!(bus.lcd.video_ram[0x09345], 2); + + bus.write_raw(0x06129345, 3); + assert_eq!(bus.lcd.video_ram[0x09345], 3); + + bus.write_raw(0x06FE9345, 4); + assert_eq!(bus.lcd.video_ram[0x09345], 4); + + bus.lcd.video_ram[0x11345] = 10; + assert_eq!(bus.read_raw(0x06019345), 10); + assert_eq!(bus.read_raw(0x06131345), 10); + } + + #[test] + fn test_mirror_oam() { + let mut bus = Bus::default(); + bus.lcd.obj_attributes[0x134] = 5; + + assert_eq!(bus.read_raw(0x07000134), 5); + assert_eq!(bus.read_raw(0x07000534), 5); + assert_eq!(bus.read_raw(0x0700F534), 5); + assert_eq!(bus.read_raw(0x07FFFD34), 5); + + bus.write_raw(0x07000134, 10); + assert_eq!(bus.lcd.obj_attributes[0x134], 10); + + bus.write_raw(0x07000534, 11); + assert_eq!(bus.lcd.obj_attributes[0x134], 11); + + bus.write_raw(0x0700F534, 12); + assert_eq!(bus.lcd.obj_attributes[0x134], 12); + + bus.write_raw(0x07FFFD34, 13); + assert_eq!(bus.lcd.obj_attributes[0x134], 13); + } } diff --git a/emu/src/cpu/arm/operations.rs b/emu/src/cpu/arm/operations.rs index ecfc25ea..c57d9169 100644 --- a/emu/src/cpu/arm/operations.rs +++ b/emu/src/cpu/arm/operations.rs @@ -954,7 +954,6 @@ mod tests { use crate::cpu::arm::instructions::{ArmModeInstruction, SingleDataTransferOffsetInfo}; use crate::cpu::condition::Condition; use crate::cpu::flags::ShiftKind; - use crate::memory::io_device::IoDevice; use pretty_assertions::assert_eq; @@ -2482,12 +2481,12 @@ mod tests { cpu.execute_arm(op_code); - let memory = cpu.bus.internal_memory; + let bus = cpu.bus; - assert_eq!(memory.read_at(0x01010101), 1); - assert_eq!(memory.read_at(0x01010101 + 1), 1); - assert_eq!(memory.read_at(0x01010101 + 2), 1); - assert_eq!(memory.read_at(0x01010101 + 3), 1); + assert_eq!(bus.read_raw(0x01010101), 1); + assert_eq!(bus.read_raw(0x01010101 + 1), 1); + assert_eq!(bus.read_raw(0x01010101 + 2), 1); + assert_eq!(bus.read_raw(0x01010101 + 3), 1); assert_eq!(cpu.registers.program_counter(), 0x03000050); } { diff --git a/emu/src/cpu/arm7tdmi.rs b/emu/src/cpu/arm7tdmi.rs index 7d018d69..99d540c5 100644 --- a/emu/src/cpu/arm7tdmi.rs +++ b/emu/src/cpu/arm7tdmi.rs @@ -18,7 +18,7 @@ use super::registers::Registers; use super::thumb; pub struct Arm7tdmi { - pub(crate) bus: Bus, + pub bus: Bus, pub cpsr: Psr, pub spsr: Psr, @@ -461,7 +461,7 @@ impl Arm7tdmi { } // This means that the instruction flushed the pipeline - if matches!(self.fetched_thumb, None) { + if self.fetched_thumb.is_none() { return; } @@ -491,7 +491,7 @@ impl Arm7tdmi { } // This means that the instruction flushed the pipeline - if matches!(self.fetched_arm, None) { + if self.fetched_arm.is_none() { return; } diff --git a/emu/src/cpu/hardware/internal_memory.rs b/emu/src/cpu/hardware/internal_memory.rs new file mode 100644 index 00000000..5de28a93 --- /dev/null +++ b/emu/src/cpu/hardware/internal_memory.rs @@ -0,0 +1,247 @@ +use std::collections::HashMap; + +use logger::log; + +use crate::bitwise::Bits; + +use super::get_unmasked_address; + +pub struct InternalMemory { + /// From 0x00000000 to 0x00003FFF (16 KBytes). + bios_system_rom: Vec, + + /// From 0x02000000 to 0x0203FFFF (256 KBytes). + working_ram: Vec, + + /// From 0x03000000 to 0x03007FFF (32kb). + working_iram: Vec, + + /// From 0x08000000 to 0x0FFFFFFF. + /// Basically here you can find different kind of rom loaded. + // TODO: Not sure if we should split this into + // 08000000-09FFFFFF Game Pak ROM/FlashROM (max 32MB) - Wait State 0 + // 0A000000-0BFFFFFF Game Pak ROM/FlashROM (max 32MB) - Wait State 1 + // 0C000000-0DFFFFFF Game Pak ROM/FlashROM (max 32MB) - Wait State 2 + // 0E000000-0E00FFFF Game Pak SRAM (max 64 KBytes) - 8bit Bus width + // 0E010000-0FFFFFFF Not used + pub rom: Vec, + + /// From 0x00004000 to 0x01FFFFFF. + /// From 0x10000000 to 0xFFFFFFFF. + unused_region: HashMap, +} + +impl Default for InternalMemory { + fn default() -> Self { + Self::new([0_u8; 0x00004000], vec![]) + } +} + +impl InternalMemory { + pub fn new(bios: [u8; 0x00004000], rom: Vec) -> Self { + Self { + bios_system_rom: bios.to_vec(), + working_ram: vec![0; 0x00040000], + working_iram: vec![0; 0x00008000], + rom, + unused_region: HashMap::new(), + } + } + + fn read_rom(&self, address: usize) -> u8 { + if address < self.rom.len() { + self.rom[address] + } else { + // Preamble: + // The GamePak ROM is an halfword addressable memory + // and it uses a 16bits bus to transfer data and a + // 24bits(32MB halfword addressed) bus to transfer the address to read. + // So technically we can't just read 1 byte from the ROM, we + // request the halfword and then we take the upper/lower 8bits + // depending on the address least significant bit. + // + // https://rust-console.github.io/gbatek-gbaonly/#auxgbagamepakbus + // In GamePak ROM, the 16bits data and the + // lower 16bits of the address are transferred on the same bus (AD0-15), + // the higher 8bits of the address (24bits in total, remember halfword addressing) + // are transferred via A16-23. + // When requesting an address which is "empty", the GamePak ROM doesn't overwrite the + // value present in the AD0-15 bus, which then will still contain the lower 16bits of the address. + // CPU will then use this as if it was the value read from the ROM. + // + // Here we get the 24bits address (halfword addressing) by shifting right by 1 + // and we take only the 16 lower bits. We use this as if it was the value read from the ROM + // and we get the 0 or 1 byte depending on the LSB in the address. + (((address >> 1) & 0xFFFF) as u16).get_byte((address & 0b1) as u8) + } + } +} + +impl InternalMemory { + pub fn read_at(&self, address: usize) -> u8 { + match address { + 0x00000000..=0x00003FFF => self.bios_system_rom[address], + 0x02000000..=0x02FFFFFF => { + self.working_ram + [get_unmasked_address(address, 0x00FF0000, 0xFF00FFFF, 16, 4) - 0x02000000] + } + 0x03000000..=0x03FFFFFF => { + self.working_iram + [get_unmasked_address(address, 0x00FFF000, 0xFF000FFF, 12, 8) - 0x03000000] + } + 0x08000000..=0x09FFFFFF => self.read_rom(address - 0x08000000), + 0x0A000000..=0x0BFFFFFF => self.read_rom(address - 0x0A000000), + 0x0C000000..=0x0DFFFFFF => self.read_rom(address - 0x0C000000), + 0x0E000000..=0x0E00FFFF => unimplemented!("SRAM region is unimplemented"), + 0x00004000..=0x01FFFFFF | 0x10000000..=0xFFFFFFFF => { + log(format!("read on unused memory {address:x}")); + self.unused_region.get(&address).map_or(0, |v| *v) + } + _ => unimplemented!("Unimplemented memory region. {address:x}"), + } + } + + pub fn write_at(&mut self, address: usize, value: u8) { + match address { + 0x00000000..=0x00003FFF => self.bios_system_rom[address] = value, + 0x02000000..=0x0203FFFF => self.working_ram[address - 0x02000000] = value, + // Mirror + 0x02040000..=0x02FFFFFF => { + self.working_ram + [get_unmasked_address(address, 0x00FF0000, 0xFF00FFFF, 16, 4) - 0x02000000] = + value; + } + 0x03000000..=0x03007FFF => self.working_iram[address - 0x03000000] = value, + // Mirror + 0x03008000..=0x03FFFFFF => { + self.working_iram + [get_unmasked_address(address, 0x00FFF000, 0xFF000FFF, 12, 8) - 0x03000000] = + value + } + 0x08000000..=0x0FFFFFFF => { + // TODO: this should be split + self.rom[address - 0x08000000] = value; + } + _ => unimplemented!("Unimplemented memory region {address:x}."), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_write_work_ram() { + let mut im = InternalMemory::default(); + + let address = 0x03000005; + im.write_at(address, 5); + + assert_eq!(im.working_iram[5], 5); + } + + #[test] + fn test_last_byte_work_ram() { + let mut im = InternalMemory::default(); + + let address = 0x03007FFF; + im.write_at(address, 5); + + assert_eq!(im.working_iram[0x7FFF], 5); + } + + #[test] + fn test_read_work_ram() { + let mut im = InternalMemory::default(); + im.working_iram[5] = 10; + + let address = 0x03000005; + assert_eq!(im.read_at(address), 10); + } + + #[test] + fn test_read_write_bios_memory() { + let mut im = InternalMemory::default(); + im.write_at(0x000001EC, 10); + assert_eq!(im.read_at(0x000001EC), 10); + } + + #[test] + fn test_read_rom() { + let im = InternalMemory { + rom: vec![1, 2, 3, 4], + ..Default::default() + }; + let address = 0x08000000; + assert_eq!(im.read_at(address), 1); + + // Testing reading in empty rom + let address = 0x09FF_FFFF; + assert_eq!(im.read_at(address), 0xFF); + + let address = 0x09FF_FFEE; + assert_eq!(im.read_at(address), 0xF7); + + let address = 0x09FF_FFEF; + assert_eq!(im.read_at(address), 0xFF); + } + + #[test] + fn test_mirror_3ffffxx() { + let mut im = InternalMemory::default(); + im.working_iram[0x7FF0] = 5; + + assert_eq!(im.read_at(0x3FFFFF0), 5); + + im.write_at(0x3FFFFA0, 10); + + assert_eq!(im.working_iram[0x7FA0], 10); + } + + #[test] + fn test_mirror_wram() { + let mut im = InternalMemory::default(); + im.working_ram[0x010003] = 5; + + assert_eq!(im.read_at(0x02010003), 5); + assert_eq!(im.read_at(0x02050003), 5); + assert_eq!(im.read_at(0x02350003), 5); + assert_eq!(im.read_at(0x02F50003), 5); + + im.write_at(0x02010003, 2); + assert_eq!(im.working_ram[0x010003], 2); + + im.write_at(0x02050003, 1); + assert_eq!(im.working_ram[0x010003], 1); + + im.write_at(0x02350010, 1); + assert_eq!(im.working_ram[0x010010], 1); + + im.write_at(0x02F5003F, 1); + assert_eq!(im.working_ram[0x01003F], 1); + } + + #[test] + fn test_mirror_iram() { + let mut im = InternalMemory::default(); + im.working_iram[0x21FF] = 5; + + assert_eq!(im.read_at(0x030021FF), 5); + assert_eq!(im.read_at(0x0300A1FF), 5); + assert_eq!(im.read_at(0x030121FF), 5); + assert_eq!(im.read_at(0x03FFA1FF), 5); + + im.write_at(0x030021FF, 2); + assert_eq!(im.working_iram[0x21FF], 2); + + im.write_at(0x0300A1FF, 1); + assert_eq!(im.working_iram[0x21FF], 1); + + im.write_at(0x030171FF, 10); + assert_eq!(im.working_iram[0x71FF], 10); + + im.write_at(0x03FFF1FF, 1); + assert_eq!(im.working_iram[0x71FF], 1); + } +} diff --git a/emu/src/cpu/hardware/lcd.rs b/emu/src/cpu/hardware/lcd.rs index c1dccf74..1ef1dbbc 100644 --- a/emu/src/cpu/hardware/lcd.rs +++ b/emu/src/cpu/hardware/lcd.rs @@ -1,6 +1,42 @@ +use logger::log; +use object_attributes::ObjAttributes; +use object_attributes::RotationScaling; + use crate::bitwise::Bits; -#[derive(Default)] +mod object_attributes; + +/// GBA display width +const LCD_WIDTH: usize = 240; + +/// GBA display height +const LCD_HEIGHT: usize = 160; + +#[derive(Default, Clone, Copy)] +pub struct Color(pub u16); + +impl Color { + pub fn from_rgb(red: u8, green: u8, blue: u8) -> Self { + let red: u16 = red.into(); + let green: u16 = green.into(); + let blue: u16 = blue.into(); + + Self((blue << 10) + (green << 5) + red) + } + + pub fn red(&self) -> u8 { + self.0.get_bits(0..=4) as u8 + } + + pub fn green(&self) -> u8 { + self.0.get_bits(5..=9) as u8 + } + + pub fn blue(&self) -> u8 { + self.0.get_bits(10..=14) as u8 + } +} + pub struct Lcd { /// LCD Control pub dispcnt: u16, @@ -79,9 +115,75 @@ pub struct Lcd { /// Brightness (Fade-In/Out) Coefficient pub bldy: u16, + /// From 0x05000000 to 0x050001FF (512 bytes, 256 colors). + pub bg_palette_ram: Vec, + /// From 0x05000200 to 0x050003FF (512 bytes, 256 colors). + pub obj_palette_ram: Vec, + /// From 0x06000000 to 0x06017FFF (96 kb). + pub video_ram: Vec, + /// From 0x07000000 to 0x070003FF (1kbyte) + pub obj_attributes: Vec, + + pub buffer: [[Color; LCD_WIDTH]; LCD_HEIGHT], pixel_index: u32, + should_draw: bool, + obj_attributes_arr: [ObjAttributes; 128], + rotation_scaling_params: [RotationScaling; 32], } +impl Default for Lcd { + fn default() -> Self { + Self { + dispcnt: 0, + green_swap: 0, + dispstat: 0, + vcount: 0, + bg0cnt: 0, + bg1cnt: 0, + bg2cnt: 0, + bg3cnt: 0, + bg0hofs: 0, + bg0vofs: 0, + bg1hofs: 0, + bg1vofs: 0, + bg2hofs: 0, + bg2vofs: 0, + bg3hofs: 0, + bg3vofs: 0, + bg2pa: 0, + bg2pb: 0, + bg2pc: 0, + bg2pd: 0, + bg2x: 0, + bg2y: 0, + bg3pa: 0, + bg3pb: 0, + bg3pc: 0, + bg3pd: 0, + bg3x: 0, + bg3y: 0, + win0h: 0, + win1h: 0, + win0v: 0, + win1v: 0, + winin: 0, + winout: 0, + mosaic: 0, + bldcnt: 0, + bldalpha: 0, + bldy: 0, + bg_palette_ram: vec![0; 0x200], + obj_palette_ram: vec![0; 0x200], + video_ram: vec![0; 0x00018000], + obj_attributes: vec![0; 0x400], + pixel_index: 0, + buffer: [[Color::default(); LCD_WIDTH]; LCD_HEIGHT], + should_draw: false, + obj_attributes_arr: [ObjAttributes::default(); 128], + rotation_scaling_params: [RotationScaling::default(); 32], + } + } +} #[derive(Default)] pub struct LcdStepOutput { pub request_vblank_irq: bool, @@ -102,7 +204,10 @@ impl Lcd { self.set_hblank_flag(false); self.set_vblank_flag(false); - // TODO: Do something + self.should_draw = true; + + (self.obj_attributes_arr, self.rotation_scaling_params) = + object_attributes::get_attributes(self.obj_attributes.as_slice()); } else if self.pixel_index == 240 { // We're entering Hblank @@ -111,18 +216,34 @@ impl Lcd { if self.get_hblank_irq_enable() { output.request_hblank_irq = true; } + + self.should_draw = false; } } else if self.vcount == 160 && self.pixel_index == 0 { - // We're drawing the first pixel of the Vdraw period + // We're drawing the first pixel of the Vblank period self.set_vblank_flag(true); if self.get_vblank_irq_enable() { output.request_vblank_irq = true; } + + self.should_draw = false; } - // TODO: draw the pixel + if self.should_draw { + let pixel_y = self.vcount; + let pixel_x = self.pixel_index; + + self.buffer[pixel_y as usize][pixel_x as usize] = Color::from_rgb(31, 31, 31); + } + + log(format!( + "mode: {:?}, BG2: {:?} BG3: {:?}", + self.get_bg_mode(), + self.get_bg2_enabled(), + self.get_bg3_enabled() + )); self.pixel_index += 1; @@ -150,6 +271,14 @@ impl Lcd { output } + fn get_bg2_enabled(&self) -> bool { + self.dispcnt.get_bit(10) + } + + fn get_bg3_enabled(&self) -> bool { + self.dispcnt.get_bit(11) + } + /// Info about vram fields used to render display. pub fn get_bg_mode(&self) -> u8 { self.dispcnt.get_bits(0..=2).try_into().unwrap() diff --git a/emu/src/cpu/hardware/lcd/object_attributes.rs b/emu/src/cpu/hardware/lcd/object_attributes.rs new file mode 100644 index 00000000..219aad38 --- /dev/null +++ b/emu/src/cpu/hardware/lcd/object_attributes.rs @@ -0,0 +1,275 @@ +// We use nomenclature coming from https://www.coranac.com/tonc/text/regobj.htm#sec-oam + +use std::ops::{Index, IndexMut}; + +use crate::bitwise::Bits; + +#[derive(Default, Clone, Copy)] +enum ObjMode { + #[default] + Normal, + Affine, + Disabled, + AffineDouble, +} + +impl From for ObjMode { + fn from(value: u16) -> Self { + match value { + 0 => Self::Normal, + 1 => Self::Affine, + 2 => Self::Disabled, + 3 => Self::AffineDouble, + _ => unreachable!(), + } + } +} + +#[derive(Default, Clone, Copy)] +enum GfxMode { + #[default] + Normal, + AlphaBlending, + ObjectWindow, +} + +impl TryFrom for GfxMode { + type Error = &'static str; + fn try_from(value: u16) -> Result { + match value { + 0 => Ok(Self::Normal), + 1 => Ok(Self::AlphaBlending), + 2 => Ok(Self::ObjectWindow), + 4 => Err("Forbidden GfxMode"), + _ => unreachable!(), + } + } +} + +#[derive(Default, Clone, Copy)] +enum ColorMode { + /// 16 colors + #[default] + Palette4bpp, + /// 256 colors + Palette8bpp, +} + +impl From for ColorMode { + fn from(value: bool) -> Self { + match value { + false => Self::Palette4bpp, + true => Self::Palette8bpp, + } + } +} + +#[derive(Default, Clone, Copy)] +enum ObjShape { + #[default] + Square, + Horizontal, + Vertical, +} + +impl TryFrom for ObjShape { + type Error = &'static str; + fn try_from(value: u16) -> Result { + match value { + 0 => Ok(Self::Square), + 1 => Ok(Self::Horizontal), + 2 => Ok(Self::Vertical), + 3 => Err("Prohibited ObjShape"), + _ => unreachable!(), + } + } +} + +#[derive(Default, Clone, Copy)] +enum ObjSize { + #[default] + Size0, + Size1, + Size2, + Size3, +} + +impl From for ObjSize { + fn from(value: u16) -> Self { + match value { + 0 => Self::Size0, + 1 => Self::Size1, + 2 => Self::Size2, + 3 => Self::Size3, + _ => unreachable!(), + } + } +} + +#[allow(dead_code)] +#[derive(Default, Clone, Copy)] +struct ObjAttribute0 { + y_coordinate: u8, + obj_mode: ObjMode, + gfx_mode: GfxMode, + obj_mosaic: bool, + color_mode: ColorMode, + obj_shape: ObjShape, +} + +impl TryFrom for ObjAttribute0 { + type Error = &'static str; + fn try_from(value: u16) -> Result { + Ok(Self { + y_coordinate: value.get_bits(0..=7) as u8, + obj_mode: value.get_bits(8..=9).into(), + gfx_mode: value.get_bits(10..=11).try_into().unwrap(), + obj_mosaic: value.get_bit(12), + color_mode: value.get_bit(13).into(), + obj_shape: value.get_bits(14..=15).try_into().unwrap(), + }) + } +} + +#[allow(dead_code)] +#[derive(Clone, Copy)] +enum TransformationKind { + RotationScaling { + rotation_scaling_parameter: u8, + }, + Flip { + horizontal_flip: bool, + vertical_flip: bool, + }, +} + +impl Default for TransformationKind { + fn default() -> Self { + Self::Flip { + horizontal_flip: false, + vertical_flip: false, + } + } +} + +#[allow(dead_code)] +#[derive(Default, Clone, Copy)] +struct ObjAttribute1 { + x_coordinate: u16, + transformation_kind: TransformationKind, + obj_size: ObjSize, +} + +impl ObjAttribute1 { + fn from_value(value: u16, obj_mode: ObjMode) -> Self { + Self { + x_coordinate: value.get_bits(0..=8), + transformation_kind: match obj_mode { + ObjMode::Affine | ObjMode::AffineDouble => TransformationKind::RotationScaling { + rotation_scaling_parameter: value.get_bits(9..=13) as u8, + }, + ObjMode::Normal | ObjMode::Disabled => TransformationKind::Flip { + horizontal_flip: value.get_bit(11), + vertical_flip: value.get_bit(12), + }, + }, + obj_size: value.get_bits(14..=15).into(), + } + } +} + +#[allow(dead_code)] +#[derive(Default, Clone, Copy)] +struct ObjAttribute2 { + tile_number: u16, + priority: u8, + palette_number: u8, +} + +impl From for ObjAttribute2 { + fn from(value: u16) -> Self { + Self { + tile_number: value.get_bits(0..=9), + priority: value.get_bits(10..=11) as u8, + palette_number: value.get_bits(12..=15) as u8, + } + } +} + +#[allow(dead_code)] +#[derive(Default, Clone, Copy)] +pub struct ObjAttributes { + attribute0: ObjAttribute0, + attribute1: ObjAttribute1, + attribute2: ObjAttribute2, +} + +impl TryFrom<[u16; 3]> for ObjAttributes { + type Error = &'static str; + fn try_from(value: [u16; 3]) -> Result { + let obj_attribute0 = value[0].try_into().unwrap(); + + Ok(Self { + attribute0: obj_attribute0, + attribute1: ObjAttribute1::from_value(value[1], obj_attribute0.obj_mode), + attribute2: value[2].into(), + }) + } +} + +#[derive(Default, Copy, Clone)] +pub struct RotationScaling { + pa: u16, + pb: u16, + pc: u16, + pd: u16, +} + +impl Index for RotationScaling { + type Output = u16; + fn index(&self, index: usize) -> &Self::Output { + match index { + 0 => &self.pa, + 1 => &self.pb, + 2 => &self.pc, + 3 => &self.pd, + _ => panic!("Index out of bound"), + } + } +} +impl IndexMut for RotationScaling { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + match index { + 0 => &mut self.pa, + 1 => &mut self.pb, + 2 => &mut self.pc, + 3 => &mut self.pd, + _ => panic!("Index out of bound"), + } + } +} + +#[allow(dead_code)] +pub fn get_attributes(oam_memory: &[u8]) -> ([ObjAttributes; 128], [RotationScaling; 32]) { + let mut obj_attributes = [ObjAttributes::default(); 128]; + let mut rotation_scalings = [RotationScaling::default(); 32]; + + for (idx, [attribute0, attribute1, attribute2, rotation_scaling]) in oam_memory + .chunks_exact(8) + .map(|values| { + let mut result = [u16::default(); 4]; + result[0] = ((values[1] as u16) << 8) | values[0] as u16; + result[1] = ((values[3] as u16) << 8) | values[2] as u16; + result[2] = ((values[5] as u16) << 8) | values[4] as u16; + result[3] = ((values[7] as u16) << 8) | values[6] as u16; + + result + }) + .enumerate() + { + obj_attributes[idx] = [attribute0, attribute1, attribute2].try_into().unwrap(); + rotation_scalings[idx / 4][idx % 4] = rotation_scaling; + } + + (obj_attributes, rotation_scalings) +} diff --git a/emu/src/cpu/hardware/mod.rs b/emu/src/cpu/hardware/mod.rs index ddea0426..ebfb076d 100644 --- a/emu/src/cpu/hardware/mod.rs +++ b/emu/src/cpu/hardware/mod.rs @@ -1,4 +1,5 @@ pub mod dma; +pub mod internal_memory; pub mod interrupt_control; pub mod keypad; pub mod lcd; @@ -6,9 +7,19 @@ pub mod serial; pub mod sound; pub mod timers; -pub trait HardwareComponent { - fn step(&mut self); +pub const fn get_unmasked_address( + address: usize, + mask_get: usize, + mask_set: usize, + mask_shift: usize, + modulo: usize, +) -> usize { + // Get the index of the mirror + let idx = (address & mask_get) >> mask_shift; + // Remove the mirror index from the address + let mut address = address & mask_set; + // Insert the unmasked index in the address + address |= (idx % modulo) << mask_shift; - // not sure if it will be useful, let's see - fn is_interrupt_pending(&self) -> bool; + address } diff --git a/emu/src/gba.rs b/emu/src/gba.rs index 96d39725..2ffbba9d 100644 --- a/emu/src/gba.rs +++ b/emu/src/gba.rs @@ -3,8 +3,7 @@ use std::sync::{Arc, Mutex}; use crate::{ bus::Bus, cartridge_header::CartridgeHeader, - cpu::arm7tdmi::Arm7tdmi, - memory::internal_memory::InternalMemory, + cpu::{arm7tdmi::Arm7tdmi, hardware::internal_memory::InternalMemory}, render::{gba_lcd::GbaLcd, ppu::PixelProcessUnit}, }; diff --git a/emu/src/lib.rs b/emu/src/lib.rs index 20073998..55263e6e 100644 --- a/emu/src/lib.rs +++ b/emu/src/lib.rs @@ -3,5 +3,4 @@ pub mod bus; pub mod cartridge_header; mod cpu; pub mod gba; -mod memory; pub mod render; diff --git a/emu/src/memory/internal_memory.rs b/emu/src/memory/internal_memory.rs deleted file mode 100644 index fd811e34..00000000 --- a/emu/src/memory/internal_memory.rs +++ /dev/null @@ -1,630 +0,0 @@ -use std::collections::HashMap; - -use logger::log; - -use crate::bitwise::Bits; -use crate::memory::io_device::IoDevice; - -pub struct InternalMemory { - /// From 0x00000000 to 0x00003FFF (16 KBytes). - bios_system_rom: Vec, - - /// From 0x02000000 to 0x0203FFFF (256 KBytes). - working_ram: Vec, - - /// From 0x03000000 to 0x03007FFF (32kb). - working_iram: Vec, - - /// From 0x05000000 to 0x050001FF (512 bytes, 256 colors). - pub bg_palette_ram: Vec, - - /// From 0x05000200 to 0x050003FF (512 bytes, 256 colors). - pub obj_palette_ram: Vec, - - /// From 0x06000000 to 0x06017FFF (96 kb). - pub video_ram: Vec, - - /// From 0x07000000 to 0x070003FF (1kbyte) - obj_attributes: Vec, - - /// From 0x08000000 to 0x0FFFFFFF. - /// Basically here you can find different kind of rom loaded. - // TODO: Not sure if we should split this into - // 08000000-09FFFFFF Game Pak ROM/FlashROM (max 32MB) - Wait State 0 - // 0A000000-0BFFFFFF Game Pak ROM/FlashROM (max 32MB) - Wait State 1 - // 0C000000-0DFFFFFF Game Pak ROM/FlashROM (max 32MB) - Wait State 2 - // 0E000000-0E00FFFF Game Pak SRAM (max 64 KBytes) - 8bit Bus width - // 0E010000-0FFFFFFF Not used - pub rom: Vec, - - /// From 0x00004000 to 0x01FFFFFF. - /// From 0x10000000 to 0xFFFFFFFF. - unused_region: HashMap, -} - -impl Default for InternalMemory { - fn default() -> Self { - Self::new([0_u8; 0x00004000], vec![]) - } -} - -impl InternalMemory { - pub fn new(bios: [u8; 0x00004000], rom: Vec) -> Self { - Self { - bios_system_rom: bios.to_vec(), - working_ram: vec![0; 0x00040000], - working_iram: vec![0; 0x00008000], - bg_palette_ram: vec![0; 0x200], - obj_palette_ram: vec![0; 0x200], - video_ram: vec![0; 0x00018000], - obj_attributes: vec![0; 0x400], - rom, - unused_region: HashMap::new(), - } - } - - fn read_rom(&self, address: usize) -> u8 { - if address < self.rom.len() { - self.rom[address] - } else { - // Preamble: - // The GamePak ROM is an halfword addressable memory - // and it uses a 16bits bus to transfer data and a - // 24bits(32MB halfword addressed) bus to transfer the address to read. - // So technically we can't just read 1 byte from the ROM, we - // request the halfword and then we take the upper/lower 8bits - // depending on the address least significant bit. - // - // https://rust-console.github.io/gbatek-gbaonly/#auxgbagamepakbus - // In GamePak ROM, the 16bits data and the - // lower 16bits of the address are transferred on the same bus (AD0-15), - // the higher 8bits of the address (24bits in total, remember halfword addressing) - // are transferred via A16-23. - // When requesting an address which is "empty", the GamePak ROM doesn't overwrite the - // value present in the AD0-15 bus, which then will still contain the lower 16bits of the address. - // CPU will then use this as if it was the value read from the ROM. - // - // Here we get the 24bits address (halfword addressing) by shifting right by 1 - // and we take only the 16 lower bits. We use this as if it was the value read from the ROM - // and we get the 0 or 1 byte depending on the LSB in the address. - (((address >> 1) & 0xFFFF) as u16).get_byte((address & 0b1) as u8) - } - } - - const fn get_unmasked_address( - address: usize, - mask_get: usize, - mask_set: usize, - mask_shift: usize, - modulo: usize, - ) -> usize { - // Get the index of the mirror - let idx = (address & mask_get) >> mask_shift; - // Remove the mirror index from the address - let mut address = address & mask_set; - // Insert the unmasked index in the address - address |= (idx % modulo) << mask_shift; - - address - } -} - -impl IoDevice for InternalMemory { - type Address = usize; - type Value = u8; - - fn read_at(&self, address: Self::Address) -> Self::Value { - match address { - 0x00000000..=0x00003FFF => self.bios_system_rom[address], - 0x02000000..=0x02FFFFFF => { - self.working_ram[Self::get_unmasked_address(address, 0x00FF0000, 0xFF00FFFF, 16, 4) - - 0x02000000] - } - 0x03000000..=0x03FFFFFF => { - self.working_iram[Self::get_unmasked_address( - address, 0x00FFF000, 0xFF000FFF, 12, 8, - ) - 0x03000000] - } - 0x05000000..=0x05FFFFFF => { - let unmasked_address = - Self::get_unmasked_address(address, 0x00FFFF00, 0xFF0000FF, 8, 4); - - match unmasked_address { - 0x05000000..=0x050001FF => self.bg_palette_ram[unmasked_address - 0x05000000], - 0x05000200..=0x050003FF => self.obj_palette_ram[unmasked_address - 0x05000200], - _ => unreachable!(), - } - } - 0x06000000..=0x06FFFFFF => { - let unmasked_address = - Self::get_unmasked_address(address, 0x00FF0000, 0xFF00FFFF, 16, 2); - - // VRAM is 64k+32k+32k with the last two 32k being one mirrors of each other - match unmasked_address { - 0x06000000..=0x06017FFF => self.video_ram[unmasked_address - 0x06000000], - 0x06018000..=0x0601FFFF => { - self.video_ram[unmasked_address - 0x06000000 - 0x8000] - } - _ => unreachable!(), - } - } - 0x07000000..=0x07FFFFFF => { - let unmasked_address = - Self::get_unmasked_address(address, 0x00FFFF00, 0xFF0000FF, 8, 4); - - self.obj_attributes[unmasked_address - 0x07000000] - } - 0x08000000..=0x09FFFFFF => self.read_rom(address - 0x08000000), - 0x0A000000..=0x0BFFFFFF => self.read_rom(address - 0x0A000000), - 0x0C000000..=0x0DFFFFFF => self.read_rom(address - 0x0C000000), - 0x0E000000..=0x0E00FFFF => unimplemented!("SRAM region is unimplemented"), - 0x00004000..=0x01FFFFFF | 0x10000000..=0xFFFFFFFF => { - log(format!("read on unused memory {address:x}")); - self.unused_region.get(&address).map_or(0, |v| *v) - } - _ => unimplemented!("Unimplemented memory region. {address:x}"), - } - } - - fn write_at(&mut self, address: Self::Address, value: Self::Value) { - match address { - 0x00000000..=0x00003FFF => self.bios_system_rom[address] = value, - 0x02000000..=0x0203FFFF => self.working_ram[address - 0x02000000] = value, - // Mirror - 0x02040000..=0x02FFFFFF => { - self.working_ram[Self::get_unmasked_address( - address, 0x00FF0000, 0xFF00FFFF, 16, 4, - ) - 0x02000000] = value; - } - 0x03000000..=0x03007FFF => self.working_iram[address - 0x03000000] = value, - // Mirror - 0x03008000..=0x03FFFFFF => { - self.working_iram[Self::get_unmasked_address( - address, 0x00FFF000, 0xFF000FFF, 12, 8, - ) - 0x03000000] = value - } - 0x05000000..=0x05FFFFFF => { - let unmasked_address = - Self::get_unmasked_address(address, 0x00FFFF00, 0xFF0000FF, 8, 4); - - match unmasked_address { - 0x05000000..=0x050001FF => { - self.bg_palette_ram[unmasked_address - 0x05000000] = value - } - 0x05000200..=0x050003FF => { - self.obj_palette_ram[unmasked_address - 0x05000200] = value - } - _ => unreachable!(), - }; - } - 0x06000000..=0x06FFFFFF => { - let unmasked_address = - Self::get_unmasked_address(address, 0x00FF0000, 0xFF00FFFF, 16, 2); - - // VRAM is 64k+32k+32k with the last two 32k being one mirrors of each other - match unmasked_address { - 0x06000000..=0x06017FFF => { - self.video_ram[unmasked_address - 0x06000000] = value - } - 0x06018000..=0x0601FFFF => { - self.video_ram[unmasked_address - 0x06000000 - 0x8000] = value - } - _ => unreachable!(), - } - } - 0x07000000..=0x07FFFFFF => { - let unmasked_address = - Self::get_unmasked_address(address, 0x00FFFF00, 0xFF0000FF, 8, 4); - - self.obj_attributes[unmasked_address - 0x07000000] = value - } - 0x00004000..=0x01FFFFFF | 0x10000000..=0xFFFFFFFF => { - log(format!("read on unused memory {address:x}")); - self.unused_region.insert(address, value); - } - 0x08000000..=0x0FFFFFFF => { - // TODO: this should be split - self.rom[address - 0x08000000] = value; - } - - _ => unimplemented!("Unimplemented memory region {address:x}."), - } - } -} - -impl InternalMemory { - pub fn read_word(&self, address: usize) -> u32 { - if address & 3 != 0 { - log("warning, read_word has address not word aligned"); - } - - let part_0: u32 = self.read_at(address).try_into().unwrap(); - let part_1: u32 = self.read_at(address + 1).try_into().unwrap(); - let part_2: u32 = self.read_at(address + 2).try_into().unwrap(); - let part_3: u32 = self.read_at(address + 3).try_into().unwrap(); - - part_3 << 24_u32 | part_2 << 16_u32 | part_1 << 8_u32 | part_0 - } - - pub fn write_word(&mut self, address: usize, value: u32) { - if address & 3 != 0 { - log("warning, write_word has address not word aligned"); - } - let part_0: u8 = value.get_bits(0..=7).try_into().unwrap(); - let part_1: u8 = value.get_bits(8..=15).try_into().unwrap(); - let part_2: u8 = value.get_bits(16..=23).try_into().unwrap(); - let part_3: u8 = value.get_bits(24..=31).try_into().unwrap(); - - self.write_at(address, part_0); - self.write_at(address + 1, part_1); - self.write_at(address + 2, part_2); - self.write_at(address + 3, part_3); - } - - pub fn read_half_word(&self, address: usize) -> u16 { - if address & 1 != 0 { - log("warning, read_half_word has address not half-word aligned"); - } - - let part_0: u16 = self.read_at(address).try_into().unwrap(); - let part_1: u16 = self.read_at(address + 1).try_into().unwrap(); - - part_1 << 8 | part_0 - } - - pub fn write_half_word(&mut self, address: usize, value: u16) { - if address & 1 != 0 { - log("warning, write_half_word has address not half-word aligned"); - } - - let part_0: u8 = value.get_bits(0..=7).try_into().unwrap(); - let part_1: u8 = value.get_bits(8..=15).try_into().unwrap(); - - self.write_at(address, part_0); - self.write_at(address + 1, part_1); - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_write_work_ram() { - let mut im = InternalMemory::default(); - - let address = 0x03000005; - im.write_at(address, 5); - - assert_eq!(im.working_iram[5], 5); - } - - #[test] - fn test_last_byte_work_ram() { - let mut im = InternalMemory::default(); - - let address = 0x03007FFF; - im.write_at(address, 5); - - assert_eq!(im.working_iram[0x7FFF], 5); - } - - #[test] - fn test_read_work_ram() { - let mut im = InternalMemory::default(); - im.working_iram[5] = 10; - - let address = 0x03000005; - assert_eq!(im.read_at(address), 10); - } - - #[test] - fn write_bg_palette_ram() { - let mut im = InternalMemory::default(); - let address = 0x05000008; - - im.write_at(address, 10); - assert_eq!(im.bg_palette_ram[8], 10); - } - - #[test] - fn read_bg_palette_ram() { - let mut im = InternalMemory::default(); - im.bg_palette_ram[8] = 15; - - let address = 0x05000008; - let value = im.read_at(address); - - assert_eq!(value, 15); - } - - #[test] - fn test_last_byte_bg_palette_ram() { - let mut im = InternalMemory::default(); - - let address = 0x050001FF; - im.write_at(address, 5); - - assert_eq!(im.bg_palette_ram[0x1FF], 5); - } - - #[test] - fn write_obj_palette_ram() { - let mut im = InternalMemory::default(); - let address = 0x05000208; - - im.write_at(address, 10); - assert_eq!(im.obj_palette_ram[8], 10); - } - - #[test] - fn read_obj_palette_ram() { - let mut im = InternalMemory::default(); - im.obj_palette_ram[8] = 15; - - let address = 0x05000208; - - let value = im.read_at(address); - - assert_eq!(value, 15); - } - - #[test] - fn test_last_byte_obj_palette_ram() { - let mut im = InternalMemory::default(); - - let address = 0x050003FF; - im.write_at(address, 5); - - assert_eq!(im.obj_palette_ram[0x1FF], 5); - } - - #[test] - fn write_vram() { - let mut im = InternalMemory::default(); - let address = 0x06000004; - - im.write_at(address, 23); - assert_eq!(im.video_ram[4], 23); - } - - #[test] - fn read_vram() { - let mut im = InternalMemory::default(); - im.video_ram[4] = 15; - - let address = 0x06000004; - let value = im.read_at(address); - - assert_eq!(value, 15); - } - - #[test] - fn test_last_byte_vram() { - let mut im = InternalMemory::default(); - - let address = 0x06017FFF; - im.write_at(address, 5); - - assert_eq!(im.video_ram[0x17FFF], 5); - } - - #[test] - fn test_read_write_bios_memory() { - let mut im = InternalMemory::default(); - im.write_at(0x000001EC, 10); - assert_eq!(im.read_at(0x000001EC), 10); - } - - #[test] - fn test_read_rom() { - let im = InternalMemory { - rom: vec![1, 2, 3, 4], - ..Default::default() - }; - let address = 0x08000000; - assert_eq!(im.read_at(address), 1); - - // Testing reading in empty rom - let address = 0x09FF_FFFF; - assert_eq!(im.read_at(address), 0xFF); - - let address = 0x09FF_FFEE; - assert_eq!(im.read_at(address), 0xF7); - - let address = 0x09FF_FFEF; - assert_eq!(im.read_at(address), 0xFF); - } - - #[test] - fn check_read_word() { - let im = InternalMemory { - bios_system_rom: vec![0x12, 0x34, 0x56, 0x78], - ..Default::default() - }; - assert_eq!(im.read_word(0), 0x78563412); - } - - #[test] - fn check_write_word() { - let mut im = InternalMemory::default(); - im.write_word(0, 0x12345678); - - assert_eq!(im.bios_system_rom[0], 0x78); - assert_eq!(im.bios_system_rom[1], 0x56); - assert_eq!(im.bios_system_rom[2], 0x34); - assert_eq!(im.bios_system_rom[3], 0x12); - } - - #[test] - fn check_write_half_word() { - let mut im = InternalMemory::default(); - im.write_half_word(0, 0x1234); - - assert_eq!(im.bios_system_rom[0], 0x34); - assert_eq!(im.bios_system_rom[1], 0x12); - } - - #[test] - fn check_read_half_word() { - let im = InternalMemory { - bios_system_rom: vec![0x12, 0x34], - ..Default::default() - }; - assert_eq!(im.read_half_word(0), 0x3412); - } - - #[test] - fn test_mirror_3ffffxx() { - let mut im = InternalMemory::default(); - im.working_iram[0x7FF0] = 5; - - assert_eq!(im.read_at(0x3FFFFF0), 5); - - im.write_at(0x3FFFFA0, 10); - - assert_eq!(im.working_iram[0x7FA0], 10); - } - - #[test] - fn test_mirror_wram() { - let mut im = InternalMemory::default(); - im.working_ram[0x010003] = 5; - - assert_eq!(im.read_at(0x02010003), 5); - assert_eq!(im.read_at(0x02050003), 5); - assert_eq!(im.read_at(0x02350003), 5); - assert_eq!(im.read_at(0x02F50003), 5); - - im.write_at(0x02010003, 2); - assert_eq!(im.working_ram[0x010003], 2); - - im.write_at(0x02050003, 1); - assert_eq!(im.working_ram[0x010003], 1); - - im.write_at(0x02350010, 1); - assert_eq!(im.working_ram[0x010010], 1); - - im.write_at(0x02F5003F, 1); - assert_eq!(im.working_ram[0x01003F], 1); - } - - #[test] - fn test_mirror_iram() { - let mut im = InternalMemory::default(); - im.working_iram[0x21FF] = 5; - - assert_eq!(im.read_at(0x030021FF), 5); - assert_eq!(im.read_at(0x0300A1FF), 5); - assert_eq!(im.read_at(0x030121FF), 5); - assert_eq!(im.read_at(0x03FFA1FF), 5); - - im.write_at(0x030021FF, 2); - assert_eq!(im.working_iram[0x21FF], 2); - - im.write_at(0x0300A1FF, 1); - assert_eq!(im.working_iram[0x21FF], 1); - - im.write_at(0x030171FF, 10); - assert_eq!(im.working_iram[0x71FF], 10); - - im.write_at(0x03FFF1FF, 1); - assert_eq!(im.working_iram[0x71FF], 1); - } - - #[test] - fn test_mirror_bg_palette() { - let mut im = InternalMemory::default(); - im.bg_palette_ram[0x134] = 5; - - assert_eq!(im.read_at(0x05000134), 5); - assert_eq!(im.read_at(0x05000534), 5); - assert_eq!(im.read_at(0x05012534), 5); - assert_eq!(im.read_at(0x05FFFD34), 5); - - im.write_at(0x05000134, 10); - assert_eq!(im.bg_palette_ram[0x134], 10); - - im.write_at(0x05000534, 11); - assert_eq!(im.bg_palette_ram[0x134], 11); - - im.write_at(0x05012534, 12); - assert_eq!(im.bg_palette_ram[0x134], 12); - - im.write_at(0x05FFFD34, 13); - assert_eq!(im.bg_palette_ram[0x134], 13); - } - - #[test] - fn test_mirror_obj_palette() { - let mut im = InternalMemory::default(); - im.obj_palette_ram[0x134] = 5; - - assert_eq!(im.read_at(0x05000334), 5); - assert_eq!(im.read_at(0x05000734), 5); - assert_eq!(im.read_at(0x05012734), 5); - assert_eq!(im.read_at(0x05FFFF34), 5); - - im.write_at(0x05000334, 10); - assert_eq!(im.obj_palette_ram[0x134], 10); - - im.write_at(0x05000734, 11); - assert_eq!(im.obj_palette_ram[0x134], 11); - - im.write_at(0x05012734, 12); - assert_eq!(im.obj_palette_ram[0x134], 12); - - im.write_at(0x05FFFF34, 13); - assert_eq!(im.obj_palette_ram[0x134], 13); - } - - #[test] - fn test_mirror_vram() { - let mut im = InternalMemory::default(); - im.video_ram[0x09345] = 5; - - assert_eq!(im.read_at(0x06009345), 5); - assert_eq!(im.read_at(0x06029345), 5); - assert_eq!(im.read_at(0x06129345), 5); - assert_eq!(im.read_at(0x06FE9345), 5); - - im.write_at(0x06009345, 1); - assert_eq!(im.video_ram[0x09345], 1); - - im.write_at(0x06029345, 2); - assert_eq!(im.video_ram[0x09345], 2); - - im.write_at(0x06129345, 3); - assert_eq!(im.video_ram[0x09345], 3); - - im.write_at(0x06FE9345, 4); - assert_eq!(im.video_ram[0x09345], 4); - - im.video_ram[0x11345] = 10; - assert_eq!(im.read_at(0x06019345), 10); - assert_eq!(im.read_at(0x06131345), 10); - } - - #[test] - fn test_mirror_oam() { - let mut im = InternalMemory::default(); - im.obj_attributes[0x134] = 5; - - assert_eq!(im.read_at(0x07000134), 5); - assert_eq!(im.read_at(0x07000534), 5); - assert_eq!(im.read_at(0x0700F534), 5); - assert_eq!(im.read_at(0x07FFFD34), 5); - - im.write_at(0x07000134, 10); - assert_eq!(im.obj_attributes[0x134], 10); - - im.write_at(0x07000534, 11); - assert_eq!(im.obj_attributes[0x134], 11); - - im.write_at(0x0700F534, 12); - assert_eq!(im.obj_attributes[0x134], 12); - - im.write_at(0x07FFFD34, 13); - assert_eq!(im.obj_attributes[0x134], 13); - } -} diff --git a/emu/src/memory/io_device.rs b/emu/src/memory/io_device.rs deleted file mode 100644 index 61b8ae44..00000000 --- a/emu/src/memory/io_device.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub trait IoDevice { - type Address; - type Value; - - fn read_at(&self, address: Self::Address) -> Self::Value; - fn write_at(&mut self, address: Self::Address, value: Self::Value); -} diff --git a/emu/src/memory/mod.rs b/emu/src/memory/mod.rs deleted file mode 100644 index a458bacf..00000000 --- a/emu/src/memory/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod internal_memory; -pub mod io_device; diff --git a/emu/src/render/ppu.rs b/emu/src/render/ppu.rs index 46534920..628413d5 100644 --- a/emu/src/render/ppu.rs +++ b/emu/src/render/ppu.rs @@ -8,16 +8,21 @@ use rand::Rng; use crate::{ cpu::arm7tdmi::Arm7tdmi, render::{ - color::{Color, PaletteType}, + // color::{Color, PaletteType}, gba_lcd::GbaLcd, - LCD_HEIGHT, LCD_WIDTH, MAX_COLORS_FULL_PALETTE, MAX_COLORS_SINGLE_PALETTE, + // LCD_HEIGHT, LCD_WIDTH, + MAX_COLORS_FULL_PALETTE, + MAX_COLORS_SINGLE_PALETTE, MAX_PALETTES_BY_TYPE, }, }; +use super::color::{Color, PaletteType}; + #[derive(Default)] pub struct PixelProcessUnit { cpu: Arc>, + #[allow(dead_code)] gba_lcd: Arc>>, } @@ -26,92 +31,12 @@ impl PixelProcessUnit { Self { cpu, gba_lcd } } - /// Render the current frame. - /// # Panics - pub fn render(&self) { - #[allow(unused_assignments)] - let mut bg_mode = self.cpu.lock().unwrap().bus.lcd.get_bg_mode(); - - // BG_MODE_3 forced for now. - bg_mode = 3; - - #[cfg(feature = "mode_3")] - { - bg_mode = 3; - } - - #[cfg(feature = "mode_5")] - { - bg_mode = 5; - } - - match bg_mode { - 0 => { - todo!("BG_MODE 0 not implemented yet") - } - 1 => { - todo!("BG_MODE 1 not implemented yet") - } - 2 => { - todo!("BG_MODE 2 not implemented yet") - } - 3 => { - let memory = &self.cpu.lock().unwrap().bus.internal_memory; - let mut gba_lcd = self.gba_lcd.lock().unwrap(); - // Bitmap mode - for x in 0..LCD_HEIGHT { - for y in 0..LCD_WIDTH { - let index_color = (x * LCD_WIDTH + y) * 2; - let color: Color = [ - memory.video_ram[index_color], - memory.video_ram[index_color + 1], - ] - .into(); - gba_lcd.set_pixel(x, y, color); - } - } - } - 4 => { - todo!("BG_MODE 4 not implemented yet") - } - 5 => { - // 06000000-06009FFF for Frame 0 - // 0600A000-06013FFF for Frame 1 - // let selected_frame = memory.lcd_registers.get_frame_select(); - // - // // Bitmap mode - // for y in 0..GBC_LCD_HEIGHT { - // for x in 0..GBC_LCD_WIDTH { - // let index_color = (y * GBC_LCD_WIDTH + x) * 2 - // + (selected_frame * GBC_LCD_HEIGHT * GBC_LCD_WIDTH); - // // let color: Color = [ - // // memory.video_ram[index_color], - // // memory.video_ram[index_color + 1], - // // ] - // // .into(); - // // FIXME - // let color = Color(0); - // - // gba_lcd.set_gbc_pixel(x, y, color); - // } - // } - } - _ => panic!("BG MODE doesn't exist."), - } - } - fn get_array_color(&self, index: usize, palette_type: &PaletteType) -> [u8; 2] { debug_assert!(index % 2 == 0); - let memory = &self.cpu.lock().unwrap().bus.internal_memory; + let lcd = &self.cpu.lock().unwrap().bus.lcd; match palette_type { - PaletteType::BG => [ - memory.bg_palette_ram[index], - memory.bg_palette_ram[index + 1], - ], - PaletteType::OBJ => [ - memory.obj_palette_ram[index], - memory.obj_palette_ram[index + 1], - ], + PaletteType::BG => [lcd.bg_palette_ram[index], lcd.bg_palette_ram[index + 1]], + PaletteType::OBJ => [lcd.obj_palette_ram[index], lcd.obj_palette_ram[index + 1]], } } @@ -199,27 +124,3 @@ impl PixelProcessUnit { } } } - -#[cfg(test)] -mod tests { - use super::PixelProcessUnit; - use crate::render::color::PaletteType; - - #[test] - fn test_get_palettes_bg() { - let ppu = PixelProcessUnit::default(); - - let bg_palettes = ppu.get_palettes(&PaletteType::BG); - assert_eq!(bg_palettes.len(), 16); - assert_eq!(bg_palettes[0].len(), 16); - } - - #[test] - fn test_get_palettes_obj() { - let ppu = PixelProcessUnit::default(); - - let obj_palettes = ppu.get_palettes(&PaletteType::OBJ); - assert_eq!(obj_palettes.len(), 16); - assert_eq!(obj_palettes[0].len(), 16); - } -} diff --git a/ui/src/gba_display.rs b/ui/src/gba_display.rs index c2b57e97..e4925e16 100644 --- a/ui/src/gba_display.rs +++ b/ui/src/gba_display.rs @@ -3,7 +3,6 @@ use egui::{self, ColorImage, Ui, Vec2}; use eframe::epaint::textures::TextureOptions; use std::sync::{Arc, Mutex}; -use emu::render::color::Color; use emu::{ gba::Gba, render::{LCD_HEIGHT, LCD_WIDTH}, @@ -12,18 +11,13 @@ use emu::{ use crate::ui_traits::UiTool; pub struct GbaDisplay { - pixels_buffer: Arc>, gba: Arc>, scale: f32, } impl GbaDisplay { pub(crate) fn new(gba: Arc>) -> Self { - Self { - pixels_buffer: Arc::new(Mutex::new([[Color::default(); LCD_WIDTH]; LCD_HEIGHT])), - gba, - scale: 1.0, - } + Self { gba, scale: 1.0 } } fn ui(&mut self, ui: &mut Ui) { @@ -39,13 +33,17 @@ impl GbaDisplay { } }); - let gba = self.gba.lock().unwrap(); - gba.ppu.render(); - + //TODO: Fix this .lock().unwrap() repeated two times let rgb_data = self - .pixels_buffer + .gba + .lock() + .unwrap() + .cpu .lock() .unwrap() + .bus + .lcd + .buffer .iter() .flat_map(|row| { row.iter().flat_map(|pixel| { @@ -57,8 +55,6 @@ impl GbaDisplay { }) .collect::>(); - drop(gba); - let image = ColorImage::from_rgb([LCD_WIDTH, LCD_HEIGHT], &rgb_data); let texture = ui