mmc1.rsannotatedmmc1.rssource237 lines · 9.8 KB · raw

Code for the MMC1 board (iNES mapper 1).

3use crate::bus::cartridge::mappers::{
4    BankSizeKb, ChrType, CpuMapResult, NametableMirroring, PpuMapResult,
5};
6use crate::bus::cartridge::{HasBasicPpuMapping, MapperImpl};
7use bincode::{Decode, Encode};
8use jgenesis_common::num::GetBit;
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
11pub(crate) enum PrgBankingMode {
12    Switch32Kb,
13    Switch16KbFirstBankFixed,
14    Switch16KbLastBankFixed,
15}
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
18pub(crate) enum ChrBankingMode {
19    Single8KbBank,
20    Two4KbBanks,
21}
22
23#[derive(Debug, Clone, Encode, Decode)]
24pub(crate) struct Mmc1 {
25    chr_type: ChrType,
26    shift_register: u8,
27    shift_register_len: u8,
28    written_this_cycle: bool,
29    written_last_cycle: bool,
30    nametable_mirroring: NametableMirroring,
31    prg_banking_mode: PrgBankingMode,
32    chr_banking_mode: ChrBankingMode,
33    chr_bank_0: u8,
34    chr_bank_1: u8,
35    prg_bank: u8,
36    prg_bank_upper_bit: u8,
37}
38
39impl Mmc1 {
40    pub(crate) fn new(chr_type: ChrType) -> Self {
41        Self {
42            chr_type,
43            shift_register: 0,
44            shift_register_len: 0,
45            written_this_cycle: false,
46            written_last_cycle: false,
47            nametable_mirroring: NametableMirroring::Horizontal,
48            prg_banking_mode: PrgBankingMode::Switch16KbLastBankFixed,
49            chr_banking_mode: ChrBankingMode::Single8KbBank,
50            chr_bank_0: 0,
51            chr_bank_1: 0,
52            prg_bank: 0,
53            prg_bank_upper_bit: 0,
54        }
55    }
56
57    fn full_prg_bank(&self) -> u8 {
58        self.prg_bank_upper_bit | self.prg_bank
59    }
60}
61
62impl MapperImpl<Mmc1> {
63    fn map_cpu_address(&self, address: u16) -> CpuMapResult {
64        match address {
65            0x0000..=0x401F => panic!("invalid CPU map address: 0x{address:04X}"),
66            0x4020..=0x5FFF => CpuMapResult::None,
67            0x6000..=0x7FFF => {
68                if !self.cartridge.prg_ram.is_empty() {
69                    CpuMapResult::PrgRAM(u32::from(address & 0x1FFF))
70                } else {
71                    CpuMapResult::None
72                }
73            }
74            0x8000..=0xFFFF => match self.data.prg_banking_mode {
75                PrgBankingMode::Switch32Kb => {
76                    // In 32KB mode, treat the bank number as a 16KB bank number but ignore the lowest bit
77                    let prg_rom_addr = BankSizeKb::ThirtyTwo
78                        .to_absolute_address(self.data.full_prg_bank() >> 1, address);
79                    CpuMapResult::PrgROM(prg_rom_addr)
80                }
81                PrgBankingMode::Switch16KbFirstBankFixed => match address {
82                    0x8000..=0xBFFF => {
83                        // If PRG ROM is 512KB, the upper bit affects the fixed bank
84                        let rom_addr = u32::from(address & BankSizeKb::Sixteen.address_mask())
85                            | (u32::from(self.data.prg_bank_upper_bit) << 18);
86                        CpuMapResult::PrgROM(rom_addr)
87                    }
88                    0xC000..=0xFFFF => {
89                        let prg_rom_addr = BankSizeKb::Sixteen
90                            .to_absolute_address(self.data.full_prg_bank(), address);
91                        CpuMapResult::PrgROM(prg_rom_addr)
92                    }
93                    _ => unreachable!("match arm should be unreachable"),
94                },
95                PrgBankingMode::Switch16KbLastBankFixed => match address {
96                    0x8000..=0xBFFF => {
97                        let prg_rom_addr = BankSizeKb::Sixteen
98                            .to_absolute_address(self.data.full_prg_bank(), address);
99                        CpuMapResult::PrgROM(prg_rom_addr)
100                    }
101                    0xC000..=0xFFFF => {
102                        // If PRG ROM is 512KB, the upper bit affects the fixed bank
103                        let last_bank = self.data.prg_bank_upper_bit | 0xF;
104                        let prg_rom_addr =
105                            BankSizeKb::Sixteen.to_absolute_address(last_bank, address);
106                        CpuMapResult::PrgROM(prg_rom_addr)
107                    }
108                    _ => unreachable!("match arm should be unreachable"),
109                },
110            },
111        }
112    }
113
114    pub(crate) fn read_cpu_address(&self, address: u16, cpu_open_bus: u8) -> u8 {
115        self.map_cpu_address(address).read(&self.cartridge).unwrap_or(cpu_open_bus)
116    }
117
118    pub(crate) fn write_cpu_address(&mut self, address: u16, value: u8) {
119        match address {
120            0x0000..=0x401F => panic!("invalid CPU map address: 0x{address:04X}"),
121            0x4020..=0x5FFF => {}
122            0x6000..=0x7FFF => {
123                if !self.cartridge.prg_ram.is_empty() {
124                    self.cartridge.set_prg_ram(u32::from(address & 0x1FFF), value);
125                }
126            }
127            0x8000..=0xFFFF => {
128                self.data.written_this_cycle = true;
129
130                if value.bit(7) {
131                    self.data.shift_register = 0;
132                    self.data.shift_register_len = 0;
133                    self.data.prg_banking_mode = PrgBankingMode::Switch16KbLastBankFixed;
134                    return;
135                }
136
137                if self.data.written_last_cycle {
138                    return;
139                }
140
141                self.data.shift_register = (self.data.shift_register >> 1) | ((value & 0x01) << 4);
142                self.data.shift_register_len += 1;
143
144                if self.data.shift_register_len == 5 {
145                    let shift_register = self.data.shift_register;
146
147                    self.data.shift_register = 0;
148                    self.data.shift_register_len = 0;
149
150                    match address {
151                        0x8000..=0x9FFF => {
152                            self.data.nametable_mirroring = match shift_register & 0x03 {
153                                0x00 => NametableMirroring::SingleScreenBank0,
154                                0x01 => NametableMirroring::SingleScreenBank1,
155                                0x02 => NametableMirroring::Vertical,
156                                0x03 => NametableMirroring::Horizontal,
157                                _ => unreachable!(
158                                    "{shift_register} & 0x03 was not 0x00/0x01/0x02/0x03",
159                                ),
160                            };
161
162                            self.data.prg_banking_mode = match shift_register & 0x0C {
163                                0x00 | 0x04 => PrgBankingMode::Switch32Kb,
164                                0x08 => PrgBankingMode::Switch16KbFirstBankFixed,
165                                0x0C => PrgBankingMode::Switch16KbLastBankFixed,
166                                _ => unreachable!(
167                                    "{shift_register} & 0x0C was not 0x00/0x04/0x08/0x0C"
168                                ),
169                            };
170
171                            self.data.chr_banking_mode = if shift_register.bit(4) {
172                                ChrBankingMode::Two4KbBanks
173                            } else {
174                                ChrBankingMode::Single8KbBank
175                            };
176                        }
177                        0xA000..=0xBFFF => {
178                            self.data.chr_bank_0 = shift_register;
179
180                            // Some MMC1 variants use bit 4 of CHR bank 0 as PRG ROM A18
181                            // Dragon Warrior 3 & 4 depend on this
182                            if self.cartridge.prg_rom.len() >= 512 * 1024 {
183                                self.data.prg_bank_upper_bit = shift_register & 0x10;
184                            }
185                        }
186                        0xC000..=0xDFFF => {
187                            self.data.chr_bank_1 = shift_register;
188
189                            // Some MMC1 variants use bit 4 of CHR bank 1 as PRG ROM A18, but only
190                            // when not in 8KB mode
191                            if self.cartridge.prg_rom.len() >= 512 * 1024
192                                && self.data.chr_banking_mode != ChrBankingMode::Single8KbBank
193                            {
194                                self.data.prg_bank_upper_bit = shift_register & 0x10;
195                            }
196                        }
197                        0xE000..=0xFFFF => {
198                            self.data.prg_bank = shift_register & 0x0F;
199                        }
200                        _ => unreachable!("match arm should be unreachable"),
201                    }
202                }
203            }
204        }
205    }
206
207    pub(crate) fn tick_cpu(&mut self) {
208        self.data.written_last_cycle = self.data.written_this_cycle;
209        self.data.written_this_cycle = false;
210    }
211}
212
213impl HasBasicPpuMapping for MapperImpl<Mmc1> {
214    fn map_ppu_address(&self, address: u16) -> PpuMapResult {
215        match address {
216            0x0000..=0x1FFF => match self.data.chr_banking_mode {
217                ChrBankingMode::Two4KbBanks => {
218                    let bank_number =
219                        if address < 0x1000 { self.data.chr_bank_0 } else { self.data.chr_bank_1 };
220                    let chr_addr = BankSizeKb::Four.to_absolute_address(bank_number, address);
221                    self.data.chr_type.to_map_result(chr_addr)
222                }
223                ChrBankingMode::Single8KbBank => {
224                    // In 8KB mode, use CHR bank 0 and treat it as a 4KB bank number while ignoring
225                    // the lowest bit
226                    let chr_addr =
227                        BankSizeKb::Eight.to_absolute_address(self.data.chr_bank_0 >> 1, address);
228                    self.data.chr_type.to_map_result(chr_addr)
229                }
230            },
231            0x2000..=0x3EFF => {
232                PpuMapResult::Vram(self.data.nametable_mirroring.map_to_vram(address))
233            }
234            _ => panic!("invalid PPU map address: 0x{address:04X}"),
235        }
236    }
237}