1//! Sega Virtua Processor (SVP), auxiliary cartridge hardware used exclusively in Virtua Racing 2//! 3//! Implementation based on documentation and reverse engineering work by notaz and Tasco Deluxe: 4//! <https://notaz.gp2x.de/docs/svpdoc.txt> 5 6mod ssp1601; 7 8use bincode::{Decode, Encode}; 9use jgenesis_common::num::{GetBit, U16Ext}; 10use std::array; 11 12const SVP_ENTRY_POINT: u16 = 0x400; 13 14const DRAM_LEN_WORDS: usize = 128 * 1024 / 2; 15const IRAM_LEN_WORDS: usize = 1024; 16const INTERNAL_RAM_LEN_WORDS: usize = 256; 17 18const STACK_LEN: u8 = 6; 19 20// External memory addresses are 21-bit 21const EXTERNAL_MEMORY_MASK: u32 = (1 << 21) - 1; 22 23type Dram = [u16; DRAM_LEN_WORDS]; 24type Iram = [u16; IRAM_LEN_WORDS]; 25type InternalRam = [u16; INTERNAL_RAM_LEN_WORDS]; 26 27// ST register, control and status bits 28#[derive(Debug, Clone, Copy, Default, Encode, Decode)] 29struct StatusRegister { 30 // Control bits 31 loop_size: u8, 32 st5: bool, 33 st6: bool, 34 // Status bits 35 zero: bool, 36 negative: bool, 37} 38 39impl StatusRegister { 40 fn loop_modulo(self) -> u8 { 41 if self.loop_size != 0 { 1 << self.loop_size } else { 0 } 42 } 43 44 // The ST5 and ST6 bits control whether register 8 maps to PM0 or XST status. 45 // They also supposedly control whether register 11 maps to PM3 or XST, but Virtua Racing never 46 // accesses register 11 with the bits set 47 fn st_bits_set(self) -> bool { 48 self.st5 || self.st6 49 } 50 51 fn write(&mut self, value: u16) { 52 self.loop_size = (value & 0x07) as u8; 53 self.st5 = value.bit(5); 54 self.st6 = value.bit(6); 55 self.zero = value.bit(13); 56 self.negative = value.bit(15); 57 } 58} 59 60impl From<StatusRegister> for u16 { 61 fn from(value: StatusRegister) -> Self { 62 (u16::from(value.negative) << 15) 63 | (u16::from(value.zero) << 13) 64 | (u16::from(value.st6) << 6) 65 | (u16::from(value.st5) << 5) 66 | u16::from(value.loop_size) 67 } 68} 69 70// STACK register, port to a 6-level hardware stack 71#[derive(Debug, Clone, Default, Encode, Decode)] 72struct StackRegister { 73 stack: [u16; STACK_LEN as usize], 74 pointer: u8, 75} 76 77impl StackRegister { 78 fn push(&mut self, value: u16) { 79 self.stack[self.pointer as usize] = value; 80 self.pointer = (self.pointer + 1) % STACK_LEN; 81 } 82 83 fn pop(&mut self) -> u16 { 84 self.pointer = if self.pointer == 0 { STACK_LEN - 1 } else { self.pointer - 1 }; 85 self.stack[self.pointer as usize] 86 } 87} 88 89// PM0-4 registers, which are ports used by the DSP to access external memory. 90// Each PM register can be individually configured with an external memory address, auto-increment 91// settings, and an overwrite mode for writes 92#[derive(Debug, Clone, Default, Encode, Decode)] 93struct ProgrammableMemoryRegister { 94 address: u32, 95 auto_increment: u32, 96 auto_increment_negative: bool, 97 auto_increment_bits: u16, 98 special_increment_mode: bool, 99 overwrite_mode: bool, 100} 101 102impl ProgrammableMemoryRegister { 103 fn initialize(&mut self, address: u16, mode: u16) { 104 // Bits 4-0 of mode are bits 20-16 of the 21-bit address 105 self.address = u32::from(address) | (u32::from(mode & 0x001F) << 16); 106 107 self.overwrite_mode = mode.bit(10); 108 109 // Auto increment bits of 0 indicate 0, 7 indicate 128, and other values indicate 2^(N-1). 110 // 7 actually indicates a custom auto-increment value instead of 128, but Virtua Racing 111 // always uses a custom value of 128 when it sets the auto-increment bits to 7 112 let auto_increment_bits = (mode >> 11) & 0x07; 113 self.auto_increment_bits = auto_increment_bits; 114 self.auto_increment = match auto_increment_bits { 115 0 => 0, 116 7 => 128, 117 _ => 1 << (auto_increment_bits - 1), 118 }; 119 120 self.special_increment_mode = mode.bit(14); 121 self.auto_increment_negative = mode.bit(15); 122 } 123 124 fn get_and_increment_address(&mut self) -> u32 { 125 let address = self.address; 126 127 if self.special_increment_mode { 128 // "Special" increment mode increments the address by 1 if it is even and 31 if it is odd 129 if !address.bit(0) { 130 self.address = (self.address + 1) & EXTERNAL_MEMORY_MASK; 131 } else { 132 self.address = (self.address + 31) & EXTERNAL_MEMORY_MASK; 133 } 134 } else if self.auto_increment != 0 { 135 if self.auto_increment_negative { 136 self.address = 137 self.address.wrapping_sub(self.auto_increment) & EXTERNAL_MEMORY_MASK; 138 } else { 139 self.address = 140 self.address.wrapping_add(self.auto_increment) & EXTERNAL_MEMORY_MASK; 141 } 142 } 143 144 address 145 } 146} 147 148#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)] 149enum PmcWaitingFor { 150 #[default] 151 Address, 152 Mode, 153} 154 155impl PmcWaitingFor { 156 fn toggle(self) -> Self { 157 match self { 158 Self::Address => Self::Mode, 159 Self::Mode => Self::Address, 160 } 161 } 162} 163 164// PMC register, used to program the PM registers 165#[derive(Debug, Clone, Default, Encode, Decode)] 166struct ProgrammableMemoryControlRegister { 167 waiting_for: PmcWaitingFor, 168 address: u16, 169 mode: u16, 170} 171 172impl ProgrammableMemoryControlRegister { 173 fn read(&mut self) -> u16 { 174 let value = match self.waiting_for { 175 PmcWaitingFor::Address => self.address, 176 PmcWaitingFor::Mode => { 177 // If waiting for mode, return address but rotated by 4; direction doesn't matter 178 // because SVP always does this with both bytes equal 179 self.address.rotate_left(4) 180 } 181 }; 182 183 self.waiting_for = self.waiting_for.toggle(); 184 185 value 186 } 187 188 fn write(&mut self, value: u16) { 189 match self.waiting_for { 190 PmcWaitingFor::Address => { 191 self.address = value; 192 } 193 PmcWaitingFor::Mode => { 194 self.mode = value; 195 } 196 } 197 198 self.waiting_for = self.waiting_for.toggle(); 199 } 200 201 fn update_from(&mut self, pm_register: &ProgrammableMemoryRegister) { 202 self.address = pm_register.address as u16; 203 self.mode = (u16::from(pm_register.auto_increment_negative) << 15) 204 | (u16::from(pm_register.special_increment_mode) << 14) 205 | (pm_register.auto_increment_bits << 11) 206 | (u16::from(pm_register.overwrite_mode) << 10) 207 | (pm_register.address >> 16) as u16; 208 209 log::trace!("Set PMC address to {:04X} and mode to {:04X}", self.address, self.mode); 210 } 211} 212 213// XST register, an R/W register used for communication between the DSP and the 68000 214#[derive(Debug, Clone, Default, Encode, Decode)] 215struct ExternalStatusRegister { 216 value: u16, 217 m68k_written: bool, 218 ssp_written: bool, 219} 220 221impl ExternalStatusRegister { 222 fn m68k_write(&mut self, value: u16) { 223 self.value = value; 224 self.m68k_written = true; 225 } 226 227 fn ssp_write(&mut self, value: u16) { 228 self.value = value; 229 self.ssp_written = true; 230 } 231 232 fn status(&self) -> u16 { 233 (u16::from(self.m68k_written) << 1) | u16::from(self.ssp_written) 234 } 235 236 fn m68k_read_status(&mut self) -> u16 { 237 let status = self.status(); 238 self.ssp_written = false; 239 status 240 } 241 242 fn ssp_read_status(&mut self) -> u16 { 243 let status = self.status(); 244 self.m68k_written = false; 245 status 246 } 247} 248 249#[derive(Debug, Clone, Encode, Decode)] 250struct Registers { 251 // General registers (0-7) 252 x: u16, 253 y: u16, 254 accumulator: u32, 255 status: StatusRegister, 256 stack: StackRegister, 257 pc: u16, 258 // External registers (8-15) 259 // PM registers are programmed separately for reads and for writes 260 pm_read: [ProgrammableMemoryRegister; 5], 261 pm_write: [ProgrammableMemoryRegister; 5], 262 pmc: ProgrammableMemoryControlRegister, 263 xst: ExternalStatusRegister, 264 // Pointer registers (0-2 and 4-6, 3 and 7 are not stored) 265 ram0_pointers: [u8; 3], 266 ram1_pointers: [u8; 3], 267} 268 269impl Registers { 270 fn new() -> Self { 271 Self { 272 x: 0, 273 y: 0, 274 accumulator: 0, 275 status: StatusRegister::default(), 276 stack: StackRegister::default(), 277 pc: SVP_ENTRY_POINT, 278 pm_read: array::from_fn(|_| ProgrammableMemoryRegister::default()), 279 pm_write: array::from_fn(|_| ProgrammableMemoryRegister::default()), 280 pmc: ProgrammableMemoryControlRegister::default(), 281 xst: ExternalStatusRegister::default(), 282 ram0_pointers: [0; 3], 283 ram1_pointers: [0; 3], 284 } 285 } 286 287 fn product(&self) -> u32 { 288 // P register always contains 2 * X * Y, where X and Y are sign extended from 16 bits to 32 bits 289 2_u32.wrapping_mul(self.x as i16 as u32).wrapping_mul(self.y as i16 as u32) 290 } 291} 292 293#[derive(Debug, Clone, Encode, Decode)] 294pub struct Svp { 295 registers: Registers, 296 dram: Box<Dram>, 297 iram: Box<Iram>, 298 ram0: Box<InternalRam>, 299 ram1: Box<InternalRam>, 300 halted: bool, 301 // Flag marking whether the 68000 has written to specific addresses in DRAM that are used for 302 // communication; used for idle loop detection 303 dram_dirty: bool, 304} 305 306impl Svp { 307 pub fn new() -> Self { 308 Self { 309 registers: Registers::new(), 310 dram: vec![0; DRAM_LEN_WORDS].into_boxed_slice().try_into().unwrap(), 311 iram: vec![0; IRAM_LEN_WORDS].into_boxed_slice().try_into().unwrap(), 312 ram0: vec![0; INTERNAL_RAM_LEN_WORDS].into_boxed_slice().try_into().unwrap(), 313 ram1: vec![0; INTERNAL_RAM_LEN_WORDS].into_boxed_slice().try_into().unwrap(), 314 halted: false, 315 dram_dirty: false, 316 } 317 } 318 319 pub fn tick(&mut self, rom: &[u16], m68k_cycles: u32) { 320 if self.halted { 321 return; 322 } 323 324 // Somewhat arbitrarily execute 3 instructions for every 68k cycle; this is close enough to 325 // the chip's actual speed of somewhere in the 20-25 MHz range, and Virtua Racing's code is 326 // not timing-sensitive 327 for _ in 0..3 * m68k_cycles { 328 // Hacky idle loop detection: if the SSP1601 is waiting for the 68000 to give it a 329 // command, don't bother executing anything until the 68000 writes to $FE06 or $FE08 in 330 // DRAM 331 if self.registers.pc == 0x0425 || self.registers.pc == 0x2789 { 332 if !self.dram_dirty { 333 return; 334 } 335 self.dram_dirty = false; 336 } 337 338 // At startup, the SVP spins until the 68000 writes to the XST; don't execute until that 339 // happens 340 if self.registers.pc == SVP_ENTRY_POINT && !self.registers.xst.m68k_written { 341 return; 342 } 343 344 ssp1601::execute_instruction(self, rom); 345 } 346 } 347 348 pub fn m68k_read(&mut self, address: u32, rom: &[u16]) -> u16 { 349 match address { 350 0xA15004 => { 351 // XST status; reads clear the SSP1601 written flag 352 self.registers.xst.m68k_read_status() 353 } 354 _ => { 355 // No other addresses require mutating inner state 356 self.m68k_peek(address, rom) 357 } 358 } 359 } 360 361 pub fn m68k_peek(&self, address: u32, rom: &[u16]) -> u16 { 362 match address { 363 0x000000..=0x1FFFFF => { 364 // ROM 365 rom[(address >> 1) as usize] 366 } 367 0x300000..=0x37FFFF => { 368 // DRAM, mirrored every 128KB / $1FFFF 369 self.dram[((address & 0x1FFFF) >> 1) as usize] 370 } 371 0xA15000 | 0xA15002 => { 372 // XST register 373 self.registers.xst.value 374 } 375 0xA15004 => { 376 // XST status 377 self.registers.xst.status() 378 } 379 _ => { 380 // Invalid or unused 381 0xFFFF 382 } 383 } 384 } 385 386 pub fn m68k_write_byte(&mut self, address: u32, value: u8) { 387 match address { 388 0x300000..=0x37FFFF => { 389 // DRAM, mirrored every 128KB / $1FFFF 390 let word_addr = ((address & 0x1FFFF) >> 1) as usize; 391 if address.bit(0) { 392 self.dram[word_addr].set_lsb(value); 393 } else { 394 self.dram[word_addr].set_msb(value); 395 } 396 397 // Specific DRAM addresses used for communication between the 68000 and DSP 398 if word_addr == 0x7F03 || word_addr == 0x7F04 { 399 self.dram_dirty = true; 400 } 401 } 402 _ => { 403 // Treat other writes as word-size 404 if address.bit(0) { 405 self.m68k_write_word(address & !1, value.into()); 406 } else { 407 self.m68k_write_word(address, u16::from(value) << 8); 408 } 409 } 410 } 411 } 412 413 pub fn m68k_write_word(&mut self, address: u32, value: u16) { 414 match address { 415 0x300000..=0x37FFFF => { 416 // DRAM, mirrored every 128KB / $1FFFF 417 let word_addr = (address & 0x1FFFF) >> 1; 418 self.dram[word_addr as usize] = value; 419 420 // Specific DRAM addresses used for communication between the 68000 and DSP 421 if word_addr == 0x7F03 || word_addr == 0x7F04 { 422 self.dram_dirty = true; 423 } 424 } 425 0xA15000 | 0xA15002 => { 426 // XST register 427 self.registers.xst.m68k_write(value); 428 } 429 0xA15006 => { 430 // SVP halt register 431 self.halted = value == 0x000A; 432 } 433 _ => { 434 // Invalid or unused 435 } 436 } 437 } 438 439 fn read_program_memory(&self, address: u16, rom: &[u16]) -> u16 { 440 match address { 441 0x0000..=0x03FF => { 442 // IRAM 443 self.iram[address as usize] 444 } 445 0x0400..=0xFFFF => { 446 // ROM (first 128KB); program memory address maps to the same address in ROM 447 rom[address as usize] 448 } 449 } 450 } 451 452 fn read_external_memory(&mut self, address: u32, rom: &[u16]) -> u16 { 453 log::trace!("External memory read: {address:06X}"); 454 455 match address { 456 0x000000..=0x0FFFFF => { 457 // ROM 458 rom[address as usize] 459 } 460 0x180000..=0x18FFFF => { 461 // DRAM 462 self.dram[(address & 0xFFFF) as usize] 463 } 464 0x1C8000..=0x1C83FF => { 465 // IRAM 466 self.iram[(address & 0x3FF) as usize] 467 } 468 _ => { 469 // Invalid or unused 470 0xFFFF 471 } 472 } 473 } 474 475 fn write_external_memory(&mut self, address: u32, value: u16) { 476 log::trace!("External memory write: {address:06X} {value:04X}"); 477 478 match address { 479 0x180000..=0x18FFFF => { 480 // DRAM 481 self.dram[(address & 0xFFFF) as usize] = value; 482 } 483 0x1C8000..=0x1C83FF => { 484 // IRAM 485 self.iram[(address & 0x3FF) as usize] = value; 486 } 487 _ => { 488 // Invalid or unused 489 } 490 } 491 } 492}