gsu.rsannotatedgsu.rssource562 lines · 15.5 KB · raw
1mod codecache;
2mod instructions;
3
4use crate::superfx::gsu::codecache::CodeCache;
5use crate::superfx::gsu::instructions::PlotState;
6use bincode::{Decode, Encode};
7use jgenesis_common::num::{GetBit, U16Ext};
8use jgenesis_proc_macros::EnumDisplay;
9use std::fmt::{Display, Formatter};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode, EnumDisplay)]
12enum MultiplierSpeed {
13    #[default]
14    Standard,
15    High,
16}
17
18impl MultiplierSpeed {
19    fn from_bit(bit: bool) -> Self {
20        if bit { Self::High } else { Self::Standard }
21    }
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
25enum ClockSpeed {
26    #[default]
27    Slow,
28    Fast,
29}
30
31impl ClockSpeed {
32    fn from_bit(bit: bool) -> Self {
33        if bit { Self::Fast } else { Self::Slow }
34    }
35
36    const fn mclk_divider(self) -> u64 {
37        match self {
38            // mclk/2 = 10.74 MHz
39            Self::Slow => 2,
40            // mclk/1 = 21.47 MHz
41            Self::Fast => 1,
42        }
43    }
44
45    const fn memory_access_cycles(self) -> u8 {
46        match self {
47            Self::Slow => 3,
48            Self::Fast => 5,
49        }
50    }
51
52    const fn rom_buffer_wait_cycles(self) -> u8 {
53        // TODO are these numbers right?
54        match self {
55            Self::Slow => 5,
56            Self::Fast => 7,
57        }
58    }
59}
60
61impl Display for ClockSpeed {
62    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
63        match self {
64            Self::Slow => write!(f, "10.74 MHz"),
65            Self::Fast => write!(f, "21.47 MHz"),
66        }
67    }
68}
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
71enum ColorGradientColors {
72    #[default]
73    Four,
74    Sixteen,
75    TwoFiftySix,
76}
77
78impl ColorGradientColors {
79    fn from_byte(byte: u8) -> Self {
80        match byte & 0x03 {
81            0x00 => Self::Four,
82            0x01 => Self::Sixteen,
83            0x02 | 0x03 => Self::TwoFiftySix,
84            _ => unreachable!("value & 0x03 is always <= 0x03"),
85        }
86    }
87
88    const fn tile_size(self) -> u32 {
89        match self {
90            Self::Four => 16,
91            Self::Sixteen => 32,
92            Self::TwoFiftySix => 64,
93        }
94    }
95
96    const fn bitplanes(self) -> u32 {
97        match self {
98            Self::Four => 2,
99            Self::Sixteen => 4,
100            Self::TwoFiftySix => 8,
101        }
102    }
103
104    const fn color_mask(self) -> u8 {
105        match self {
106            Self::Four => 0x03,
107            Self::Sixteen => 0x0F,
108            Self::TwoFiftySix => 0xFF,
109        }
110    }
111}
112
113impl Display for ColorGradientColors {
114    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
115        match self {
116            Self::Four => write!(f, "4-color"),
117            Self::Sixteen => write!(f, "16-color"),
118            Self::TwoFiftySix => write!(f, "256-color"),
119        }
120    }
121}
122
123#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
124enum ScreenHeight {
125    #[default]
126    Bg128Pixel,
127    Bg160Pixel,
128    Bg192Pixel,
129    ObjMode,
130}
131
132impl ScreenHeight {
133    fn from_byte(byte: u8) -> Self {
134        match (byte.bit(5), byte.bit(2)) {
135            (false, false) => Self::Bg128Pixel,
136            (false, true) => Self::Bg160Pixel,
137            (true, false) => Self::Bg192Pixel,
138            (true, true) => Self::ObjMode,
139        }
140    }
141}
142
143impl Display for ScreenHeight {
144    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
145        match self {
146            Self::Bg128Pixel => write!(f, "128-pixel"),
147            Self::Bg160Pixel => write!(f, "160-pixel"),
148            Self::Bg192Pixel => write!(f, "192-pixel"),
149            Self::ObjMode => write!(f, "OBJ mode"),
150        }
151    }
152}
153
154#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
155pub enum BusAccess {
156    #[default]
157    Snes,
158    Gsu,
159}
160
161impl BusAccess {
162    fn from_bit(bit: bool) -> Self {
163        if bit { Self::Gsu } else { Self::Snes }
164    }
165}
166
167impl Display for BusAccess {
168    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
169        match self {
170            Self::Snes => write!(f, "SNES"),
171            Self::Gsu => write!(f, "GSU"),
172        }
173    }
174}
175
176const NOP_OPCODE: u8 = 0x01;
177
178#[derive(Debug, Clone, Encode, Decode)]
179struct GsuState {
180    opcode_buffer: u8,
181    rom_buffer: u8,
182    rom_buffer_wait_cycles: u8,
183    ram_buffer_wait_cycles: u8,
184    ram_address_buffer: u16,
185    rom_pointer_changed: bool,
186    ram_buffer_written: bool,
187    just_jumped: bool,
188}
189
190impl GsuState {
191    fn new() -> Self {
192        Self {
193            opcode_buffer: NOP_OPCODE,
194            rom_buffer: 0,
195            rom_buffer_wait_cycles: 0,
196            ram_buffer_wait_cycles: 0,
197            ram_address_buffer: 0,
198            rom_pointer_changed: false,
199            ram_buffer_written: false,
200            just_jumped: false,
201        }
202    }
203}
204
205#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
206enum StopState {
207    // GSU is running normally
208    #[default]
209    None,
210    // STOP instruction just executed, stop state has not been progressed yet
211    StopExecuted,
212    // Previous instruction was a STOP instruction and stop state was progressed post-execution
213    StopPending,
214}
215
216impl StopState {
217    #[must_use]
218    fn next(self) -> Self {
219        match self {
220            Self::None => Self::None,
221            Self::StopExecuted | Self::StopPending => Self::StopPending,
222        }
223    }
224}
225
226const VERSION_REGISTER: u8 = 0x04;
227
228#[derive(Debug, Clone, Encode, Decode)]
229pub struct GraphicsSupportUnit {
230    r: [u16; 16],
231    r_latch: u8,
232    pbr: u8,
233    rombr: u8,
234    code_cache: CodeCache,
235    state: GsuState,
236    stop_state: StopState,
237    plot_state: PlotState,
238    zero_flag: bool,
239    carry_flag: bool,
240    sign_flag: bool,
241    overflow_flag: bool,
242    go: bool,
243    alt1: bool,
244    alt2: bool,
245    b: bool,
246    sreg: u8,
247    dreg: u8,
248    irq: bool,
249    irq_enabled: bool,
250    multiplier_speed: MultiplierSpeed,
251    clock_speed: ClockSpeed,
252    screen_base: u32,
253    color: u8,
254    color_gradient: ColorGradientColors,
255    screen_height: ScreenHeight,
256    plot_transparent_pixels: bool,
257    dither_on: bool,
258    por_high_nibble_flag: bool,
259    por_freeze_high_nibble: bool,
260    force_obj_mode: bool,
261    rom_access: BusAccess,
262    ram_access: BusAccess,
263    wait_cycles: u8,
264}
265
266impl GraphicsSupportUnit {
267    pub fn new() -> Self {
268        Self {
269            r: [0; 16],
270            r_latch: 0,
271            pbr: 0,
272            rombr: 0,
273            code_cache: CodeCache::new(),
274            state: GsuState::new(),
275            stop_state: StopState::default(),
276            plot_state: PlotState::new(),
277            zero_flag: false,
278            carry_flag: false,
279            sign_flag: false,
280            overflow_flag: false,
281            go: false,
282            alt1: false,
283            alt2: false,
284            b: false,
285            sreg: 0,
286            dreg: 0,
287            irq: false,
288            irq_enabled: false,
289            multiplier_speed: MultiplierSpeed::default(),
290            clock_speed: ClockSpeed::default(),
291            screen_base: 0,
292            color: 0,
293            color_gradient: ColorGradientColors::default(),
294            screen_height: ScreenHeight::default(),
295            plot_transparent_pixels: false,
296            dither_on: false,
297            por_high_nibble_flag: false,
298            por_freeze_high_nibble: false,
299            force_obj_mode: false,
300            rom_access: BusAccess::default(),
301            ram_access: BusAccess::default(),
302            wait_cycles: 0,
303        }
304    }
305
306    pub fn read_register(&mut self, address: u32) -> Option<u8> {
307        log::trace!("GSU register read {:04X}", address & 0xFFFF);
308
309        if self.go {
310            // Only SFR and VCR can be read while the GSU is running
311            return match address & 0x3F {
312                0x30 => Some(self.read_sfr_low()),
313                0x31 => Some(self.read_sfr_high()),
314                0x3B => Some(VERSION_REGISTER),
315                _ => None,
316            };
317        }
318
319        match address & 0x3F {
320            0x00..=0x1F => Some(self.read_r(address)),
321            0x20 | 0x30 => Some(self.read_sfr_low()),
322            0x21 | 0x31 => Some(self.read_sfr_high()),
323            0x24 | 0x34 => Some(self.pbr),
324            0x26 | 0x36 => Some(self.rombr),
325            0x2B | 0x3B => Some(VERSION_REGISTER),
326            0x2E | 0x3E => Some(self.code_cache.cbr().msb()),
327            0x2F | 0x3F => Some(self.code_cache.cbr().lsb()),
328            _ => Some(0x00),
329        }
330    }
331
332    #[allow(clippy::match_same_arms)]
333    pub fn write_register(&mut self, address: u32, value: u8) {
334        log::trace!("GSU register write {:04X} {value:02X}", address & 0xFFFF);
335
336        if self.go {
337            // Only SFR and SCMR can be written while the GSU is running
338            match address & 0xFFFF {
339                0x3030 => self.write_sfr(value),
340                0x303A => self.write_scmr(value),
341                _ => {}
342            }
343            return;
344        }
345
346        match address & 0xFFFF {
347            0x3000..=0x301F => self.write_r(address, value),
348            0x3030 => self.write_sfr(value),
349            0x3034 => self.write_pbr(value),
350            0x3037 => self.write_cfgr(value),
351            0x3038 => self.write_scbr(value),
352            0x3039 => self.write_clsr(value),
353            0x303A => self.write_scmr(value),
354            _ => {}
355        }
356    }
357
358    pub fn read_code_cache_ram(&self, address: u32) -> Option<u8> {
359        if self.go {
360            return None;
361        }
362
363        let ram_addr = map_snes_code_cache_address(address, self.code_cache.cbr());
364        Some(self.code_cache.read_ram(ram_addr as u16))
365    }
366
367    pub fn write_code_cache_ram(&mut self, address: u32, value: u8) {
368        if self.go {
369            return;
370        }
371
372        let ram_addr = map_snes_code_cache_address(address, self.code_cache.cbr());
373        self.code_cache.write_ram(ram_addr as u16, value);
374    }
375
376    pub fn is_running(&self) -> bool {
377        self.go
378    }
379
380    pub fn rom_access(&self) -> BusAccess {
381        self.rom_access
382    }
383
384    pub fn ram_access(&self) -> BusAccess {
385        self.ram_access
386    }
387
388    pub fn tick(&mut self, master_cycles_elapsed: u64, rom: &[u8], ram: &mut [u8]) {
389        if !self.go {
390            self.wait_cycles = 0;
391            return;
392        }
393
394        let mut gsu_cycles = master_cycles_elapsed / self.clock_speed.mclk_divider();
395        while gsu_cycles >= u64::from(self.wait_cycles) {
396            gsu_cycles -= u64::from(self.wait_cycles);
397            self.wait_cycles = instructions::execute(self, rom, ram);
398
399            // Check if a STOP was executed
400            if !self.go {
401                self.wait_cycles = 0;
402                return;
403            }
404        }
405
406        self.wait_cycles -= gsu_cycles as u8;
407    }
408
409    pub fn irq(&self) -> bool {
410        self.irq_enabled && self.irq
411    }
412
413    pub fn reset(&mut self) {
414        self.go = false;
415        self.code_cache.full_clear();
416        self.irq = false;
417    }
418
419    fn read_r(&self, address: u32) -> u8 {
420        let idx = (address & 0x1F) >> 1;
421        if !address.bit(0) { self.r[idx as usize].lsb() } else { self.r[idx as usize].msb() }
422    }
423
424    fn read_sfr_low(&self) -> u8 {
425        (u8::from(self.zero_flag) << 1)
426            | (u8::from(self.carry_flag) << 2)
427            | (u8::from(self.sign_flag) << 3)
428            | (u8::from(self.overflow_flag) << 4)
429            | (u8::from(self.go) << 5)
430            | (u8::from(self.state.rom_buffer_wait_cycles != 0) << 6)
431    }
432
433    fn read_sfr_high(&mut self) -> u8 {
434        let value = (u8::from(self.alt1))
435            | (u8::from(self.alt2) << 1)
436            | (u8::from(self.b) << 4)
437            | (u8::from(self.irq) << 7);
438
439        // Reading SFR high byte clears pending IRQ
440        self.irq = false;
441
442        value
443    }
444
445    fn write_r(&mut self, address: u32, value: u8) {
446        // R0-R15: General registers (16x 16-bit)
447        // R14 is always ROM pointer and R15 is always program counter
448
449        // Writing LSB latches the write
450        // Writing MSB writes the register using the latched value + incoming value
451        if !address.bit(0) {
452            self.r_latch = value;
453        } else {
454            let idx = ((address & 0x1F) >> 1) as usize;
455            self.r[idx] = u16::from_le_bytes([self.r_latch, value]);
456            log::trace!("  R{idx}: {:04X}", self.r[idx]);
457        }
458
459        // Writing R15 MSB ($301F) causes the GSU to begin execution
460        if address & 0x1F == 0x1F {
461            self.go = true;
462            self.state.just_jumped = true;
463            log::trace!("  Started GSU execution");
464        }
465    }
466
467    fn write_sfr(&mut self, value: u8) {
468        // SFR: Status/flag register
469        // Bits 1-5 are R/W, rest are read-only
470        self.zero_flag = value.bit(1);
471        self.carry_flag = value.bit(2);
472        self.sign_flag = value.bit(3);
473        self.overflow_flag = value.bit(4);
474
475        let prev_go = self.go;
476        self.go = value.bit(5);
477
478        if !self.go {
479            // Writing GO=0 sets CBR=0 and clears all code cache lines
480            self.code_cache.full_clear();
481        }
482
483        if !prev_go && self.go {
484            self.state.just_jumped = true;
485        }
486
487        log::trace!("  GO: {}", self.go);
488    }
489
490    fn write_pbr(&mut self, value: u8) {
491        self.pbr = value;
492        log::trace!("  PBR: {value:02X}");
493    }
494
495    fn write_cfgr(&mut self, value: u8) {
496        // CFGR: Config register
497        self.multiplier_speed = MultiplierSpeed::from_bit(value.bit(5));
498        self.irq_enabled = !value.bit(7);
499
500        log::trace!("  Multiplier speed: {}", self.multiplier_speed);
501        log::trace!("  GSU IRQ enabled: {}", self.irq_enabled);
502    }
503
504    fn write_clsr(&mut self, value: u8) {
505        // CLSR: Clock select register
506        self.clock_speed = ClockSpeed::from_bit(value.bit(0));
507
508        log::trace!("  Clock speed: {}", self.clock_speed);
509    }
510
511    fn write_scbr(&mut self, value: u8) {
512        // SCBR: Screen base register
513        // value is in 1KB units
514        self.screen_base = u32::from(value) << 10;
515
516        log::trace!("  Screen base: {:05X}", self.screen_base);
517    }
518
519    fn write_scmr(&mut self, value: u8) {
520        // SCMR: Screen mode register
521        self.ram_access = BusAccess::from_bit(value.bit(3));
522        self.rom_access = BusAccess::from_bit(value.bit(4));
523
524        // TODO it doesn't seem like these fields can be altered while the GSU is running?
525        if !self.go {
526            self.color_gradient = ColorGradientColors::from_byte(value);
527            self.screen_height = ScreenHeight::from_byte(value);
528        }
529
530        log::trace!("  ROM bus access: {}", self.rom_access);
531        log::trace!("  RAM bus access: {}", self.ram_access);
532        log::trace!("  Color gradient: {}", self.color_gradient);
533        log::trace!("  Screen height: {}", self.screen_height);
534    }
535}
536
537fn map_snes_code_cache_address(address: u32, cbr: u16) -> u32 {
538    let snes_offset = (address & 0xFFFF) - 0x3100;
539    snes_offset.wrapping_sub((cbr & 0x1FF).into()) & 0x1FF
540}
541
542#[cfg(test)]
543mod tests {
544    use super::*;
545
546    #[test]
547    fn snes_code_cache_mapping() {
548        assert_eq!(0, map_snes_code_cache_address(0x003100, 0));
549        assert_eq!(0x1FF, map_snes_code_cache_address(0x0032FF, 0));
550
551        assert_eq!(0, map_snes_code_cache_address(0xC03100, 0));
552        assert_eq!(0x1FF, map_snes_code_cache_address(0xC032FF, 0));
553
554        assert_eq!(0, map_snes_code_cache_address(0x003250, 0x9150));
555        assert_eq!(0x0AF, map_snes_code_cache_address(0xBF32FF, 0x9150));
556        assert_eq!(0x0B0, map_snes_code_cache_address(0x003100, 0x9150));
557
558        assert_eq!(0, map_snes_code_cache_address(0x003250, 0x9B50));
559        assert_eq!(0x0AF, map_snes_code_cache_address(0xBF32FF, 0x9B50));
560        assert_eq!(0x0B0, map_snes_code_cache_address(0x003100, 0x9B50));
561    }
562}