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}