Code for the MMC5 board (iNES mapper 5).
3use crate::apu; 4use crate::apu::FrameCounter; 5use crate::apu::pulse::{PulseChannel, SweepStatus}; 6use crate::bus::cartridge::mappers::{BankSizeKb, CpuMapResult}; 7use crate::bus::cartridge::{Cartridge, MapperImpl}; 8use bincode::{Decode, Encode}; 9use jgenesis_common::frontend::TimingMode; 10use jgenesis_common::num::GetBit;
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 13enum PrgBankingMode { 14 Mode0, 15 Mode1, 16 Mode2, 17 Mode3, 18} 19 20impl PrgBankingMode { 21 fn map_result( 22 bank_number: u8, 23 bank_size: BankSizeKb, 24 address: u16, 25 prg_ram_len: u32, 26 ) -> CpuMapResult { 27 let is_rom = bank_number.bit(7); 28 29 if !is_rom && prg_ram_len == 16 * 1024 && bank_size == BankSizeKb::Eight { 30 // Every MMC5 cartridge with 16KB of PRG RAM has it split across two 8KB chips 31 // Bank bit 2 is used as a chip select; other bank bits do nothing 32 let bank: u8 = bank_number.bit(2).into(); 33 return CpuMapResult::PrgRAM(BankSizeKb::Eight.to_absolute_address(bank, address)); 34 } 35 36 let masked_bank_number = if is_rom { bank_number & 0x7F } else { bank_number & 0x0F }; 37 38 // All bank numbers are treated as 8KB banks while selectively ignoring lower bits 39 let shifted_bank_number = match bank_size { 40 BankSizeKb::Eight => masked_bank_number, 41 BankSizeKb::Sixteen => masked_bank_number >> 1, 42 BankSizeKb::ThirtyTwo => masked_bank_number >> 2, 43 _ => panic!("MMC5 mapper should only have 8KB/16KB/32KB bank size, was {bank_size:?}"), 44 }; 45 46 let mapped_address = bank_size.to_absolute_address(shifted_bank_number, address); 47 if is_rom { 48 CpuMapResult::PrgROM(mapped_address) 49 } else { 50 CpuMapResult::PrgRAM(mapped_address) 51 } 52 } 53 54 fn map_prg_address( 55 self, 56 prg_bank_registers: [u8; 5], 57 address: u16, 58 prg_ram_len: u32, 59 ) -> CpuMapResult { 60 match address { 61 0x0000..=0x5FFF => panic!("invalid MMC5 PRG map address: {address:04X}"), 62 0x6000..=0x7FFF => { 63 Self::map_result(prg_bank_registers[0], BankSizeKb::Eight, address, prg_ram_len) 64 } 65 0x8000..=0xFFFF => { 66 let (bank_register, bank_size) = match self { 67 // 1x32KB 68 Self::Mode0 => (4, BankSizeKb::ThirtyTwo), 69 // 2x16KB 70 Self::Mode1 => { 71 let bank_register = match address { 72 0x8000..=0xBFFF => 2, 73 0xC000..=0xFFFF => 4, 74 0x0000..=0x7FFF => unreachable!("nested match expressions"), 75 }; 76 (bank_register, BankSizeKb::Sixteen) 77 } 78 // 1x16KB + 2x8KB 79 Self::Mode2 => match address { 80 0x8000..=0xBFFF => (2, BankSizeKb::Sixteen), 81 0xC000..=0xDFFF => (3, BankSizeKb::Eight), 82 0xE000..=0xFFFF => (4, BankSizeKb::Eight), 83 0x0000..=0x7FFF => unreachable!("nested match expressions"), 84 }, 85 // 4x8KB 86 Self::Mode3 => { 87 // 0x8000..=0x9FFF to bank 1 88 // 0xA000..=0xBFFF to bank 2 89 // 0xC000..=0xDFFF to bank 3 90 // 0xE000..=0xFFFF to bank 4 91 let bank_register = (address & 0x7FFF) / 0x2000 + 1; 92 (bank_register, BankSizeKb::Eight) 93 } 94 }; 95 96 Self::map_result( 97 prg_bank_registers[bank_register as usize], 98 bank_size, 99 address, 100 prg_ram_len, 101 ) 102 } 103 } 104 } 105} 106 107#[derive(Debug, Clone, Encode, Decode)] 108struct ChrMapper { 109 bank_size: BankSizeKb, 110 bank_registers: [u8; 12], 111 double_height_sprites: bool, 112 last_register_written: usize, 113 next_access_from_ppu_data: bool, 114} 115 116impl ChrMapper { 117 fn new() -> Self { 118 Self { 119 bank_size: BankSizeKb::Eight, 120 bank_registers: [0; 12], 121 double_height_sprites: false, 122 last_register_written: 0, 123 next_access_from_ppu_data: false, 124 } 125 } 126 127 fn map_sprite_chr_address(&self, address: u16) -> u32 { 128 assert!((0x0000..=0x1FFF).contains(&address)); 129 130 match self.bank_size { 131 BankSizeKb::Eight => { 132 BankSizeKb::Eight.to_absolute_address(self.bank_registers[7], address) 133 } 134 BankSizeKb::Four => { 135 // 0x0000..=0x0FFF to bank 3 136 // 0x1000..=0x1FFF to bank 7 137 let bank_register = 4 * (address / 0x1000) + 3; 138 BankSizeKb::Four 139 .to_absolute_address(self.bank_registers[bank_register as usize], address) 140 } 141 BankSizeKb::Two => { 142 // 0x0000..=0x07FF to bank 1 143 // 0x0800..=0x0FFF to bank 3 144 // 0x1000..=0x17FF to bank 5 145 // 0x1800..=0x1FFF to bank 7 146 let bank_register = 2 * (address / 0x0800) + 1; 147 BankSizeKb::Two 148 .to_absolute_address(self.bank_registers[bank_register as usize], address) 149 } 150 BankSizeKb::One => { 151 // 0x0000..=0x03FF to bank 0 152 // 0x0400..=0x07FF to bank 1 153 // 0x0800..=0x0BFF to bank 2 154 // 0x0C00..=0x0FFF to bank 3 155 // 0x1000..=0x13FF to bank 4 156 // 0x1400..=0x17FF to bank 5 157 // 0x1800..=0x1BFF to bank 6 158 // 0x1C00..=0x1FFF to bank 7 159 let bank_register = address / 0x0400; 160 BankSizeKb::One 161 .to_absolute_address(self.bank_registers[bank_register as usize], address) 162 } 163 _ => panic!("MMC5 bank size should always be 1/2/4/8"), 164 } 165 } 166 167 fn map_bg_chr_address(&self, address: u16) -> u32 { 168 assert!((0x0000..=0x1FFF).contains(&address)); 169 170 match self.bank_size { 171 BankSizeKb::Eight => { 172 BankSizeKb::Eight.to_absolute_address(self.bank_registers[11], address) 173 } 174 BankSizeKb::Four => { 175 BankSizeKb::Four.to_absolute_address(self.bank_registers[11], address) 176 } 177 BankSizeKb::Two => { 178 // 0x0000..=0x07FF and 0x1000..=0x17FF to bank 9 179 // 0x0800..=0x0FFF and 0x1800..=0x1FFF to bank 11 180 let bank_register = 2 * ((address & 0x0FFF) / 0x0800) + 9; 181 BankSizeKb::Two 182 .to_absolute_address(self.bank_registers[bank_register as usize], address) 183 } 184 BankSizeKb::One => { 185 // 0x0000..=0x03FF and 0x1000..=0x13FF to bank 8 186 // 0x0400..=0x07FF and 0x1400..=0x17FF to bank 9 187 // 0x0800..=0x0BFF and 0x1800..=0x1BFF to bank 10 188 // 0x0C00..=0x0FFF and 0x1C00..=0x1FFF to bank 11 189 let bank_register = (address & 0x0FFF) / 0x0400 + 8; 190 BankSizeKb::One 191 .to_absolute_address(self.bank_registers[bank_register as usize], address) 192 } 193 _ => panic!("MMC5 CHR bank size should always be 1/2/4/8"), 194 } 195 } 196 197 fn map_chr_address(&mut self, address: u16, tile_type: TileType) -> u32 { 198 assert!((0x0000..=0x1FFF).contains(&address)); 199 200 if self.next_access_from_ppu_data { 201 self.next_access_from_ppu_data = false; 202 203 if self.last_register_written < 8 { 204 self.map_sprite_chr_address(address) 205 } else { 206 self.map_bg_chr_address(address) 207 } 208 } else if self.double_height_sprites && tile_type == TileType::Background { 209 self.map_bg_chr_address(address) 210 } else { 211 self.map_sprite_chr_address(address) 212 } 213 } 214 215 fn process_ppu_ctrl_update(&mut self, ppu_ctrl_value: u8) { 216 self.double_height_sprites = ppu_ctrl_value.bit(5); 217 log::trace!("Double height sprites update detected: {}", self.double_height_sprites); 218 } 219 220 fn process_bank_register_update(&mut self, address: u16, value: u8) { 221 assert!((0x5120..=0x512B).contains(&address)); 222 223 let register_index = (address - 0x5120) as usize; 224 self.bank_registers[register_index] = value; 225 self.last_register_written = register_index; 226 } 227} 228 229#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 230enum ExtendedRamMode { 231 Nametable, 232 NametableExtendedAttributes, 233 ReadWrite, 234 ReadOnly, 235} 236 237#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 238enum NametableMapping { 239 VramPage0, 240 VramPage1, 241 ExtendedRam, 242 FillMode, 243} 244 245impl NametableMapping { 246 fn from_bits(bits: u8) -> Self { 247 match bits { 248 0x00 => Self::VramPage0, 249 0x01 => Self::VramPage1, 250 0x02 => Self::ExtendedRam, 251 0x03 => Self::FillMode, 252 _ => panic!("invalid nametable mapping bits: {bits:02X}"), 253 } 254 } 255} 256 257#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 258enum VerticalSplitMode { 259 Left, 260 Right, 261} 262 263#[derive(Debug, Clone, Encode, Decode)] 264struct VerticalSplit { 265 enabled: bool, 266 mode: VerticalSplitMode, 267 split_tile_index: u8, 268 y_scroll: u8, 269 chr_bank: u8, 270} 271 272impl VerticalSplit { 273 fn new() -> Self { 274 Self { 275 enabled: false, 276 mode: VerticalSplitMode::Left, 277 split_tile_index: 0, 278 y_scroll: 0, 279 chr_bank: 0, 280 } 281 } 282 283 fn inside_split(&self, scanline_counter: &ScanlineCounter) -> bool { 284 if !self.enabled { 285 return false; 286 } 287 288 match self.mode { 289 VerticalSplitMode::Left => { 290 scanline_counter.current_tile_index() < self.split_tile_index 291 } 292 VerticalSplitMode::Right => { 293 scanline_counter.current_tile_index() >= self.split_tile_index 294 } 295 } 296 } 297} 298 299#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 300enum TileType { 301 Background, 302 Sprite, 303} 304 305#[derive(Debug, Clone, Encode, Decode)] 306struct ScanlineCounter { 307 scanline: u8, 308 scanline_tile_byte_fetches: u8, 309 compare_value: u8, 310 irq_enabled: bool, 311 irq_pending: bool, 312 in_frame: bool, 313 last_nametable_address: u16, 314 same_nametable_addr_fetch_count: u8, 315 cpu_ticks_no_read: u32, 316} 317 318impl ScanlineCounter { 319 fn new() -> Self { 320 Self { 321 scanline: 0, 322 scanline_tile_byte_fetches: 0, 323 compare_value: 0, 324 irq_enabled: false, 325 irq_pending: false, 326 in_frame: false, 327 last_nametable_address: 0, 328 same_nametable_addr_fetch_count: 0, 329 cpu_ticks_no_read: 0, 330 } 331 } 332 333 // Should be called immediately before a PPU memory access; checks if the scanline has changed 334 fn pre_fetch(&mut self) { 335 if self.same_nametable_addr_fetch_count == 3 { 336 self.same_nametable_addr_fetch_count = 0; 337 338 if self.in_frame { 339 log::trace!( 340 "Detected new scanline; nametable address = {:04X}", 341 self.last_nametable_address 342 ); 343 344 self.scanline += 1; 345 // Reset the tile byte fetch counter on every scanline in case the game disabled 346 // and re-enabled rendering mid-scanline 347 self.scanline_tile_byte_fetches = 4; 348 349 if self.scanline == 241 { 350 log::trace!("Reached VBlank scanline, resetting state"); 351 self.scanline = 0; 352 self.irq_pending = false; 353 self.in_frame = false; 354 } else if self.compare_value != 0 && self.scanline == self.compare_value { 355 log::trace!("Setting IRQ pending flag"); 356 self.irq_pending = true; 357 } 358 } else { 359 log::trace!("Detected new frame"); 360 self.scanline = 0; 361 self.in_frame = true; 362 } 363 } 364 } 365 366 // This should be called *after* mapping the tile address in case the increment changes the 367 // current tile type 368 fn increment_tile_bytes_fetched(&mut self) { 369 log::trace!("Tile byte fetched, current fetches={}", self.scanline_tile_byte_fetches); 370 371 self.cpu_ticks_no_read = 0; 372 373 self.scanline_tile_byte_fetches += 1; 374 375 // 68 BG tile bytes + 8 sprite tiles * 2 pattern table bytes per tile 376 if self.scanline_tile_byte_fetches == 68 + 16 { 377 self.scanline_tile_byte_fetches = 0; 378 } 379 } 380 381 fn nametable_address_fetched(&mut self, address: u16) { 382 assert!((0x2000..=0x2FFF).contains(&address)); 383 384 self.cpu_ticks_no_read = 0; 385 386 if self.last_nametable_address == address && self.same_nametable_addr_fetch_count < 3 { 387 self.same_nametable_addr_fetch_count += 1; 388 } else if self.last_nametable_address != address { 389 self.last_nametable_address = address; 390 self.same_nametable_addr_fetch_count = 1; 391 } 392 } 393 394 fn nmi_vector_fetched(&mut self) { 395 self.scanline = 0; 396 self.irq_pending = false; 397 self.in_frame = false; 398 } 399 400 fn current_tile_type(&self) -> TileType { 401 // 34 BG tiles * 2 pattern table bytes per tile 402 let tile_type = if self.scanline_tile_byte_fetches < 68 { 403 TileType::Background 404 } else { 405 TileType::Sprite 406 }; 407 408 log::trace!("current tile type is {tile_type:?}"); 409 410 tile_type 411 } 412 413 fn current_tile_index(&self) -> u8 { 414 self.scanline_tile_byte_fetches / 2 415 } 416 417 fn interrupt_flag(&self) -> bool { 418 self.irq_enabled && self.irq_pending 419 } 420 421 fn tick_cpu(&mut self) { 422 if self.cpu_ticks_no_read == 3 { 423 log::trace!("Went 3 CPU cycles with no PPU reads, clearing in frame flag"); 424 self.in_frame = false; 425 // Set to 4 so that the counter increments correctly starting from the pre-render scanline 426 // 2 tiles * 2 bytes per tile 427 self.scanline_tile_byte_fetches = 4; 428 } 429 430 self.cpu_ticks_no_read += 1; 431 } 432} 433 434#[derive(Debug, Clone, Encode, Decode)] 435struct ExtendedAttributesState { 436 last_nametable_addr: u16, 437} 438 439impl ExtendedAttributesState { 440 fn new() -> Self { 441 Self { last_nametable_addr: 0 } 442 } 443 444 fn get_attribute_byte(&self, extended_ram: &[u8; 1024]) -> u8 { 445 let extended_attributes = extended_ram[(self.last_nametable_addr & 0x03FF) as usize]; 446 let palette_index = extended_attributes >> 6; 447 (palette_index << 6) | (palette_index << 4) | (palette_index << 2) | palette_index 448 } 449 450 fn get_pattern_table_byte( 451 &self, 452 pattern_table_addr: u16, 453 extended_ram: &[u8; 1024], 454 cartridge: &Cartridge, 455 ) -> u8 { 456 let extended_attributes = extended_ram[(self.last_nametable_addr & 0x03FF) as usize]; 457 let chr_4kb_bank = extended_attributes & 0x3F; 458 let chr_addr = BankSizeKb::Four.to_absolute_address(chr_4kb_bank, pattern_table_addr); 459 cartridge.get_chr_rom(chr_addr) 460 } 461} 462 463#[derive(Debug, Clone, Copy, Encode, Decode)] 464struct MultiplierUnit { 465 operand_l: u16, 466 operand_r: u16, 467} 468 469impl MultiplierUnit { 470 fn new() -> Self { 471 Self { operand_l: 0xFF, operand_r: 0xFF } 472 } 473 474 fn output(self) -> u16 { 475 self.operand_l * self.operand_r 476 } 477} 478 479#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 480enum PcmMode { 481 Read, 482 Write, 483} 484 485impl PcmMode { 486 fn bit(self) -> u8 { 487 match self { 488 Self::Read => 0x01, 489 Self::Write => 0x00, 490 } 491 } 492} 493 494#[derive(Debug, Clone, Encode, Decode)] 495struct PcmChannel { 496 output_level: u8, 497 mode: PcmMode, 498 irq_enabled: bool, 499 irq_pending: bool, 500} 501 502impl PcmChannel { 503 fn new() -> Self { 504 Self { output_level: 0, mode: PcmMode::Write, irq_enabled: false, irq_pending: false } 505 } 506 507 fn process_control_update(&mut self, value: u8) { 508 self.mode = if value.bit(0) { PcmMode::Read } else { PcmMode::Write }; 509 self.irq_enabled = value.bit(7); 510 511 if !self.irq_enabled { 512 self.irq_pending = false; 513 } 514 } 515 516 fn read_control(&mut self) -> u8 { 517 let control = (u8::from(self.irq_pending) << 7) | self.mode.bit(); 518 self.irq_pending = false; 519 control 520 } 521 522 fn process_cpu_read(&mut self, address: u16, value: u8) { 523 if self.mode == PcmMode::Read && (0x8000..=0xBFFF).contains(&address) { 524 if value != 0 { 525 self.output_level = value; 526 } else if self.irq_enabled { 527 self.irq_pending = true; 528 } 529 } 530 } 531 532 fn process_raw_pcm_update(&mut self, value: u8) { 533 if self.mode == PcmMode::Write { 534 if value != 0 { 535 self.output_level = value; 536 } else if self.irq_enabled { 537 self.irq_pending = true; 538 } 539 } 540 } 541} 542 543#[derive(Debug, Clone, Encode, Decode)] 544pub(crate) struct Mmc5 { 545 extended_ram: [u8; 1024], 546 extended_ram_mode: ExtendedRamMode, 547 prg_banking_mode: PrgBankingMode, 548 prg_bank_registers: [u8; 5], 549 chr_mapper: ChrMapper, 550 nametable_mappings: [NametableMapping; 4], 551 fill_mode_tile_data: u8, 552 fill_mode_attributes: u8, 553 vertical_split: VerticalSplit, 554 scanline_counter: ScanlineCounter, 555 extended_attributes_state: ExtendedAttributesState, 556 multiplier: MultiplierUnit, 557 pulse_channel_1: PulseChannel, 558 pulse_channel_2: PulseChannel, 559 pcm_channel: PcmChannel, 560 frame_counter: FrameCounter, 561 ram_writes_enabled_1: bool, 562 ram_writes_enabled_2: bool, 563 rendering_enabled: bool, 564 ppu_open_bus: u8, 565} 566 567impl Mmc5 { 568 pub(crate) fn new() -> Self { 569 Self { 570 extended_ram: [0; 1024], 571 extended_ram_mode: ExtendedRamMode::ReadOnly, 572 prg_banking_mode: PrgBankingMode::Mode3, 573 prg_bank_registers: [0xFF; 5], 574 chr_mapper: ChrMapper::new(), 575 nametable_mappings: [NametableMapping::VramPage0; 4], 576 fill_mode_tile_data: 0, 577 fill_mode_attributes: 0, 578 vertical_split: VerticalSplit::new(), 579 scanline_counter: ScanlineCounter::new(), 580 extended_attributes_state: ExtendedAttributesState::new(), 581 multiplier: MultiplierUnit::new(), 582 pulse_channel_1: PulseChannel::new_channel_1(SweepStatus::Disabled), 583 pulse_channel_2: PulseChannel::new_channel_2(SweepStatus::Disabled), 584 pcm_channel: PcmChannel::new(), 585 frame_counter: FrameCounter::new(TimingMode::Ntsc), 586 ram_writes_enabled_1: false, 587 ram_writes_enabled_2: false, 588 rendering_enabled: false, 589 ppu_open_bus: 0, 590 } 591 } 592} 593 594impl MapperImpl<Mmc5> { 595 pub(crate) fn process_ppu_ctrl_update(&mut self, value: u8) { 596 self.data.chr_mapper.process_ppu_ctrl_update(value); 597 } 598 599 pub(crate) fn process_ppu_mask_update(&mut self, value: u8) { 600 self.data.rendering_enabled = value & 0x18 != 0; 601 } 602 603 pub(crate) fn about_to_access_ppu_data(&mut self) { 604 self.data.chr_mapper.next_access_from_ppu_data = true; 605 } 606 607 fn read_internal_register(&mut self, address: u16, cpu_open_bus: u8) -> u8 { 608 match address { 609 0x5010 => self.data.pcm_channel.read_control(), 610 0x5015 => { 611 (u8::from(self.data.pulse_channel_2.length_counter() != 0) << 1) 612 | u8::from(self.data.pulse_channel_1.length_counter() != 0) 613 } 614 0x5204 => { 615 log::trace!("Scanline IRQ status register read, clearing IRQ pending flag"); 616 617 let result = (u8::from(self.data.scanline_counter.irq_pending) << 7) 618 | (u8::from(self.data.scanline_counter.in_frame) << 6); 619 self.data.scanline_counter.irq_pending = false; 620 result 621 } 622 0x5205 => (self.data.multiplier.output() & 0x00FF) as u8, 623 0x5206 => (self.data.multiplier.output() >> 8) as u8, 624 _ => cpu_open_bus, 625 } 626 } 627 628 fn write_internal_register(&mut self, address: u16, value: u8) { 629 match address { 630 0x5000 => { 631 self.data.pulse_channel_1.process_vol_update(value); 632 } 633 0x5002 => { 634 self.data.pulse_channel_1.process_lo_update(value); 635 } 636 0x5003 => { 637 self.data.pulse_channel_1.process_hi_update(value); 638 } 639 0x5004 => { 640 self.data.pulse_channel_2.process_vol_update(value); 641 } 642 0x5006 => { 643 self.data.pulse_channel_2.process_lo_update(value); 644 } 645 0x5007 => { 646 self.data.pulse_channel_2.process_hi_update(value); 647 } 648 0x5010 => { 649 self.data.pcm_channel.process_control_update(value); 650 } 651 0x5011 => { 652 self.data.pcm_channel.process_raw_pcm_update(value); 653 } 654 0x5015 => { 655 self.data.pulse_channel_1.process_snd_chn_update(value); 656 self.data.pulse_channel_2.process_snd_chn_update(value); 657 } 658 0x5100 => { 659 self.data.prg_banking_mode = match value & 0x03 { 660 0x00 => PrgBankingMode::Mode0, 661 0x01 => PrgBankingMode::Mode1, 662 0x02 => PrgBankingMode::Mode2, 663 0x03 => PrgBankingMode::Mode3, 664 _ => unreachable!("value & 0x03 should always be 0x00/0x01/0x02/0x03"), 665 }; 666 log::trace!("PRG banking mode set to {:?}", self.data.prg_banking_mode); 667 } 668 0x5101 => { 669 self.data.chr_mapper.bank_size = match value & 0x03 { 670 0x00 => BankSizeKb::Eight, 671 0x01 => BankSizeKb::Four, 672 0x02 => BankSizeKb::Two, 673 0x03 => BankSizeKb::One, 674 _ => unreachable!("value & 0x03 should always be 0x00/0x01/0x02/0x03"), 675 }; 676 log::trace!("CHR bank size set to {:?}", self.data.chr_mapper.bank_size); 677 } 678 0x5102 => { 679 self.data.ram_writes_enabled_1 = value & 0x03 == 0x02; 680 } 681 0x5103 => { 682 self.data.ram_writes_enabled_2 = value & 0x03 == 0x01; 683 } 684 0x5104 => { 685 self.data.extended_ram_mode = match value & 0x03 { 686 0x00 => ExtendedRamMode::Nametable, 687 0x01 => ExtendedRamMode::NametableExtendedAttributes, 688 0x02 => ExtendedRamMode::ReadWrite, 689 0x03 => ExtendedRamMode::ReadOnly, 690 _ => unreachable!("value & 0x03 should be 0x00/0x01/0x02/0x03"), 691 }; 692 log::trace!("Extended RAM mode set to {:?}", self.data.extended_ram_mode); 693 } 694 0x5105 => { 695 self.data.nametable_mappings[0] = NametableMapping::from_bits(value & 0x03); 696 self.data.nametable_mappings[1] = NametableMapping::from_bits((value >> 2) & 0x03); 697 self.data.nametable_mappings[2] = NametableMapping::from_bits((value >> 4) & 0x03); 698 self.data.nametable_mappings[3] = NametableMapping::from_bits((value >> 6) & 0x03); 699 log::trace!("Nametable mappings set to {:?}", self.data.nametable_mappings); 700 } 701 0x5106 => { 702 self.data.fill_mode_tile_data = value; 703 log::trace!("Fill mode tile set to {value:02X}"); 704 } 705 0x5107 => { 706 let palette_index = value & 0x03; 707 self.data.fill_mode_attributes = palette_index 708 | (palette_index << 2) 709 | (palette_index << 4) 710 | (palette_index << 6); 711 log::trace!("Fill mode palette index set to {value:02X}"); 712 } 713 0x5113..=0x5117 => { 714 let bank = match address { 715 0x5113 => { 716 // Bank 0 can only ever map to PRG RAM 717 value & 0x7F 718 } 719 0x5117 => { 720 // Bank 4 can only ever map to PRG ROM 721 value | 0x80 722 } 723 _ => { 724 // Banks 1-3 can map to PRG ROM or RAM, depending on bit 7 725 value 726 } 727 }; 728 729 self.data.prg_bank_registers[(address - 0x5113) as usize] = bank; 730 log::trace!("PRG bank {:02X} set to {value:02X}", address - 0x5113); 731 } 732 0x5120..=0x512B => { 733 self.data.chr_mapper.process_bank_register_update(address, value); 734 log::trace!("CHR bank {:02X} set to {value:02X}", address - 0x5120); 735 } 736 0x5200 => { 737 self.data.vertical_split.enabled = value.bit(7); 738 self.data.vertical_split.mode = 739 if value.bit(6) { VerticalSplitMode::Right } else { VerticalSplitMode::Left }; 740 self.data.vertical_split.split_tile_index = value & 0x1F; 741 log::trace!( 742 "Vertical split enabled/mode/index set: {:?}", 743 self.data.vertical_split 744 ); 745 } 746 0x5201 => { 747 self.data.vertical_split.y_scroll = value; 748 log::trace!("Vertical split Y scroll set to {value}"); 749 } 750 0x5202 => { 751 self.data.vertical_split.chr_bank = value; 752 log::trace!("Vertical split CHR bank set to {value:02X}"); 753 } 754 0x5203 => { 755 self.data.scanline_counter.compare_value = value; 756 log::trace!("Scanline counter compare value set to {value}"); 757 } 758 0x5204 => { 759 self.data.scanline_counter.irq_enabled = value.bit(7); 760 log::trace!( 761 "Scanline IRQ enabled set to {}", 762 self.data.scanline_counter.irq_enabled 763 ); 764 } 765 0x5205 => { 766 self.data.multiplier.operand_l = value.into(); 767 } 768 0x5206 => { 769 self.data.multiplier.operand_r = value.into(); 770 } 771 _ => {} 772 } 773 } 774 775 pub(crate) fn read_cpu_address(&mut self, address: u16, cpu_open_bus: u8) -> u8 { 776 if address == 0xFFFA || address == 0xFFFB { 777 self.data.scanline_counter.nmi_vector_fetched(); 778 } 779 780 match address { 781 0x0000..=0x401F => panic!("invalid CPU map address: {address:04X}"), 782 0x4020..=0x4FFF => cpu_open_bus, 783 0x5000..=0x5BFF => self.read_internal_register(address, cpu_open_bus), 784 0x5C00..=0x5FFF => match self.data.extended_ram_mode { 785 ExtendedRamMode::ReadWrite | ExtendedRamMode::ReadOnly => { 786 self.data.extended_ram[(address - 0x5C00) as usize] 787 } 788 ExtendedRamMode::Nametable | ExtendedRamMode::NametableExtendedAttributes => { 789 cpu_open_bus 790 } 791 }, 792 0x6000..=0xFFFF => { 793 let value = self 794 .data 795 .prg_banking_mode 796 .map_prg_address( 797 self.data.prg_bank_registers, 798 address, 799 self.cartridge.prg_ram.len() as u32, 800 ) 801 .read(&self.cartridge) 802 .unwrap_or(cpu_open_bus); 803 804 self.data.pcm_channel.process_cpu_read(address, value); 805 806 value 807 } 808 } 809 } 810 811 pub(crate) fn write_cpu_address(&mut self, address: u16, value: u8) { 812 match address { 813 0x0000..=0x401F => panic!("invalid CPU map address: {address:04X}"), 814 0x4020..=0x4FFF => {} 815 0x5000..=0x5BFF => { 816 self.write_internal_register(address, value); 817 } 818 0x5C00..=0x5FFF => { 819 if self.data.extended_ram_mode != ExtendedRamMode::ReadOnly { 820 self.data.extended_ram[(address - 0x5C00) as usize] = value; 821 } 822 } 823 0x6000..=0xFFFF => { 824 if self.prg_ram_writes_enabled() { 825 self.data 826 .prg_banking_mode 827 .map_prg_address( 828 self.data.prg_bank_registers, 829 address, 830 self.cartridge.prg_ram.len() as u32, 831 ) 832 .write(value, &mut self.cartridge); 833 } 834 } 835 } 836 } 837 838 pub(crate) fn read_ppu_address(&mut self, address: u16, vram: &[u8; 2048]) -> u8 { 839 self.data.scanline_counter.pre_fetch(); 840 841 let value = match address { 842 0x0000..=0x1FFF => { 843 let tile_type = self.data.scanline_counter.current_tile_type(); 844 let pattern_table_byte = if self.data.rendering_enabled 845 && tile_type == TileType::Background 846 && self.data.extended_ram_mode == ExtendedRamMode::NametableExtendedAttributes 847 { 848 self.data.extended_attributes_state.get_pattern_table_byte( 849 address, 850 &self.data.extended_ram, 851 &self.cartridge, 852 ) 853 } else if self.data.rendering_enabled 854 && tile_type == TileType::Background 855 && self.data.vertical_split.inside_split(&self.data.scanline_counter) 856 { 857 let fine_y_scroll = self.data.vertical_split.y_scroll & 0x07; 858 let pattern_table_addr = 859 (address & 0xFFF8) | ((address + u16::from(fine_y_scroll)) & 0x07); 860 861 let chr_addr = BankSizeKb::Four 862 .to_absolute_address(self.data.vertical_split.chr_bank, pattern_table_addr); 863 self.cartridge.get_chr_rom(chr_addr) 864 } else { 865 let chr_addr = self.data.chr_mapper.map_chr_address(address, tile_type); 866 self.cartridge.get_chr_rom(chr_addr) 867 }; 868 869 self.data.scanline_counter.increment_tile_bytes_fetched(); 870 871 pattern_table_byte 872 } 873 0x2000..=0x3EFF => { 874 let relative_addr = address & 0x0FFF; 875 let nametable_addr = 0x2000 | relative_addr; 876 877 self.data.scanline_counter.nametable_address_fetched(nametable_addr); 878 879 let tile_type = self.data.scanline_counter.current_tile_type(); 880 if tile_type == TileType::Background 881 && self.data.extended_ram_mode == ExtendedRamMode::NametableExtendedAttributes 882 && address & 0x03FF >= 0x03C0 883 { 884 // In extended attributes mode, replace attribute table fetches with lookups 885 // from extended RAM 886 return self 887 .data 888 .extended_attributes_state 889 .get_attribute_byte(&self.data.extended_ram); 890 } 891 self.data.extended_attributes_state.last_nametable_addr = address; 892 893 if self.data.vertical_split.inside_split(&self.data.scanline_counter) 894 && matches!( 895 self.data.extended_ram_mode, 896 ExtendedRamMode::Nametable | ExtendedRamMode::NametableExtendedAttributes 897 ) 898 { 899 // Ignore nametable mapping when inside the vertical split, always read from 900 // extended RAM 901 let scanline = self.data.scanline_counter.scanline; 902 let y_scroll = self.data.vertical_split.y_scroll; 903 let tile_x_index = self.data.scanline_counter.current_tile_index() & 0x1F; 904 let tile_y_index = ((u16::from(scanline) + u16::from(y_scroll)) >> 3) % 30; 905 906 let extended_ram_addr = if relative_addr & 0x03FF < 0x03C0 { 907 // Nametable lookup 908 (tile_y_index << 5) | u16::from(tile_x_index) 909 } else { 910 // Attribute table lookup 911 0x03C0 + (((tile_y_index >> 2) << 3) | (u16::from(tile_x_index) >> 2)) 912 }; 913 914 return self.data.extended_ram[extended_ram_addr as usize]; 915 } 916 917 let nametable_mapping = 918 self.data.nametable_mappings[(relative_addr >> 10) as usize]; 919 match nametable_mapping { 920 NametableMapping::VramPage0 => vram[(relative_addr & 0x03FF) as usize], 921 NametableMapping::VramPage1 => { 922 vram[(0x0400 | (relative_addr & 0x03FF)) as usize] 923 } 924 NametableMapping::ExtendedRam => match self.data.extended_ram_mode { 925 ExtendedRamMode::Nametable 926 | ExtendedRamMode::NametableExtendedAttributes => { 927 self.data.extended_ram[(relative_addr & 0x03FF) as usize] 928 } 929 ExtendedRamMode::ReadWrite | ExtendedRamMode::ReadOnly => { 930 self.data.ppu_open_bus 931 } 932 }, 933 NametableMapping::FillMode => { 934 if relative_addr & 0x03FF < 0x03C0 { 935 // Nametable fetch 936 self.data.fill_mode_tile_data 937 } else { 938 // Attribute table fetch 939 self.data.fill_mode_attributes 940 } 941 } 942 } 943 } 944 0x3F00..=0xFFFF => panic!("invalid PPU map address: {address:04X}"), 945 }; 946 947 self.data.ppu_open_bus = value; 948 value 949 } 950 951 pub(crate) fn write_ppu_address(&mut self, address: u16, value: u8, vram: &mut [u8; 2048]) { 952 self.data.ppu_open_bus = value; 953 954 match address { 955 0x0000..=0x1FFF => {} 956 0x2000..=0x3EFF => { 957 let relative_addr = address & 0x0FFF; 958 959 let nametable_mapping = 960 self.data.nametable_mappings[(relative_addr >> 10) as usize]; 961 match nametable_mapping { 962 NametableMapping::VramPage0 => { 963 vram[(relative_addr & 0x03FF) as usize] = value; 964 } 965 NametableMapping::VramPage1 => { 966 vram[(0x0400 | (relative_addr & 0x03FF)) as usize] = value; 967 } 968 NametableMapping::ExtendedRam => { 969 self.data.extended_ram[(relative_addr & 0x03FF) as usize] = value; 970 } 971 NametableMapping::FillMode => {} 972 } 973 } 974 0x3F00..=0xFFFF => panic!("invalid PPU map address: {address:04X}"), 975 } 976 } 977 978 fn prg_ram_writes_enabled(&self) -> bool { 979 self.data.ram_writes_enabled_1 && self.data.ram_writes_enabled_2 980 } 981 982 pub(crate) fn interrupt_flag(&self) -> bool { 983 self.data.scanline_counter.interrupt_flag() || self.data.pcm_channel.irq_pending 984 } 985 986 pub(crate) fn tick_cpu(&mut self) { 987 self.data.scanline_counter.tick_cpu(); 988 989 self.data.pulse_channel_1.tick_cpu(); 990 self.data.pulse_channel_2.tick_cpu(); 991 self.data.frame_counter.tick(); 992 993 if self.data.frame_counter.generate_quarter_frame_clock() { 994 // MMC5 channels clock both length counter and envelope at 240Hz 995 self.data.pulse_channel_1.clock_quarter_frame(); 996 self.data.pulse_channel_1.clock_half_frame(); 997 998 self.data.pulse_channel_2.clock_quarter_frame(); 999 self.data.pulse_channel_2.clock_half_frame(); 1000 } 1001 } 1002 1003 pub(crate) fn sample_audio(&self, mixed_apu_sample: f64) -> f64 { 1004 let pulse1_sample = self.data.pulse_channel_1.sample(); 1005 let pulse2_sample = self.data.pulse_channel_2.sample(); 1006 let mmc5_pulse_mix = apu::mix_pulse_samples(pulse1_sample, pulse2_sample); 1007 1008 // Partial formula from from https://www.nesdev.org/wiki/APU_Mixer 1009 let pcm_sample = self.data.pcm_channel.output_level; 1010 let scaled_pcm_sample = if pcm_sample != 0 { 1011 159.79 / (1.0 / (f64::from(pcm_sample) / 22638.0) + 100.0) 1012 } else { 1013 0.0 1014 }; 1015 1016 mixed_apu_sample - mmc5_pulse_mix - scaled_pcm_sample 1017 } 1018}