From e6c57e8046b5fa02fe47a48fd522211c9c957f68 Mon Sep 17 00:00:00 2001 From: Franco Colmenarez Date: Mon, 4 Nov 2024 15:34:46 -0500 Subject: [PATCH] cgram read and write logic --- snes-core/src/ppu/registers.rs | 150 +++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) diff --git a/snes-core/src/ppu/registers.rs b/snes-core/src/ppu/registers.rs index 0afe96f..de1b0f4 100644 --- a/snes-core/src/ppu/registers.rs +++ b/snes-core/src/ppu/registers.rs @@ -147,6 +147,13 @@ pub enum Background { } +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum CGRamDataReadFlipflop{ + FirstAccess, // Lower 8 bits + SecondAccess, // Upper 8 bits +} + + pub struct PPURegisters { data: [u8; 64], vram: [u16; 0x8000], @@ -154,6 +161,7 @@ pub struct PPURegisters { pub vblank_nmi: bool, pub h_count: u16, pub v_count: u16, + cgram_data_read_flipflop: CGRamDataReadFlipflop } impl PPURegisters { @@ -165,6 +173,7 @@ impl PPURegisters { vblank_nmi: false, h_count: 0, v_count: 0, + cgram_data_read_flipflop: CGRamDataReadFlipflop::FirstAccess, } } @@ -202,6 +211,10 @@ impl PPURegisters { match address { VMDATAH => self.handle_vram_addr_auto_increment(Some(result), None), VMDATAL => self.handle_vram_addr_auto_increment(None, Some(result)), + RDCGRAM => { + let value = self.get_rdcgram(); + self._write(RDCGRAM, value); + }, _ => {}, }; self._read(address) @@ -218,6 +231,11 @@ impl PPURegisters { self.handle_write_vram(None, Some(value)); }, RDVRAML | RDVRAMH => {}, + CGADD => { + self._write(address, value); + self.cgram_data_read_flipflop = CGRamDataReadFlipflop::FirstAccess; + }, + CGDATA => self.write_cgram(value), _ => self._write(address, value), }; } @@ -402,6 +420,48 @@ impl PPURegisters { let current_bg_mode = self._read(BGMODE); current_bg_mode == 5 || current_bg_mode == 6 } + + fn get_cgram_index(&self) -> u8 { + self._read(CGADD) + } + + fn handle_cgram_flipflop(&mut self) { + match self.cgram_data_read_flipflop { + CGRamDataReadFlipflop::FirstAccess => { + self.cgram_data_read_flipflop = CGRamDataReadFlipflop::SecondAccess; + }, + CGRamDataReadFlipflop::SecondAccess => { + let current_index = self._read(CGADD); + self._write(CGADD, current_index.wrapping_add(1)); + self.cgram_data_read_flipflop = CGRamDataReadFlipflop::FirstAccess; + }, + }; + } + + fn get_rdcgram(&mut self) -> u8 { + let cgram_index = self.get_cgram_index() as usize; + let value = match self.cgram_data_read_flipflop { + CGRamDataReadFlipflop::FirstAccess => self.cgram[cgram_index] as u8, + CGRamDataReadFlipflop::SecondAccess => (self.cgram[cgram_index] >> 8) as u8, + }; + self.handle_cgram_flipflop(); + value + } + + fn write_cgram(&mut self, data: u8) { + let cgram_index = self.get_cgram_index() as usize; + match self.cgram_data_read_flipflop { + CGRamDataReadFlipflop::FirstAccess => { + let current_value = self.cgram[cgram_index]; + self.cgram[cgram_index] = (current_value & 0xFF00) | (data as u16); + }, + CGRamDataReadFlipflop::SecondAccess => { + let current_value = self.cgram[cgram_index]; + self.cgram[cgram_index] = (current_value & 0x00FF) | ((data as u16) << 8); + }, + }; + self.handle_cgram_flipflop(); + } } impl Default for PPURegisters { @@ -678,4 +738,94 @@ mod ppu_registers_test { registers._write(SETINI, 0x01); assert_eq!(registers.get_current_res(), (512, 448)); } + + #[test] + fn test_get_cgram_index() { + let mut registers = PPURegisters::new(); + registers._write(CGADD, 0x00); + assert_eq!(registers.get_cgram_index(), 0x00); + registers._write(CGADD, 0x10); + assert_eq!(registers.get_cgram_index(), 0x10); + registers._write(CGADD, 0xFF); + assert_eq!(registers.get_cgram_index(), 0xFF); + registers._write(CGADD, 0xAB); + assert_eq!(registers.get_cgram_index(), 0xAB); + } + + #[test] + fn test_handle_cgram_flipflop() { + let mut registers = PPURegisters::new(); + registers.cgram_data_read_flipflop = CGRamDataReadFlipflop::FirstAccess; + registers.handle_cgram_flipflop(); + assert_eq!( + registers.cgram_data_read_flipflop, + CGRamDataReadFlipflop::SecondAccess, + ); + + registers.cgram_data_read_flipflop = CGRamDataReadFlipflop::SecondAccess; + registers.handle_cgram_flipflop(); + assert_eq!( + registers.cgram_data_read_flipflop, + CGRamDataReadFlipflop::FirstAccess, + ); + + // When CGADD is written to, the flipflop is reset to first access + registers.cgram_data_read_flipflop = CGRamDataReadFlipflop::FirstAccess; + registers.write(CGADD, 0x00); + assert_eq!( + registers.cgram_data_read_flipflop, + CGRamDataReadFlipflop::FirstAccess, + ); + + registers.cgram_data_read_flipflop = CGRamDataReadFlipflop::SecondAccess; + registers.write(CGADD, 0x00); + assert_eq!( + registers.cgram_data_read_flipflop, + CGRamDataReadFlipflop::FirstAccess, + ); + } + + #[test] + fn test_rdcgram_registers() { + let mut registers = PPURegisters::new(); + registers.cgram_data_read_flipflop = CGRamDataReadFlipflop::FirstAccess; + registers.cgram[0x10] = 0x1234; + registers._write(CGADD, 0x10); + let first_access_value = registers.read(RDCGRAM); + assert_eq!(first_access_value, 0x34); + assert_eq!( + registers.cgram_data_read_flipflop, + CGRamDataReadFlipflop::SecondAccess, + ); + + let second_access_value = registers.read(RDCGRAM); + assert_eq!(registers._read(CGADD), 0x11); + assert_eq!(second_access_value, 0x12); + assert_eq!( + registers.cgram_data_read_flipflop, + CGRamDataReadFlipflop::FirstAccess, + ); + } + + #[test] + fn test_cgdata_registers() { + let mut registers = PPURegisters::new(); + registers.cgram_data_read_flipflop = CGRamDataReadFlipflop::FirstAccess; + registers.cgram[0x10] = 0x0000; + registers._write(CGADD, 0x10); + registers.write(CGDATA, 0x34); + assert_eq!(registers.cgram[0x10], 0x0034); + assert_eq!( + registers.cgram_data_read_flipflop, + CGRamDataReadFlipflop::SecondAccess, + ); + + registers.write(CGDATA, 0x12); + assert_eq!(registers._read(CGADD), 0x11); + assert_eq!(registers.cgram[0x10], 0x1234); + assert_eq!( + registers.cgram_data_read_flipflop, + CGRamDataReadFlipflop::FirstAccess, + ); + } }