rustynes_mappers/m004_mmc3.rs
1//! MMC3 (iNES mapper 4) implementation.
2//!
3//! See `docs/mappers.md` §MMC3 and `ref-docs/research-report.md` §MMC3.
4//!
5//! # Banking
6//!
7//! Eight internal registers `R0`-`R7` selected by the low 3 bits of the
8//! value written to `$8000` (bank-select). Subsequent writes to `$8001`
9//! (bank-data) commit a value into the selected register:
10//!
11//! | Register | Purpose |
12//! |----------|--------------------------------------------------|
13//! | R0 | 2 KiB CHR bank @ `$0000-$07FF` (CHR mode 0) |
14//! | R1 | 2 KiB CHR bank @ `$0800-$0FFF` (CHR mode 0) |
15//! | R2 | 1 KiB CHR bank @ `$1000-$13FF` (CHR mode 0) |
16//! | R3 | 1 KiB CHR bank @ `$1400-$17FF` (CHR mode 0) |
17//! | R4 | 1 KiB CHR bank @ `$1800-$1BFF` (CHR mode 0) |
18//! | R5 | 1 KiB CHR bank @ `$1C00-$1FFF` (CHR mode 0) |
19//! | R6 | 8 KiB PRG bank @ `$8000-$9FFF` (PRG mode 0) |
20//! | R7 | 8 KiB PRG bank @ `$A000-$BFFF` |
21//!
22//! `$8000` bit 6 swaps the PRG window: in mode 1, R6 maps to `$C000-$DFFF`
23//! and the second-to-last bank is fixed at `$8000-$9FFF`. Bit 7 swaps the
24//! CHR layout: in mode 1, the 2 KiB R0/R1 banks occupy `$1000-$1FFF` and
25//! the four 1 KiB banks occupy `$0000-$0FFF`.
26//!
27//! `$E000-$FFFF` is hardwired to the LAST 8 KiB PRG bank.
28//!
29//! `$A000` even (`$A000-$BFFE` even addresses): mirroring (bit 0).
30//! `$A001` odd: PRG-RAM enable + protect (bit 7 enable, bit 6 write-protect).
31//! `$C000` even: IRQ counter reload value.
32//! `$C001` odd: latches `irq_reload_pending` and forces counter to 0.
33//! `$E000` even: disable IRQ + acknowledge any pending IRQ line.
34//! `$E001` odd: enable IRQ.
35//!
36//! # IRQ counter
37//!
38//! Clocked by PPU A12 rising edges, filtered to ignore rising edges within
39//! 3 M2 (CPU) cycles of the previous A12 fall. Standard pattern-table
40//! layout (BG @ `$0000`, sprites @ `$1000`) yields exactly one filtered
41//! edge per scanline, at PPU dot 260. Reversed layout (BG @ `$1000`,
42//! sprites @ `$0000`) places the edge at the END of the previous
43//! scanline's sprite fetches (Wario's Woods relies on this).
44//!
45//! On each filtered rising edge (`clock_irq`):
46//! - a `$C001` reload (`irq_reload_pending`): counter = `irq_reload_value`,
47//! flag cleared; BOTH revisions assert if the new value is 0 and IRQs are
48//! enabled (v3.1.0; the alternate one used not to, see `clock_irq`);
49//! - else if `counter == 0`: counter = `irq_reload_value`; only **Sharp**
50//! asserts if the new value is 0, so a latch of 0 fires every scanline on
51//! Sharp and stops on the alternate chip;
52//! - else: counter -= 1; if it reached 0 and IRQs are enabled, assert.
53//!
54//! Default revision is **Sharp** per project policy (Star Trek: 25th
55//! Anniversary requires it). NES 2.0 submapper 4 selects the alternate
56//! behaviour of the MMC3A and non-Sharp MMC3B ([`Mmc3Revision::Nec`]), 1 the
57//! MMC6 (v2.9.6 corrected both; this paragraph said "submapper 1 selects
58//! MMC3B (NEC)" until v3.1.0). For an iNES 1.0 dump, which cannot say,
59//! `Mapper::set_mmc3_revision_override` selects it (v3.1.0).
60
61#![allow(
62 clippy::cast_possible_truncation,
63 clippy::cast_lossless,
64 clippy::missing_const_for_fn,
65 clippy::struct_excessive_bools,
66 clippy::match_same_arms,
67 clippy::manual_range_patterns,
68 clippy::too_many_arguments,
69 clippy::useless_let_if_seq,
70 clippy::doc_markdown,
71 clippy::if_not_else,
72 clippy::nonminimal_bool
73)]
74
75use crate::cartridge::Mirroring;
76use crate::mapper::{Mapper, MapperCaps, MapperError};
77use alloc::{boxed::Box, vec::Vec};
78use alloc::{format, vec};
79
80const PRG_BANK_8K: usize = 0x2000;
81const CHR_BANK_1K: usize = 0x0400;
82const PRG_RAM_DEFAULT: usize = 0x2000;
83const NAMETABLE_SIZE: usize = 0x0400;
84const NAMETABLE_SIZE_U16: u16 = 0x0400;
85
86/// v3 (v2.9.6) appends the MMC6 PRG-RAM state and the MC-ACC prescaler.
87/// Only v3 is read since v2.9.8 (ADR 0042); v1 and v2 used to load with the
88/// later fields at defaults.
89const SAVE_STATE_VERSION: u8 = 4;
90
91/// MMC6 internal PRG-RAM: 1 KiB, two 512-byte halves (`MMC6.md`).
92const MMC6_RAM: usize = 0x0400;
93
94/// MMC3 IRQ-counter behaviour. Default is the Sharp ("new") behaviour; the
95/// alternative suppresses the "reload to 0 asserts IRQ" behaviour.
96///
97/// **Naming, corrected in v2.9.6.** These two variants were documented as
98/// "Sharp MMC3A" and "NEC MMC3B". `MMC3.md` says otherwise: the old or
99/// alternate behaviour belongs to the MMC3A and to non-Sharp MMC3B chips
100/// ("1 (Sharp MMC3B, MMC3C) or 2 (MMC3A, Non-Sharp MMC3B) to 256 scanlines"),
101/// and the NES 2.0 submapper for it is 4, not 1 (`NES_2_0_submappers.md`).
102/// The behaviour of each variant was always right; only the labels were
103/// wrong, together with the submapper mapping that followed them.
104#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
105pub enum Mmc3Revision {
106 /// Sharp MMC3B and MMC3C: reloading the IRQ counter to 0 asserts IRQ if
107 /// IRQs are enabled, so a latch of 0 fires every scanline. Default.
108 #[default]
109 Sharp,
110 /// NEC MMC3B and the MMC3A: the IRQ fires on the counter's 1 -> 0
111 /// transition, so a latch of 0 stops IRQs. NES 2.0 submapper 4.
112 Nec,
113}
114
115/// Which chip or board wiring an [`Mmc3`] models, beyond the IRQ revision
116/// (the NES 2.0 submappers of mapper 4, `NES_2_0_submappers.md`). v2.9.6.
117#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
118pub enum Mmc3Variant {
119 /// A standard MMC3 board (submappers 0 and 4).
120 #[default]
121 Standard,
122 /// Submapper 1, the MMC6 (`MMC6.md`): 1 KiB of internal PRG-RAM at
123 /// `$7000-$7FFF`, enabled by `$8000` bit 5, with separate read and write
124 /// enables for each 512-byte half in `$A001` bits 4-7.
125 Mmc6,
126 /// Submapper 2: an MMC3C with hard-wired mirroring; `$A000` does nothing.
127 HardwiredMirroring,
128 /// Submapper 3, Acclaim's MC-ACC (`MMC3.md`, "IRQ Specifics"): the
129 /// scanline counter is clocked by FALLING edges of PPU A12 through a
130 /// divide-by-8 prescaler instead of the M2 filter. The page gives only
131 /// that. The two details used here come from the forum measurement it
132 /// links (forums.nesdev.org, p=242427): writing `$C001` resets the
133 /// prescaler, and the counter clocks on the first edge of each group of
134 /// eight. That evidence is forum-level, so mapper 4 submapper 3 is
135 /// BestEffort in `tier.rs`.
136 McAcc,
137}
138
139/// MMC3 mapper (iNES mapper 4).
140pub struct Mmc3 {
141 prg_rom: Box<[u8]>,
142 chr: Box<[u8]>,
143 prg_ram: Box<[u8]>,
144 vram: Box<[u8]>,
145 chr_is_ram: bool,
146
147 // R0..R7 bank registers (8 internal regs selected via $8000).
148 regs: [u8; 8],
149 // Selected register index (low 3 bits of $8000).
150 bank_select: u8,
151 // PRG mode (bit 6 of $8000): 0 = R6 @ $8000, last-1 fixed @ $C000;
152 // 1 = R6 @ $C000, last-1 fixed @ $8000.
153 prg_mode: bool,
154 // CHR mode (bit 7 of $8000): 0 = 2K@$0000 + 1K@$1000;
155 // 1 = 1K@$0000 + 2K@$1000.
156 chr_mode: bool,
157
158 // Mirroring (set via $A000 even). Ignored on 4-screen carts.
159 mirroring: Mirroring,
160 fixed_4screen: bool,
161
162 // PRG-RAM enable + protect ($A001 odd).
163 prg_ram_enabled: bool,
164 prg_ram_protect: bool,
165
166 // IRQ counter state.
167 irq_counter: u8,
168 irq_reload_value: u8,
169 irq_reload_pending: bool,
170 irq_enabled: bool,
171 irq_pending_line: bool,
172 // A clocking A12 rise sets this instead of the IRQ line, and the first
173 // `notify_cpu_cycle` after the rise moves it to `irq_pending_line`
174 // (T-ORACLE-001, v2.9.9). WHICH cycle that is depends on the bus, not on
175 // this mapper: `Cpu::start_cycle` catches the PPU up to the access and
176 // then calls `SystemBus::cpu_clock`, which calls `notify_cpu_cycle`. So a
177 // rise caught up before the access is raised in its own cycle, and one
178 // caught up after the access (`end_cycle`) from the next cycle on. (This
179 // comment said "one CPU cycle after the rise" for every rise until the
180 // #583 review; ADR 0002's 2026-10-05 correction has the detail.)
181 //
182 // Why: the oracle raised it a cycle early relative to the MiSTer
183 // sibling's MMC3, which registers its IRQ output on the CPU clock enable
184 // as a synchronous design must. Its per-cycle trace of
185 // `mapper4mmc3irq065` and of blargg's `4-scanline_timing` put the /IRQ
186 // fall one cycle apart, and with the oracle deferred by one cycle the two
187 // traces agree for 6,253,826 cycles instead of 1,250,766. The deferral
188 // also makes `mmc3_test` v1 `5-MMC3` pass and moves `4-scanline_timing`'s
189 // first failure from sub-test 3 to sub-test 9. It is not a filter: which
190 // rises clock the counter is unchanged, only when the line is seen.
191 //
192 // This replaced v2.0.0's `mmc3-m2-phase-irq` experiment, which deferred
193 // only rises seen in the M2-high half of a cycle. Measured against the
194 // same ROMs it passed the same set, because the split above is in effect
195 // the same one; this form is kept because it gets it from the order of
196 // the catch-up and the per-cycle hook, with no phase data from the bus.
197 // A delay of one cycle for EVERY rise cannot be built here: a pre-access
198 // rise of cycle N and a post-access rise of cycle N-1 both arrive between
199 // the same two `notify_cpu_cycle` calls.
200 irq_assert_pending_next_cycle: bool,
201
202 // A12 filter state.
203 last_a12: bool,
204 // CPU cycle at which A12 last went low; used to filter rising edges
205 // closer than 3 M2 cycles.
206 a12_low_cycle: u64,
207 cpu_cycle: u64,
208
209 revision: Mmc3Revision,
210 /// v3.1.0 (`T-MMC3-NEC-OVERRIDE`): the revision the cartridge header
211 /// selected, which `revision` returns to when an override is cleared.
212 /// Board identity, fixed at construction; not save-state (a state carries
213 /// the live `revision`).
214 header_revision: Mmc3Revision,
215 variant: Mmc3Variant,
216 /// MMC6: `$8000` bit 5, the PRG-RAM enable.
217 mmc6_ram_enabled: bool,
218 /// MMC6: `$A001` bits 4-7 (`HhLl`): read/write enables of each half.
219 mmc6_protect: u8,
220 /// MC-ACC: falling A12 edges counted, modulo 8.
221 mcacc_prescaler: u8,
222
223 // v2.1.5 F5.0 MMC3 R1/R2 residual instrumentation study (`mmc3-a12-phase-
224 // probe`, default-off): purely OBSERVATIONAL tallies of *qualifying*
225 // (`gap >= 3`) A12 rising edges, bucketed by the M2-phase half of the host
226 // CPU cycle in which they were observed. `sub_dot < 2` is the PRE-access
227 // (M2-low, φ1) half; `sub_dot >= 2` is the POST-access (M2-high, φ2) half.
228 // The `*_irq_*` pair further restricts to rises that actually clocked the
229 // counter to a state that asserts the IRQ line (`clock_irq()` returned
230 // true). These are counters only — they do NOT influence `irq_pending_line`
231 // or any emulated state, so the timeline is byte-identical to the default
232 // build. Surfaced via `debug_state().extra` for the study fixture to read.
233 // See ADR 0002 §"Decision update (2026-07-11, v2.1.5 F5.0 instrumentation
234 // study)". Compiled out entirely when the feature is off.
235 #[cfg(feature = "mmc3-a12-phase-probe")]
236 probe: Mmc3A12PhaseProbe,
237}
238
239/// Observational A12-phase probe state for the v2.1.5 F5.0 MMC3 R1/R2 residual
240/// instrumentation study. See the field doc on [`Mmc3::probe`]. Every counter
241/// is monotonic over a run; none feeds back into emulated state.
242#[cfg(feature = "mmc3-a12-phase-probe")]
243#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
244struct Mmc3A12PhaseProbe {
245 /// Qualifying (`gap >= 3`) A12 rises seen in the PRE-access (M2-low, φ1,
246 /// `sub_dot < 2`) half of a host CPU cycle.
247 qual_rises_pre: u64,
248 /// Qualifying (`gap >= 3`) A12 rises seen in the POST-access (M2-high, φ2,
249 /// `sub_dot >= 2`) half of a host CPU cycle. **The study's headline metric:
250 /// if this is 0 across all four failing sub-tests, no A12-phase / M2-half-
251 /// cycle filter refinement can move the residual (axis B is dead).**
252 qual_rises_post: u64,
253 /// Of the qualifying rises, those that clocked the counter to an
254 /// IRQ-asserting state (`clock_irq()` true) in the PRE-access half.
255 irq_clock_pre: u64,
256 /// Of the qualifying rises, those that clocked the counter to an
257 /// IRQ-asserting state (`clock_irq()` true) in the POST-access half.
258 irq_clock_post: u64,
259}
260
261impl Mmc3 {
262 /// Construct a new MMC3 mapper.
263 ///
264 /// `prg_rom` must be a non-zero multiple of 8 KiB (typical 32-512 KiB).
265 /// CHR-RAM is selected when `chr_rom` is empty; otherwise CHR-ROM
266 /// length must be a multiple of 1 KiB. `prg_ram_bytes == 0` selects
267 /// the default 8 KiB. Set `revision` from the iNES NES 2.0 submapper
268 /// (default Sharp).
269 ///
270 /// # Errors
271 ///
272 /// Returns [`MapperError::Invalid`] on size mismatch.
273 pub fn new(
274 prg_rom: Box<[u8]>,
275 chr_rom: Box<[u8]>,
276 initial_mirroring: Mirroring,
277 prg_ram_bytes: usize,
278 revision: Mmc3Revision,
279 ) -> Result<Self, MapperError> {
280 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_8K) {
281 return Err(MapperError::Invalid(format!(
282 "MMC3 PRG-ROM size {} is not a non-zero multiple of 8 KiB",
283 prg_rom.len()
284 )));
285 }
286 let chr_is_ram = chr_rom.is_empty();
287 let chr: Box<[u8]> = if chr_is_ram {
288 vec![0u8; 8 * CHR_BANK_1K].into_boxed_slice()
289 } else if chr_rom.len().is_multiple_of(CHR_BANK_1K) {
290 chr_rom
291 } else {
292 return Err(MapperError::Invalid(format!(
293 "MMC3 CHR-ROM size {} is not a multiple of 1 KiB",
294 chr_rom.len()
295 )));
296 };
297 let prg_ram_size = if prg_ram_bytes == 0 {
298 PRG_RAM_DEFAULT
299 } else {
300 prg_ram_bytes
301 };
302 let fixed_4screen = matches!(initial_mirroring, Mirroring::FourScreen);
303 // For four-screen, allocate the full 4 KiB nametable VRAM region.
304 let vram_size = if fixed_4screen {
305 4 * NAMETABLE_SIZE
306 } else {
307 2 * NAMETABLE_SIZE
308 };
309 Ok(Self {
310 prg_rom,
311 chr,
312 prg_ram: vec![0u8; prg_ram_size].into_boxed_slice(),
313 vram: vec![0u8; vram_size].into_boxed_slice(),
314 chr_is_ram,
315 regs: [0; 8],
316 bank_select: 0,
317 prg_mode: false,
318 chr_mode: false,
319 mirroring: initial_mirroring,
320 fixed_4screen,
321 prg_ram_enabled: true,
322 prg_ram_protect: false,
323 irq_counter: 0,
324 irq_reload_value: 0,
325 irq_reload_pending: false,
326 irq_assert_pending_next_cycle: false,
327 irq_enabled: false,
328 irq_pending_line: false,
329 last_a12: false,
330 a12_low_cycle: 0,
331 cpu_cycle: 0,
332 revision,
333 header_revision: revision,
334 variant: Mmc3Variant::Standard,
335 mmc6_ram_enabled: false,
336 mmc6_protect: 0,
337 mcacc_prescaler: 0,
338 #[cfg(feature = "mmc3-a12-phase-probe")]
339 probe: Mmc3A12PhaseProbe::default(),
340 })
341 }
342
343 /// Select the board wiring (the NES 2.0 submapper), v2.9.6. An MMC6 gets
344 /// its 1 KiB of internal RAM in place of the board PRG-RAM.
345 ///
346 /// Call it once, on a board fresh from a constructor: choosing
347 /// [`Mmc3Variant::Mmc6`] replaces the board PRG-RAM with the 1 KiB
348 /// internal RAM, and a later call with another variant does not restore
349 /// the original allocation.
350 #[must_use]
351 pub fn with_variant(mut self, variant: Mmc3Variant) -> Self {
352 self.variant = variant;
353 if variant == Mmc3Variant::Mmc6 {
354 self.prg_ram = vec![0u8; MMC6_RAM].into_boxed_slice();
355 }
356 self
357 }
358
359 /// MMC6: which half `$7000-$7FFF` addresses, and its read / write
360 /// enables. Half 0 is `$7000-$71FF` (bits 5 / 4), half 1 `$7200-$73FF`
361 /// (bits 7 / 6), mirrored through `$7FFF`.
362 const fn mmc6_half(&self, addr: u16) -> (usize, bool, bool) {
363 let high = addr & 0x0200 != 0;
364 let (r, w) = if high { (0x80, 0x40) } else { (0x20, 0x10) };
365 (
366 addr as usize & (MMC6_RAM - 1),
367 self.mmc6_protect & r != 0,
368 self.mmc6_protect & w != 0,
369 )
370 }
371
372 /// MMC6: the `$7000-$7FFF` window floats when RAM is disabled or neither
373 /// half is readable.
374 const fn mmc6_window_open(&self) -> bool {
375 !self.mmc6_ram_enabled || self.mmc6_protect & 0xA0 == 0
376 }
377
378 /// An MMC3 used only as a register file and IRQ counter, for boards that
379 /// resolve PRG and CHR themselves (`mmc3_boards.rs`, v2.9.6).
380 ///
381 /// Those boards put an outer bank or a CHR-RAM overlay between the MMC3's
382 /// bank outputs and the memories, so they own the ROM and read the raw
383 /// outputs through [`Self::prg_bank_raw`] and [`Self::chr_bank_1k`]. The
384 /// core therefore carries only placeholder memories: 8 KiB of PRG and
385 /// 1 KiB of CHR it never reads, no PRG-RAM, and the real nametable VRAM
386 /// (which the board delegates to it). Nothing in the Nintendo MMC3 path
387 /// calls this, so mapper 4 is unchanged.
388 pub(crate) fn register_core(mirroring: Mirroring, revision: Mmc3Revision) -> Self {
389 // Both sizes are valid by construction, so `new` cannot fail.
390 let mut core = Self::new(
391 vec![0u8; PRG_BANK_8K].into_boxed_slice(),
392 vec![0u8; CHR_BANK_1K].into_boxed_slice(),
393 mirroring,
394 PRG_RAM_DEFAULT,
395 revision,
396 )
397 .unwrap_or_else(|_| unreachable!("fixed valid sizes"));
398 core.prg_ram = Box::new([]);
399 core
400 }
401
402 /// The MMC3's raw 8 KiB PRG bank output for the CPU address `addr`
403 /// (`$8000-$FFFF`), before any board masking: R6 / R7 as written, and
404 /// the fixed banks as the chip drives them, all-ones (`$FF`) for the last
405 /// and `$FE` for the second-last. A multicart ANDs this with its inner
406 /// mask and ORs its outer bank in, which is why the fixed banks must be
407 /// the chip's own all-ones pattern and not "last bank of the ROM"
408 /// (`nesdev_wiki/output/INES_Mapper_045.md`, "PRG-AND").
409 pub(crate) fn prg_bank_raw(&self, addr: u16) -> u8 {
410 match (addr & 0xE000, self.prg_mode) {
411 (0x8000, false) | (0xC000, true) => self.regs[6],
412 (0x8000, true) | (0xC000, false) => 0xFE,
413 (0xA000, _) => self.regs[7],
414 _ => 0xFF,
415 }
416 }
417
418 /// `$A001` bit 7: the PRG-RAM chip enable. Boards that put a register in
419 /// the PRG-RAM window (mappers 37 and 47) accept a write only while the
420 /// MMC3 would let it reach RAM.
421 pub(crate) const fn prg_ram_enabled(&self) -> bool {
422 self.prg_ram_enabled
423 }
424
425 /// `$A001` bit 7 set and bit 6 clear: a write to `$6000-$7FFF` would
426 /// reach RAM.
427 pub(crate) const fn prg_ram_writable(&self) -> bool {
428 self.prg_ram_enabled && !self.prg_ram_protect
429 }
430
431 /// Resolve a CPU PRG address (`$8000-$FFFF`) to a byte offset in
432 /// `prg_rom`. Implements PRG modes 0 and 1.
433 fn prg_offset(&self, addr: u16) -> usize {
434 let total_banks = self.prg_rom.len() / PRG_BANK_8K;
435 let last = total_banks.saturating_sub(1);
436 let second_last = total_banks.saturating_sub(2);
437 // R6/R7 are masked to total_banks (typical sizes <= 64 banks => 6 bits).
438 let r6 = (self.regs[6] as usize) & last;
439 let r7 = (self.regs[7] as usize) & last;
440 let bank = match (addr & 0xE000, self.prg_mode) {
441 (0x8000, false) => r6,
442 (0x8000, true) => second_last,
443 (0xA000, _) => r7,
444 (0xC000, false) => second_last,
445 (0xC000, true) => r6,
446 (0xE000, _) => last,
447 _ => 0,
448 };
449 bank * PRG_BANK_8K + ((addr as usize) & 0x1FFF)
450 }
451
452 /// Resolve a PPU CHR address (`$0000-$1FFF`) to the **raw** 1 KiB CHR
453 /// bank number selected by the active CHR bank registers, *before* any
454 /// masking against the installed CHR size.
455 ///
456 /// This is the value a TQROM-style board (mapper 119) inspects: bit 6
457 /// of the bank number selects CHR-RAM vs CHR-ROM, and the low bits
458 /// index within the selected memory. The MMC3 itself never exposes this
459 /// distinction (it masks the bank straight into a single CHR slice), so
460 /// the helper is provided for variant boards that embed an [`Mmc3`].
461 #[must_use]
462 pub fn chr_bank_1k(&self, addr: u16) -> usize {
463 let addr = (addr & 0x1FFF) as usize;
464 let slot = addr / CHR_BANK_1K;
465 self.chr_bank_1k_for_slot(slot)
466 }
467
468 /// The raw (unmasked) 1 KiB CHR bank number selected for `slot` (0..8),
469 /// honoring the current CHR-A12-inversion mode. Shared by [`Self::chr_offset`]
470 /// and [`Self::chr_bank_1k`].
471 fn chr_bank_1k_for_slot(&self, slot: usize) -> usize {
472 if !self.chr_mode {
473 match slot {
474 0 => (self.regs[0] as usize) & !1, // 2K @ $0000
475 1 => ((self.regs[0] as usize) & !1) | 1,
476 2 => (self.regs[1] as usize) & !1, // 2K @ $0800
477 3 => ((self.regs[1] as usize) & !1) | 1,
478 4 => self.regs[2] as usize, // 1K @ $1000
479 5 => self.regs[3] as usize,
480 6 => self.regs[4] as usize,
481 7 => self.regs[5] as usize,
482 _ => 0,
483 }
484 } else {
485 match slot {
486 0 => self.regs[2] as usize, // 1K @ $0000
487 1 => self.regs[3] as usize,
488 2 => self.regs[4] as usize,
489 3 => self.regs[5] as usize,
490 4 => (self.regs[0] as usize) & !1, // 2K @ $1000
491 5 => ((self.regs[0] as usize) & !1) | 1,
492 6 => (self.regs[1] as usize) & !1, // 2K @ $1800
493 7 => ((self.regs[1] as usize) & !1) | 1,
494 _ => 0,
495 }
496 }
497 }
498
499 /// Resolve a PPU CHR address (`$0000-$1FFF`) to an offset in `chr`.
500 fn chr_offset(&self, addr: u16) -> usize {
501 let addr = (addr & 0x1FFF) as usize;
502 let total_banks_1k = self.chr.len() / CHR_BANK_1K;
503 let mask = total_banks_1k.saturating_sub(1);
504 // Slot index in 1K units (0..8).
505 let slot = addr / CHR_BANK_1K;
506 // chr_mode false (0): 2K + 2K + 1K + 1K + 1K + 1K
507 // R0/R0+1 R1/R1+1 R2 R3 R4 R5
508 // chr_mode true (1): 1K + 1K + 1K + 1K + 2K + 2K
509 // R2 R3 R4 R5 R0/R0+1 R1/R1+1
510 let bank_1k = self.chr_bank_1k_for_slot(slot);
511 let bank = bank_1k & mask;
512 bank * CHR_BANK_1K + (addr & (CHR_BANK_1K - 1))
513 }
514
515 /// Compute the CIRAM byte offset for a PPU address in
516 /// `$2000-$3EFF`. Honors per-mapper mirroring; supports four-screen
517 /// (the extra 2 KiB lives in our `vram`).
518 fn nametable_offset(&self, addr: u16) -> usize {
519 let table = (((addr - 0x2000) / NAMETABLE_SIZE_U16) & 0x03) as u8;
520 let local = (addr as usize) & (NAMETABLE_SIZE - 1);
521 if self.fixed_4screen {
522 (table as usize) * NAMETABLE_SIZE + local
523 } else {
524 let physical = self.mirroring.physical_bank(table);
525 physical * NAMETABLE_SIZE + local
526 }
527 }
528
529 /// Clock the IRQ counter on a filtered A12 rising edge, and report
530 /// whether the IRQ should assert.
531 ///
532 /// The NESdev MMC3 page's rule: "When the IRQ is clocked (filtered A12
533 /// 0→1), the counter value is checked - if zero or the reload flag is
534 /// true, it's reloaded with the IRQ latched value at $C000; otherwise, it
535 /// decrements. If the IRQ counter is zero and IRQs are enabled ($E001),
536 /// an IRQ is triggered." That is the Sharp chip; the NEC chip asserts
537 /// only on a decrement to zero, not on a reload that leaves the counter
538 /// at zero ("Old/alternate behavior").
539 ///
540 /// 1. `irq_reload_pending` (set by a `$C001` write): reload from
541 /// `irq_reload_value`, clear the flag.
542 /// 2. Counter already zero: reload from `irq_reload_value`.
543 /// 3. Otherwise: decrement.
544 ///
545 /// After any of the three, Sharp asserts when the counter is zero and
546 /// IRQs are enabled. The alternate revision (`Nec`) asserts after path 3,
547 /// and after path 1 when the reloaded value is 0, but never after path 2.
548 ///
549 /// **Changed in v3.1.0 (`T-MMC3-NEC-OVERRIDE`).** The alternate revision
550 /// asserted only after path 3. `MMC3.md`: "The 'alternate revision' checks
551 /// the IRQ counter transition 1→0, whether from decrementing or
552 /// reloading", and "writing to $C001 with $C000 still at $00 will result in
553 /// another single IRQ being generated". blargg's `mmc3_test_2/6-MMC3_alt`
554 /// says the same ("IRQ should be set when reloading due to clear, even if
555 /// counter was already 0") and could not run until v3.1.0 added a way to
556 /// select this revision for an iNES 1.0 ROM; it failed there on exactly
557 /// this, and passes now. The Sharp path is unchanged.
558 ///
559 /// **Changed in v2.9.9 (T-ORACLE-001).** Path 1 used to assert only when
560 /// the `$C001` write had cleared a non-zero counter (a latch named
561 /// `irq_reload_pending_with_nonzero_clear`). The page has no such
562 /// condition; the latch existed so `4-scanline_timing` sub-test 2 would
563 /// pass, and it did so by raising the IRQ a scanline late, which is what
564 /// failed sub-test 3. With the IRQ output deferred to the next per-cycle hook
565 /// (`irq_assert_pending_next_cycle`) the page's rule passes sub-test 2
566 /// by itself. ADR 0002 keeps the history of the earlier attempts.
567 fn clock_irq(&mut self) -> bool {
568 let mut would_assert = false;
569 if self.irq_reload_pending {
570 // Path 1: explicit $C001 reload. Both revisions assert when the
571 // reloaded value is 0 (for the alternate one, the single IRQ a
572 // $C001 write with $C000 = $00 produces).
573 self.irq_counter = self.irq_reload_value;
574 self.irq_reload_pending = false;
575 if self.irq_enabled && self.irq_counter == 0 {
576 would_assert = true;
577 }
578 } else if self.irq_counter == 0 {
579 // Path 2: natural counter-at-zero reload. Sharp asserts when
580 // the new value is 0; NEC does not.
581 self.irq_counter = self.irq_reload_value;
582 if self.irq_enabled
583 && self.irq_counter == 0
584 && matches!(self.revision, Mmc3Revision::Sharp)
585 {
586 would_assert = true;
587 }
588 } else {
589 // Path 3: decrement. Assert on transition to 0.
590 self.irq_counter = self.irq_counter.wrapping_sub(1);
591 if self.irq_counter == 0 && self.irq_enabled {
592 would_assert = true;
593 }
594 }
595 would_assert
596 }
597}
598
599impl Mapper for Mmc3 {
600 /// v3.1.0 (`T-MMC3-NEC-OVERRIDE`): `Some` forces the IRQ revision, `None`
601 /// returns to the header's. Applies to mapper 4 itself; the MMC3-derived
602 /// boards that embed this core keep their own revision.
603 fn set_mmc3_revision_override(&mut self, revision: Option<Mmc3Revision>) -> bool {
604 self.revision = revision.unwrap_or(self.header_revision);
605 true
606 }
607
608 fn sram(&self) -> &[u8] {
609 &self.prg_ram
610 }
611 fn sram_mut(&mut self) -> &mut [u8] {
612 &mut self.prg_ram
613 }
614 // v2.8.0 Phase 4 — CPU-cycle hook + IRQ source; no on-cart audio.
615 fn caps(&self) -> MapperCaps {
616 MapperCaps::CYCLE_IRQ
617 }
618
619 fn cpu_read_unmapped(&self, addr: u16) -> bool {
620 match addr {
621 0x4020..=0x5FFF => true,
622 0x6000..=0x6FFF if self.variant == Mmc3Variant::Mmc6 => true,
623 0x7000..=0x7FFF if self.variant == Mmc3Variant::Mmc6 => self.mmc6_window_open(),
624 0x6000..=0x7FFF => self.prg_ram.is_empty(),
625 _ => false,
626 }
627 }
628
629 fn cpu_read(&mut self, addr: u16) -> u8 {
630 match addr {
631 0x6000..=0x7FFF if self.variant == Mmc3Variant::Mmc6 => {
632 // "If only one bank is enabled for reading, the other reads
633 // back as zero" (`MMC6.md`).
634 let (off, readable, _) = self.mmc6_half(addr);
635 if addr >= 0x7000 && readable && self.mmc6_ram_enabled {
636 self.prg_ram[off]
637 } else {
638 0
639 }
640 }
641 0x6000..=0x7FFF => {
642 if self.prg_ram_enabled && !self.prg_ram.is_empty() {
643 let off = (addr - 0x6000) as usize;
644 if off < self.prg_ram.len() {
645 return self.prg_ram[off];
646 }
647 }
648 0
649 }
650 0x8000..=0xFFFF => {
651 let off = self.prg_offset(addr);
652 self.prg_rom[off % self.prg_rom.len()]
653 }
654 _ => 0,
655 }
656 }
657
658 fn cpu_write(&mut self, addr: u16, value: u8) {
659 match addr {
660 0x6000..=0x7FFF if self.variant == Mmc3Variant::Mmc6 => {
661 // "The write-enable bits only have effect if that bank is
662 // enabled for reading".
663 let (off, readable, writable) = self.mmc6_half(addr);
664 if addr >= 0x7000 && self.mmc6_ram_enabled && readable && writable {
665 self.prg_ram[off] = value;
666 }
667 }
668 0x6000..=0x7FFF => {
669 if self.prg_ram_enabled && !self.prg_ram_protect && !self.prg_ram.is_empty() {
670 let off = (addr - 0x6000) as usize;
671 if off < self.prg_ram.len() {
672 self.prg_ram[off] = value;
673 }
674 }
675 }
676 0x8000..=0x9FFF => {
677 if addr & 1 == 0 {
678 // $8000 even: bank-select.
679 self.bank_select = value & 0x07;
680 self.prg_mode = (value & 0x40) != 0;
681 self.chr_mode = (value & 0x80) != 0;
682 if self.variant == Mmc3Variant::Mmc6 {
683 // "When PRG RAM is disabled via $8000, the mapper
684 // continuously sets $A001 to $00".
685 self.mmc6_ram_enabled = value & 0x20 != 0;
686 if !self.mmc6_ram_enabled {
687 self.mmc6_protect = 0;
688 }
689 }
690 } else {
691 // $8001 odd: bank-data.
692 self.regs[(self.bank_select & 0x07) as usize] = value;
693 }
694 }
695 0xA000..=0xBFFF => {
696 if addr & 1 == 0 {
697 // Mirroring (ignored on 4-screen carts and on the
698 // hard-wired MMC3C board, submapper 2).
699 if !self.fixed_4screen && self.variant != Mmc3Variant::HardwiredMirroring {
700 self.mirroring = if value & 1 == 0 {
701 Mirroring::Vertical
702 } else {
703 Mirroring::Horizontal
704 };
705 }
706 } else if self.variant == Mmc3Variant::Mmc6 {
707 if self.mmc6_ram_enabled {
708 self.mmc6_protect = value & 0xF0;
709 }
710 } else {
711 // PRG-RAM protect / enable.
712 self.prg_ram_enabled = (value & 0x80) != 0;
713 self.prg_ram_protect = (value & 0x40) != 0;
714 }
715 }
716 0xC000..=0xDFFF => {
717 if addr & 1 == 0 {
718 self.irq_reload_value = value;
719 } else {
720 // $C001: clear the counter; the next filtered A12 rise
721 // reloads it (`clock_irq` path 1).
722 self.irq_counter = 0;
723 self.irq_reload_pending = true;
724 // MC-ACC: "Writing to $C001 resets pulse counter".
725 self.mcacc_prescaler = 0;
726 }
727 }
728 0xE000..=0xFFFF => {
729 if addr & 1 == 0 {
730 self.irq_enabled = false;
731 self.irq_pending_line = false;
732 // The ack/disable also cancels an assertion still in
733 // flight: it is the same IRQ line, one cycle earlier.
734 self.irq_assert_pending_next_cycle = false;
735 } else {
736 self.irq_enabled = true;
737 }
738 }
739 _ => {}
740 }
741 }
742
743 fn chr_phys(&self, addr: u16) -> Option<u32> {
744 if self.chr_is_ram {
745 None
746 } else {
747 // The same per-bank offset `ppu_read` resolves (the 2/4 KiB MMC3 banks).
748 u32::try_from(self.chr_offset(addr & 0x1FFF) % self.chr.len().max(1)).ok()
749 }
750 }
751
752 fn ppu_read(&mut self, addr: u16) -> u8 {
753 let addr = addr & 0x3FFF;
754 match addr {
755 0x0000..=0x1FFF => {
756 let off = self.chr_offset(addr);
757 self.chr[off % self.chr.len()]
758 }
759 0x2000..=0x3EFF => self.vram[self.nametable_offset(addr) % self.vram.len()],
760 _ => 0,
761 }
762 }
763
764 fn ppu_write(&mut self, addr: u16, value: u8) {
765 let addr = addr & 0x3FFF;
766 match addr {
767 0x0000..=0x1FFF => {
768 if self.chr_is_ram {
769 let off = self.chr_offset(addr);
770 let len = self.chr.len();
771 self.chr[off % len] = value;
772 }
773 }
774 0x2000..=0x3EFF => {
775 let off = self.nametable_offset(addr) % self.vram.len();
776 self.vram[off] = value;
777 }
778 _ => {}
779 }
780 }
781
782 fn nametable_address(&self, addr: u16) -> u16 {
783 // For 2 KiB CIRAM (the bus's PPU vram) the offset must fit in 0..0x800.
784 // For 4-screen we keep the full 4 KiB on-cart and serve via ppu_read/write,
785 // so the bus's CIRAM index does not matter — just return a canonical 0.
786 let off = self.nametable_offset(addr);
787 u16::try_from(off & 0x07FF).unwrap_or(0)
788 }
789
790 fn current_mirroring(&self) -> Mirroring {
791 self.mirroring
792 }
793
794 fn notify_a12(&mut self, level: bool) {
795 // Plumbing is in place to receive the sub-dot via
796 // `notify_a12_at_sub_dot`, but the MMC3 implementation does not
797 // yet differentiate behavior by M2 phase — see ADR-0002 →
798 // "Sub-dot plumbing landed (2026-05-14)" for the open
799 // implementation choice. This legacy entry point treats the
800 // unknown sub-dot as M2-low (immediate assertion), matching
801 // the pre-M2-phase-pipeline behavior.
802 self.notify_a12_at_sub_dot(level, 1);
803 }
804
805 fn notify_a12_at_sub_dot(&mut self, level: bool, sub_dot: u8) {
806 // Track the M2-cycles-since-last-fall filter. A rising edge that
807 // arrives < 3 CPU cycles after the prior fall is filtered.
808 //
809 //
810 // `sub_dot` (the M2 half of the CPU cycle the rise landed in) is read
811 // only by the `mmc3-a12-phase-probe` tally below. Until v2.9.9 the
812 // `mmc3-m2-phase-irq` experiment also used it to defer M2-high rises.
813 // The deferral that replaced it (`irq_assert_pending_next_cycle`)
814 // needs no phase: the bus's order gives it the same split (see that
815 // field's doc and ADR 0002's 2026-10-05 correction).
816 #[cfg(not(feature = "mmc3-a12-phase-probe"))]
817 let _ = sub_dot;
818 if self.variant == Mmc3Variant::McAcc {
819 // Falling edges through the divide-by-8 prescaler; the counter
820 // clocks on the first edge of each group of eight.
821 if self.last_a12 && !level {
822 if self.mcacc_prescaler == 0 && self.clock_irq() {
823 self.irq_pending_line = true;
824 }
825 self.mcacc_prescaler = (self.mcacc_prescaler + 1) & 0x07;
826 }
827 self.last_a12 = level;
828 return;
829 }
830 if !self.last_a12 && level {
831 // Rising edge.
832 let gap = self.cpu_cycle.saturating_sub(self.a12_low_cycle);
833 // Restructured from the original `if gap >= 3 && self.clock_irq()`
834 // into a nested form so the v2.1.5 F5.0 probe can observe the
835 // *qualifying* rise (gap accepted) independently of whether it went
836 // on to clock the counter. This is behavior-identical: `clock_irq`
837 // (which mutates) is still only evaluated when `gap >= 3`, exactly
838 // the short-circuit the original `&&` provided.
839 if gap >= 3 {
840 // v2.1.5 F5.0 instrumentation study (observational only — no
841 // emulated-state change): bucket this qualifying A12 rise by the
842 // M2-phase half of the host CPU cycle it landed in. See ADR 0002
843 // F5.0. `sub_dot` carries real phase data only when the paired
844 // `rustynes-core/mmc3-a12-phase-probe` seeds it on the live
845 // one-clock scheduler path.
846 #[cfg(feature = "mmc3-a12-phase-probe")]
847 {
848 if sub_dot >= 2 {
849 self.probe.qual_rises_post += 1;
850 } else {
851 self.probe.qual_rises_pre += 1;
852 }
853 }
854 if self.clock_irq() {
855 // Of the qualifying rises, count those that actually clocked
856 // the counter into an IRQ-asserting state, by phase half —
857 // the strictest form of the study's question.
858 #[cfg(feature = "mmc3-a12-phase-probe")]
859 {
860 if sub_dot >= 2 {
861 self.probe.irq_clock_post += 1;
862 } else {
863 self.probe.irq_clock_pre += 1;
864 }
865 }
866 // Seen by the CPU from the next cycle on; see the field
867 // doc on `irq_assert_pending_next_cycle`.
868 self.irq_assert_pending_next_cycle = true;
869 }
870 }
871 } else if self.last_a12 && !level {
872 // Falling edge.
873 self.a12_low_cycle = self.cpu_cycle;
874 }
875 self.last_a12 = level;
876 }
877
878 fn notify_cpu_cycle(&mut self) {
879 self.cpu_cycle = self.cpu_cycle.wrapping_add(1);
880 if self.irq_assert_pending_next_cycle {
881 self.irq_assert_pending_next_cycle = false;
882 self.irq_pending_line = true;
883 }
884 }
885
886 fn irq_pending(&self) -> bool {
887 self.irq_pending_line
888 }
889
890 fn irq_acknowledge(&mut self) {
891 // Hardware: the IRQ line stays asserted until $E000 disables / acks.
892 // The CPU's interrupt service does not clear it; the program does.
893 // We expose ack as a no-op to satisfy the trait but $E000 is the
894 // real path.
895 }
896
897 fn debug_info(&self) -> crate::mapper::MapperDebugInfo {
898 let mut info = crate::mapper::MapperDebugInfo {
899 mapper_id: 4,
900 name: format!("MMC3 ({:?})", self.revision),
901 mirroring: crate::mapper::mirroring_name(self.mirroring),
902 ..Default::default()
903 };
904 info.prg_banks
905 .push(("mode".into(), format!("{}", u8::from(self.prg_mode))));
906 info.prg_banks
907 .push(("R6".into(), format!("{:#04x}", self.regs[6])));
908 info.prg_banks
909 .push(("R7".into(), format!("{:#04x}", self.regs[7])));
910 info.chr_banks
911 .push(("mode".into(), format!("{}", u8::from(self.chr_mode))));
912 for i in 0..6 {
913 info.chr_banks
914 .push((format!("R{i}"), format!("{:#04x}", self.regs[i])));
915 }
916 info.irq_state
917 .push(("counter".into(), format!("{:#04x}", self.irq_counter)));
918 info.irq_state
919 .push(("reload".into(), format!("{:#04x}", self.irq_reload_value)));
920 info.irq_state
921 .push(("enabled".into(), format!("{}", self.irq_enabled)));
922 info.irq_state
923 .push(("pending".into(), format!("{}", self.irq_pending_line)));
924 info.extra
925 .push(("bank_select".into(), format!("{:#04x}", self.bank_select)));
926 info.extra.push((
927 "prg_ram".into(),
928 format!("en={} prot={}", self.prg_ram_enabled, self.prg_ram_protect),
929 ));
930 // v2.1.5 F5.0 instrumentation study: surface the observational A12-phase
931 // tallies so the `mmc3_r1r2_phase_probe` fixture can read them after a
932 // run without new trait methods. Present only under the (default-off)
933 // probe feature — the shipped `debug_state` is unchanged. See ADR 0002.
934 #[cfg(feature = "mmc3-a12-phase-probe")]
935 {
936 info.extra.push((
937 "probe_qual_pre".into(),
938 format!("{}", self.probe.qual_rises_pre),
939 ));
940 info.extra.push((
941 "probe_qual_post".into(),
942 format!("{}", self.probe.qual_rises_post),
943 ));
944 info.extra.push((
945 "probe_irq_pre".into(),
946 format!("{}", self.probe.irq_clock_pre),
947 ));
948 info.extra.push((
949 "probe_irq_post".into(),
950 format!("{}", self.probe.irq_clock_post),
951 ));
952 }
953 info
954 }
955
956 fn save_state(&self) -> Vec<u8> {
957 // Tagged blob: version + scalar regs + RAM blocks.
958 let mut out =
959 Vec::with_capacity(64 + self.prg_ram.len() + self.vram.len() + self.chr.len());
960 out.push(SAVE_STATE_VERSION);
961 out.extend_from_slice(&self.regs);
962 out.push(self.bank_select);
963 out.push(u8::from(self.prg_mode));
964 out.push(u8::from(self.chr_mode));
965 out.push(self.mirroring as u8);
966 out.push(u8::from(self.fixed_4screen));
967 out.push(u8::from(self.prg_ram_enabled));
968 out.push(u8::from(self.prg_ram_protect));
969 out.push(self.irq_counter);
970 out.push(self.irq_reload_value);
971 out.push(u8::from(self.irq_reload_pending));
972 out.push(u8::from(self.irq_assert_pending_next_cycle));
973 out.push(u8::from(self.irq_enabled));
974 out.push(u8::from(self.irq_pending_line));
975 out.push(u8::from(self.last_a12));
976 out.extend_from_slice(&self.a12_low_cycle.to_le_bytes());
977 out.extend_from_slice(&self.cpu_cycle.to_le_bytes());
978 out.push(match self.revision {
979 Mmc3Revision::Sharp => 0,
980 Mmc3Revision::Nec => 1,
981 });
982 out.extend_from_slice(&self.prg_ram);
983 out.extend_from_slice(&self.vram);
984 if self.chr_is_ram {
985 out.extend_from_slice(&self.chr);
986 }
987 // v3 tail.
988 out.push(u8::from(self.mmc6_ram_enabled));
989 out.push(self.mmc6_protect);
990 out.push(self.mcacc_prescaler);
991 out
992 }
993
994 #[allow(clippy::too_many_lines)] // tagged-blob deserializer + v1/v2 fork
995 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
996 let chr_part = if self.chr_is_ram { self.chr.len() } else { 0 };
997 // Tagged scalars laid out below. Only the current version is read
998 // (v2.9.8, ADR 0042): v1 and v2 used to load with defaults. v3
999 // (v2.9.8) is refused too: its byte 19 was the retired
1000 // `irq_reload_pending_with_nonzero_clear` latch, which v4 replaces
1001 // with `irq_assert_pending_next_cycle` (T-ORACLE-001).
1002 if data.is_empty() {
1003 return Err(MapperError::WrongLength {
1004 expected: 1,
1005 got: 0,
1006 });
1007 }
1008 let version = data[0];
1009 if version != SAVE_STATE_VERSION {
1010 return Err(MapperError::UnsupportedVersion(version));
1011 }
1012 let tail = 3;
1013 let scalar_len = 1 + 8 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 8 + 8 + 1 + 1;
1014 let expected = scalar_len + self.prg_ram.len() + self.vram.len() + chr_part + tail;
1015 if data.len() != expected {
1016 return Err(MapperError::WrongLength {
1017 expected,
1018 got: data.len(),
1019 });
1020 }
1021 self.regs.copy_from_slice(&data[1..9]);
1022 self.bank_select = data[9];
1023 self.prg_mode = data[10] != 0;
1024 self.chr_mode = data[11] != 0;
1025 self.mirroring = match data[12] {
1026 0 => Mirroring::Horizontal,
1027 1 => Mirroring::Vertical,
1028 2 => Mirroring::SingleScreenA,
1029 3 => Mirroring::SingleScreenB,
1030 4 => Mirroring::FourScreen,
1031 5 => Mirroring::MapperControlled,
1032 other => {
1033 return Err(MapperError::Invalid(format!(
1034 "unknown mirroring tag {other}"
1035 )));
1036 }
1037 };
1038 self.fixed_4screen = data[13] != 0;
1039 self.prg_ram_enabled = data[14] != 0;
1040 self.prg_ram_protect = data[15] != 0;
1041 self.irq_counter = data[16];
1042 self.irq_reload_value = data[17];
1043 self.irq_reload_pending = data[18] != 0;
1044 let mut cur = 19usize;
1045 self.irq_assert_pending_next_cycle = data[cur] != 0;
1046 cur += 1;
1047 self.irq_enabled = data[cur] != 0;
1048 cur += 1;
1049 self.irq_pending_line = data[cur] != 0;
1050 cur += 1;
1051 self.last_a12 = data[cur] != 0;
1052 cur += 1;
1053 self.a12_low_cycle = u64::from_le_bytes(
1054 data[cur..cur + 8]
1055 .try_into()
1056 .map_err(|_| MapperError::Invalid("a12_low_cycle truncated".into()))?,
1057 );
1058 cur += 8;
1059 self.cpu_cycle = u64::from_le_bytes(
1060 data[cur..cur + 8]
1061 .try_into()
1062 .map_err(|_| MapperError::Invalid("cpu_cycle truncated".into()))?,
1063 );
1064 cur += 8;
1065 self.revision = match data[cur] {
1066 0 => Mmc3Revision::Sharp,
1067 1 => Mmc3Revision::Nec,
1068 other => {
1069 return Err(MapperError::Invalid(format!(
1070 "unknown MMC3 revision tag {other}"
1071 )));
1072 }
1073 };
1074 cur += 1;
1075 self.prg_ram
1076 .copy_from_slice(&data[cur..cur + self.prg_ram.len()]);
1077 cur += self.prg_ram.len();
1078 self.vram.copy_from_slice(&data[cur..cur + self.vram.len()]);
1079 cur += self.vram.len();
1080 if self.chr_is_ram {
1081 self.chr.copy_from_slice(&data[cur..cur + self.chr.len()]);
1082 cur += self.chr.len();
1083 }
1084 self.mmc6_ram_enabled = data[cur] != 0;
1085 self.mmc6_protect = data[cur + 1] & 0xF0;
1086 self.mcacc_prescaler = data[cur + 2] & 0x07;
1087 Ok(())
1088 }
1089}
1090
1091#[cfg(test)]
1092#[allow(clippy::cast_possible_truncation)]
1093mod tests {
1094 use super::*;
1095
1096 fn synth_prg(banks_8k: usize) -> Box<[u8]> {
1097 let mut v = vec![0u8; banks_8k * PRG_BANK_8K];
1098 for b in 0..banks_8k {
1099 v[b * PRG_BANK_8K] = b as u8;
1100 }
1101 v.into_boxed_slice()
1102 }
1103
1104 fn synth_chr(banks_1k: usize) -> Box<[u8]> {
1105 let mut v = vec![0u8; banks_1k * CHR_BANK_1K];
1106 for b in 0..banks_1k {
1107 v[b * CHR_BANK_1K] = b as u8;
1108 }
1109 v.into_boxed_slice()
1110 }
1111
1112 fn fresh(prg_banks: usize, chr_banks: usize) -> Mmc3 {
1113 Mmc3::new(
1114 synth_prg(prg_banks),
1115 synth_chr(chr_banks),
1116 Mirroring::Horizontal,
1117 0,
1118 Mmc3Revision::Sharp,
1119 )
1120 .unwrap()
1121 }
1122
1123 #[test]
1124 fn last_8k_bank_fixed_at_e000() {
1125 let mut m = fresh(8, 8);
1126 // Default state: PRG mode 0, R6=R7=0. $E000 should map to last bank.
1127 assert_eq!(m.cpu_read(0xE000), 7);
1128 }
1129
1130 #[test]
1131 fn second_to_last_bank_fixed_at_c000_in_mode0() {
1132 let mut m = fresh(8, 8);
1133 m.cpu_write(0x8000, 0); // mode 0
1134 assert_eq!(m.cpu_read(0xC000), 6);
1135 }
1136
1137 #[test]
1138 fn r6_swaps_8000_in_mode0_and_c000_in_mode1() {
1139 let mut m = fresh(8, 8);
1140 m.cpu_write(0x8000, 6); // select R6
1141 m.cpu_write(0x8001, 3); // R6 = 3
1142 // Mode 0: $8000 -> R6 = bank 3
1143 assert_eq!(m.cpu_read(0x8000), 3);
1144 // Mode 1: $8000 -> second-to-last (bank 6); $C000 -> R6 = bank 3.
1145 m.cpu_write(0x8000, 0x40 | 6); // PRG mode bit
1146 assert_eq!(m.cpu_read(0x8000), 6);
1147 assert_eq!(m.cpu_read(0xC000), 3);
1148 }
1149
1150 #[test]
1151 fn chr_mode0_layout_2k_2k_1k_1k_1k_1k() {
1152 let mut m = fresh(8, 8);
1153 m.cpu_write(0x8000, 0); // R0
1154 m.cpu_write(0x8001, 4); // R0 = 4 (LSB ignored, so bank 4)
1155 m.cpu_write(0x8000, 1); // R1
1156 m.cpu_write(0x8001, 6); // R1 = 6
1157 m.cpu_write(0x8000, 2); // R2
1158 m.cpu_write(0x8001, 1); // R2 = bank 1
1159 // $0000-$03FF (slot 0) -> R0 & ~1 = 4.
1160 assert_eq!(m.ppu_read(0x0000), 4);
1161 // $0400 (slot 1) -> R0 | 1 = 5.
1162 assert_eq!(m.ppu_read(0x0400), 5);
1163 // $0800 (slot 2) -> R1 & ~1 = 6.
1164 assert_eq!(m.ppu_read(0x0800), 6);
1165 // $1000 (slot 4) -> R2 = 1.
1166 assert_eq!(m.ppu_read(0x1000), 1);
1167 }
1168
1169 #[test]
1170 fn chr_mode1_swaps_2k_and_1k_regions() {
1171 let mut m = fresh(8, 8);
1172 m.cpu_write(0x8000, 0x80); // CHR mode 1
1173 m.cpu_write(0x8000, 0x80); // R0
1174 m.cpu_write(0x8001, 4);
1175 m.cpu_write(0x8000, 0x80 | 2);
1176 m.cpu_write(0x8001, 1); // R2
1177 // Mode 1: $0000 (slot 0) -> R2 = 1; $1000 (slot 4) -> R0 & ~1 = 4.
1178 assert_eq!(m.ppu_read(0x0000), 1);
1179 assert_eq!(m.ppu_read(0x1000), 4);
1180 }
1181
1182 #[test]
1183 fn mirroring_register_toggles_h_v() {
1184 let mut m = fresh(8, 8);
1185 m.cpu_write(0xA000, 0);
1186 assert_eq!(m.mirroring, Mirroring::Vertical);
1187 m.cpu_write(0xA000, 1);
1188 assert_eq!(m.mirroring, Mirroring::Horizontal);
1189 }
1190
1191 #[test]
1192 fn prg_ram_enable_protect_via_a001() {
1193 let mut m = fresh(8, 8);
1194 // PRG-RAM defaults to enabled, not protected.
1195 m.cpu_write(0x6000, 0xAB);
1196 assert_eq!(m.cpu_read(0x6000), 0xAB);
1197 // Disable.
1198 m.cpu_write(0xA001, 0x00);
1199 m.cpu_write(0x6000, 0xCD); // ignored
1200 assert_eq!(m.cpu_read(0x6000), 0); // returns 0 (open bus stub)
1201 // Re-enable + write-protect.
1202 m.cpu_write(0xA001, 0x80 | 0x40);
1203 m.cpu_write(0x6000, 0x12); // ignored (protected)
1204 assert_eq!(m.cpu_read(0x6000), 0xAB); // original value preserved
1205 }
1206
1207 #[test]
1208 fn irq_counter_decrements_and_asserts() {
1209 let mut m = fresh(8, 8);
1210 m.cpu_write(0xC000, 3); // reload = 3
1211 m.cpu_write(0xC001, 0); // pending reload
1212 m.cpu_write(0xE001, 0); // enable IRQ
1213 // Simulate four filtered A12 rising edges, advancing CPU cycles
1214 // between each so the M2 filter accepts.
1215 for _ in 0..4 {
1216 // Fall A12 low, advance >= 3 CPU cycles, then raise.
1217 m.notify_a12(false);
1218 for _ in 0..4 {
1219 m.notify_cpu_cycle();
1220 }
1221 m.notify_a12(true);
1222 }
1223 // First edge: reload to 3. Edges 2,3,4: decrement to 2,1,0 (assert),
1224 // visible from the next CPU cycle.
1225 m.notify_cpu_cycle();
1226 assert!(m.irq_pending());
1227 }
1228
1229 #[test]
1230 fn irq_disabled_no_assert() {
1231 let mut m = fresh(8, 8);
1232 m.cpu_write(0xC000, 1);
1233 m.cpu_write(0xC001, 0);
1234 // No $E001 enable.
1235 for _ in 0..3 {
1236 m.notify_a12(false);
1237 for _ in 0..4 {
1238 m.notify_cpu_cycle();
1239 }
1240 m.notify_a12(true);
1241 }
1242 assert!(!m.irq_pending());
1243 }
1244
1245 #[test]
1246 fn e000_acks_pending_irq() {
1247 let mut m = fresh(8, 8);
1248 m.irq_pending_line = true;
1249 m.cpu_write(0xE000, 0);
1250 assert!(!m.irq_pending());
1251 }
1252
1253 #[test]
1254 fn a12_filter_rejects_close_rising_edges() {
1255 let mut m = fresh(8, 8);
1256 m.cpu_write(0xC000, 1);
1257 m.cpu_write(0xC001, 0);
1258 m.cpu_write(0xE001, 0);
1259 // First edge: filter accepts (reload to 1).
1260 m.notify_a12(false);
1261 for _ in 0..4 {
1262 m.notify_cpu_cycle();
1263 }
1264 m.notify_a12(true);
1265 // Now toggle low->high again with only 1 cycle gap: filter REJECTS.
1266 m.notify_a12(false);
1267 m.notify_cpu_cycle();
1268 m.notify_a12(true);
1269 // Counter should still be 1 (only first edge was accepted).
1270 assert_eq!(m.irq_counter, 1);
1271 assert!(!m.irq_pending());
1272 }
1273
1274 /// Helper: emit one filter-accepted A12 toggle (low ≥ 3 M2 cycles
1275 /// then high), then run the CPU cycle after it, from which an IRQ the
1276 /// rise asserted is visible (`irq_assert_pending_next_cycle`). Used by
1277 /// the Sharp/NEC reload-to-zero unit tests below.
1278 fn a12_rise<F: Mapper>(m: &mut F) {
1279 m.notify_a12(false);
1280 for _ in 0..4 {
1281 m.notify_cpu_cycle();
1282 }
1283 m.notify_a12(true);
1284 m.notify_cpu_cycle();
1285 }
1286
1287 /// Sharp asserts IRQ when the counter is decremented to 0 via a
1288 /// natural A12 clock (decrement-to-0 path). This is the primary
1289 /// Sharp/NEC commonality — both revisions assert here. See
1290 /// `clock_irq` path 3 (decrement).
1291 #[test]
1292 fn sharp_asserts_on_decrement_to_zero() {
1293 let mut m = fresh(8, 8);
1294 // Reload value = 1. Pre-condition: counter at 0, reload_pending
1295 // set (from $C001 after start-up).
1296 m.cpu_write(0xC000, 1);
1297 m.cpu_write(0xC001, 0);
1298 m.cpu_write(0xE001, 0);
1299 // First A12 rise: silent reload to 1 (was_nonzero_at_clear = false
1300 // — counter was already 0 at the $C001 write).
1301 a12_rise(&mut m);
1302 assert_eq!(m.irq_counter, 1, "first rise reloaded silently");
1303 assert!(
1304 !m.irq_pending(),
1305 "first $C001-induced reload (counter was 0) must not assert"
1306 );
1307 // Second A12 rise: counter decrements from 1 to 0 → asserts (both
1308 // Sharp and NEC).
1309 a12_rise(&mut m);
1310 assert_eq!(m.irq_counter, 0);
1311 assert!(
1312 m.irq_pending(),
1313 "decrement-to-0 asserts on both Sharp and NEC"
1314 );
1315 }
1316
1317 /// Sharp's Rev-A-specific "reload-to-0 asserts" rule. Distinct from
1318 /// NEC (Rev B) in `nec_does_not_assert_on_reload_to_zero` below.
1319 /// This is the path `mmc3_test_2/5-MMC3.nes` ("set IRQ every clock
1320 /// when reload is 0") exercises in the steady state.
1321 ///
1322 /// Setup: `$C001` clears a non-zero counter; the next A12 rise reloads
1323 /// it to 0 and Sharp asserts. Whether the cleared counter was non-zero
1324 /// does not matter (see the next test); before v2.9.9 it did.
1325 #[test]
1326 fn sharp_asserts_on_reload_to_zero_after_nonzero_clear() {
1327 let mut m = fresh(8, 8);
1328 // Prime the counter to a non-zero value: write reload_value=1,
1329 // $C001 (counter was 0 — silent), one A12 (silent reload to 1).
1330 m.cpu_write(0xC000, 1);
1331 m.cpu_write(0xC001, 0);
1332 m.cpu_write(0xE001, 0);
1333 a12_rise(&mut m);
1334 assert_eq!(m.irq_counter, 1);
1335 assert!(!m.irq_pending(), "silent reload, no assertion");
1336 // Now write reload_value=0 and $C001 again (counter WAS non-zero
1337 // at this write, so the next A12 rise asserts on Sharp).
1338 m.cpu_write(0xC000, 0);
1339 m.cpu_write(0xC001, 0);
1340 // Filter accepts after ≥ 3 M2 cycles; the prior `a12_rise` already
1341 // re-armed the filter low.
1342 a12_rise(&mut m);
1343 assert_eq!(m.irq_counter, 0);
1344 assert!(
1345 m.irq_pending(),
1346 "Sharp asserts on reload-to-0 after non-zero-to-zero $C001 clear"
1347 );
1348 }
1349
1350 /// The page's rule has no "was the cleared counter non-zero" condition:
1351 /// a `$C001` written while the counter is already 0 still reloads on the
1352 /// next rise, and a reload to 0 with IRQs enabled asserts on Sharp.
1353 ///
1354 /// Until v2.9.9 this test pinned the opposite (a "no-op clear" that
1355 /// reloaded silently), which existed only to pass `4-scanline_timing`
1356 /// sub-test 2 and raised that test's IRQ a scanline late
1357 /// (T-ORACLE-001). The IRQ output deferral passes sub-test 2 under
1358 /// the page's rule.
1359 #[test]
1360 fn sharp_asserts_on_reload_to_zero_after_zero_to_zero_clear() {
1361 let mut m = fresh(8, 8);
1362 m.cpu_write(0xC000, 0);
1363 m.cpu_write(0xC001, 0); // counter already 0
1364 m.cpu_write(0xE001, 0);
1365 a12_rise(&mut m);
1366 assert_eq!(m.irq_counter, 0);
1367 assert!(
1368 m.irq_pending(),
1369 "a reload to 0 asserts on Sharp whatever the cleared counter held"
1370 );
1371 m.cpu_write(0xE000, 0);
1372 m.cpu_write(0xE001, 0);
1373 a12_rise(&mut m);
1374 assert!(
1375 m.irq_pending(),
1376 "natural reload-to-0 (no pending reload) asserts on Sharp"
1377 );
1378 }
1379
1380 /// NEC (the "alternate" revision) asserts once on a `$C001` reload to 0,
1381 /// and never on the natural `was_zero` reload. `MMC3.md`: it "generates
1382 /// only a single IRQ when `$C000` is `$00`", and "writing to `$C001` with
1383 /// `$C000` still at `$00` will result in another single IRQ"; blargg's
1384 /// `6-MMC3_alt` fails with "IRQ should be set when reloading due to
1385 /// clear" otherwise. v3.1.0 corrected this: until then the test pinned
1386 /// NEC as silent on both paths.
1387 #[test]
1388 fn nec_asserts_once_on_a_c001_reload_to_zero_and_not_after() {
1389 let mut m = Mmc3::new(
1390 synth_prg(8),
1391 synth_chr(8),
1392 Mirroring::Horizontal,
1393 0,
1394 Mmc3Revision::Nec,
1395 )
1396 .unwrap();
1397 m.cpu_write(0xC000, 1);
1398 m.cpu_write(0xC001, 0);
1399 m.cpu_write(0xE001, 0);
1400 a12_rise(&mut m);
1401 m.cpu_write(0xC000, 0);
1402 m.cpu_write(0xC001, 0);
1403 a12_rise(&mut m);
1404 assert_eq!(m.irq_counter, 0);
1405 assert!(m.irq_pending(), "the $C001 reload to 0 asserts on NEC too");
1406 // Acknowledge, then the natural was_zero reload stays silent.
1407 m.cpu_write(0xE000, 0);
1408 m.cpu_write(0xE001, 0);
1409 assert!(!m.irq_pending());
1410 a12_rise(&mut m);
1411 a12_rise(&mut m);
1412 assert!(!m.irq_pending(), "NEC: the was_zero reload to 0 is silent");
1413 // A second $C001 write with $C000 still 0: another single IRQ.
1414 m.cpu_write(0xC001, 0);
1415 a12_rise(&mut m);
1416 assert!(m.irq_pending(), "each $C001 write gives one more IRQ");
1417 }
1418
1419 /// T-41-005 — reversed pattern-table layout (`PPUCTRL` bit 4 set,
1420 /// bit 3 clear: BG=`$1000`, sprites=`$0000`). Canary: Wario's Woods.
1421 ///
1422 /// In the standard layout (BG=`$0000`, sprites=`$1000`) the per-scanline
1423 /// A12 rising edge happens during the sprite tile fetch group at PPU
1424 /// dot 260, after the BG fetches at dots 1-256 (all of which used the
1425 /// `$0000` pattern table). In the reversed layout the per-scanline A12
1426 /// rising edge happens during the next scanline's BG fetch group at
1427 /// dot 1, after the sprite tile fetches at dots 260-320 (which used the
1428 /// `$0000` pattern table).
1429 ///
1430 /// From MMC3's perspective the two layouts produce **the same sequence
1431 /// of A12 transitions per scanline** — one fall after the prior
1432 /// scanline's BG fetches (or this scanline's sprite fetches), followed
1433 /// by a rise after enough M2 cycles for the filter to accept. The IRQ
1434 /// counter should clock identically. This test exercises both layouts
1435 /// in the same `Mmc3` and asserts the per-rising-edge counter behavior
1436 /// matches.
1437 #[test]
1438 fn reversed_pattern_table_layout_clocks_irq_identically() {
1439 // Helper: emit one filter-accepted A12 rise (low for ≥ 3 M2 cycles,
1440 // then high). Returns the IRQ-pending flag immediately after.
1441 fn pulse_a12(m: &mut Mmc3) -> bool {
1442 m.notify_a12(false);
1443 for _ in 0..4 {
1444 m.notify_cpu_cycle();
1445 }
1446 m.notify_a12(true);
1447 m.notify_cpu_cycle();
1448 m.irq_pending()
1449 }
1450
1451 // Standard layout simulation (BG=$0000, sprites=$1000). Per-scanline:
1452 // 1. BG fetches at dots 1-256 read patterns from $0000-$0FFF (A12 low).
1453 // 2. Sprite tile fetches at dots 260-320 read from $1000-$1FFF (A12
1454 // rises around dot 260).
1455 // 3. After dot 320 the BG fetches for the *next* scanline run at
1456 // dots 321-336 from $0000-$0FFF (A12 falls again).
1457 // We model this as: A12=false (during BG fetches) -> A12=true (sprite
1458 // fetches) per scanline. Filter sees one rise per scanline.
1459 let mut std_layout = fresh(8, 8);
1460 std_layout.cpu_write(0xC000, 4); // reload = 4
1461 std_layout.cpu_write(0xC001, 0); // pending reload
1462 std_layout.cpu_write(0xE001, 0); // enable IRQ
1463 // 5 scanlines: edges 1 (reload 4), 2 (3), 3 (2), 4 (1), 5 (0 + assert).
1464 for n in 0..5 {
1465 let pending = pulse_a12(&mut std_layout);
1466 // Only the 5th edge should assert (counter went 4→reload, then
1467 // 4→3→2→1→0).
1468 assert_eq!(
1469 pending,
1470 n == 4,
1471 "standard layout edge #{n} pending should be {} (counter={})",
1472 n == 4,
1473 std_layout.irq_counter
1474 );
1475 }
1476 std_layout.cpu_write(0xE000, 0); // ack
1477
1478 // Reversed layout simulation (BG=$1000, sprites=$0000). Per-scanline:
1479 // 1. BG fetches at dots 1-256 read patterns from $1000-$1FFF (A12
1480 // high — but the rising edge happened at the start of the BG fetch
1481 // group, not at dot 260).
1482 // 2. Sprite tile fetches at dots 260-320 read from $0000-$0FFF (A12
1483 // falls).
1484 // 3. Next scanline's BG fetches at dots 321-336 read from $1000 again
1485 // (A12 rises).
1486 // From the mapper's perspective: one fall + one rise per scanline,
1487 // just shifted relative to where the rise happens within the
1488 // scanline. The filter behavior is identical.
1489 let mut rev_layout = fresh(8, 8);
1490 rev_layout.cpu_write(0xC000, 4);
1491 rev_layout.cpu_write(0xC001, 0);
1492 rev_layout.cpu_write(0xE001, 0);
1493 for n in 0..5 {
1494 let pending = pulse_a12(&mut rev_layout);
1495 assert_eq!(
1496 pending,
1497 n == 4,
1498 "reversed layout edge #{n} pending should be {} (counter={})",
1499 n == 4,
1500 rev_layout.irq_counter
1501 );
1502 }
1503
1504 // Both layouts must reach the same internal state at the same edge.
1505 assert_eq!(
1506 std_layout.irq_counter, rev_layout.irq_counter,
1507 "standard and reversed layouts must produce identical IRQ counter \
1508 values after the same number of filter-accepted A12 rises"
1509 );
1510 }
1511
1512 /// T-41-005 follow-up — the A12 filter must remain stable against
1513 /// the **fast-low-high** pulse pattern that the reversed layout
1514 /// produces at the boundary between sprite fetches (dot 320, A12
1515 /// low) and the next scanline's BG fetches (dot 321 onward, A12
1516 /// high). Real silicon's 3-M2-cycle filter rejects rises that come
1517 /// less than ~3 CPU cycles after the previous fall. We assert the
1518 /// filter rejects the rise if too few cycles have elapsed AND
1519 /// accepts it when enough have.
1520 #[test]
1521 fn reversed_layout_a12_filter_3_m2_boundary() {
1522 let mut m = fresh(8, 8);
1523 m.cpu_write(0xC000, 2);
1524 m.cpu_write(0xC001, 0);
1525 m.cpu_write(0xE001, 0);
1526
1527 // First rise — filter primes from low state.
1528 m.notify_a12(false);
1529 for _ in 0..4 {
1530 m.notify_cpu_cycle();
1531 }
1532 m.notify_a12(true);
1533 let counter_after_first = m.irq_counter;
1534 assert_eq!(counter_after_first, 2, "first rise reloads to 2");
1535
1536 // Rapid fall+rise with only 1 CPU cycle gap: filter REJECTS.
1537 m.notify_a12(false);
1538 m.notify_cpu_cycle();
1539 m.notify_a12(true);
1540 assert_eq!(
1541 m.irq_counter, counter_after_first,
1542 "rapid rise within < 3 M2 cycles must be filtered out"
1543 );
1544
1545 // Same pulse but with 3 cycles between fall and rise: ACCEPTED.
1546 m.notify_a12(false);
1547 for _ in 0..4 {
1548 m.notify_cpu_cycle();
1549 }
1550 m.notify_a12(true);
1551 assert!(
1552 m.irq_counter < counter_after_first,
1553 "rise after >= 3 M2 cycles must clock the counter; counter={}",
1554 m.irq_counter
1555 );
1556 }
1557
1558 #[test]
1559 fn save_load_round_trip() {
1560 let mut m = fresh(8, 8);
1561 m.cpu_write(0x8000, 6);
1562 m.cpu_write(0x8001, 3);
1563 m.cpu_write(0x8000, 7);
1564 m.cpu_write(0x8001, 5);
1565 m.cpu_write(0xC000, 0x42);
1566 m.cpu_write(0xE001, 0);
1567 let blob = m.save_state();
1568 let mut other = fresh(8, 8);
1569 other.load_state(&blob).unwrap();
1570 assert_eq!(other.regs, m.regs);
1571 assert_eq!(other.bank_select, m.bank_select);
1572 assert_eq!(other.irq_reload_value, m.irq_reload_value);
1573 assert_eq!(other.irq_enabled, m.irq_enabled);
1574 }
1575
1576 // T-ORACLE-001 (v2.9.9): a clocking A12 rise raises the IRQ line at the
1577 // first `notify_cpu_cycle` after it, whatever `sub_dot` the rise carries.
1578 // Which CPU cycle that is comes from the bus's order (see the field doc on
1579 // `irq_assert_pending_next_cycle`): the next test pins both cases.
1580
1581 #[test]
1582 fn irq_becomes_visible_one_cpu_cycle_after_the_rise() {
1583 for sub_dot in [0u8, 2] {
1584 let mut m = fresh(8, 8);
1585 m.cpu_write(0xC000, 1);
1586 m.cpu_write(0xC001, 0);
1587 m.cpu_write(0xE001, 0);
1588 a12_rise(&mut m); // reload to 1, silent
1589 m.notify_a12_at_sub_dot(false, 0);
1590 for _ in 0..4 {
1591 m.notify_cpu_cycle();
1592 }
1593 m.notify_a12_at_sub_dot(true, sub_dot);
1594 assert_eq!(m.irq_counter, 0, "the counter itself moves at the rise");
1595 assert!(
1596 !m.irq_pending(),
1597 "sub_dot {sub_dot}: not visible in the rise's own cycle"
1598 );
1599 m.notify_cpu_cycle();
1600 assert!(
1601 m.irq_pending(),
1602 "sub_dot {sub_dot}: visible from the next cycle"
1603 );
1604 }
1605 }
1606
1607 /// #583 review (Copilot): the deferral is "until the next per-cycle
1608 /// hook", not "one cycle for every rise". In the bus, `start_cycle` runs
1609 /// the pre-access PPU catch-up and then the hook, so a rise caught up
1610 /// there is followed by its own cycle's hook and raised in that cycle,
1611 /// while a rise caught up after the access waits for the next cycle's.
1612 /// Both orders, as the mapper sees them.
1613 #[test]
1614 fn irq_line_is_raised_at_the_first_cpu_cycle_hook_after_the_rise() {
1615 let armed = || {
1616 let mut m = fresh(8, 8);
1617 m.cpu_write(0xC000, 1);
1618 m.cpu_write(0xC001, 0);
1619 m.cpu_write(0xE001, 0);
1620 a12_rise(&mut m); // reload to 1, silent
1621 m.notify_a12(false);
1622 for _ in 0..4 {
1623 m.notify_cpu_cycle();
1624 }
1625 m
1626 };
1627 // Pre-access: the rise, then this cycle's hook in `cpu_clock`.
1628 let mut pre = armed();
1629 pre.notify_a12(true);
1630 pre.notify_cpu_cycle();
1631 assert!(
1632 pre.irq_pending(),
1633 "a pre-access rise is raised in its own cycle"
1634 );
1635 // Post-access: this cycle's hook already ran; the rise comes after it
1636 // and is raised only by the next cycle's.
1637 let mut post = armed();
1638 post.notify_cpu_cycle();
1639 post.notify_a12(true);
1640 assert!(
1641 !post.irq_pending(),
1642 "a post-access rise is not raised in its own cycle"
1643 );
1644 post.notify_cpu_cycle();
1645 assert!(post.irq_pending(), "it is raised from the next cycle on");
1646 }
1647
1648 #[test]
1649 fn e000_ack_cancels_an_assertion_in_flight() {
1650 let mut m = fresh(8, 8);
1651 m.cpu_write(0xC000, 1);
1652 m.cpu_write(0xC001, 0);
1653 m.cpu_write(0xE001, 0);
1654 a12_rise(&mut m);
1655 m.notify_a12(false);
1656 for _ in 0..4 {
1657 m.notify_cpu_cycle();
1658 }
1659 m.notify_a12(true); // assertion queued
1660 assert!(!m.irq_pending());
1661 m.cpu_write(0xE000, 0); // ack/disable before it lands
1662 m.notify_cpu_cycle();
1663 assert!(
1664 !m.irq_pending(),
1665 "an ack write must cancel an in-flight assertion"
1666 );
1667 }
1668
1669 #[test]
1670 fn assertion_in_flight_survives_a_save_state_round_trip() {
1671 let mut m = fresh(8, 8);
1672 m.cpu_write(0xC000, 1);
1673 m.cpu_write(0xC001, 0);
1674 m.cpu_write(0xE001, 0);
1675 a12_rise(&mut m);
1676 m.notify_a12(false);
1677 for _ in 0..4 {
1678 m.notify_cpu_cycle();
1679 }
1680 m.notify_a12(true);
1681 let state = m.save_state();
1682 let mut other = fresh(8, 8);
1683 other.load_state(&state).unwrap();
1684 assert!(!other.irq_pending());
1685 other.notify_cpu_cycle();
1686 assert!(other.irq_pending(), "the queued assertion was restored");
1687 }
1688
1689 // ---- v2.9.6: the NES 2.0 submapper variants --------------------------
1690
1691 fn variant(v: Mmc3Variant) -> Mmc3 {
1692 Mmc3::new(
1693 synth_prg(16),
1694 synth_chr(64),
1695 Mirroring::Vertical,
1696 0,
1697 Mmc3Revision::Sharp,
1698 )
1699 .unwrap()
1700 .with_variant(v)
1701 }
1702
1703 /// `MMC6.md`: RAM at `$7000` only, off until `$8000` bit 5, then per
1704 /// half read/write enables; `$6000-$6FFF` floats.
1705 #[test]
1706 fn mmc6_prg_ram_follows_its_own_protect_scheme() {
1707 let mut m = variant(Mmc3Variant::Mmc6);
1708 assert_eq!(m.sram().len(), MMC6_RAM, "1 KiB of internal RAM");
1709 assert!(m.cpu_read_unmapped(0x6000));
1710 assert!(m.cpu_read_unmapped(0x7000), "RAM disabled at power-on");
1711 m.cpu_write(0xA001, 0xF0);
1712 assert!(
1713 m.cpu_read_unmapped(0x7000),
1714 "$A001 ignored while $8000.5 = 0"
1715 );
1716 m.cpu_write(0x8000, 0x20);
1717 m.cpu_write(0xA001, 0x30); // low half: read + write
1718 m.cpu_write(0x7001, 0x11);
1719 m.cpu_write(0x7201, 0x22); // high half not writable
1720 assert_eq!(m.cpu_read(0x7001), 0x11);
1721 assert_eq!(m.cpu_read(0x7201), 0x00, "the unreadable half reads zero");
1722 assert_eq!(m.cpu_read(0x7401), 0x11, "mirrored every 1 KiB");
1723 m.cpu_write(0xA001, 0x80); // high half readable only
1724 m.cpu_write(0x7201, 0x33);
1725 assert_eq!(m.cpu_read(0x7201), 0x00, "readable but not writable");
1726 m.cpu_write(0xA001, 0x20); // low half read-only
1727 m.cpu_write(0x7001, 0x44);
1728 assert_eq!(m.cpu_read(0x7001), 0x11);
1729 m.cpu_write(0x8000, 0x00); // disable: $A001 forced to $00
1730 m.cpu_write(0x8000, 0x20);
1731 assert!(m.cpu_read_unmapped(0x7000), "the protect bits were cleared");
1732 }
1733
1734 #[test]
1735 fn hardwired_board_ignores_a000() {
1736 let mut m = variant(Mmc3Variant::HardwiredMirroring);
1737 m.cpu_write(0xA000, 1);
1738 assert_eq!(m.current_mirroring(), Mirroring::Vertical);
1739 let mut s = variant(Mmc3Variant::Standard);
1740 s.cpu_write(0xA000, 1);
1741 assert_eq!(s.current_mirroring(), Mirroring::Horizontal);
1742 }
1743
1744 /// MC-ACC: falling A12 edges, /8, first edge of each group, `$C001`
1745 /// resets the prescaler.
1746 #[test]
1747 fn mc_acc_counts_falling_edges_through_a_prescaler() {
1748 let mut m = variant(Mmc3Variant::McAcc);
1749 m.cpu_write(0xC000, 1);
1750 m.cpu_write(0xC001, 0);
1751 m.cpu_write(0xE001, 0);
1752 let fall = |m: &mut Mmc3| {
1753 m.notify_a12(true);
1754 m.notify_a12(false);
1755 };
1756 fall(&mut m); // edge 0 of group 0: reload to 1
1757 assert!(!m.irq_pending());
1758 for _ in 0..7 {
1759 fall(&mut m); // edges 1-7: no clock
1760 }
1761 assert!(!m.irq_pending());
1762 fall(&mut m); // edge 0 of group 1: 1 -> 0, IRQ
1763 assert!(m.irq_pending());
1764 // `$C001` resets the prescaler. Four edges into a group (latch 1,
1765 // reloaded on edge 0), a `$C001` makes the next edge a group start:
1766 // it reloads, and the 9th edge after the write decrements 1 -> 0.
1767 // Without the reset the group would restart only at the 5th edge and
1768 // the IRQ would come at the 13th.
1769 let mut p = variant(Mmc3Variant::McAcc);
1770 p.cpu_write(0xC000, 1);
1771 p.cpu_write(0xE001, 0);
1772 for _ in 0..4 {
1773 fall(&mut p);
1774 }
1775 p.cpu_write(0xC001, 0);
1776 for _ in 0..8 {
1777 fall(&mut p);
1778 }
1779 assert!(!p.irq_pending());
1780 fall(&mut p);
1781 assert!(p.irq_pending(), "the 9th edge after $C001");
1782 // Rising edges alone never clock it.
1783 let mut r = variant(Mmc3Variant::McAcc);
1784 r.cpu_write(0xC000, 0);
1785 r.cpu_write(0xE001, 0);
1786 for _ in 0..16 {
1787 r.notify_a12(true);
1788 r.notify_cpu_cycle();
1789 r.notify_cpu_cycle();
1790 r.notify_cpu_cycle();
1791 r.notify_cpu_cycle();
1792 }
1793 assert!(!r.irq_pending());
1794 }
1795
1796 #[test]
1797 fn variant_state_round_trips_and_v2_is_refused() {
1798 let mut a = variant(Mmc3Variant::Mmc6);
1799 a.cpu_write(0x8000, 0x20);
1800 a.cpu_write(0xA001, 0x30);
1801 a.cpu_write(0x7003, 0x99);
1802 let blob = a.save_state();
1803 let mut b = variant(Mmc3Variant::Mmc6);
1804 b.load_state(&blob).unwrap();
1805 assert_eq!(b.cpu_read(0x7003), 0x99);
1806 assert_eq!(b.save_state(), blob);
1807 // A v2 blob (no tail) is refused since v2.9.8 (ADR 0042); it used to
1808 // load with the MMC6 / MC-ACC state at defaults.
1809 let std_blob = variant(Mmc3Variant::Standard).save_state();
1810 let mut v2 = std_blob;
1811 v2[0] = 2;
1812 v2.truncate(v2.len() - 3);
1813 assert!(matches!(
1814 variant(Mmc3Variant::Standard).load_state(&v2),
1815 Err(MapperError::UnsupportedVersion(2))
1816 ));
1817 }
1818}