Assumptions made to simplify implementation at the cost of timing accuracy (and possibly bus contention accuracy):

  • Every instruction's opcode and operands will be located within the same memory area (code cache / ROM / RAM)
  • If an instruction's opcode is cached, the operands are also cached
5mod alu;
6mod disassemble;
7mod flags;
8mod flow;
9mod load;
10mod plot;
12use crate::superfx;
13use crate::superfx::gsu::{BusAccess, ClockSpeed, GraphicsSupportUnit, StopState};
14
15use crate::superfx::gsu;
16pub use plot::PlotState;
17
18pub fn execute(gsu: &mut GraphicsSupportUnit, rom: &[u8], ram: &mut [u8]) -> u8 {
19    let memory_type = next_opcode_memory_type(gsu);
20    let opcode = gsu.state.opcode_buffer;
21    if (gsu.rom_access == BusAccess::Snes
22        && (memory_type == MemoryType::Rom || is_rom_access_opcode(opcode)))
23        || (gsu.ram_access == BusAccess::Snes
24            && (memory_type == MemoryType::Ram || is_ram_access_opcode(opcode, gsu.alt1, gsu.alt2)))
25    {
26        // GSU is waiting for ROM/RAM access
27        return 1;
28    }
29
30    let mut cycles = 0;
31
32    if gsu.state.just_jumped {
33        gsu.state.just_jumped = false;
34        cycles += fill_cache_to_pc(gsu, gsu.r[15], rom, ram);
35    }
36
37    if memory_type == MemoryType::Rom && gsu.state.rom_buffer_wait_cycles != 0 {
38        cycles += gsu.state.rom_buffer_wait_cycles;
39        gsu.state.rom_buffer_wait_cycles = 0;
40    }
41
42    if memory_type == MemoryType::Ram && gsu.state.ram_buffer_wait_cycles != 0 {
43        cycles += gsu.state.ram_buffer_wait_cycles;
44        gsu.state.ram_buffer_wait_cycles = 0;
45    }
46
47    log::trace!(
48        "Executing opcode {opcode:02X} ({}) from {memory_type:?}; PBR={:02X}, R15={:04X}",
49        disassemble::instruction_str(opcode, gsu.alt1, gsu.alt2),
50        gsu.pbr,
51        gsu.r[15],
52    );
53    log::trace!(
54        "  R0={:04X}, R1={:04X}, R2={:04X}, R3={:04X}, R4={:04X}, R5={:04X}, R6={:04X}, R7={:04X}, R8={:04X}, R9={:04X}, R10={:04X}, R11={:04X}, R12={:04X}, R13={:04X}, R14={:04X}, Z={}, C={}, S={}, OV={}",
55        gsu.r[0],
56        gsu.r[1],
57        gsu.r[2],
58        gsu.r[3],
59        gsu.r[4],
60        gsu.r[5],
61        gsu.r[6],
62        gsu.r[7],
63        gsu.r[8],
64        gsu.r[9],
65        gsu.r[10],
66        gsu.r[11],
67        gsu.r[12],
68        gsu.r[13],
69        gsu.r[14],
70        u8::from(gsu.zero_flag),
71        u8::from(gsu.carry_flag),
72        u8::from(gsu.sign_flag),
73        u8::from(gsu.overflow_flag)
74    );
75
76    fetch_opcode(gsu, rom, ram);
77    cycles += execute_opcode(opcode, memory_type, gsu, rom, ram);
78
79    log::trace!("  Cycle count: {cycles}");
80
81    if gsu.state.rom_pointer_changed {
82        gsu.state.rom_pointer_changed = false;
83    } else {
84        gsu.state.rom_buffer_wait_cycles = gsu.state.rom_buffer_wait_cycles.saturating_sub(cycles);
85    }
86
87    if gsu.state.ram_buffer_written {
88        gsu.state.ram_buffer_written = false;
89    } else {
90        gsu.state.ram_buffer_wait_cycles = gsu.state.ram_buffer_wait_cycles.saturating_sub(cycles);
91    }
92
93    gsu.plot_state.tick(cycles);
94
95    if gsu.stop_state == StopState::StopPending {
96        // Stop GSU
97        gsu.stop_state = StopState::None;
98        gsu.go = false;
99        gsu.irq = true;
100
101        // Ensure the GSU starts execution from the right place if the SNES CPU starts it back up
102        // by writing GO=1
103        gsu.state.opcode_buffer = gsu::NOP_OPCODE;
104        gsu.r[15] = gsu.r[15].wrapping_sub(1);
105    } else {
106        gsu.stop_state = gsu.stop_state.next();
107    }
108
109    cycles
110}
111
112fn is_rom_access_opcode(opcode: u8) -> bool {
113    // GETB/GETBH/GETBL/GETBS ($EF)
114    // GETC/ROMB ($DF)
115    opcode == 0xDF || opcode == 0xEF
116}
117
118fn is_ram_access_opcode(opcode: u8, alt1: bool, alt2: bool) -> bool {
119    // STB/STW ($3x for x=0-B)
120    // LDB/LDW ($4x for x=0-B)
121    // LM/SM ($Fn for n=0-F when ALT1 or ALT2 is set)
122    // LMS/SMS ($An for n=0-F when ALT1 or ALT2 is set)
123    // SBK ($90)
124    matches!(opcode, 0x30..=0x3B | 0x40..=0x4B | 0x90)
125        || ((alt1 || alt2) && matches!(opcode, 0xA0..=0xAF | 0xF0..=0xFF))
126}
127
128fn u24_address(bank: u8, offset: u16) -> u32 {
129    (u32::from(bank) << 16) | u32::from(offset)
130}
131
132#[derive(Debug, Clone, Copy, PartialEq, Eq)]
133enum MemoryType {
134    CodeCache,
135    Rom,
136    Ram,
137}
138
139impl MemoryType {
140    fn access_cycles(self, clock_speed: ClockSpeed) -> u8 {
141        match self {
142            Self::CodeCache => 1,
143            Self::Rom | Self::Ram => clock_speed.memory_access_cycles(),
144        }
145    }
146}
147
148fn read_memory(bank: u8, address: u16, rom: &[u8], ram: &[u8]) -> (u8, MemoryType) {
149    match bank & 0x7F {
150        0x00..=0x3F => {
151            // ROM, LoROM mapping (mirrored in $0000-$7FFF and $8000-$FFFF)
152            let rom_addr = superfx::map_lorom_address(u24_address(bank, address), rom.len() as u32);
153            (rom[rom_addr as usize], MemoryType::Rom)
154        }
155        0x40..=0x5F => {
156            // ROM, HiROM mapping
157            let rom_addr = superfx::map_hirom_address(u24_address(bank, address), rom.len() as u32);
158            (rom[rom_addr as usize], MemoryType::Rom)
159        }
160        0x70..=0x71 => {
161            // RAM
162            // Ignore bank since no existing Super FX cartridges have more than 64KB of RAM
163            let ram_addr = (address as usize) & (ram.len() - 1);
164            (ram[ram_addr], MemoryType::Ram)
165        }
166        _ => {
167            log::error!("GSU read unmapped address: ${bank:02X}:{address:04X}");
168            (0, MemoryType::CodeCache)
169        }
170    }
171}
172
173fn fetch_opcode(gsu: &mut GraphicsSupportUnit, rom: &[u8], ram: &[u8]) {
174    let is_cacheable = gsu.code_cache.pc_is_cacheable(gsu.r[15]);
175    if is_cacheable && let Some(opcode) = gsu.code_cache.get(gsu.r[15]) {
176        gsu.state.opcode_buffer = opcode;
177        gsu.r[15] = gsu.r[15].wrapping_add(1);
178        return;
179    }
180
181    let (opcode, _) = read_memory(gsu.pbr, gsu.r[15], rom, ram);
182    gsu.state.opcode_buffer = opcode;
183
184    log::trace!("  Read opcode {opcode:02X}");
185
186    if is_cacheable {
187        gsu.code_cache.set(gsu.r[15], opcode);
188    }
189
190    gsu.r[15] = gsu.r[15].wrapping_add(1);
191}
192
193fn next_opcode_memory_type(gsu: &GraphicsSupportUnit) -> MemoryType {
194    if gsu.code_cache.pc_is_cacheable(gsu.r[15]) && gsu.code_cache.get(gsu.r[15]).is_some() {
195        MemoryType::CodeCache
196    } else {
197        match gsu.pbr & 0x7F {
198            0x00..=0x5F => MemoryType::Rom,
199            0x70..=0x71 => MemoryType::Ram,
200            _ => panic!("invalid GSU bank {:02X}", gsu.pbr),
201        }
202    }
203}
204
205#[must_use]
206fn fill_cache_to_pc(gsu: &mut GraphicsSupportUnit, pc: u16, rom: &[u8], ram: &[u8]) -> u8 {
207    if !gsu.code_cache.pc_is_cacheable(pc) || gsu.code_cache.get(pc).is_some() {
208        // Not cacheable or already cached
209        return 0;
210    }
211
212    for i in 0..(pc & 0xF) {
213        let cache_addr = (pc & 0xFFF0) | i;
214        let (opcode, _) = read_memory(gsu.pbr, cache_addr, rom, ram);
215        gsu.code_cache.set(cache_addr, opcode);
216    }
217
218    gsu.clock_speed.memory_access_cycles() * (pc & 0xF) as u8
219}
220
221#[must_use]
222fn cache_at_pc(gsu: &mut GraphicsSupportUnit, pc: u16, rom: &[u8], ram: &[u8]) -> u8 {
223    if !gsu.code_cache.pc_is_cacheable(pc) || gsu.code_cache.get(pc).is_some() {
224        // Not cacheable or already cached
225        return 0;
226    }
227
228    let (opcode, _) = read_memory(gsu.pbr, pc, rom, ram);
229    gsu.code_cache.set(pc, opcode);
230
231    gsu.clock_speed.memory_access_cycles()
232}
233
234#[must_use]
235fn fill_cache_from_pc(gsu: &mut GraphicsSupportUnit, rom: &[u8], ram: &[u8]) -> u8 {
236    if gsu.r[15] & 0xF == 0x0 {
237        // PC is at the beginning of a cache line; no need to fill
238        return 0;
239    }
240
241    if !gsu.code_cache.pc_is_cacheable(gsu.r[15]) || gsu.code_cache.get(gsu.r[15]).is_some() {
242        // Not cacheable or already cached
243        return 0;
244    }
245
246    for i in (gsu.r[15] & 0xF)..0x10 {
247        let cache_addr = (gsu.r[15] & 0xFFF0) | i;
248        let (opcode, _) = read_memory(gsu.pbr, cache_addr, rom, ram);
249        gsu.code_cache.set(cache_addr, opcode);
250    }
251
252    gsu.clock_speed.memory_access_cycles() * (0x10 - (gsu.r[15] & 0xF) as u8)
253}
254
255fn read_register(gsu: &GraphicsSupportUnit, register: u8) -> u16 {
256    match register {
257        // Subtract 1 from R15 to account for PC increment happening concurrently with execution
258        15 => gsu.r[15].wrapping_sub(1),
259        _ => gsu.r[register as usize],
260    }
261}
262
263#[must_use]
264fn write_register(
265    gsu: &mut GraphicsSupportUnit,
266    register: u8,
267    value: u16,
268    rom: &[u8],
269    ram: &[u8],
270) -> u8 {
271    let cycles = if register == 14 {
272        // Writing to R14 triggers a ROM buffer reload
273        // Note that changing ROMBR does *not* reload the ROM buffer until R14 is written to
274        let (byte, _) = read_memory(gsu.rombr, value, rom, ram);
275        gsu.state.rom_buffer = byte;
276        gsu.state.rom_buffer_wait_cycles = gsu.clock_speed.rom_buffer_wait_cycles();
277        gsu.state.rom_pointer_changed = true;
278
279        0
280    } else if register == 15 {
281        // Writing to R15 fills out the remainder of the current cache line
282        gsu.state.just_jumped = true;
283        fill_cache_from_pc(gsu, rom, ram)
284    } else {
285        0
286    };
287
288    gsu.r[register as usize] = value;
289
290    log::trace!("  Wrote {value:04X} to R{register}");
291
292    cycles
293}
294
295fn clear_prefix_flags(gsu: &mut GraphicsSupportUnit) {
296    gsu.alt1 = false;
297    gsu.alt2 = false;
298    gsu.b = false;
299    gsu.sreg = 0;
300    gsu.dreg = 0;
301}
302
303fn execute_opcode(
304    opcode: u8,
305    memory_type: MemoryType,
306    gsu: &mut GraphicsSupportUnit,
307    rom: &[u8],
308    ram: &mut [u8],
309) -> u8 {
310    match opcode {
311        0x00 => stop(memory_type, gsu),
312        0x01 => nop(memory_type, gsu),
313        0x02 => cache(memory_type, gsu, rom, ram),
314        0x03 => alu::lsr(memory_type, gsu, rom, ram),
315        0x04 => alu::rol(memory_type, gsu, rom, ram),
316        0x05 => flow::bra(memory_type, gsu, rom, ram),
317        0x06 => flow::bge(memory_type, gsu, rom, ram),
318        0x07 => flow::blt(memory_type, gsu, rom, ram),
319        0x08 => flow::bne(memory_type, gsu, rom, ram),
320        0x09 => flow::beq(memory_type, gsu, rom, ram),
321        0x0A => flow::bpl(memory_type, gsu, rom, ram),
322        0x0B => flow::bmi(memory_type, gsu, rom, ram),
323        0x0C => flow::bcc(memory_type, gsu, rom, ram),
324        0x0D => flow::bcs(memory_type, gsu, rom, ram),
325        0x0E => flow::bvc(memory_type, gsu, rom, ram),
326        0x0F => flow::bvs(memory_type, gsu, rom, ram),
327        0x10..=0x1F => flags::to(opcode, memory_type, gsu, rom, ram),
328        0x20..=0x2F => flags::with(opcode, memory_type, gsu),
329        0x30..=0x3B => {
330            if gsu.alt1 {
331                load::stb(opcode, memory_type, gsu, ram)
332            } else {
333                load::stw(opcode, memory_type, gsu, ram)
334            }
335        }
336        0x3C => flow::loop_(memory_type, gsu, rom, ram),
337        0x3D => flags::alt1(memory_type, gsu),
338        0x3E => flags::alt2(memory_type, gsu),
339        0x3F => flags::alt3(memory_type, gsu),
340        0x40..=0x4B => {
341            if gsu.alt1 {
342                load::ldb(opcode, memory_type, gsu, rom, ram)
343            } else {
344                load::ldw(opcode, memory_type, gsu, rom, ram)
345            }
346        }
347        0x4C => {
348            if gsu.alt1 {
349                plot::rpix(memory_type, gsu, rom, ram)
350            } else {
351                plot::plot(memory_type, gsu, ram)
352            }
353        }
354        0x4D => load::swap(memory_type, gsu, rom, ram),
355        0x4E => {
356            if gsu.alt1 {
357                plot::cmode(memory_type, gsu)
358            } else {
359                plot::color(memory_type, gsu)
360            }
361        }
362        0x4F => alu::not(memory_type, gsu, rom, ram),
363        0x50..=0x5F => alu::add(opcode, memory_type, gsu, rom, ram),
364        0x60..=0x6F => alu::sub(opcode, memory_type, gsu, rom, ram),
365        0x70 => load::merge(memory_type, gsu, rom, ram),
366        0x71..=0x7F => alu::and(opcode, memory_type, gsu, rom, ram),
367        0x80..=0x8F => alu::mult(opcode, memory_type, gsu, rom, ram),
368        0x90 => load::sbk(memory_type, gsu, ram),
369        0x91..=0x94 => flow::link(opcode, memory_type, gsu),
370        0x95 => alu::sex(memory_type, gsu, rom, ram),
371        0x96 => alu::asr(memory_type, gsu, rom, ram),
372        0x97 => alu::ror(memory_type, gsu, rom, ram),
373        0x98..=0x9D => {
374            if gsu.alt1 {
375                flow::ljmp(opcode, memory_type, gsu, rom, ram)
376            } else {
377                flow::jmp(opcode, memory_type, gsu, rom, ram)
378            }
379        }
380        0x9E => load::lob(memory_type, gsu, rom, ram),
381        0x9F => alu::fmult(memory_type, gsu, rom, ram),
382        0xA0..=0xAF => match (gsu.alt2, gsu.alt1) {
383            (false, false) => load::ibt(opcode, memory_type, gsu, rom, ram),
384            (_, true) => load::lms(opcode, memory_type, gsu, rom, ram),
385            (true, false) => load::sms(opcode, memory_type, gsu, rom, ram),
386        },
387        0xB0..=0xBF => flags::from(opcode, memory_type, gsu, rom, ram),
388        0xC0 => load::hib(memory_type, gsu, rom, ram),
389        0xC1..=0xCF => alu::or(opcode, memory_type, gsu, rom, ram),
390        0xD0..=0xDE => alu::inc(opcode, memory_type, gsu, rom, ram),
391        0xDF => match (gsu.alt2, gsu.alt1) {
392            (false, _) => plot::getc(memory_type, gsu),
393            (true, false) => {
394                // RAMB; treat as a NOP because no Super FX cartridge has more than 64KB of RAM
395                nop(memory_type, gsu)
396            }
397            (true, true) => load::romb(memory_type, gsu),
398        },
399        0xE0..=0xEE => alu::dec(opcode, memory_type, gsu, rom, ram),
400        0xEF => load::getb(memory_type, gsu, rom, ram),
401        0xF0..=0xFF => match (gsu.alt2, gsu.alt1) {
402            (false, false) => load::iwt(opcode, memory_type, gsu, rom, ram),
403            (_, true) => load::lm(opcode, memory_type, gsu, rom, ram),
404            (true, false) => load::sm(opcode, memory_type, gsu, rom, ram),
405        },
406    }
407}
408
409fn stop(memory_type: MemoryType, gsu: &mut GraphicsSupportUnit) -> u8 {
410    // STOP: Stop the GSU
411    gsu.stop_state = StopState::StopExecuted;
412
413    clear_prefix_flags(gsu);
414    memory_type.access_cycles(gsu.clock_speed)
415}
416
417fn nop(memory_type: MemoryType, gsu: &mut GraphicsSupportUnit) -> u8 {
418    // NOP: No-op
419    clear_prefix_flags(gsu);
420    memory_type.access_cycles(gsu.clock_speed)
421}
422
423fn cache(memory_type: MemoryType, gsu: &mut GraphicsSupportUnit, rom: &[u8], ram: &[u8]) -> u8 {
424    // CACHE: Set cache bank register
425    let cbr = gsu.r[15].wrapping_sub(1) & 0xFFF0;
426    let (updated, cycles) = if cbr != gsu.code_cache.cbr() {
427        gsu.code_cache.update_cbr(cbr);
428
429        let mut cycles = fill_cache_to_pc(gsu, gsu.r[15].wrapping_sub(1), rom, ram);
430        cycles += cache_at_pc(gsu, gsu.r[15].wrapping_sub(1), rom, ram);
431
432        (true, cycles)
433    } else {
434        (false, 0)
435    };
436
437    cycles
438        + match memory_type {
439            MemoryType::CodeCache => 1,
440            MemoryType::Rom | MemoryType::Ram => {
441                memory_type.access_cycles(gsu.clock_speed) + u8::from(updated)
442            }
443        }
444}