1mod dma; 2 3use crate::sa1::Iram; 4use crate::sa1::mmc::Sa1Mmc; 5use crate::sa1::timer::Sa1Timer; 6use bincode::{Decode, Encode}; 7use jgenesis_common::num::{GetBit, SignBit, U16Ext, U24Ext}; 8use std::ops::Range; 9use std::{array, cmp}; 10 11#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)] 12pub enum InterruptVectorSource { 13 #[default] 14 Rom, 15 IoPorts, 16} 17 18impl InterruptVectorSource { 19 fn from_bit(bit: bool) -> Self { 20 if bit { Self::IoPorts } else { Self::Rom } 21 } 22 23 fn to_bit(self) -> bool { 24 self == Self::IoPorts 25 } 26} 27 28#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)] 29pub enum DmaSourceDevice { 30 #[default] 31 Rom, 32 Iram, 33 Bwram, 34} 35 36impl DmaSourceDevice { 37 fn from_byte(byte: u8) -> Self { 38 match byte & 0x03 { 39 0x00 => Self::Rom, 40 0x01 => Self::Bwram, 41 0x02 => Self::Iram, 42 0x03 => { 43 log::warn!("SA-1 set unsupported DMA source 3; defaulting to ROM"); 44 Self::Rom 45 } 46 _ => unreachable!("value & 0x03 is always <= 0x03"), 47 } 48 } 49} 50 51#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)] 52pub enum DmaDestinationDevice { 53 #[default] 54 Iram, 55 Bwram, 56} 57 58impl DmaDestinationDevice { 59 fn from_bit(bit: bool) -> Self { 60 if bit { Self::Bwram } else { Self::Iram } 61 } 62} 63 64#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)] 65pub enum DmaType { 66 #[default] 67 Normal, 68 CharacterConversion, 69} 70 71impl DmaType { 72 fn from_bit(bit: bool) -> Self { 73 if bit { Self::CharacterConversion } else { Self::Normal } 74 } 75} 76 77#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)] 78pub enum DmaPriority { 79 #[default] 80 Cpu, 81 Dma, 82} 83 84impl DmaPriority { 85 fn from_bit(bit: bool) -> Self { 86 if bit { Self::Dma } else { Self::Cpu } 87 } 88} 89 90#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)] 91pub enum CharacterConversionType { 92 One, 93 #[default] 94 Two, 95} 96 97impl CharacterConversionType { 98 fn from_bit(bit: bool) -> Self { 99 if bit { Self::One } else { Self::Two } 100 } 101} 102 103#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)] 104pub enum CharacterConversionColorBits { 105 Two, 106 Four, 107 #[default] 108 Eight, 109} 110 111impl CharacterConversionColorBits { 112 fn from_byte(byte: u8) -> Self { 113 match byte & 0x03 { 114 0x00 => Self::Eight, 115 0x01 => Self::Four, 116 0x02 | 0x03 => Self::Two, 117 _ => unreachable!("value & 0x03 is always <= 0x03"), 118 } 119 } 120 121 fn bit_mask(self) -> u8 { 122 match self { 123 Self::Two => 0x03, 124 Self::Four => 0x0F, 125 Self::Eight => 0xFF, 126 } 127 } 128 129 fn tile_size(self) -> u32 { 130 match self { 131 Self::Two => 16, 132 Self::Four => 32, 133 Self::Eight => 64, 134 } 135 } 136 137 fn bitplanes(self) -> u32 { 138 match self { 139 Self::Two => 2, 140 Self::Four => 4, 141 Self::Eight => 8, 142 } 143 } 144} 145 146#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)] 147pub enum DmaState { 148 #[default] 149 Idle, 150 NormalCopying, 151 NormalWaitCycle, 152 CharacterConversion2 { 153 buffer_idx: u8, 154 rows_copied: u8, 155 }, 156 CharacterConversion1Initial { 157 cycles_remaining: u8, 158 }, 159 CharacterConversion1Active { 160 buffer_idx: u8, 161 dma_bytes_remaining: u8, 162 next_tile_number: u16, 163 }, 164} 165 166impl DmaState { 167 fn is_character_conversion(self) -> bool { 168 matches!( 169 self, 170 Self::CharacterConversion2 { .. } 171 | Self::CharacterConversion1Initial { .. } 172 | Self::CharacterConversion1Active { .. } 173 ) 174 } 175} 176 177#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)] 178pub enum ArithmeticOp { 179 #[default] 180 Multiply, 181 Divide, 182 MultiplyAccumulate, 183} 184 185impl ArithmeticOp { 186 fn from_byte(byte: u8) -> Self { 187 match byte & 0x03 { 188 0x00 => Self::Multiply, 189 0x01 => Self::Divide, 190 0x02 | 0x03 => Self::MultiplyAccumulate, 191 _ => unreachable!("value & 0x03 is always <= 0x03"), 192 } 193 } 194} 195 196#[derive(Debug, Clone, Encode, Decode)] 197pub struct Sa1Registers { 198 // CCNT / SA-1 CPU Control 199 pub sa1_irq_from_snes: bool, 200 pub sa1_nmi: bool, 201 pub sa1_reset: bool, 202 pub sa1_wait: bool, 203 pub message_to_sa1: u8, 204 // SCNT / SNES CPU Control 205 pub snes_irq_from_sa1: bool, 206 pub snes_irq_vector_source: InterruptVectorSource, 207 pub snes_nmi_vector_source: InterruptVectorSource, 208 pub message_to_snes: u8, 209 // SIE / SNES CPU Interrupt Enable 210 pub snes_irq_from_sa1_enabled: bool, 211 pub snes_irq_from_dma_enabled: bool, 212 // CIE / SA-1 CPU Interrupt Enable 213 pub sa1_irq_from_snes_enabled: bool, 214 pub timer_irq_enabled: bool, 215 pub dma_irq_enabled: bool, 216 pub sa1_nmi_enabled: bool, 217 // CRV / SA-1 CPU RESET Vector 218 pub sa1_reset_vector: u16, 219 // CNV / SA-1 CPU NMI Vector 220 pub sa1_nmi_vector: u16, 221 // CIV / SA-1 CPU IRQ Vector 222 pub sa1_irq_vector: u16, 223 // SNV / SNES CPU NMI Vector 224 pub snes_nmi_vector: u16, 225 // SIV / SNES CPU IRQ Vector 226 pub snes_irq_vector: u16, 227 // SBWE/CBWE / BW-RAM Writes Enabled 228 pub bwram_writes_enabled: bool, 229 // BWPA / BW-RAM Write Protected Area 230 pub bwram_write_protection_size: u32, 231 // SIWP / SNES I-RAM Write Protection 232 pub snes_iram_writes_enabled: [bool; 8], 233 // CIWP / SA-1 I-RAM Write Protection 234 pub sa1_iram_writes_enabled: [bool; 8], 235 // DCNT / DMA Control 236 pub dma_source: DmaSourceDevice, 237 pub dma_destination: DmaDestinationDevice, 238 pub dma_type: DmaType, 239 pub character_conversion_type: CharacterConversionType, 240 pub dma_priority: DmaPriority, 241 pub dma_enabled: bool, 242 // CDMA / Character Conversion DMA Parameters 243 pub ccdma_color_depth: CharacterConversionColorBits, 244 pub virtual_vram_width_tiles: u8, 245 // SDA / DMA Source Device Start Address 246 pub dma_source_address: u32, 247 // DDA / DMA Destination Device Start Address 248 pub dma_destination_address: u32, 249 // DTC / DMA Terminal Counter 250 pub dma_terminal_counter: u16, 251 // BRF / Bitmap Register File 252 pub bitmap_pixels: [u8; 16], 253 // MCNT / Arithmetic Control 254 pub arithmetic_op: ArithmeticOp, 255 // MA / Arithmetic Parameter A 256 pub arithmetic_param_a: u16, 257 // MB / Arithmetic Parameter B 258 pub arithmetic_param_b: u16, 259 // MR / Arithmetic Result 260 pub arithmetic_result: u64, 261 // OF / Arithmetic Overflow Flag 262 pub arithmetic_overflow: bool, 263 // VDA / Variable-Length Bit Data ROM Start Address 264 pub varlen_bit_start_address: u32, 265 // VDP / Variable-Length Bit Data Read Port 266 pub varlen_bit_data: u32, 267 pub varlen_bits_remaining: u8, 268 // TODO varlen auto-increment? not used by any games 269 // Miscellaneous internal state 270 pub dma_state: DmaState, 271 pub ccdma_transfer_in_progress: bool, 272 pub character_conversion_irq: bool, 273 pub sa1_dma_irq: bool, 274} 275 276impl Sa1Registers { 277 pub fn new() -> Self { 278 Self { 279 sa1_irq_from_snes: false, 280 sa1_nmi: false, 281 sa1_reset: true, 282 sa1_wait: false, 283 message_to_sa1: 0, 284 snes_irq_from_sa1: false, 285 snes_irq_vector_source: InterruptVectorSource::default(), 286 snes_nmi_vector_source: InterruptVectorSource::default(), 287 message_to_snes: 0, 288 snes_irq_from_sa1_enabled: false, 289 snes_irq_from_dma_enabled: false, 290 sa1_irq_from_snes_enabled: false, 291 timer_irq_enabled: false, 292 dma_irq_enabled: false, 293 sa1_nmi_enabled: false, 294 sa1_reset_vector: 0, 295 sa1_nmi_vector: 0, 296 sa1_irq_vector: 0, 297 snes_nmi_vector: 0, 298 snes_irq_vector: 0, 299 bwram_writes_enabled: false, 300 bwram_write_protection_size: 1 << 23, 301 snes_iram_writes_enabled: [false; 8], 302 sa1_iram_writes_enabled: [false; 8], 303 dma_source: DmaSourceDevice::default(), 304 dma_destination: DmaDestinationDevice::default(), 305 dma_type: DmaType::default(), 306 character_conversion_type: CharacterConversionType::default(), 307 dma_priority: DmaPriority::default(), 308 dma_enabled: false, 309 ccdma_color_depth: CharacterConversionColorBits::default(), 310 virtual_vram_width_tiles: 1, 311 dma_source_address: 0, 312 dma_destination_address: 0, 313 dma_terminal_counter: 0, 314 bitmap_pixels: [0; 16], 315 arithmetic_op: ArithmeticOp::default(), 316 arithmetic_param_a: 0, 317 arithmetic_param_b: 0, 318 arithmetic_result: 0, 319 arithmetic_overflow: false, 320 varlen_bit_start_address: 0, 321 varlen_bit_data: 0, 322 varlen_bits_remaining: 0, 323 dma_state: DmaState::default(), 324 ccdma_transfer_in_progress: false, 325 character_conversion_irq: false, 326 sa1_dma_irq: false, 327 } 328 } 329 330 pub fn snes_read(&self, address: u32) -> Option<u8> { 331 // Only $2300 (SFR) is readable by SNES CPU 332 // $230E is supposed to be a version code register, but hardware tests discovered that it 333 // is actually open bus 334 (address & 0xFFFF == 0x2300).then(|| self.read_sfr()) 335 } 336 337 pub fn sa1_read(&self, address: u32, timer: &mut Sa1Timer) -> u8 { 338 log::trace!("SA-1 register read: {:04X}", address & 0xFFFF); 339 340 match address & 0xFFFF { 341 0x2301 => self.read_cfr(timer), 342 0x2302 => timer.read_hcr_low(), 343 0x2303 => timer.read_hcr_high(), 344 0x2304 => timer.read_vcr_low(), 345 0x2305 => timer.read_vcr_high(), 346 0x2306..=0x230A => self.read_mr(address), 347 0x230B => self.read_of(), 348 0x230C => self.read_vdp_low(), 349 0x230D => self.read_vdp_high(), 350 _ => 0, 351 } 352 } 353 354 pub fn snes_write(&mut self, address: u32, value: u8, mmc: &mut Sa1Mmc) { 355 log::trace!("SNES register write: {address:06X} {value:02X}"); 356 357 match address & 0xFFFF { 358 0x2200 => self.write_ccnt(value), 359 0x2201 => self.write_sie(value), 360 0x2202 => self.write_sic(value), 361 0x2203 => self.write_crv_low(value), 362 0x2204 => self.write_crv_high(value), 363 0x2205 => self.write_cnv_low(value), 364 0x2206 => self.write_cnv_high(value), 365 0x2207 => self.write_civ_low(value), 366 0x2208 => self.write_civ_high(value), 367 0x2220 => mmc.write_cxb(value), 368 0x2221 => mmc.write_dxb(value), 369 0x2222 => mmc.write_exb(value), 370 0x2223 => mmc.write_fxb(value), 371 0x2224 => mmc.write_bmaps(value), 372 0x2226 => self.write_sbwe(value), 373 0x2228 => self.write_bwpa(value), 374 0x2229 => self.write_siwp(value), 375 0x2231 => self.write_cdma(value), 376 0x2232 => self.write_sda_low(value), 377 0x2233 => self.write_sda_mid(value), 378 0x2234 => self.write_sda_high(value), 379 0x2235 => self.write_dda_low(value), 380 0x2236 => self.write_dda_mid(value), 381 0x2237 => self.write_dda_high(value), 382 _ => {} 383 } 384 } 385 386 pub fn sa1_write( 387 &mut self, 388 address: u32, 389 value: u8, 390 timer: &mut Sa1Timer, 391 mmc: &mut Sa1Mmc, 392 rom: &[u8], 393 iram: &mut Iram, 394 ) { 395 log::trace!("SA-1 register write: {address:06X} {value:02X}"); 396 397 match address & 0xFFFF { 398 0x2209 => self.write_scnt(value), 399 0x220A => self.write_cie(value), 400 0x220B => self.write_cic(value, timer), 401 0x220C => self.write_snv_low(value), 402 0x220D => self.write_snv_high(value), 403 0x220E => self.write_siv_low(value), 404 0x220F => self.write_siv_high(value), 405 0x2210 => timer.write_tmc(value), 406 0x2211 => timer.reset(), 407 0x2212 => timer.write_hcnt_low(value), 408 0x2213 => timer.write_hcnt_high(value), 409 0x2214 => timer.write_vcnt_low(value), 410 0x2215 => timer.write_vcnt_high(value), 411 0x2225 => mmc.write_bmap(value), 412 0x2227 => self.write_cbwe(value), 413 0x222A => self.write_ciwp(value), 414 0x2230 => self.write_dcnt(value), 415 0x2231 => self.write_cdma(value), 416 0x2232 => self.write_sda_low(value), 417 0x2233 => self.write_sda_mid(value), 418 0x2234 => self.write_sda_high(value), 419 0x2235 => self.write_dda_low(value), 420 0x2236 => self.write_dda_mid(value), 421 0x2237 => self.write_dda_high(value), 422 0x2238 => self.write_dtc_low(value), 423 0x2239 => self.write_dtc_high(value), 424 0x223F => mmc.write_bbf(value), 425 0x2240..=0x224F => self.write_brf(address, value, iram), 426 0x2250 => self.write_mcnt(value), 427 0x2251 => self.write_ma_low(value), 428 0x2252 => self.write_ma_high(value), 429 0x2253 => self.write_mb_low(value), 430 0x2254 => self.write_mb_high(value), 431 0x2258 => self.write_vbd(value, mmc, rom), 432 0x2259 => self.write_vda_low(value), 433 0x225A => self.write_vda_mid(value), 434 0x225B => self.write_vda_high(value, mmc, rom), 435 _ => {} 436 } 437 } 438 439 fn read_sfr(&self) -> u8 { 440 (u8::from(self.snes_irq_from_sa1) << 7) 441 | (u8::from(self.snes_irq_vector_source.to_bit()) << 6) 442 | (u8::from(self.character_conversion_irq) << 5) 443 | (u8::from(self.snes_nmi_vector_source.to_bit()) << 4) 444 | self.message_to_snes 445 } 446 447 fn read_cfr(&self, timer: &Sa1Timer) -> u8 { 448 (u8::from(self.sa1_irq_from_snes) << 7) 449 | (u8::from(timer.irq_pending) << 6) 450 | (u8::from(self.sa1_dma_irq) << 5) 451 | (u8::from(self.sa1_nmi) << 4) 452 | self.message_to_sa1 453 } 454 455 fn read_mr(&self, address: u32) -> u8 { 456 // $2306 is bits 0-7, $2307 is bits 8-15, $2308 is bits 16-23, etc. 457 let shift = 8 * ((address & 0xF) - 0x6); 458 (self.arithmetic_result >> shift) as u8 459 } 460 461 fn read_of(&self) -> u8 { 462 u8::from(self.arithmetic_overflow) << 7 463 } 464 465 fn read_vdp_low(&self) -> u8 { 466 (self.varlen_bit_data as u16).lsb() 467 } 468 469 fn read_vdp_high(&self) -> u8 { 470 (self.varlen_bit_data as u16).msb() 471 } 472 473 fn write_ccnt(&mut self, value: u8) { 474 if value.bit(7) { 475 self.sa1_irq_from_snes = true; 476 log::trace!(" Generating SA-1 IRQ from SNES"); 477 } 478 479 self.sa1_wait = value.bit(6); 480 self.sa1_reset = value.bit(5); 481 482 if value.bit(4) { 483 self.sa1_nmi = true; 484 log::trace!(" Generating SA-1 NMI"); 485 } 486 487 self.message_to_sa1 = value & 0x0F; 488 489 log::trace!(" SA-1 wait: {}", self.sa1_wait); 490 log::trace!(" SA-1 reset: {}", self.sa1_reset); 491 log::trace!(" Message to SA-1: {:X}", self.message_to_sa1); 492 } 493 494 fn write_sie(&mut self, value: u8) { 495 self.snes_irq_from_sa1_enabled = value.bit(7); 496 self.snes_irq_from_dma_enabled = value.bit(5); 497 498 log::trace!(" SNES IRQs from SA-1 enabled: {}", self.snes_irq_from_sa1_enabled); 499 log::trace!( 500 " SNES character conversion DMA IRQs enabled: {}", 501 self.snes_irq_from_dma_enabled 502 ); 503 } 504 505 fn write_sic(&mut self, value: u8) { 506 if value.bit(7) { 507 self.snes_irq_from_sa1 = false; 508 log::trace!(" SNES IRQ from SA-1 cleared"); 509 } 510 if value.bit(5) { 511 self.character_conversion_irq = false; 512 log::trace!(" SNES character conversion DMA IRQ cleared"); 513 } 514 } 515 516 fn write_cie(&mut self, value: u8) { 517 self.sa1_irq_from_snes_enabled = value.bit(7); 518 self.timer_irq_enabled = value.bit(6); 519 self.dma_irq_enabled = value.bit(5); 520 self.sa1_nmi_enabled = value.bit(4); 521 522 log::trace!(" SA-1 IRQs from SNES enabled: {}", self.sa1_irq_from_snes_enabled); 523 log::trace!(" SA-1 timer IRQs enabled: {}", self.timer_irq_enabled); 524 log::trace!(" SA-1 DMA IRQs enabled: {}", self.dma_irq_enabled); 525 log::trace!(" SA-1 NMIs enabled: {}", self.sa1_nmi_enabled); 526 } 527 528 fn write_cic(&mut self, value: u8, timer: &mut Sa1Timer) { 529 if value.bit(7) { 530 self.sa1_irq_from_snes = false; 531 log::trace!(" Cleared SA-1 IRQ from SNES"); 532 } 533 534 if value.bit(6) { 535 timer.irq_pending = false; 536 log::trace!(" Cleared SA-1 timer IRQ"); 537 } 538 539 if value.bit(5) { 540 self.sa1_dma_irq = false; 541 log::trace!(" Cleared SA-1 DMA IRQ"); 542 } 543 544 if value.bit(4) { 545 self.sa1_nmi = false; 546 log::trace!(" Cleared SA-1 NMI"); 547 } 548 } 549 550 fn write_crv_low(&mut self, value: u8) { 551 self.sa1_reset_vector.set_lsb(value); 552 553 log::trace!(" SA-1 RESET vector: {:04X}", self.sa1_reset_vector); 554 } 555 556 fn write_crv_high(&mut self, value: u8) { 557 self.sa1_reset_vector.set_msb(value); 558 559 log::trace!(" SA-1 RESET vector: {:04X}", self.sa1_reset_vector); 560 } 561 562 fn write_cnv_low(&mut self, value: u8) { 563 self.sa1_nmi_vector.set_lsb(value); 564 565 log::trace!(" SA-1 NMI vector: {:04X}", self.sa1_nmi_vector); 566 } 567 568 fn write_cnv_high(&mut self, value: u8) { 569 self.sa1_nmi_vector.set_msb(value); 570 571 log::trace!(" SA-1 NMI vector: {:04X}", self.sa1_nmi_vector); 572 } 573 574 fn write_civ_low(&mut self, value: u8) { 575 self.sa1_irq_vector.set_lsb(value); 576 577 log::trace!(" SA-1 IRQ vector: {:04X}", self.sa1_irq_vector); 578 } 579 580 fn write_civ_high(&mut self, value: u8) { 581 self.sa1_irq_vector.set_msb(value); 582 583 log::trace!(" SA-1 IRQ vector: {:04X}", self.sa1_irq_vector); 584 } 585 586 fn write_snv_low(&mut self, value: u8) { 587 self.snes_nmi_vector.set_lsb(value); 588 589 log::trace!(" SNES NMI vector: {:04X}", self.snes_nmi_vector); 590 } 591 592 fn write_snv_high(&mut self, value: u8) { 593 self.snes_nmi_vector.set_msb(value); 594 595 log::trace!(" SNES NMI vector: {:04X}", self.snes_nmi_vector); 596 } 597 598 fn write_siv_low(&mut self, value: u8) { 599 self.snes_irq_vector.set_lsb(value); 600 601 log::trace!(" SNES IRQ vector: {:04X}", self.snes_irq_vector); 602 } 603 604 fn write_siv_high(&mut self, value: u8) { 605 self.snes_irq_vector.set_msb(value); 606 607 log::trace!(" SNES IRQ vector: {:04X}", self.snes_irq_vector); 608 } 609 610 fn write_scnt(&mut self, value: u8) { 611 if value.bit(7) { 612 self.snes_irq_from_sa1 = true; 613 log::trace!(" Generated SNES IRQ from SA-1"); 614 } 615 616 self.snes_irq_vector_source = InterruptVectorSource::from_bit(value.bit(6)); 617 self.snes_nmi_vector_source = InterruptVectorSource::from_bit(value.bit(4)); 618 self.message_to_snes = value & 0x0F; 619 620 log::trace!(" SNES IRQ vector source: {:?}", self.snes_irq_vector_source); 621 log::trace!(" SNES NMI vector source: {:?}", self.snes_nmi_vector_source); 622 log::trace!(" Message to SNES: {:X}", self.message_to_snes); 623 } 624 625 fn write_sbwe(&mut self, value: u8) { 626 self.bwram_writes_enabled = value.bit(7); 627 628 log::trace!(" BW-RAM writes enabled: {}", self.bwram_writes_enabled); 629 } 630 631 fn write_cbwe(&mut self, value: u8) { 632 self.bwram_writes_enabled = value.bit(7); 633 634 log::trace!(" BW-RAM writes enabled: {}", self.bwram_writes_enabled); 635 } 636 637 fn write_bwpa(&mut self, value: u8) { 638 // Write protected area size is 256 * 2^N bytes 639 self.bwram_write_protection_size = 1 << (8 + (value & 0x0F)); 640 641 log::trace!(" BW-RAM write protection size: {:X}", self.bwram_write_protection_size); 642 } 643 644 fn write_siwp(&mut self, value: u8) { 645 self.snes_iram_writes_enabled = array::from_fn(|i| value.bit(i as u8)); 646 647 log::trace!(" SNES I-RAM writes enabled: {value:02X}"); 648 } 649 650 fn write_ciwp(&mut self, value: u8) { 651 self.sa1_iram_writes_enabled = array::from_fn(|i| value.bit(i as u8)); 652 653 log::trace!(" SA-1 I-RAM writes enabled: {value:02X}"); 654 } 655 656 fn write_dcnt(&mut self, value: u8) { 657 self.dma_source = DmaSourceDevice::from_byte(value); 658 self.dma_destination = DmaDestinationDevice::from_bit(value.bit(2)); 659 self.character_conversion_type = CharacterConversionType::from_bit(value.bit(4)); 660 self.dma_type = DmaType::from_bit(value.bit(5)); 661 self.dma_priority = DmaPriority::from_bit(value.bit(6)); 662 self.dma_enabled = value.bit(7); 663 664 log::trace!(" DMA source: {:?}", self.dma_source); 665 log::trace!(" DMA destination: {:?}", self.dma_destination); 666 log::trace!(" DMA character conversion type: {:?}", self.character_conversion_type); 667 log::trace!(" DMA type: {:?}", self.dma_type); 668 log::trace!(" DMA enabled: {}", self.dma_enabled); 669 } 670 671 fn write_cdma(&mut self, value: u8) { 672 self.ccdma_color_depth = CharacterConversionColorBits::from_byte(value); 673 self.virtual_vram_width_tiles = cmp::min(32, 1 << ((value >> 2) & 0x07)); 674 675 if value.bit(7) && self.dma_state.is_character_conversion() { 676 log::trace!(" Terminating character conversion DMA"); 677 self.dma_state = DmaState::Idle; 678 } 679 680 log::trace!(" Character conversion DMA color depth bits: {:?}", self.ccdma_color_depth); 681 log::trace!(" Virtual VRAM width in tiles: {}", self.virtual_vram_width_tiles); 682 } 683 684 fn write_sda_low(&mut self, value: u8) { 685 self.dma_source_address.set_low_byte(value); 686 687 log::trace!(" DMA source address: {:06X}", self.dma_source_address); 688 } 689 690 fn write_sda_mid(&mut self, value: u8) { 691 self.dma_source_address.set_mid_byte(value); 692 693 log::trace!(" DMA source address: {:06X}", self.dma_source_address); 694 } 695 696 fn write_sda_high(&mut self, value: u8) { 697 self.dma_source_address.set_high_byte(value); 698 699 log::trace!(" DMA source address: {:06X}", self.dma_source_address); 700 } 701 702 fn write_dda_low(&mut self, value: u8) { 703 self.dma_destination_address.set_low_byte(value); 704 705 log::trace!(" DMA destination address: {:06X}", self.dma_destination_address); 706 } 707 708 fn write_dda_mid(&mut self, value: u8) { 709 self.dma_destination_address.set_mid_byte(value); 710 711 log::trace!(" DMA destination address: {:06X}", self.dma_destination_address); 712 713 match (self.dma_enabled, self.dma_type, self.dma_destination) { 714 (true, DmaType::Normal, DmaDestinationDevice::Iram) => { 715 log::trace!(" Starting SA-1 DMA to I-RAM"); 716 self.dma_state = DmaState::NormalCopying; 717 } 718 (true, DmaType::CharacterConversion, _) => { 719 log::trace!(" Starting character conversion DMA"); 720 self.dma_state = match self.character_conversion_type { 721 CharacterConversionType::Two => { 722 DmaState::CharacterConversion2 { buffer_idx: 0, rows_copied: 0 } 723 } 724 CharacterConversionType::One => DmaState::CharacterConversion1Initial { 725 cycles_remaining: self.ccdma_color_depth.tile_size() as u8, 726 }, 727 }; 728 } 729 _ => {} 730 } 731 } 732 733 fn write_dda_high(&mut self, value: u8) { 734 self.dma_destination_address.set_high_byte(value); 735 736 log::trace!(" DMA destination address: {:06X}", self.dma_destination_address); 737 738 if self.dma_enabled 739 && self.dma_type == DmaType::Normal 740 && self.dma_destination == DmaDestinationDevice::Bwram 741 { 742 log::trace!(" Starting SA-1 DMA to BW-RAM"); 743 self.dma_state = DmaState::NormalCopying; 744 } 745 } 746 747 fn write_dtc_low(&mut self, value: u8) { 748 self.dma_terminal_counter.set_lsb(value); 749 750 log::trace!(" DMA terminal counter: {:04X}", self.dma_terminal_counter); 751 } 752 753 fn write_dtc_high(&mut self, value: u8) { 754 self.dma_terminal_counter.set_msb(value); 755 756 log::trace!(" DMA terminal counter: {:04X}", self.dma_terminal_counter); 757 } 758 759 fn write_brf(&mut self, address: u32, value: u8, iram: &mut Iram) { 760 // BRF registers are $2240-$224F; lowest 4 bits of address are the register index 761 let idx = (address & 0xF) as usize; 762 self.bitmap_pixels[idx] = value & self.ccdma_color_depth.bit_mask(); 763 764 log::trace!(" Bitmap register file #{idx}: {value:02X}"); 765 766 if idx & 0x7 == 0x7 { 767 // Perform character conversion any time register 7 or 15 is written 768 if let DmaState::CharacterConversion2 { buffer_idx, rows_copied } = self.dma_state { 769 self.character_conversion_2(idx & 0x8, buffer_idx, rows_copied, iram); 770 } 771 } 772 } 773 774 fn write_mcnt(&mut self, value: u8) { 775 self.arithmetic_op = ArithmeticOp::from_byte(value); 776 777 // Setting multiply-accumulate clears result 778 if self.arithmetic_op == ArithmeticOp::MultiplyAccumulate { 779 self.arithmetic_result = 0; 780 self.arithmetic_overflow = false; 781 } 782 783 log::trace!(" Arithmetic mode: {:?}", self.arithmetic_op); 784 } 785 786 fn write_ma_low(&mut self, value: u8) { 787 self.arithmetic_param_a.set_lsb(value); 788 789 log::trace!(" Arithmetic parameter A: {:04X}", self.arithmetic_param_a); 790 } 791 792 fn write_ma_high(&mut self, value: u8) { 793 self.arithmetic_param_a.set_msb(value); 794 795 log::trace!(" Arithmetic parameter A: {:04X}", self.arithmetic_param_a); 796 } 797 798 fn write_mb_low(&mut self, value: u8) { 799 self.arithmetic_param_b.set_lsb(value); 800 801 log::trace!(" Arithmetic parameter B: {:04X}", self.arithmetic_param_b); 802 } 803 804 fn write_mb_high(&mut self, value: u8) { 805 self.arithmetic_param_b.set_msb(value); 806 807 // Writing MB high byte begins arithmetic operation 808 self.perform_arithmetic_op(); 809 810 log::trace!(" Arithmetic parameter B: {:04X}", self.arithmetic_param_b); 811 } 812 813 fn write_vbd(&mut self, value: u8, mmc: &Sa1Mmc, rom: &[u8]) { 814 if self.varlen_bits_remaining == 0 { 815 // Variable-length bit data reading not initialized; do nothing 816 return; 817 } 818 819 let shift = if value & 0x0F == 0 { 16 } else { value & 0x0F }; 820 self.varlen_bit_data >>= shift; 821 self.varlen_bits_remaining -= shift; 822 823 if self.varlen_bits_remaining < 16 { 824 // Read next word 825 let word = mmc.map_rom_address(self.varlen_bit_start_address).map_or(0, |rom_addr| { 826 let lsb = rom.get(rom_addr as usize).copied().unwrap_or(0); 827 let msb = rom.get((rom_addr + 1) as usize).copied().unwrap_or(0); 828 u16::from_le_bytes([lsb, msb]) 829 }); 830 let word: u32 = word.into(); 831 832 self.varlen_bit_data |= word << self.varlen_bits_remaining; 833 self.varlen_bit_start_address = (self.varlen_bit_start_address + 2) & 0xFFFFFF; 834 self.varlen_bits_remaining += 16; 835 } 836 837 log::trace!(" Variable-length bit data shift: {shift}"); 838 } 839 840 fn write_vda_low(&mut self, value: u8) { 841 self.varlen_bit_start_address.set_low_byte(value); 842 843 log::trace!( 844 " Variable-length bit data ROM start address: {:06X}", 845 self.varlen_bit_start_address 846 ); 847 } 848 849 fn write_vda_mid(&mut self, value: u8) { 850 self.varlen_bit_start_address.set_mid_byte(value); 851 852 log::trace!( 853 " Variable-length bit data ROM start address: {:06X}", 854 self.varlen_bit_start_address 855 ); 856 } 857 858 fn write_vda_high(&mut self, value: u8, mmc: &Sa1Mmc, rom: &[u8]) { 859 self.varlen_bit_start_address.set_high_byte(value); 860 861 // Writing MSB starts the variable-length bit data read 862 if let Some(rom_addr) = mmc.map_rom_address(self.varlen_bit_start_address) { 863 let lsb = rom[rom_addr as usize]; 864 let msb = rom.get((rom_addr + 1) as usize).copied().unwrap_or(0); 865 self.varlen_bit_data = u16::from_le_bytes([lsb, msb]).into(); 866 self.varlen_bit_start_address = (self.varlen_bit_start_address + 2) & 0xFFFFFF; 867 self.varlen_bits_remaining = 16; 868 } 869 870 log::trace!( 871 " Variable-length bit data ROM start address: {:06X}", 872 self.varlen_bit_start_address 873 ); 874 } 875 876 pub fn can_write_bwram(&self, bwram_addr: u32) -> bool { 877 self.bwram_writes_enabled || bwram_addr >= self.bwram_write_protection_size 878 } 879 880 pub fn tick_dma(&mut self, mmc: &Sa1Mmc, rom: &[u8], iram: &mut Iram, bwram: &mut [u8]) { 881 // Progress normal DMA or character conversion DMA type 1 882 match self.dma_state { 883 DmaState::NormalCopying => { 884 self.progress_normal_dma(mmc, rom, iram, bwram); 885 } 886 DmaState::NormalWaitCycle => { 887 self.dma_state = DmaState::NormalCopying; 888 } 889 DmaState::CharacterConversion1Initial { cycles_remaining } => { 890 if cycles_remaining == 1 { 891 self.start_ccdma_type_1(iram, bwram); 892 } else { 893 self.dma_state = DmaState::CharacterConversion1Initial { 894 cycles_remaining: cycles_remaining - 1, 895 }; 896 } 897 } 898 DmaState::Idle 899 | DmaState::CharacterConversion2 { .. } 900 | DmaState::CharacterConversion1Active { .. } => {} 901 } 902 } 903 904 pub fn notify_snes_dma_start(&mut self, source_address: u32) { 905 // TODO check exact source address? 906 if matches!(self.dma_state, DmaState::CharacterConversion1Active { .. }) 907 && (0x400000..0x500000).contains(&source_address) 908 { 909 self.ccdma_transfer_in_progress = true; 910 } 911 } 912 913 pub fn notify_snes_dma_end(&mut self) { 914 self.ccdma_transfer_in_progress = false; 915 } 916 917 fn perform_arithmetic_op(&mut self) { 918 const I40_RANGE: Range<i64> = -(1 << 39)..1 << 39; 919 const I40_MASK: u64 = (1 << 40) - 1; 920 921 match self.arithmetic_op { 922 ArithmeticOp::Multiply => { 923 // Signed 16-bit x Signed 16-bit -> Signed 32-bit 924 self.arithmetic_result = 925 (multiply(self.arithmetic_param_a, self.arithmetic_param_b) as u64) & I40_MASK; 926 } 927 ArithmeticOp::Divide => { 928 // Signed 16-bit / Unsigned 16-bit -> Signed 16-bit Quotient, Unsigned 16-bit Remainder 929 let (quotient, remainder) = 930 divide(self.arithmetic_param_a, self.arithmetic_param_b); 931 let quotient: u64 = (quotient as u16).into(); 932 let remainder: u64 = remainder.into(); 933 934 self.arithmetic_result = quotient | (remainder << 16); 935 936 // Division apparently clears parameter A in addition to B 937 self.arithmetic_param_a = 0; 938 } 939 ArithmeticOp::MultiplyAccumulate => { 940 // Signed 16-bit x Signed 16-bit -> Signed 32-bit 941 // Accumulates into a signed 40-bit sum 942 let product = multiply(self.arithmetic_param_a, self.arithmetic_param_b); 943 let sum = (((self.arithmetic_result as i64) << 24) >> 24) + product; 944 945 self.arithmetic_result = (sum as u64) & I40_MASK; 946 self.arithmetic_overflow = !I40_RANGE.contains(&sum); 947 } 948 } 949 950 // All ops apparently clear parameter B 951 self.arithmetic_param_b = 0; 952 } 953 954 pub fn reset(&mut self, timer: &mut Sa1Timer, mmc: &mut Sa1Mmc) { 955 self.write_ccnt(0x20); 956 self.write_sie(0x00); 957 self.write_sic(0x00); 958 self.write_scnt(0x00); 959 self.write_cie(0x00); 960 self.write_cic(0x00, timer); 961 timer.write_tmc(0x00); 962 self.write_sbwe(0x00); 963 self.write_cbwe(0x00); 964 self.write_bwpa(0xFF); 965 self.write_siwp(0x00); 966 self.write_ciwp(0x00); 967 self.write_dcnt(0x00); 968 self.write_cdma(0x80); 969 self.write_mcnt(0x00); 970 971 mmc.write_cxb(0x00); 972 mmc.write_dxb(0x01); 973 mmc.write_exb(0x02); 974 mmc.write_fxb(0x03); 975 mmc.write_bmaps(0x00); 976 mmc.write_bmap(0x00); 977 mmc.write_bbf(0x00); 978 } 979 980 pub fn cpu_halted(&self) -> bool { 981 matches!(self.dma_state, DmaState::NormalCopying | DmaState::NormalWaitCycle) 982 && (self.dma_priority == DmaPriority::Dma || self.dma_source == DmaSourceDevice::Rom) 983 } 984} 985 986fn multiply(a: u16, b: u16) -> i64 { 987 let a: i64 = (a as i16).into(); 988 let b: i64 = (b as i16).into(); 989 a * b 990} 991 992fn divide(a: u16, b: u16) -> (i16, u16) { 993 if b == 0 { 994 // Divide by zero 995 return if a.sign_bit() { (1, (!a).wrapping_add(1)) } else { (-1, a) }; 996 } 997 998 // Signed dividend, unsigned divisor 999 let a: i32 = (a as i16).into(); 1000 let b: i32 = b.into(); 1001 1002 // Signed quotient, unsigned remainder 1003 let quotient = a.div_euclid(b); 1004 let remainder = a.rem_euclid(b); 1005 1006 (quotient as i16, remainder as u16) 1007}