mmc3.rsannotatedmmc3.rssource548 lines · 20.1 KB · raw

Code for the MMC3 and MMC6 boards (iNES mapper 4).

This module also contains code for some other boards that are extremely similar to MMC3:

  • Namco 108 (iNES mapper 206)
  • Namco 108 with 128KB CHR ROM (iNES mapper 88)
  • NAMCOT-3446 (iNES mapper 76)
  • NAMCOT-3453 (iNES mapper 154)
9use crate::bus::cartridge::MapperImpl;
10use crate::bus::cartridge::mappers::{BankSizeKb, ChrType, NametableMirroring, PpuMapResult};
11use bincode::{Decode, Encode};
12use jgenesis_common::num::GetBit;
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
15enum PrgMode {
16    Mode0,
17    Mode1,
18}
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
21enum ChrMode {
22    Mode0,
23    Mode1,
24}
25
26#[derive(Debug, Clone, Encode, Decode)]
27struct BankMapping {
28    prg_mode: PrgMode,
29    chr_mode: ChrMode,
30    prg_rom_len: u32,
31    chr_len: u32,
32    prg_bank_0: u8,
33    prg_bank_1: u8,
34    chr_banks: [u8; 6],
35}
36
37impl BankMapping {
38    fn new(prg_rom_len: u32, chr_len: u32) -> Self {
39        Self {
40            prg_mode: PrgMode::Mode0,
41            chr_mode: ChrMode::Mode0,
42            prg_rom_len,
43            chr_len,
44            prg_bank_0: 0,
45            prg_bank_1: 1,
46            chr_banks: [0; 6],
47        }
48    }
49
50    fn map_prg_rom_address(&self, address: u16) -> u32 {
51        match (self.prg_mode, address) {
52            (_, 0x0000..=0x7FFF) => panic!("invalid MMC3 PRG ROM address: 0x{address:04X}"),
53            (PrgMode::Mode0, 0x8000..=0x9FFF) | (PrgMode::Mode1, 0xC000..=0xDFFF) => {
54                BankSizeKb::Eight.to_absolute_address(self.prg_bank_0, address)
55            }
56            (_, 0xA000..=0xBFFF) => BankSizeKb::Eight.to_absolute_address(self.prg_bank_1, address),
57            (PrgMode::Mode0, 0xC000..=0xDFFF) | (PrgMode::Mode1, 0x8000..=0x9FFF) => {
58                // Fixed at second-to-last bank
59                BankSizeKb::Eight.to_absolute_address_from_end(2_u32, self.prg_rom_len, address)
60            }
61            (_, 0xE000..=0xFFFF) => {
62                // Fixed at last bank
63                BankSizeKb::Eight.to_absolute_address_last_bank(self.prg_rom_len, address)
64            }
65        }
66    }
67
68    fn map_pattern_table_address(&self, address: u16) -> u32 {
69        let mapped_address = match (self.chr_mode, address) {
70            // 2KB banks are treated as 1KB bank numbers while ignoring the lowest bit
71            (ChrMode::Mode0, 0x0000..=0x07FF) | (ChrMode::Mode1, 0x1000..=0x17FF) => {
72                BankSizeKb::Two.to_absolute_address(self.chr_banks[0] >> 1, address)
73            }
74            (ChrMode::Mode0, 0x0800..=0x0FFF) | (ChrMode::Mode1, 0x1800..=0x1FFF) => {
75                BankSizeKb::Two.to_absolute_address(self.chr_banks[1] >> 1, address)
76            }
77            (ChrMode::Mode0, 0x1000..=0x13FF) | (ChrMode::Mode1, 0x0000..=0x03FF) => {
78                BankSizeKb::One.to_absolute_address(self.chr_banks[2], address)
79            }
80            (ChrMode::Mode0, 0x1400..=0x17FF) | (ChrMode::Mode1, 0x0400..=0x07FF) => {
81                BankSizeKb::One.to_absolute_address(self.chr_banks[3], address)
82            }
83            (ChrMode::Mode0, 0x1800..=0x1BFF) | (ChrMode::Mode1, 0x0800..=0x0BFF) => {
84                BankSizeKb::One.to_absolute_address(self.chr_banks[4], address)
85            }
86            (ChrMode::Mode0, 0x1C00..=0x1FFF) | (ChrMode::Mode1, 0x0C00..=0x0FFF) => {
87                BankSizeKb::One.to_absolute_address(self.chr_banks[5], address)
88            }
89            (_, 0x2000..=0xFFFF) => {
90                panic!("invalid MMC3 CHR pattern table address: 0x{address:04X}")
91            }
92        };
93        mapped_address & (self.chr_len - 1)
94    }
95}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
98enum BankUpdate {
99    PrgBank0,
100    PrgBank1,
101    ChrBank(u8),
102}
103
104#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
105enum Variant {
106    Mmc3,
107    Mmc6,
108    McAcc,
109    Namco108,
110    Namco108LargeChr,
111    Namcot3425,
112    Namcot3446,
113    Namcot3453,
114}
115
116impl Variant {
117    fn name(self) -> &'static str {
118        match self {
119            Self::Mmc3 => "MMC3",
120            Self::Mmc6 => "MMC6",
121            Self::McAcc => "MMC3 (MC-ACC variant)",
122            Self::Namco108 | Self::Namco108LargeChr => "Namco 108",
123            Self::Namcot3425 => "NAMCOT-3425",
124            Self::Namcot3446 => "NAMCOT-3446",
125            Self::Namcot3453 => "NAMCOT-3453",
126        }
127    }
128
129    fn is_namco_variant(self) -> bool {
130        matches!(
131            self,
132            Self::Namco108
133                | Self::Namco108LargeChr
134                | Self::Namcot3425
135                | Self::Namcot3446
136                | Self::Namcot3453
137        )
138    }
139}
140
141#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
142enum RamMode {
143    Mmc3Enabled,
144    Mmc3WritesDisabled,
145    Mmc6Enabled {
146        first_half_reads: bool,
147        first_half_writes: bool,
148        second_half_reads: bool,
149        second_half_writes: bool,
150    },
151    Disabled,
152}
153
154impl RamMode {
155    fn reads_enabled(self, address: u16) -> bool {
156        match self {
157            Self::Mmc3Enabled | Self::Mmc3WritesDisabled => true,
158            Self::Mmc6Enabled { first_half_reads, second_half_reads, .. } => {
159                if address.bit(9) {
160                    second_half_reads
161                } else {
162                    first_half_reads
163                }
164            }
165            Self::Disabled => false,
166        }
167    }
168
169    fn writes_enabled(self, address: u16) -> bool {
170        match self {
171            Self::Mmc3Enabled => true,
172            Self::Mmc6Enabled { first_half_writes, second_half_writes, .. } => {
173                if address.bit(9) {
174                    second_half_writes
175                } else {
176                    first_half_writes
177                }
178            }
179            Self::Disabled | Self::Mmc3WritesDisabled => false,
180        }
181    }
182}
183
184#[derive(Debug, Clone, Encode, Decode)]
185enum Mmc3NametableMirroring {
186    Standard(NametableMirroring),
187    FourScreenVram { external_vram: Box<[u8; 4096]> },
188}
189
190#[derive(Debug, Clone, Encode, Decode)]
191pub(crate) struct Mmc3 {
192    variant: Variant,
193    chr_type: ChrType,
194    bank_mapping: BankMapping,
195    nametable_mirroring: Mmc3NametableMirroring,
196    bank_update_select: BankUpdate,
197    ram_mode: RamMode,
198    interrupt_flag: bool,
199    irq_counter: u8,
200    irq_reload_value: u8,
201    irq_reload_flag: bool,
202    irq_enabled: bool,
203    last_a12_read: bool,
204    a12_low_cycles: u32,
205    mc_acc_pulse_counter: u8,
206}
207
208const ACC_COUNTER_INIT_VALUE: u8 = 6;
209
210impl Mmc3 {
211    pub(crate) fn new(
212        chr_type: ChrType,
213        prg_rom_len: u32,
214        chr_size: u32,
215        mapper_number: u16,
216        sub_mapper_number: u8,
217        nametable_mirroring: NametableMirroring,
218        has_four_screen_vram: bool,
219    ) -> Self {
220        let variant = match (mapper_number, sub_mapper_number) {
221            (4, 1) => Variant::Mmc6,
222            (4, 3) => Variant::McAcc,
223            (4, _) => Variant::Mmc3,
224            (76, _) => Variant::Namcot3446,
225            (88, _) => Variant::Namco108LargeChr,
226            (95, _) => Variant::Namcot3425,
227            (154, _) => Variant::Namcot3453,
228            (206, _) => Variant::Namco108,
229            _ => panic!("invalid MMC3 mapper number: {mapper_number}"),
230        };
231        Self {
232            variant,
233            chr_type,
234            bank_mapping: BankMapping::new(prg_rom_len, chr_size),
235            nametable_mirroring: if has_four_screen_vram {
236                Mmc3NametableMirroring::FourScreenVram { external_vram: Box::new([0; 4096]) }
237            } else if variant == Variant::Namcot3453 {
238                Mmc3NametableMirroring::Standard(NametableMirroring::SingleScreenBank0)
239            } else if variant.is_namco_variant() {
240                Mmc3NametableMirroring::Standard(nametable_mirroring)
241            } else {
242                Mmc3NametableMirroring::Standard(NametableMirroring::Vertical)
243            },
244            bank_update_select: BankUpdate::ChrBank(0),
245            ram_mode: RamMode::Disabled,
246            interrupt_flag: false,
247            irq_counter: 0,
248            irq_reload_value: 0,
249            irq_reload_flag: false,
250            irq_enabled: false,
251            last_a12_read: false,
252            a12_low_cycles: 0,
253            mc_acc_pulse_counter: ACC_COUNTER_INIT_VALUE,
254        }
255    }
256}
257
258impl MapperImpl<Mmc3> {
259    pub(crate) fn read_cpu_address(&self, address: u16, cpu_open_bus: u8) -> u8 {
260        match address {
261            0x0000..=0x401F => panic!("invalid CPU map address: 0x{address:04X}"),
262            0x4020..=0x5FFF => cpu_open_bus,
263            0x6000..=0x7FFF => {
264                if self.data.ram_mode.reads_enabled(address) && !self.cartridge.prg_ram.is_empty() {
265                    self.cartridge.get_prg_ram(u32::from(address & 0x1FFF))
266                } else {
267                    cpu_open_bus
268                }
269            }
270            0x8000..=0xFFFF => {
271                self.cartridge.get_prg_rom(self.data.bank_mapping.map_prg_rom_address(address))
272            }
273        }
274    }
275
276    pub(crate) fn write_cpu_address(&mut self, address: u16, value: u8) {
277        match address {
278            0x0000..=0x401F => panic!("invalid CPU map address: 0x{address:04X}"),
279            0x4020..=0x5FFF => {}
280            0x6000..=0x7FFF => {
281                if self.data.ram_mode.writes_enabled(address) && !self.cartridge.prg_ram.is_empty()
282                {
283                    self.cartridge.set_prg_ram(u32::from(address & 0x1FFF), value);
284                }
285            }
286            0x8000..=0x9FFF => {
287                if !address.bit(0) {
288                    if !self.data.variant.is_namco_variant() {
289                        self.data.bank_mapping.chr_mode =
290                            if value.bit(7) { ChrMode::Mode1 } else { ChrMode::Mode0 };
291                        self.data.bank_mapping.prg_mode =
292                            if value.bit(6) { PrgMode::Mode1 } else { PrgMode::Mode0 };
293                    }
294
295                    self.data.bank_update_select = match value & 0x07 {
296                        masked_value @ 0x00..=0x05 => BankUpdate::ChrBank(masked_value),
297                        0x06 => BankUpdate::PrgBank0,
298                        0x07 => BankUpdate::PrgBank1,
299                        _ => unreachable!(
300                            "masking with 0x07 should always be in the range 0x00..=0x07"
301                        ),
302                    };
303
304                    if self.data.variant == Variant::Mmc6 {
305                        let ram_enabled = value.bit(5);
306                        if !ram_enabled {
307                            self.data.ram_mode = RamMode::Disabled;
308                        } else if ram_enabled && self.data.ram_mode == RamMode::Disabled {
309                            self.data.ram_mode = RamMode::Mmc6Enabled {
310                                first_half_reads: false,
311                                first_half_writes: false,
312                                second_half_reads: false,
313                                second_half_writes: false,
314                            };
315                        }
316                    }
317                } else {
318                    match self.data.bank_update_select {
319                        BankUpdate::ChrBank(chr_bank) => {
320                            self.data.bank_mapping.chr_banks[chr_bank as usize] = value;
321                        }
322                        BankUpdate::PrgBank0 => {
323                            self.data.bank_mapping.prg_bank_0 = value;
324                        }
325                        BankUpdate::PrgBank1 => {
326                            self.data.bank_mapping.prg_bank_1 = value;
327                        }
328                    }
329                }
330            }
331            0xA000..=0xBFFF => {
332                if !address.bit(0)
333                    && !self.data.variant.is_namco_variant()
334                    && matches!(self.data.nametable_mirroring, Mmc3NametableMirroring::Standard(..))
335                {
336                    let nametable_mirroring = if value.bit(0) {
337                        NametableMirroring::Horizontal
338                    } else {
339                        NametableMirroring::Vertical
340                    };
341                    self.data.nametable_mirroring =
342                        Mmc3NametableMirroring::Standard(nametable_mirroring);
343                } else if address.bit(0) {
344                    match self.data.variant {
345                        Variant::Mmc6 => {
346                            self.data.ram_mode = if self.data.ram_mode == RamMode::Disabled {
347                                // $A001 writes are ignored if RAM is disabled via $8000
348                                RamMode::Disabled
349                            } else {
350                                let first_half_writes = value.bit(4);
351                                let first_half_reads = value.bit(5);
352                                let second_half_writes = value.bit(6);
353                                let second_half_reads = value.bit(7);
354                                RamMode::Mmc6Enabled {
355                                    first_half_reads,
356                                    first_half_writes,
357                                    second_half_reads,
358                                    second_half_writes,
359                                }
360                            };
361                        }
362                        Variant::Mmc3 | Variant::McAcc => {
363                            self.data.ram_mode = if !value.bit(7) {
364                                RamMode::Disabled
365                            } else if value.bit(6) {
366                                RamMode::Mmc3WritesDisabled
367                            } else {
368                                RamMode::Mmc3Enabled
369                            };
370                        }
371                        Variant::Namco108
372                        | Variant::Namco108LargeChr
373                        | Variant::Namcot3425
374                        | Variant::Namcot3446
375                        | Variant::Namcot3453 => {}
376                    }
377                }
378            }
379            0xC000..=0xDFFF => {
380                if !address.bit(0) {
381                    self.data.irq_reload_value = value;
382                } else {
383                    self.data.irq_reload_flag = true;
384                    self.data.mc_acc_pulse_counter = ACC_COUNTER_INIT_VALUE;
385                }
386            }
387            0xE000..=0xFFFF => {
388                if !address.bit(0) {
389                    self.data.irq_enabled = false;
390                    self.data.interrupt_flag = false;
391                } else {
392                    self.data.irq_enabled = true;
393                }
394            }
395        }
396
397        if self.data.variant == Variant::Namcot3453 && (0x8000..=0xFFFF).contains(&address) {
398            self.data.nametable_mirroring = if value.bit(6) {
399                Mmc3NametableMirroring::Standard(NametableMirroring::SingleScreenBank0)
400            } else {
401                Mmc3NametableMirroring::Standard(NametableMirroring::SingleScreenBank1)
402            };
403        }
404    }
405
406    fn clock_irq(&mut self) {
407        log::trace!(
408            "IRQ clocked; counter={}, reload_flag={}, reload_value={}",
409            self.data.irq_counter,
410            self.data.irq_reload_flag,
411            self.data.irq_reload_value
412        );
413
414        if self.data.irq_counter == 0 || self.data.irq_reload_flag {
415            self.data.irq_counter = self.data.irq_reload_value;
416            self.data.irq_reload_flag = false;
417        } else {
418            self.data.irq_counter -= 1;
419        }
420
421        if self.data.irq_counter == 0 && self.data.irq_enabled {
422            self.data.interrupt_flag = true;
423        }
424    }
425
426    fn process_ppu_address(&mut self, address: u16) {
427        log::trace!("PPU bus address: {address:04X}");
428
429        let a12 = address.bit(12);
430
431        match self.data.variant {
432            Variant::Mmc3 | Variant::Mmc6 => {
433                if a12 && !self.data.last_a12_read && self.data.a12_low_cycles >= 10 {
434                    self.clock_irq();
435                }
436            }
437            Variant::McAcc => {
438                if !a12 && self.data.last_a12_read {
439                    self.data.mc_acc_pulse_counter += 1;
440                    if self.data.mc_acc_pulse_counter == 8 {
441                        self.clock_irq();
442                        self.data.mc_acc_pulse_counter = 0;
443                    }
444                }
445            }
446            Variant::Namco108
447            | Variant::Namco108LargeChr
448            | Variant::Namcot3425
449            | Variant::Namcot3446
450            | Variant::Namcot3453 => {}
451        }
452
453        self.data.last_a12_read = a12;
454    }
455
456    fn map_pattern_table_address(&self, address: u16) -> PpuMapResult {
457        match self.data.variant {
458            Variant::Namco108LargeChr | Variant::Namcot3453 => {
459                let chr_outer_bank = address.bit(12);
460                let chr_addr = (self.data.bank_mapping.map_pattern_table_address(address)
461                    & 0x0000FFFF)
462                    | (u32::from(chr_outer_bank) << 16);
463                self.data.chr_type.to_map_result(chr_addr)
464            }
465            Variant::Namcot3446 => {
466                let bank_index = address / 0x0800 + 2;
467                let bank_number = self.data.bank_mapping.chr_banks[bank_index as usize];
468                let chr_addr = BankSizeKb::Two.to_absolute_address(bank_number, address);
469                self.data.chr_type.to_map_result(chr_addr)
470            }
471            _ => self
472                .data
473                .chr_type
474                .to_map_result(self.data.bank_mapping.map_pattern_table_address(address)),
475        }
476    }
477
478    fn map_namcot_3425_nametable_addr(&self, address: u16) -> u32 {
479        let bank_index = (address & 0x0FFF) / 0x0800;
480        let bank_number = self.data.bank_mapping.chr_banks[bank_index as usize];
481        let vram_bank = bank_number.bit(5);
482        (u32::from(vram_bank) << 10) | u32::from(address & 0x03FF)
483    }
484
485    pub(crate) fn read_ppu_address(&mut self, address: u16, vram: &[u8; 2048]) -> u8 {
486        match address & 0x3FFF {
487            0x0000..=0x1FFF => self.map_pattern_table_address(address).read(&self.cartridge, vram),
488            0x2000..=0x3EFF => match self.data.variant {
489                Variant::Namcot3425 => {
490                    let vram_addr = self.map_namcot_3425_nametable_addr(address);
491                    vram[vram_addr as usize]
492                }
493                _ => match &self.data.nametable_mirroring {
494                    Mmc3NametableMirroring::Standard(nametable_mirroring) => {
495                        vram[nametable_mirroring.map_to_vram(address) as usize]
496                    }
497                    Mmc3NametableMirroring::FourScreenVram { external_vram } => {
498                        external_vram[(address & 0x0FFF) as usize]
499                    }
500                },
501            },
502            0x3F00..=0xFFFF => panic!("invalid PPU map address: 0x{address:04X}"),
503        }
504    }
505
506    pub(crate) fn write_ppu_address(&mut self, address: u16, value: u8, vram: &mut [u8; 2048]) {
507        self.process_ppu_address(address);
508
509        match address & 0x3FFF {
510            0x0000..=0x1FFF => {
511                self.map_pattern_table_address(address).write(value, &mut self.cartridge, vram);
512            }
513            0x2000..=0x3EFF => match self.data.variant {
514                Variant::Namcot3425 => {
515                    let vram_addr = self.map_namcot_3425_nametable_addr(address);
516                    vram[vram_addr as usize] = value;
517                }
518                _ => match &mut self.data.nametable_mirroring {
519                    Mmc3NametableMirroring::Standard(nametable_mirroring) => {
520                        vram[nametable_mirroring.map_to_vram(address) as usize] = value;
521                    }
522                    Mmc3NametableMirroring::FourScreenVram { external_vram } => {
523                        external_vram[(address & 0x0FFF) as usize] = value;
524                    }
525                },
526            },
527            0x3F00..=0xFFFF => panic!("invalid PPU map address: 0x{address:04X}"),
528        }
529    }
530
531    pub(crate) fn interrupt_flag(&self) -> bool {
532        self.data.interrupt_flag
533    }
534
535    pub(crate) fn tick(&mut self, ppu_bus_address: u16) {
536        self.process_ppu_address(ppu_bus_address);
537
538        if !self.data.last_a12_read {
539            self.data.a12_low_cycles += 1;
540        } else {
541            self.data.a12_low_cycles = 0;
542        }
543    }
544
545    pub(crate) fn name(&self) -> &'static str {
546        self.data.variant.name()
547    }
548}