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}