NEC µPD77C25 and µPD96050 CPUs, used in the following SNES coprocessor chips:
- DSP-1 (~16 games, including Super Mario Kart and Pilotwings)
- DSP-2 (1 game, Dungeon Master)
- DSP-3 (1 game, SD Gundam GX)
- DSP-4 (1 game, Top Gear 3000)
- ST010 (1 game, F1 ROC II: Race of Champions)
- ST011 (1 game, Hayazashi Nidan Morita Shougi)
9mod instructions;
11use crate::common; 12use bincode::{Decode, Encode}; 13use jgenesis_common::frontend::TimingMode; 14use jgenesis_common::num::{GetBit, U16Ext}; 15 16pub const ST01X_RAM_LEN_BYTES: usize = Upd77c25Variant::St011.ram_len_words() << 1; 17 18#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 19pub enum Upd77c25Variant { 20 // DSP-1 / DSP-2 / DSP-3 / DSP-4 21 Dsp, 22 St010, 23 St011, 24} 25 26impl Upd77c25Variant { 27 const fn program_rom_len_opcodes(self) -> usize { 28 match self { 29 Self::Dsp => 1 << 11, 30 Self::St010 | Self::St011 => 1 << 14, 31 } 32 } 33 34 const fn data_rom_len_words(self) -> usize { 35 match self { 36 Self::Dsp => 1 << 10, 37 Self::St010 | Self::St011 => 1 << 11, 38 } 39 } 40 41 const fn ram_len_words(self) -> usize { 42 match self { 43 Self::Dsp => 1 << 8, 44 Self::St010 | Self::St011 => 1 << 11, 45 } 46 } 47 48 const fn stack_len(self) -> u8 { 49 match self { 50 Self::Dsp => 4, 51 Self::St010 | Self::St011 => 8, 52 } 53 } 54 55 const fn clock_speed(self) -> u64 { 56 match self { 57 Self::Dsp => 8_000_000, 58 Self::St010 => 10_000_000, 59 Self::St011 => 15_000_000, 60 } 61 } 62} 63 64#[derive(Debug, Clone, Copy, Default, Encode, Decode)] 65struct FlagsRegister { 66 z: bool, 67 c: bool, 68 s0: bool, 69 s1: bool, 70 ov0: bool, 71 ov1: bool, 72} 73 74#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)] 75enum DataRegisterBits { 76 Eight, 77 #[default] 78 Sixteen, 79} 80 81impl DataRegisterBits { 82 fn to_bit(self) -> bool { 83 self == Self::Eight 84 } 85 86 fn from_bit(bit: bool) -> Self { 87 if bit { Self::Eight } else { Self::Sixteen } 88 } 89} 90 91#[derive(Debug, Clone, Copy, Default, Encode, Decode)] 92struct StatusRegister { 93 request_for_master: bool, 94 user_flag_0: bool, 95 user_flag_1: bool, 96 dr_busy: bool, 97 dr_control: DataRegisterBits, 98} 99 100impl StatusRegister { 101 fn write(&mut self, value: u16) { 102 self.user_flag_1 = value.bit(14); 103 self.user_flag_0 = value.bit(13); 104 self.dr_control = DataRegisterBits::from_bit(value.bit(10)); 105 log::trace!("DR control set to {:?}", self.dr_control); 106 } 107} 108 109impl From<StatusRegister> for u8 { 110 fn from(value: StatusRegister) -> Self { 111 (u8::from(value.request_for_master) << 7) 112 | (u8::from(value.user_flag_1) << 6) 113 | (u8::from(value.user_flag_0) << 5) 114 | (u8::from(value.dr_busy) << 4) 115 | (u8::from(value.dr_control.to_bit()) << 2) 116 } 117} 118 119#[derive(Debug, Clone, Encode, Decode)] 120struct Registers { 121 dp: u16, 122 rp: u16, 123 pc: u16, 124 stack: [u16; 8], 125 stack_idx: u8, 126 stack_len: u8, 127 k: i16, 128 l: i16, 129 accumulator_a: i16, 130 accumulator_b: i16, 131 flags_a: FlagsRegister, 132 flags_b: FlagsRegister, 133 tr: i16, 134 trb: i16, 135 sr: StatusRegister, 136 dr: u16, 137 so: u16, 138} 139 140impl Registers { 141 fn new(variant: Upd77c25Variant) -> Self { 142 let stack_len = variant.stack_len(); 143 144 Self { 145 dp: 0, 146 rp: 0x3FF, 147 pc: 0, 148 stack: [0; 8], 149 stack_idx: 0, 150 stack_len, 151 k: 0, 152 l: 0, 153 accumulator_a: 0, 154 accumulator_b: 0, 155 flags_a: FlagsRegister::default(), 156 flags_b: FlagsRegister::default(), 157 tr: 0, 158 trb: 0, 159 sr: StatusRegister::default(), 160 dr: 0, 161 so: 0, 162 } 163 } 164 165 fn snes_read_data(&mut self) -> u8 { 166 match self.sr.dr_control { 167 DataRegisterBits::Eight => { 168 self.sr.request_for_master = false; 169 170 self.dr.lsb() 171 } 172 DataRegisterBits::Sixteen => { 173 if self.sr.dr_busy { 174 self.sr.dr_busy = false; 175 self.sr.request_for_master = false; 176 177 self.dr.msb() 178 } else { 179 self.sr.dr_busy = true; 180 181 self.dr.lsb() 182 } 183 } 184 } 185 } 186 187 fn snes_write_data(&mut self, value: u8) { 188 match self.sr.dr_control { 189 DataRegisterBits::Eight => { 190 self.sr.request_for_master = false; 191 192 self.dr = value.into(); 193 } 194 DataRegisterBits::Sixteen => { 195 if self.sr.dr_busy { 196 self.sr.dr_busy = false; 197 self.sr.request_for_master = false; 198 199 self.dr.set_msb(value); 200 } else { 201 self.sr.dr_busy = true; 202 203 self.dr.set_lsb(value); 204 } 205 } 206 } 207 } 208 209 fn upd_write_data(&mut self, value: u16) { 210 log::trace!("Wrote {value:04X} to DR"); 211 212 self.dr = value; 213 self.sr.request_for_master = true; 214 } 215 216 fn reset(&mut self) { 217 self.pc = 0; 218 self.flags_a = FlagsRegister::default(); 219 self.flags_b = FlagsRegister::default(); 220 self.sr = StatusRegister::default(); 221 self.rp = 0x3FF; 222 } 223 224 fn kl(&self) -> i32 { 225 i32::from(self.k) * i32::from(self.l) 226 } 227 228 fn push_stack(&mut self, pc: u16) { 229 self.stack[self.stack_idx as usize] = pc; 230 self.stack_idx = (self.stack_idx + 1) & (self.stack_len - 1); 231 } 232 233 fn pop_stack(&mut self) -> u16 { 234 log::trace!("Returning, current stack IDX is {}", self.stack_idx); 235 236 self.stack_idx = self.stack_idx.wrapping_sub(1) & (self.stack_len - 1); 237 self.stack[self.stack_idx as usize] 238 } 239} 240 241#[derive(Debug, Clone, Encode, Decode)] 242pub struct Upd77c25 { 243 program_rom: Box<[u32]>, 244 data_rom: Box<[u16]>, 245 ram: Box<[u16]>, 246 registers: Registers, 247 idling: bool, 248 pc_mask: u16, 249 dp_mask: u16, 250 rp_mask: u16, 251 variant: Upd77c25Variant, 252 clock_speed: u64, 253 snes_mclk_speed: u64, 254 master_cycles_product: u64, 255} 256 257impl Upd77c25 { 258 #[must_use] 259 pub fn new(rom: &[u8], variant: Upd77c25Variant, sram: &[u8], timing_mode: TimingMode) -> Self { 260 let (program_rom, data_rom) = convert_rom(rom, variant); 261 262 let ram = match variant { 263 Upd77c25Variant::Dsp => vec![0; variant.ram_len_words()], 264 Upd77c25Variant::St010 | Upd77c25Variant::St011 => { 265 convert_to_u16(sram, Endianness::Little) 266 } 267 }; 268 269 let snes_mclk_speed = match timing_mode { 270 TimingMode::Ntsc => common::NTSC_MASTER_CLOCK_FREQUENCY, 271 TimingMode::Pal => common::PAL_MASTER_CLOCK_FREQUENCY, 272 }; 273 274 Self { 275 program_rom: program_rom.into_boxed_slice(), 276 data_rom: data_rom.into_boxed_slice(), 277 ram: ram.into_boxed_slice(), 278 registers: Registers::new(variant), 279 idling: false, 280 pc_mask: (variant.program_rom_len_opcodes() - 1) as u16, 281 dp_mask: (variant.ram_len_words() - 1) as u16, 282 rp_mask: (variant.data_rom_len_words() - 1) as u16, 283 variant, 284 clock_speed: variant.clock_speed(), 285 snes_mclk_speed, 286 master_cycles_product: 0, 287 } 288 } 289 290 pub fn read_data(&mut self) -> u8 { 291 let value = self.registers.snes_read_data(); 292 293 if !self.registers.sr.request_for_master { 294 self.idling = false; 295 } 296 297 log::trace!("Data read: {value:02X}"); 298 299 value 300 } 301 302 pub fn write_data(&mut self, value: u8) { 303 self.registers.snes_write_data(value); 304 305 log::trace!("Data write: {value:02X}"); 306 307 if !self.registers.sr.request_for_master { 308 self.idling = false; 309 } 310 } 311 312 #[inline] 313 #[must_use] 314 pub fn read_status(&self) -> u8 { 315 self.registers.sr.into() 316 } 317 318 #[inline] 319 #[must_use] 320 pub fn read_ram(&self, address: u32) -> u8 { 321 let word = self.ram[((address >> 1) & 0x7FF) as usize]; 322 if !address.bit(0) { word.lsb() } else { word.msb() } 323 } 324 325 #[inline] 326 pub fn write_ram(&mut self, address: u32, value: u8) { 327 let word_addr = ((address >> 1) & 0x7FF) as usize; 328 if !address.bit(0) { 329 self.ram[word_addr].set_lsb(value); 330 } else { 331 self.ram[word_addr].set_msb(value); 332 } 333 } 334 335 #[inline] 336 #[must_use] 337 pub fn sram(&self) -> &[u8] { 338 bytemuck::cast_slice(self.ram.as_ref()) 339 } 340 341 #[inline] 342 pub fn tick(&mut self, master_cycles_elapsed: u64) { 343 if self.idling { 344 return; 345 } 346 347 self.master_cycles_product += master_cycles_elapsed * self.clock_speed; 348 while self.master_cycles_product >= self.snes_mclk_speed { 349 instructions::execute(self); 350 self.master_cycles_product -= self.snes_mclk_speed; 351 } 352 } 353 354 pub fn reset(&mut self) { 355 self.registers.reset(); 356 } 357} 358 359#[derive(Debug, Clone, Copy, PartialEq, Eq)] 360enum Endianness { 361 Little, 362 Big, 363} 364 365impl Endianness { 366 fn chunk_to_u16(self, chunk: [u8; 2]) -> u16 { 367 match self { 368 Self::Little => u16::from_le_bytes(chunk), 369 Self::Big => u16::from_be_bytes(chunk), 370 } 371 } 372 373 fn chunk_to_u32(self, chunk: [u8; 3]) -> u32 { 374 match self { 375 Self::Little => u32::from_le_bytes([chunk[0], chunk[1], chunk[2], 0]), 376 Self::Big => u32::from_be_bytes([0, chunk[0], chunk[1], chunk[2]]), 377 } 378 } 379}
Parse the ROM into program ROM and data ROM
382fn convert_rom(rom: &[u8], variant: Upd77c25Variant) -> (Vec<u32>, Vec<u16>) { 383 let endianness = detect_program_rom_endianness(rom); 384 log::info!("Detected ROM endian-ness: {endianness:?}"); 385 386 let program_rom_len = 3 * variant.program_rom_len_opcodes(); 387 388 let program_rom = convert_program_rom(&rom[..program_rom_len], endianness); 389 let data_rom = convert_to_u16(&rom[program_rom_len..], endianness); 390 (program_rom, data_rom) 391}
Convert program ROM from bytes to 24-bit opcodes
Convert data ROM from bytes to 16-bit words
All program ROMs used for this chip contain the opcode $97C00x in the first 4 opcodes, where x is 4 times the opcode number
405fn detect_program_rom_endianness(program_rom: &[u8]) -> Endianness { 406 for (i, &chunk) in program_rom.as_chunks::<3>().0.iter().enumerate().take(4) { 407 if chunk == [(i << 2) as u8, 0xC0, 0x97] { 408 return Endianness::Little; 409 } 410 411 if chunk == [0x97, 0xC0, (i << 2) as u8] { 412 return Endianness::Big; 413 } 414 } 415 416 log::warn!("Unable to detect uPD77C25 endian-ness; defaulting to little-endian"); 417 418 Endianness::Little 419}