1//! Assumptions made to simplify implementation at the cost of timing accuracy (and possibly bus contention accuracy): 2//! * Every instruction's opcode and operands will be located within the same memory area (code cache / ROM / RAM) 3//! * If an instruction's opcode is cached, the operands are also cached 4 5mod alu; 6mod disassemble; 7mod flags; 8mod flow; 9mod load; 10mod plot; 11 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}