1//! Super FX GSU (Graphics Support Unit), a programmable custom RISC-like CPU 2//! 3//! There were 3 different Super FX chips used: Mario Chip 1, GSU-1, and GSU-2. The only differences 4//! between chips seem to be clock speed and memory capacity. 5//! 6//! Mario Chip 1 runs at 10.74 MHz, GSU-1 and GSU-2 run at 21.47 MHz. 7//! 8//! Mario Chip 1 and GSU-1 apparently only supported up to 1MB of ROM, while GSU-2 supported up 9//! to 2MB of ROM. GSU-2 also supported "backup RAM" and "CPU ROM" but no released cartridges used 10//! these features. 11//! 12//! Used by 8 released games including Star Fox and Yoshi's Island, as well as the originally unreleased Star Fox 2 13 14mod gsu; 15 16use crate::common; 17use crate::common::{Rom, impl_take_set_rom}; 18use crate::superfx::gsu::{BusAccess, GraphicsSupportUnit}; 19use bincode::{Decode, Encode}; 20use jgenesis_common::num::U16Ext; 21use jgenesis_proc_macros::PartialClone; 22use std::num::NonZeroU64; 23 24#[derive(Debug, Clone, Encode, Decode, PartialClone)] 25pub struct SuperFx { 26 #[partial_clone(default)] 27 rom: Rom, 28 ram: Box<[u8]>, 29 gsu: GraphicsSupportUnit, 30 gsu_overclock_factor: u64, 31} 32 33impl SuperFx { 34 #[must_use] 35 pub fn new(rom: Box<[u8]>, ram: Box<[u8]>, gsu_overclock_factor: NonZeroU64) -> Self { 36 Self { 37 rom: Rom(rom), 38 ram, 39 gsu: GraphicsSupportUnit::new(), 40 gsu_overclock_factor: gsu_overclock_factor.get(), 41 } 42 } 43 44 #[inline] 45 #[must_use] 46 pub fn read(&mut self, address: u32) -> Option<u8> { 47 let bank = (address >> 16) & 0xFF; 48 let offset = address & 0xFFFF; 49 match (bank, offset) { 50 (0x00..=0x3F | 0x80..=0xBF, 0x3000..=0x30FF | 0x3300..=0x34FF) => { 51 // GSU I/O ports 52 self.gsu.read_register(address) 53 } 54 (0x00..=0x3F | 0x80..=0xBF, 0x3100..=0x32FF) => { 55 // GSU code cache RAM 56 self.gsu.read_code_cache_ram(address) 57 } 58 (0x00..=0x3F | 0x80..=0xBF, 0x8000..=0xFFFF) => { 59 // ROM, LoROM mapping 60 match (self.gsu.is_running(), self.gsu.rom_access()) { 61 (false, _) | (true, BusAccess::Snes) => { 62 let rom_addr = map_lorom_address(address, self.rom.len() as u32); 63 Some(self.rom[rom_addr as usize]) 64 } 65 (true, BusAccess::Gsu) => fixed_sfx_interrupt_vector(address), 66 } 67 } 68 (0x40..=0x5F | 0xC0..=0xDF, _) => { 69 // ROM, HiROM mapping 70 match (self.gsu.is_running(), self.gsu.rom_access()) { 71 (false, _) | (true, BusAccess::Snes) => { 72 let rom_addr = map_hirom_address(address, self.rom.len() as u32); 73 Some(self.rom[rom_addr as usize]) 74 } 75 (true, BusAccess::Gsu) => fixed_sfx_interrupt_vector(address), 76 } 77 } 78 (0x00..=0x3F | 0x80..=0xBF, 0x6000..=0x7FFF) => { 79 // First 8KB of RAM 80 (!self.gsu.is_running() || self.gsu.ram_access() == BusAccess::Snes) 81 .then(|| self.ram[(address & 0x1FFF) as usize]) 82 } 83 (0x70..=0x71 | 0xF0..=0xF1, _) => { 84 // RAM 85 (!self.gsu.is_running() || self.gsu.ram_access() == BusAccess::Snes) 86 .then(|| self.ram[(address as usize) & (self.ram.len() - 1)]) 87 } 88 _ => None, 89 } 90 } 91 92 #[inline] 93 pub fn write(&mut self, address: u32, value: u8) { 94 let bank = (address >> 16) & 0xFF; 95 let offset = address & 0xFFFF; 96 match (bank, offset) { 97 (0x00..=0x3F | 0x80..=0xBF, 0x3000..=0x30FF | 0x3300..=0x34FF) => { 98 // GSU I/O ports 99 self.gsu.write_register(address, value); 100 } 101 (0x00..=0x3F | 0x80..=0xBF, 0x3100..=0x32FF) => { 102 // GSU code cache RAM 103 self.gsu.write_code_cache_ram(address, value); 104 } 105 (0x00..=0x3F | 0x80..=0xBF, 0x6000..=0x7FFF) 106 if !self.gsu.is_running() || self.gsu.ram_access() == BusAccess::Snes => 107 { 108 // First 8KB of RAM 109 self.ram[(address & 0x1FFF) as usize] = value; 110 } 111 (0x70..=0x71 | 0xF0..=0xF1, _) 112 if !self.gsu.is_running() || self.gsu.ram_access() == BusAccess::Snes => 113 { 114 // RAM 115 self.ram[(address as usize) & (self.ram.len() - 1)] = value; 116 } 117 _ => {} 118 } 119 } 120 121 #[inline] 122 pub fn tick(&mut self, master_cycles_elapsed: u64) { 123 self.gsu.tick(self.gsu_overclock_factor * master_cycles_elapsed, &self.rom, &mut self.ram); 124 } 125 126 #[inline] 127 #[must_use] 128 pub fn irq(&self) -> bool { 129 self.gsu.irq() 130 } 131 132 pub fn reset(&mut self) { 133 self.gsu.reset(); 134 } 135 136 #[inline] 137 #[must_use] 138 pub fn has_battery(&self) -> bool { 139 // Most of the Super FX games do not have battery backup but some do, e.g. Yoshi's Island 140 // This is indicated by a chipset byte of $15 or $1A instead of $13 or $14 141 let chipset_byte = self.rom[common::LOROM_CHIPSET_BYTE_ADDRESS]; 142 chipset_byte == 0x15 || chipset_byte == 0x1A 143 } 144 145 #[inline] 146 #[must_use] 147 pub fn sram(&self) -> &[u8] { 148 self.ram.as_ref() 149 } 150 151 impl_take_set_rom!(rom); 152 153 pub fn update_gsu_overclock_factor(&mut self, overclock_factor: NonZeroU64) { 154 self.gsu_overclock_factor = overclock_factor.get(); 155 } 156} 157 158fn map_lorom_address(address: u32, rom_len: u32) -> u32 { 159 let rom_addr = (address & 0x7FFF) | ((address & 0x7F0000) >> 1); 160 rom_addr & (rom_len - 1) 161} 162 163fn map_hirom_address(address: u32, rom_len: u32) -> u32 { 164 let rom_addr = address & 0x3FFFFF; 165 rom_addr & (rom_len - 1) 166} 167 168const SFX_COP_VECTOR: u16 = 0x0104; 169const SFX_BRK_VECTOR: u16 = 0x0100; 170const SFX_ABORT_VECTOR: u16 = 0x0100; 171const SFX_NMI_VECTOR: u16 = 0x0108; 172const SFX_IRQ_VECTOR: u16 = 0x010C; 173 174fn fixed_sfx_interrupt_vector(address: u32) -> Option<u8> { 175 // If the SNES CPU accesses ROM while the GSU is running and has control of the ROM bus, the 176 // SNES CPU reads fixed values based on the last 4 bits of the address (intended to allow the 177 // SNES to read interrupt vectors while the GSU is running) 178 match address & 0xF { 179 0x4 => Some(SFX_COP_VECTOR.lsb()), 180 0x5 => Some(SFX_COP_VECTOR.msb()), 181 0x6 => Some(SFX_BRK_VECTOR.lsb()), 182 0x7 => Some(SFX_BRK_VECTOR.msb()), 183 0x8 => Some(SFX_ABORT_VECTOR.lsb()), 184 0x9 => Some(SFX_ABORT_VECTOR.msb()), 185 0xA => Some(SFX_NMI_VECTOR.lsb()), 186 0xB => Some(SFX_NMI_VECTOR.msb()), 187 0xE => Some(SFX_IRQ_VECTOR.lsb()), 188 0xF => Some(SFX_IRQ_VECTOR.msb()), 189 _ => None, 190 } 191} 192 193#[must_use] 194pub fn guess_ram_len(rom: &[u8]) -> usize { 195 // $7FDA == maker code; $33 indicates extended header 196 // $7FBD == expansion RAM size in extended header, as kilobytes as a power of 2 197 // Older Super FX games don't have an extended header, so default to 32KB if the header doesn't 198 // explicitly specify 64KB 199 match (rom[0x7FDA], rom[0x7FBD]) { 200 (0x33, 0x06) => 64 * 1024, 201 _ => { 202 if &rom[0x7FC0..0x7FD2] == "Voxels in progress".as_bytes() { 203 // VOXEL.smc demo does not specify RAM size but requires 64KB of Super FX RAM 204 64 * 1024 205 } else { 206 // Header does not specify RAM size (e.g. Star Fox), default to 32KB 207 32 * 1024 208 } 209 } 210 } 211}