nes_core/mapper/
m005_exrom.rs

1//! `ExROM`/`MMC5` (Mapper 5)
2//!
3//! <https://wiki.nesdev.com/w/index.php/ExROM>
4//! <https://wiki.nesdev.com/w/index.php/MMC5>
5
6use alloc::vec;
7use alloc::vec::Vec;
8use crate::{
9    apu::{
10        dmc::Dmc,
11        pulse::{OutputFreq, Pulse, PulseChannel},
12        PULSE_TABLE,
13    },
14    audio::Audio,
15    cart::Cart,
16    common::{Clock, ResetKind, NesRegion, Regional, Reset},
17    cpu::Cpu,
18    mapper::{Mapped, MappedRead, MappedWrite, Mapper, MemMap},
19    mem::MemBanks,
20    ppu::{bus::PpuAddr, Mirroring, Ppu},
21};
22use bitflags::bitflags;
23use serde::{Deserialize, Serialize};
24
25#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
26#[must_use]
27enum PrgMode {
28    Bank32k,
29    Bank16k,
30    Bank16_8k,
31    Bank8k,
32}
33
34#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
35#[must_use]
36enum ChrMode {
37    Bank8k,
38    Bank4k,
39    Bank2k,
40    Bank1k,
41}
42
43#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
44#[must_use]
45enum ChrBank {
46    Spr,
47    Bg,
48}
49
50bitflags! {
51    #[derive(Default, Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq)]
52    #[must_use]
53    struct ExRamRW: u8 {
54        const W = 0x01;
55        const R = 0x02;
56        const RW = Self::R.bits() | Self::W.bits();
57    }
58}
59
60#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
61#[must_use]
62struct ExRamMode {
63    bits: u8,
64    nametable: bool,
65    attr: bool,
66    rw: ExRamRW,
67}
68
69impl ExRamMode {
70    const fn new() -> Self {
71        Self {
72            bits: 0x00,
73            nametable: false,
74            attr: false,
75            rw: ExRamRW::W,
76        }
77    }
78
79    fn set(&mut self, val: u8) {
80        let val = val & 0x03;
81        self.bits = val;
82        self.nametable = matches!(val, 0b00 | 0b01);
83        self.attr = val == 0b01;
84        self.rw = match val {
85            0b00 | 0b01 => ExRamRW::W,
86            0b10 => ExRamRW::RW,
87            0b11 => ExRamRW::R,
88            _ => unreachable!("invalid exram_mode"),
89        };
90    }
91}
92
93#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
94#[must_use]
95enum Nametable {
96    ScreenA,
97    ScreenB,
98    ExRam,
99    Fill,
100}
101
102#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
103#[must_use]
104struct NametableMapping {
105    mode: u8,
106    select: [Nametable; 4],
107}
108
109impl NametableMapping {
110    const fn new() -> Self {
111        Self {
112            mode: 0x00,
113            select: [Nametable::ScreenA; 4],
114        }
115    }
116
117    fn set(&mut self, val: u8) {
118        let nametable = |val: u8| match val & 0x03 {
119            0 => Nametable::ScreenA,
120            1 => Nametable::ScreenB,
121            2 => Nametable::ExRam,
122            3 => Nametable::Fill,
123            _ => unreachable!("invalid Nametable value"),
124        };
125        self.mode = val;
126        self.select = [
127            nametable(val),
128            nametable(val >> 2),
129            nametable(val >> 4),
130            nametable(val >> 6),
131        ];
132    }
133}
134
135#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
136#[must_use]
137struct Fill {
138    tile: u8,    // $5106
139    attr: usize, // $5107
140}
141
142impl Fill {
143    const fn new() -> Self {
144        Self {
145            attr: 0x03,
146            tile: 0xFF,
147        }
148    }
149}
150
151#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
152#[must_use]
153enum Side {
154    Left,
155    Right,
156}
157
158#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
159#[must_use]
160struct VSplit {
161    mode: u8,      // $5200 [ES.T TTTT]
162    enabled: bool, // $5200 [E... ....]
163    side: Side,    // $5200 [.S.. ....]
164    tile: u8,      // $5200 [...T TTTT]
165    scroll: u8,    // $5201
166    bank: u8,      // $5202
167    in_region: bool,
168}
169
170impl VSplit {
171    const fn new() -> Self {
172        Self {
173            mode: 0x00,
174            enabled: false,
175            side: Side::Left,
176            tile: 0x00,
177            scroll: 0x00,
178            bank: 0x00,
179            in_region: false,
180        }
181    }
182}
183
184#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
185#[must_use]
186struct ExRegs {
187    prg_mode: PrgMode,                   // $5100
188    chr_mode: ChrMode,                   // $5101
189    prg_ram_protect: [u8; 2],            // $5102 - $5103
190    exram_mode: ExRamMode,               // $5104
191    nametable_mapping: NametableMapping, // $5105
192    fill: Fill,                          // $5106 - $5107
193    prg_banks: [usize; 5],               // $5113 - $5117
194    chr_banks: [usize; 16],              // $5120 - $512B
195    chr_hi: usize,                       // $5130
196    vsplit: VSplit,                      // $5200 - $5202
197    irq_scanline: u16,                   // $5203: Write $00 to disable IRQs
198    irq_enabled: bool,                   // $5204
199    multiplicand: u8,                    // $5205: write
200    multiplier: u8,                      // $5206: write
201    mult_result: u16,                    // $5205: read lo, $5206: read hi
202}
203
204impl ExRegs {
205    const fn new() -> Self {
206        Self {
207            prg_mode: PrgMode::Bank8k,
208            chr_mode: ChrMode::Bank1k,
209            prg_ram_protect: [0x00; 2],
210            exram_mode: ExRamMode::new(),
211            nametable_mapping: NametableMapping::new(),
212            fill: Fill::new(),
213            prg_banks: [0x00; 5],
214            chr_banks: [0x00; 16],
215            chr_hi: 0x00,
216            vsplit: VSplit::new(),
217            irq_scanline: 0x00,
218            irq_enabled: false,
219            multiplicand: 0xFF,
220            multiplier: 0xFF,
221            mult_result: 0xFE01, // e.g. 0xFF * 0xFF
222        }
223    }
224}
225
226#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
227#[must_use]
228struct PpuStatus {
229    fetch_count: u32,
230    prev_addr: u16,
231    prev_match: u8,
232    reading: bool,
233    idle: u8,
234    sprite8x16: bool, // $2000 PPUCTRL: false = 8x8, true = 8x16
235    rendering: bool,
236    scanline: u16,
237    in_frame: bool,
238}
239
240impl PpuStatus {
241    fn write(&mut self, addr: u16, val: u8) {
242        match addr {
243            0x2000 => self.sprite8x16 = val & 0x20 > 0,
244            0x2001 => {
245                self.rendering = val & 0x18 > 0; // 1, 2, or 3
246                if !self.rendering {
247                    self.in_frame = false;
248                    self.prev_addr = 0x0000;
249                }
250            }
251            _ => (),
252        }
253    }
254}
255
256#[derive(Clone, Serialize, Deserialize)]
257#[must_use]
258pub struct Exrom {
259    regs: ExRegs,
260    mirroring: Mirroring,
261    irq_pending: bool,
262    ppu_status: PpuStatus,
263    exram: Vec<u8>,
264    prg_ram_banks: MemBanks,
265    prg_rom_banks: MemBanks,
266    chr_banks: MemBanks,
267    tile_cache: usize,
268    last_chr_write: ChrBank,
269    region: NesRegion,
270    pulse1: Pulse,
271    pulse2: Pulse,
272    dmc: Dmc,
273    dmc_mode: u8,
274    cpu_cycle: usize,
275    pulse_timer: f32,
276}
277
278impl Exrom {
279    const PRG_WINDOW: usize = 0x2000;
280    const PRG_RAM_SIZE: usize = 0x10000; // Provide 64K since mappers don't always specify
281    const EXRAM_SIZE: usize = 0x0400;
282    const CHR_WINDOW: usize = 0x0400;
283
284    const ROM_SELECT_MASK: usize = 0x80; // High bit targets ROM bank switching
285    const BANK_MASK: usize = 0x7F; // Ignore high bit for ROM select
286
287    const SPR_FETCH_START: u32 = 64;
288    const SPR_FETCH_END: u32 = 81;
289
290    // This conveniently mirrors a 2-bit palette attribute to all four indexes
291    // https://www.nesdev.org/wiki/MMC5#Fill-mode_color_($5107)
292    const ATTR_MIRROR: [u8; 4] = [0x00, 0x55, 0xAA, 0xFF];
293
294    // // TODO: See about generating these using oncecell
295    // const ATTR_LOC: [u8; 256] = [
296    //     0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
297    //     0x07, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05,
298    //     0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x08, 0x09, 0x0A, 0x0B, 0x0C,
299    //     0x0D, 0x0E, 0x0F, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x08, 0x09, 0x0A, 0x0B,
300    //     0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x10, 0x11, 0x12,
301    //     0x13, 0x14, 0x15, 0x16, 0x17, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x10, 0x11,
302    //     0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x18,
303    //     0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
304    //     0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
305    //     0x27, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25,
306    //     0x26, 0x27, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C,
307    //     0x2D, 0x2E, 0x2F, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x28, 0x29, 0x2A, 0x2B,
308    //     0x2C, 0x2D, 0x2E, 0x2F, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32,
309    //     0x33, 0x34, 0x35, 0x36, 0x37, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x30, 0x31,
310    //     0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
311    //     0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
312    //     0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E,
313    //     0x3F,
314    // ];
315    // const ATTR_SHIFT: [u8; 128] = [
316    //     0, 0, 2, 2, 0, 0, 2, 2, 0, 0, 2, 2, 0, 0, 2, 2, 0, 0, 2, 2, 0, 0, 2, 2, 0, 0, 2, 2, 0, 0,
317    //     2, 2, 0, 0, 2, 2, 0, 0, 2, 2, 0, 0, 2, 2, 0, 0, 2, 2, 0, 0, 2, 2, 0, 0, 2, 2, 0, 0, 2, 2,
318    //     0, 0, 2, 2, 4, 4, 6, 6, 4, 4, 6, 6, 4, 4, 6, 6, 4, 4, 6, 6, 4, 4, 6, 6, 4, 4, 6, 6, 4, 4,
319    //     6, 6, 4, 4, 6, 6, 4, 4, 6, 6, 4, 4, 6, 6, 4, 4, 6, 6, 4, 4, 6, 6, 4, 4, 6, 6, 4, 4, 6, 6,
320    //     4, 4, 6, 6, 4, 4, 6, 6,
321    // ];
322
323    pub fn load(cart: &mut Cart) -> Mapper {
324        cart.add_prg_ram(Self::PRG_RAM_SIZE);
325
326        let mut exrom = Self {
327            regs: ExRegs::new(),
328            mirroring: cart.mirroring(),
329            irq_pending: false,
330            ppu_status: PpuStatus {
331                fetch_count: 0x00,
332                prev_addr: 0xFFFF,
333                prev_match: 0x0000,
334                reading: false,
335                idle: 0x00,
336                sprite8x16: false,
337                rendering: false,
338                scanline: 0x0000,
339                in_frame: false,
340            },
341            exram: vec![0x00; Self::EXRAM_SIZE],
342            prg_ram_banks: MemBanks::new(0x6000, 0xFFFF, cart.prg_ram.len(), Self::PRG_WINDOW),
343            prg_rom_banks: MemBanks::new(0x8000, 0xFFFF, cart.prg_rom.len(), Self::PRG_WINDOW),
344            chr_banks: MemBanks::new(0x0000, 0x1FFF, cart.chr_rom.len(), Self::CHR_WINDOW),
345            tile_cache: 0,
346            last_chr_write: ChrBank::Spr,
347            region: NesRegion::default(),
348            pulse1: Pulse::new(PulseChannel::One, OutputFreq::Ultrasonic),
349            pulse2: Pulse::new(PulseChannel::Two, OutputFreq::Ultrasonic),
350            dmc: Dmc::new(),
351            dmc_mode: 0x01, // Default to read mode
352            cpu_cycle: 0,
353            pulse_timer: 0.0,
354        };
355        exrom.regs.prg_banks[4] = exrom.prg_rom_banks.last() | Self::ROM_SELECT_MASK;
356        exrom.update_prg_banks();
357        exrom.into()
358    }
359
360    //              $6000   $8000   $A000   $C000   $E000
361    //            +-------+-------------------------------+
362    // P=%00:     | $5113 |           <<$5117>>           |
363    //            +-------+-------------------------------+
364    // P=%01:     | $5113 |    <$5115>    |    <$5117>    |
365    //            +-------+---------------+-------+-------+
366    // P=%10:     | $5113 |    <$5115>    | $5116 | $5117 |
367    //            +-------+---------------+-------+-------+
368    // P=%11:     | $5113 | $5114 | $5115 | $5116 | $5117 |
369    //            +-------+-------+-------+-------+-------+
370    fn update_prg_banks(&mut self) {
371        let mode = self.regs.prg_mode;
372        let banks = self.regs.prg_banks;
373
374        self.prg_ram_banks.set(0, banks[0]); // $5113 always selects RAM
375        match mode {
376            // $5117 always selects ROM
377            PrgMode::Bank32k => self.prg_rom_banks.set_range(0, 3, banks[4]),
378            PrgMode::Bank16k => {
379                self.set_prg_bank_range(0, 1, banks[2]);
380                self.prg_rom_banks
381                    .set_range(2, 3, banks[4] & Self::BANK_MASK);
382            }
383            PrgMode::Bank16_8k => {
384                self.set_prg_bank_range(0, 1, banks[2]);
385                self.set_prg_bank_range(2, 2, banks[3]);
386                self.prg_rom_banks.set(3, banks[4] & Self::BANK_MASK);
387            }
388            PrgMode::Bank8k => {
389                self.set_prg_bank_range(0, 0, banks[1]);
390                self.set_prg_bank_range(1, 1, banks[2]);
391                self.set_prg_bank_range(2, 2, banks[3]);
392                self.prg_rom_banks.set(3, banks[4] & Self::BANK_MASK);
393            }
394        };
395    }
396
397    fn set_prg_bank_range(&mut self, start: usize, end: usize, bank: usize) {
398        let rom = bank & Self::ROM_SELECT_MASK == Self::ROM_SELECT_MASK;
399        let bank = bank & Self::BANK_MASK;
400        if rom {
401            self.prg_rom_banks.set_range(start, end, bank);
402        } else {
403            self.prg_ram_banks.set_range(start + 1, end + 1, bank);
404        }
405    }
406
407    fn rom_select(&self, addr: u16) -> bool {
408        let mode = self.regs.prg_mode;
409        if matches!(addr, 0x6000..=0x7FFF) {
410            false
411        } else if matches!(addr, 0xE000..=0xFFFF) || mode == PrgMode::Bank32k {
412            true
413        } else {
414            use PrgMode::{Bank16_8k, Bank16k, Bank8k};
415            let banks = self.regs.prg_banks;
416            let bank = match (addr, mode) {
417                (0x8000..=0x9FFF, Bank8k) => banks[1],
418                (0x8000..=0xBFFF, Bank16k | Bank16_8k) | (0xA000..=0xBFFF, Bank8k) => banks[2],
419                (0xC000..=0xDFFF, Bank8k | Bank16_8k) => banks[3],
420                (0xC000..=0xDFFF, Bank16k) => banks[4],
421                _ => 0x00,
422            };
423            bank & Self::ROM_SELECT_MASK == Self::ROM_SELECT_MASK
424        }
425    }
426
427    // 'A' Set (Sprites):
428    //               $0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00
429    //             +---------------------------------------------------------------+
430    //   C=%00:    |                             $5127                             |
431    //             +---------------------------------------------------------------+
432    //   C=%01:    |             $5123             |             $5127             |
433    //             +-------------------------------+-------------------------------+
434    //   C=%10:    |     $5121     |     $5123     |     $5125     |     $5127     |
435    //             +---------------+---------------+---------------+---------------+
436    //   C=%11:    | $5120 | $5121 | $5122 | $5123 | $5124 | $5125 | $5126 | $5127 |
437    //             +-------+-------+-------+-------+-------+-------+-------+-------+
438    //
439    // 'B' Set (BG):
440    //               $0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00
441    //             +-------------------------------+-------------------------------+
442    //   C=%00:    |                             $512B                             |
443    //             +-------------------------------+-------------------------------+
444    //   C=%01:    |             $512B             |             $512B             |
445    //             +-------------------------------+-------------------------------+
446    //   C=%10:    |     $5129     |     $512B     |     $5129     |     $512B     |
447    //             +---------------+---------------+---------------+---------------+
448    //   C=%11:    | $5128 | $5129 | $512A | $512B | $5128 | $5129 | $512A | $512B |
449    //             +-------+-------+-------+-------+-------+-------+-------+-------+
450    fn update_chr_banks(&mut self, chr_bank: ChrBank) {
451        let hi = self.regs.chr_hi;
452        let banks = match chr_bank {
453            ChrBank::Spr => &self.regs.chr_banks[0..8],
454            ChrBank::Bg => &self.regs.chr_banks[8..16],
455        };
456        // CHR banks are in actual page sizes which means they need to be shifted appropriately
457        match self.regs.chr_mode {
458            ChrMode::Bank8k => self.chr_banks.set_range(0, 7, hi | banks[7] << 3),
459            ChrMode::Bank4k => {
460                self.chr_banks.set_range(0, 3, hi | banks[3] << 2);
461                self.chr_banks.set_range(4, 7, hi | banks[7] << 2);
462            }
463            ChrMode::Bank2k => {
464                self.chr_banks.set_range(0, 1, hi | banks[1] << 1);
465                self.chr_banks.set_range(2, 3, hi | banks[3] << 1);
466                self.chr_banks.set_range(4, 5, hi | banks[5] << 1);
467                self.chr_banks.set_range(6, 7, hi | banks[7] << 1);
468            }
469            ChrMode::Bank1k => {
470                self.chr_banks.set(0, hi | banks[0]);
471                self.chr_banks.set(1, hi | banks[1]);
472                self.chr_banks.set(2, hi | banks[2]);
473                self.chr_banks.set(3, hi | banks[3]);
474                self.chr_banks.set(4, hi | banks[4]);
475                self.chr_banks.set(5, hi | banks[5]);
476                self.chr_banks.set(6, hi | banks[6]);
477                self.chr_banks.set(7, hi | banks[7]);
478            }
479        };
480    }
481
482    #[inline]
483    fn read_exram(&self, addr: u16) -> u8 {
484        self.exram[(addr & 0x03FF) as usize]
485    }
486
487    #[inline]
488    fn write_exram(&mut self, addr: u16, val: u8) {
489        self.exram[(addr & 0x03FF) as usize] = val;
490    }
491
492    #[inline]
493    fn inc_fetch_count(&mut self) {
494        self.ppu_status.fetch_count += 1;
495    }
496
497    #[inline]
498    const fn fetch_count(&self) -> u32 {
499        self.ppu_status.fetch_count
500    }
501
502    #[inline]
503    const fn sprite8x16(&self) -> bool {
504        self.ppu_status.sprite8x16
505    }
506
507    #[inline]
508    fn spr_fetch(&self) -> bool {
509        (Self::SPR_FETCH_START..Self::SPR_FETCH_END).contains(&self.fetch_count())
510    }
511
512    #[inline]
513    const fn nametable_select(&self, addr: u16) -> Nametable {
514        self.regs.nametable_mapping.select[((addr >> 10) & 0x03) as usize]
515    }
516}
517
518impl Mapped for Exrom {
519    #[inline]
520    fn irq_pending(&self) -> bool {
521        self.regs.irq_enabled && self.irq_pending
522    }
523
524    #[inline]
525    fn mirroring(&self) -> Mirroring {
526        self.mirroring
527    }
528
529    #[inline]
530    fn set_mirroring(&mut self, mirroring: Mirroring) {
531        self.mirroring = mirroring;
532    }
533
534    #[inline]
535    fn cpu_bus_write(&mut self, addr: u16, val: u8) {
536        self.ppu_status.write(addr, val);
537    }
538}
539
540impl Regional for Exrom {
541    #[inline]
542    fn region(&self) -> NesRegion {
543        self.dmc.region()
544    }
545
546    #[inline]
547    fn set_region(&mut self, region: NesRegion) {
548        self.dmc.set_region(region);
549    }
550}
551
552impl MemMap for Exrom {
553    // CHR mode 0
554    // PPU $0000..=$1FFF 8K switchable CHR bank
555    //
556    // CHR mode 1
557    // PPU $0000..=$0FFF 4K switchable CHR bank
558    // PPU $1000..=$1FFF 4K switchable CHR bank
559    //
560    // CHR mode 2
561    // PPU $0000..=$07FF 2K switchable CHR bank
562    // PPU $0800..=$0FFF 2K switchable CHR bank
563    // PPU $1000..=$17FF 2K switchable CHR bank
564    // PPU $1800..=$1FFF 2K switchable CHR bank
565    //
566    // CHR mode 3
567    // PPU $0000..=$03FF 1K switchable CHR bank
568    // PPU $0400..=$07FF 1K switchable CHR bank
569    // PPU $0800..=$0BFF 1K switchable CHR bank
570    // PPU $0C00..=$0FFF 1K switchable CHR bank
571    // PPU $1000..=$13FF 1K switchable CHR bank
572    // PPU $1400..=$17FF 1K switchable CHR bank
573    // PPU $1800..=$1BFF 1K switchable CHR bank
574    // PPU $1C00..=$1FFF 1K switchable CHR bank
575    //
576    // PPU $2000..=$3EFF Up to 3 Nametables + Fill mode
577    //
578    // PRG mode 0
579    // CPU $6000..=$7FFF 8K switchable PRG RAM bank
580    // CPU $8000..=$FFFF 32K switchable PRG ROM bank
581    //
582    // PRG mode 1
583    // CPU $6000..=$7FFF 8K switchable PRG RAM bank
584    // CPU $8000..=$BFFF 16K switchable PRG ROM/RAM bank
585    // CPU $C000..=$FFFF 16K switchable PRG ROM bank
586    //
587    // PRG mode 2
588    // CPU $6000..=$7FFF 8K switchable PRG RAM bank
589    // CPU $8000..=$BFFF 16K switchable PRG ROM/RAM bank
590    // CPU $C000..=$DFFF 8K switchable PRG ROM/RAM bank
591    // CPU $E000..=$FFFF 8K switchable PRG ROM bank
592    //
593    // PRG mode 3
594    // CPU $6000..=$7FFF 8K switchable PRG RAM bank
595    // CPU $8000..=$9FFF 8K switchable PRG ROM/RAM bank
596    // CPU $A000..=$BFFF 8K switchable PRG ROM/RAM bank
597    // CPU $C000..=$DFFF 8K switchable PRG ROM/RAM bank
598    // CPU $E000..=$FFFF 8K switchable PRG ROM bank
599
600    fn map_read(&mut self, addr: u16) -> MappedRead {
601        match addr {
602            0x0000..=0x1FFF => {
603                self.inc_fetch_count();
604                if self.sprite8x16() {
605                    match self.fetch_count() {
606                        Self::SPR_FETCH_START => self.update_chr_banks(ChrBank::Spr),
607                        Self::SPR_FETCH_END => self.update_chr_banks(ChrBank::Bg),
608                        _ => (),
609                    }
610                }
611            }
612            0x2000..=0x3EFF => {
613                let is_attr = addr.is_attr();
614                // Cache BG tile fetch for later attribute byte fetch
615                if self.regs.exram_mode.attr && !is_attr && !self.spr_fetch() {
616                    self.tile_cache = (addr & 0x03FF).into();
617                }
618
619                // Detect split
620                // if self.regs.vsplit.in_region && !addr.is_attr() {
621                //     self.regs.vsplit.tile = (((self.regs.vsplit.scroll & 0xF8)) << 2)
622                //         | ((self.fetch_count() / 4) & 0x1F) as usize;
623                // }
624
625                // Monitor tile fetches to trigger IRQs
626                // https://wiki.nesdev.org/w/index.php?title=MMC5#Scanline_Detection_and_Scanline_IRQ
627                let status = &mut self.ppu_status;
628                if !is_attr && addr == status.prev_addr {
629                    status.prev_match += 1;
630                    if status.prev_match == 2 {
631                        if status.in_frame {
632                            status.scanline = status.scanline.wrapping_add(1);
633                            if status.scanline == self.regs.irq_scanline {
634                                self.irq_pending = true;
635                            }
636                        } else {
637                            status.in_frame = true;
638                            status.scanline = 0;
639                        }
640                        status.fetch_count = 0;
641                    }
642                } else {
643                    status.prev_match = 0;
644                }
645                status.prev_addr = addr;
646                status.reading = true;
647            }
648            0xFFFA | 0xFFFB => {
649                self.ppu_status.in_frame = false; // NMI clears in_frame
650                self.ppu_status.prev_addr = 0x0000;
651            }
652            _ => (),
653        }
654        let val = self.map_peek(addr);
655        match addr {
656            0x5204 => self.irq_pending = false, // Reading from IRQ status clears it
657            0x5010 => self.dmc.acknowledge_irq(),
658            _ => (),
659        }
660        val
661    }
662
663    fn map_peek(&self, addr: u16) -> MappedRead {
664        match addr {
665            0x0000..=0x1FFF => {
666                if self.regs.exram_mode.attr && !self.spr_fetch() {
667                    // Bits 6-7 of 4K CHR bank. Already shifted left by 8
668                    let bank_hi = self.regs.chr_hi << 10;
669                    // Bits 0-5 of 4k CHR bank
670                    let bank_lo = ((self.exram[self.tile_cache] & 0x3F) as usize) << 12;
671                    let addr = bank_hi | bank_lo | (addr as usize) & 0x0FFF;
672                    MappedRead::Chr(addr)
673                } else {
674                    MappedRead::Chr(self.chr_banks.translate(addr))
675                }
676            }
677            0x2000..=0x3EFF => {
678                let is_attr = addr.is_attr();
679                if self.regs.vsplit.in_region {
680                    if is_attr {
681                        todo!()
682                        // let addr =
683                        //     Self::ATTR_OFFSET | u16::from(ATTR_LOC[(self.regs.vsplit.tile as usize) >> 2]);
684                        // let attr = self.read_exram(addr - 0x2000) as usize;
685                        // let shift = ATTR_SHIFT[(self.regs.vsplit.tile as usize) & 0x7F] as usize;
686                        // MappedRead::Data(ATTR_BITS[(attr >> shift) & 0x03])
687                    } else {
688                        MappedRead::Data(self.read_exram(self.regs.vsplit.tile.into()))
689                    }
690                } else if self.regs.exram_mode.attr && is_attr && !self.spr_fetch() {
691                    // ExAttr mode returns attr bits for all nametables, regardless of mapping
692                    let attr = (self.exram[self.tile_cache] >> 6) & 0x03;
693                    MappedRead::Data(Self::ATTR_MIRROR[attr as usize])
694                } else {
695                    let nametable_mode = self.regs.exram_mode.nametable;
696                    match self.nametable_select(addr) {
697                        Nametable::ScreenA => MappedRead::CIRam((addr & 0x03FF).into()),
698                        Nametable::ScreenB => {
699                            MappedRead::CIRam((Ppu::NT_SIZE | (addr & 0x03FF)).into())
700                        }
701                        Nametable::ExRam if nametable_mode => {
702                            MappedRead::Data(self.read_exram(addr))
703                        }
704                        Nametable::Fill if nametable_mode => MappedRead::Data(if is_attr {
705                            Self::ATTR_MIRROR[self.regs.fill.attr]
706                        } else {
707                            self.regs.fill.tile
708                        }),
709                        // If nametable mode is not set, zero is read back
710                        _ => MappedRead::Data(0x00),
711                    }
712                }
713            }
714            0x5010 => {
715                // [I... ...M] DMC
716                // I = IRQ (0 = No IRQ triggered. 1 = IRQ was triggered.) Reading $5010 acknowledges the IRQ and clears this flag.
717                // M = Mode select (0 = write mode. 1 = read mode.)
718                let irq = self.dmc.irq_pending() && self.dmc.irq_enabled();
719                MappedRead::Data(u8::from(irq) << 7 | self.dmc_mode)
720            }
721            0x5100 => MappedRead::Data(self.regs.prg_mode as u8),
722            0x5101 => MappedRead::Data(self.regs.chr_mode as u8),
723            0x5104 => MappedRead::Data(self.regs.exram_mode.bits),
724            0x5105 => MappedRead::Data(self.regs.nametable_mapping.mode),
725            0x5106 => MappedRead::Data(self.regs.fill.tile),
726            0x5107 => MappedRead::Data(self.regs.fill.attr as u8),
727            0x5015 => {
728                // [.... ..BA]   Length status for Pulse 1 (A), 2 (B)
729                let mut status = 0x00;
730                if self.pulse1.length_counter() > 0 {
731                    status |= 0x01;
732                }
733                if self.pulse2.length_counter() > 0 {
734                    status |= 0x02;
735                }
736                MappedRead::Data(status)
737            }
738            0x5113..=0x5117 => {
739                MappedRead::Data(self.regs.prg_banks[(addr - 0x5113) as usize] as u8)
740            }
741            0x5120..=0x512B => {
742                MappedRead::Data(self.regs.chr_banks[(addr - 0x5120) as usize] as u8)
743            }
744            0x5130 => MappedRead::Data(self.regs.chr_hi as u8),
745            0x5200 => MappedRead::Data(self.regs.vsplit.mode),
746            0x5201 => MappedRead::Data(self.regs.vsplit.scroll),
747            0x5202 => MappedRead::Data(self.regs.vsplit.bank),
748            0x5203 => MappedRead::Data(self.regs.irq_scanline as u8),
749            0x5204 => {
750                // $5204:  [PI.. ....]
751                //   P = IRQ currently pending
752                //   I = "In Frame" signal
753
754                // Reading $5204 will clear the pending flag (acknowledging the IRQ).
755                // Clearing is done in the read() function
756                MappedRead::Data(
757                    u8::from(self.irq_pending) << 7 | u8::from(self.ppu_status.in_frame) << 6,
758                )
759            }
760            0x5205 => MappedRead::Data((self.regs.mult_result & 0xFF) as u8),
761            0x5206 => MappedRead::Data(((self.regs.mult_result >> 8) & 0xFF) as u8),
762            0x5C00..=0x5FFF if matches!(self.regs.exram_mode.rw, ExRamRW::R | ExRamRW::RW) => {
763                // Nametable/Attr modes are not used for RAM, thus are not readable
764                MappedRead::Data(self.read_exram(addr))
765            }
766            0x6000..=0xDFFF => {
767                if self.rom_select(addr) {
768                    MappedRead::PrgRom(self.prg_rom_banks.translate(addr))
769                } else {
770                    MappedRead::PrgRam(self.prg_ram_banks.translate(addr))
771                }
772            }
773            0xE000..=0xFFFF => MappedRead::PrgRom(self.prg_rom_banks.translate(addr)),
774            0x5207..=0x5209 => MappedRead::Data(0),
775            _ => MappedRead::None,
776        }
777    }
778
779    fn map_write(&mut self, addr: u16, val: u8) -> MappedWrite {
780        match addr {
781            0x2000..=0x3EFF => match self.nametable_select(addr) {
782                Nametable::ScreenA => return MappedWrite::CIRam((addr & 0x03FF).into(), val),
783                Nametable::ScreenB => {
784                    return MappedWrite::CIRam((Ppu::NT_SIZE | (addr & 0x03FF)).into(), val)
785                }
786                Nametable::ExRam if self.regs.exram_mode.nametable => {
787                    self.write_exram(addr, val);
788                }
789                _ => (),
790            },
791            0x5000 => self.pulse1.write_ctrl(val),
792            // 0x5001 Has no effect since there is no Sweep unit
793            0x5002 => self.pulse1.write_timer_lo(val),
794            0x5003 => self.pulse1.write_timer_hi(val),
795            0x5004 => self.pulse2.write_ctrl(val),
796            // 0x5005 Has no effect since there is no Sweep unit
797            0x5006 => self.pulse2.write_timer_lo(val),
798            0x5007 => self.pulse2.write_timer_hi(val),
799            0x5010 => {
800                // [I... ...M] DMC
801                //   I = PCM IRQ enable (1 = enabled.)
802                //   M = Mode select (0 = write mode. 1 = read mode.)
803                self.dmc_mode = val & 0x01;
804                self.dmc.set_enabled(val & 0x80 == 0x80, self.cpu_cycle);
805            }
806            0x5011 => {
807                // [DDDD DDDD] PCM Data
808                // Write mode - writing $00 has no effect
809                if self.dmc_mode == 0 && val != 0x00 {
810                    self.dmc.write_output(val);
811                }
812            }
813            0x5015 => {
814                //  [.... ..BA]   Enable flags for Pulse 1 (A), 2 (B)  (0=disable, 1=enable)
815                self.pulse1.set_enabled(val & 0x01 == 0x01);
816                self.pulse2.set_enabled(val & 0x02 == 0x02);
817            }
818            0x5100 => {
819                // [.... ..PP] PRG Mode
820                self.regs.prg_mode = match val & 0x03 {
821                    0 => PrgMode::Bank32k,
822                    1 => PrgMode::Bank16k,
823                    2 => PrgMode::Bank16_8k,
824                    3 => PrgMode::Bank8k,
825                    _ => {
826                        log::warn!("invalid PrgMode value: ${:02X}", val);
827                        self.regs.prg_mode
828                    }
829                };
830                self.update_prg_banks();
831            }
832            0x5101 => {
833                // [.... ..CC] CHR Mode
834                if self.regs.exram_mode.attr {
835                    // Bank switching is ignored in extended attribute mode, banks are always 4K
836                    self.regs.chr_mode = ChrMode::Bank4k;
837                } else {
838                    self.regs.chr_mode = match val & 0x03 {
839                        0 => ChrMode::Bank8k,
840                        1 => ChrMode::Bank4k,
841                        2 => ChrMode::Bank2k,
842                        3 => ChrMode::Bank1k,
843                        _ => {
844                            log::warn!("invalid ChrMode value: ${:02X}", val);
845                            self.regs.chr_mode
846                        }
847                    };
848                }
849                self.update_chr_banks(self.last_chr_write);
850            }
851            0x5102 | 0x5103 => {
852                // [.... ..AA]    PRG-RAM Protect A
853                // [.... ..BB]    PRG-RAM Protect B
854                self.regs.prg_ram_protect[(addr - 0x5102) as usize] = val & 0x03;
855                // To allow writing to PRG-RAM you must set:
856                //    A=%10
857                //    B=%01
858                // Any other value will prevent PRG-RAM writing.
859                let writable =
860                    self.regs.prg_ram_protect[0] == 0b10 && self.regs.prg_ram_protect[1] == 0b01;
861                return MappedWrite::PrgRamProtect(!writable);
862            }
863            0x5104 => {
864                // [.... ..XX] ExRam mode
865                //   Value  RAM $5C00-$5FFF  RAM Nametable  Extended Attr
866                //   %00    Write Only       Yes            No
867                //   %01    Write Only       Yes            Yes
868                //   %10    Read/Write       No             No
869                //   %11    Read Only        No             No
870                self.regs.exram_mode.set(val);
871            }
872            0x5105 => {
873                // [.... ..HH]
874                // [DDCC BBAA]
875                //
876                // Allows each Nametable slot to be configured:
877                //   [   A   ][   B   ]
878                //   [   C   ][   D   ]
879                //
880                // Values can be the following:
881                //   %00 = NES internal NTA
882                //   %01 = NES internal NTB
883                //   %10 = use ExRAM as NT
884                //   %11 = Fill Mode
885                self.regs.nametable_mapping.set(val);
886
887                // Typical mirroring setups would be:
888                //                          D  C  B  A
889                //   Horizontal:     $50    01 01 00 00
890                //   Vertical:       $44    01 00 01 00
891                //   SingleScreenA:  $00    00 00 00 00
892                //   SingleScreenB:  $55    01 01 01 01
893                //   SingleScreen ExRAM:   $AA    10 10 10 10
894                //   SingleScreen Fill:    $FF    11 11 11 11
895                self.mirroring = match val {
896                    0x50 => Mirroring::Horizontal,
897                    0x44 => Mirroring::Vertical,
898                    0x00 => Mirroring::SingleScreenA,
899                    0x55 => Mirroring::SingleScreenB,
900                    // Any other combination means Mapper provides nametables
901                    _ => Mirroring::FourScreen,
902                };
903            }
904            0x5106 => self.regs.fill.tile = val, // [TTTT TTTT] Fill Tile
905            0x5107 => self.regs.fill.attr = (val & 0x03).into(), // [.... ..AA] Fill Attribute bits
906            0x5113..=0x5117 => {
907                // PRG Bank Switching
908                // $5113: [.... .PPP]
909                //      8k PRG-RAM @ $6000
910                // $5114-5117: [RPPP PPPP]
911                //      R = ROM select (0=select RAM, 1=select ROM)  **unused in $5117**
912                //      P = PRG page
913                let bank = (addr - 0x5113) as usize;
914                self.regs.prg_banks[bank] = val as usize;
915                self.update_prg_banks();
916            }
917            0x5120..=0x512B => {
918                let bank = (addr - 0x5120) as usize;
919                self.regs.chr_banks[bank] = val as usize;
920                if addr < 0x5128 {
921                    self.update_chr_banks(ChrBank::Spr);
922                } else {
923                    // Mirroring BG
924                    self.regs.chr_banks[bank + 4] = self.regs.chr_banks[bank];
925                    self.update_chr_banks(ChrBank::Bg);
926                }
927            }
928            0x5130 => self.regs.chr_hi = (val as usize & 0x03) << 8, // [.... ..HH]  CHR Bank Hi bits
929            0x5200 => {
930                // [ES.T TTTT]    Split control
931                //   E = Enable  (0=split mode disabled, 1=split mode enabled)
932                //   S = Vsplit side  (0=split will be on left side, 1=split will be on right)
933                //   T = tile number to split at
934                self.regs.vsplit.enabled = val & 0x80 == 0x80;
935                self.regs.vsplit.side = if val & 0x40 == 0x40 {
936                    Side::Right
937                } else {
938                    Side::Left
939                };
940                self.regs.vsplit.tile = val & 0x1F;
941            }
942            0x5201 => self.regs.vsplit.scroll = val, // [YYYY YYYY]  Split Y scroll
943            0x5202 => self.regs.vsplit.bank = val,   // [CCCC CCCC]  4k CHR Page for split
944            0x5203 => self.regs.irq_scanline = u16::from(val), // [IIII IIII]  IRQ Target
945            0x5204 => self.regs.irq_enabled = val & 0x80 > 0, // [E... ....] IRQ Enable (0=disabled, 1=enabled)
946            0x5205 => {
947                self.regs.multiplicand = val;
948                self.regs.mult_result =
949                    u16::from(self.regs.multiplicand) * u16::from(self.regs.multiplier);
950            }
951            0x5206 => {
952                self.regs.multiplier = val;
953                self.regs.mult_result =
954                    u16::from(self.regs.multiplicand) * u16::from(self.regs.multiplier);
955            }
956            0x5207..=0x5209 => {}
957            0x5C00..=0x5FFF => match self.regs.exram_mode.rw {
958                ExRamRW::W => {
959                    let val = if self.ppu_status.rendering { val } else { 0x00 };
960                    self.write_exram(addr, val);
961                }
962                ExRamRW::RW => self.write_exram(addr, val),
963                _ => (),
964            },
965            0x6000..=0xDFFF if !self.rom_select(addr) => {
966                return MappedWrite::PrgRam(self.prg_ram_banks.translate(addr), val);
967            }
968            _ => (),
969        }
970        MappedWrite::None
971    }
972}
973
974impl Audio for Exrom {
975    #[must_use]
976    fn output(&self) -> f32 {
977        let pulse1 = self.pulse1.output();
978        let pulse2 = self.pulse2.output();
979        let dmc = self.dmc.output();
980        let pulse_scale = PULSE_TABLE[PULSE_TABLE.len() - 1] / 15.0;
981        let out = -(pulse1 + pulse2 + dmc);
982        pulse_scale * out
983    }
984}
985
986impl Clock for Exrom {
987    fn clock(&mut self) -> usize {
988        if self.ppu_status.reading {
989            self.ppu_status.idle = 0;
990        } else {
991            self.ppu_status.idle += 1;
992            // 3 CPU clocks == 1 ppu clock
993            if self.ppu_status.idle == 3 {
994                self.ppu_status.idle = 0;
995                self.ppu_status.in_frame = false;
996                self.ppu_status.prev_addr = 0x0000;
997            }
998        }
999        self.ppu_status.reading = false;
1000        if self.cpu_cycle & 0x01 == 0x00 {
1001            self.pulse1.clock();
1002            self.pulse2.clock();
1003            self.dmc.clock();
1004        }
1005        self.pulse_timer -= 1.0;
1006        if self.pulse_timer <= 0.0 {
1007            self.pulse1.clock_quarter_frame();
1008            self.pulse1.clock_half_frame();
1009            self.pulse2.clock_quarter_frame();
1010            self.pulse2.clock_half_frame();
1011            self.pulse_timer = Cpu::region_clock_rate(self.region) / 240.0;
1012        }
1013        self.cpu_cycle = self.cpu_cycle.wrapping_add(1);
1014        1
1015    }
1016}
1017
1018impl Reset for Exrom {
1019    fn reset(&mut self, _kind: ResetKind) {
1020        self.regs.prg_mode = PrgMode::Bank8k;
1021        self.regs.chr_mode = ChrMode::Bank1k;
1022    }
1023}
1024
1025impl core::fmt::Debug for Exrom {
1026    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1027        f.debug_struct("Exrom")
1028            .field("regs", &self.regs)
1029            .field("mirroring", &self.mirroring)
1030            .field("irq_pending", &self.irq_pending)
1031            .field("ppu_status", &self.ppu_status)
1032            .field("exram_len", &self.exram.len())
1033            .field("prg_ram_banks", &self.prg_ram_banks)
1034            .field("prg_rom_banks", &self.prg_rom_banks)
1035            .field("chr_banks", &self.chr_banks)
1036            .field("tile_cache", &self.tile_cache)
1037            .field("last_chr_write", &self.last_chr_write)
1038            .field("region", &self.region)
1039            .field("pulse1", &self.pulse1)
1040            .field("pulse2", &self.pulse2)
1041            .field("dmc", &self.dmc)
1042            .field("dmc_mode", &self.dmc_mode)
1043            .field("cpu_cycle", &self.cpu_cycle)
1044            .field("pulse_timer", &self.pulse_timer)
1045            .finish()
1046    }
1047}