rustynes_cpu/cpu.rs
1// SPDX-License-Identifier: GPL-3.0-or-later
2//
3// Provenance: the 6502/2A03 core is RustyNES's own, but the unstable-store opcode group (SHA/SHX/SHY/SHS/TAS — the `SyaSxaAxa` family) is derived from Mesen2 (GPL-3.0-or-later), `Core/NES/NesCpu.h`. See docs/originality-and-provenance.md (Section 1)
4// and NOTICE for the complete, audited derivation record.
5//! Ricoh 2A03 CPU (6502 derivative without BCD mode).
6//!
7//! See `docs/cpu-6502.md` for the spec. The implementation here matches:
8//!
9//! - all 151 documented 6502 opcodes,
10//! - all 105 unofficial / illegal opcodes that real software depends on,
11//! - the 12 JAM / KIL / STP halt opcodes,
12//! - cycle counts including page-crossing penalties on indexed reads, the
13//! `+1 if branch taken / +2 if branch crosses page` branch convention, and
14//! the dummy-read / dummy-write cycles of read-modify-write opcodes,
15//! - NMI (edge), IRQ (level), and BRK with the documented BRK/IRQ B-flag
16//! distinction,
17//! - the `JMP ($XXFF)` indirect page-bug.
18//!
19//! The CPU steps one *instruction* at a time, returning the cycle count. The
20//! `Bus::on_cpu_cycle` callback is invoked once per consumed cycle so the
21//! scheduler / test harness can advance the PPU and count cycles. This is
22//! sufficient for nestest, blargg `instr_test_v5`, `cpu_timing_test`, and
23//! `branch_timing_tests` — the Phase-2 lockstep `tick()` is layered on top in
24//! a later sprint without changing this stepping interface.
25
26// Truncating casts are intentional throughout: this module is byte-arithmetic
27// against the 6502's 8/16-bit register file. `as u8` / `as i8` is the
28// canonical encoding of the wrap behavior the hardware exhibits.
29#![allow(
30 clippy::cast_possible_truncation,
31 clippy::cast_lossless,
32 clippy::cast_possible_wrap,
33 clippy::cast_sign_loss
34)]
35
36use crate::bus::Bus;
37// `M2Phase` is used only by the legacy lockstep interrupt-sampling paths
38// (idle_tick / read1 / write1), which are gated off under the R1 substrate.
39use crate::status::Status;
40
41/// Stack base address: the CPU stack lives at `$0100 + S`.
42const STACK_BASE: u16 = 0x0100;
43
44// v2.0 master-clock R1 substrate constants (Phase 2; `mc-r1-substrate`).
45// The PPU sub-cycle offset and the read/write master-clock split (`pre` in
46// start_cycle, `post` in end_cycle). Mesen `_ppuOffset=1` /
47// `_startClockCount`/`_endClockCount`. Master clocks per CPU cycle are NOT a
48// constant — they are the cartridge region's `cpu_divider` (NTSC 12 / PAL 16 /
49// Dendy 15), read from `bus.cpu_divider()` and fed to `read_split`/
50// `write_split`; the master-clock unit is shared with the bus's `run_ppu_to`,
51// which does the regioned dot conversion off `ppu_divider`.
52/// PPU sub-cycle offset: the PPU is run to `master_clock - PPU_OFFSET` in BOTH
53/// halves of every access (the double catch-up). Mesen `_ppuOffset = 1`.
54const PPU_OFFSET: u64 = 1;
55
56/// The effective PPU-sample offset for `run_ppu_to` (no BP sweep: the constant).
57#[inline]
58const fn ppu_sample_offset() -> u64 {
59 PPU_OFFSET
60}
61/// READ access master-clock split (`+= pre` in `start_cycle`, `+= post` in
62/// `end_cycle`); `pre + post = div`, the region's `cpu_divider`. Derived per
63/// region from `bus.cpu_divider()` so PAL (16) / Dendy (15) get the right
64/// CPU<->PPU phase; for the NTSC divisor 12 these are exactly (5, 7), so the
65/// NTSC path is byte-identical to the prior `const`s.
66#[inline]
67const fn read_split(div: u64) -> (u64, u64) {
68 let pre = div / 2 - PPU_OFFSET;
69 (pre, div - pre)
70}
71/// WRITE access split — swapped (writes commit `2 * PPU_OFFSET` mc later than
72/// reads). NTSC divisor 12 → (7, 5), byte-identical to the prior `const`s.
73#[inline]
74const fn write_split(div: u64) -> (u64, u64) {
75 let pre = div / 2 + PPU_OFFSET;
76 (pre, div - pre)
77}
78
79/// NMI vector low byte address (`$FFFA/B`).
80const NMI_VECTOR: u16 = 0xFFFA;
81
82/// Reset vector low byte address (`$FFFC/D`).
83const RESET_VECTOR: u16 = 0xFFFC;
84
85/// IRQ / BRK vector low byte address (`$FFFE/F`).
86const IRQ_VECTOR: u16 = 0xFFFE;
87
88/// 6502 CPU core.
89//
90// Multiple boolean state bits track distinct interrupt-pipeline stages
91// (jam, NMI pending, NMI armed, IRQ pending, IRQ armed). These map directly
92// onto orthogonal hardware latches; collapsing them into an enum or bitflags
93// would obscure rather than clarify the model.
94#[allow(clippy::struct_excessive_bools)]
95#[derive(Debug, Clone)]
96pub struct Cpu {
97 /// Accumulator.
98 pub a: u8,
99 /// X index register.
100 pub x: u8,
101 /// Y index register.
102 pub y: u8,
103 /// Program counter.
104 pub pc: u16,
105 /// Stack pointer (low byte; effective address `0x0100 | s`).
106 pub s: u8,
107 /// Processor status.
108 pub p: Status,
109 /// Cumulative CPU cycle count.
110 pub cycles: u64,
111 /// `true` when the CPU has executed a JAM/KIL/STP and is waiting for reset.
112 pub jammed: bool,
113 /// Edge-detected NMI latch. Real hardware samples the NMI line at the
114 /// second-to-last cycle of every instruction; if asserted, the NMI
115 /// sequence is queued for AFTER the current instruction completes. Our
116 /// `step` model dispatches all bus operations atomically before ticking
117 /// cycles, so a write that itself raises NMI (e.g. enabling NMI in
118 /// `$2000` while VBL is set) is observed by the bus's edge detector
119 /// during this instruction's cycle tally. Hardware would not have
120 /// observed it at the second-to-last cycle (the write logically happens
121 /// at the LAST cycle), so the NMI is queued for the NEXT instruction's
122 /// sample point and serviced AFTER the next instruction completes.
123 /// We approximate that by promoting `pending_nmi` to `armed_nmi` after
124 /// each instruction; only `armed_nmi` actually services.
125 pub(crate) pending_nmi: bool,
126 /// NMI ready to be serviced before the next instruction starts.
127 pub(crate) armed_nmi: bool,
128 /// IRQ pending — captured edge of the level line that fires once the
129 /// I-flag is clear. Same double-latch promotion as NMI.
130 pub(crate) pending_irq: bool,
131 /// IRQ ready to be serviced before the next instruction starts.
132 pub(crate) armed_irq: bool,
133 /// First tick (within the current instruction's tally loop) at which
134 /// NMI was sampled high; `u8::MAX` if not seen. Used by the
135 /// second-to-last-cycle interrupt classification.
136 pub(crate) nmi_first_tick: u8,
137 /// First tick at which IRQ was sampled high; `u8::MAX` if not seen.
138 pub(crate) irq_first_tick: u8,
139 /// Snapshot of the I flag at the *start* of the current instruction.
140 /// Hardware samples IRQ near the end of the instruction with the old
141 /// I-flag value; CLI / SEI / PLP / RTI take effect at the very last
142 /// cycle, AFTER the IRQ sample point. We arm IRQ only when this
143 /// snapshot says I was clear at sample time.
144 pub(crate) irq_sample_i_flag: bool,
145 /// Number of cycles emitted by the per-cycle helpers
146 /// (`read1`/`write1`/`idle_tick`) within the *current* instruction.
147 /// Reset to zero at the top of `step()`. Used by the trailing
148 /// "burn remaining cycles" loop so we can incrementally migrate
149 /// opcodes from atomic-dispatch + trailing-loop to fully per-cycle
150 /// emission.
151 pub(crate) cycles_emitted: u8,
152 /// When `true`, [`Cpu::idle_tick`] does NOT update `irq_first_tick`.
153 /// Used by branch opcodes to model the `branch_delays_irq` quirk:
154 /// real 6502 branches poll IRQ at the same point a 2-cycle untaken
155 /// branch would (the opcode-fetch cycle, which `step()` performs
156 /// before entering dispatch). The operand fetch and any extra
157 /// taken / page-cross cycles do *not* re-sample IRQ. The branch
158 /// dispatch sets this flag *before* the operand fetch and `step()`
159 /// clears it at the top of every instruction.
160 /// NMI sampling is unaffected — the quirk is IRQ-only.
161 pub(crate) skip_irq_sample: bool,
162
163 // === v2.0 master-clock R1 substrate (Phase 2; `mc-r1-substrate`) ===
164 /// The CPU's authoritative master clock (Mesen `_masterClock` / `TetaNES`
165 /// `Cpu::master_clock`). Advanced by `start_cycle`/`end_cycle`; the bus is
166 /// caught up to `master_clock - PPU_OFFSET` from BOTH halves (double
167 /// catch-up). Only live under `mc-r1-substrate`.
168 pub(crate) master_clock: u64,
169 /// NMI edge-recognition latch (set on a /NMI rising edge in
170 /// `handle_interrupts`; consumed by the cycle-5 hijack in
171 /// `service_interrupt`). Mesen `_needNmi`.
172 pub(crate) mc_need_nmi: bool,
173 /// One-cycle-delayed copy of `mc_need_nmi` (the dispatch + hijack gate).
174 /// Mesen `_prevNeedNmi`.
175 pub(crate) mc_prev_need_nmi: bool,
176 /// Live IRQ-recognition latch (`irq_level && !irq_sample_i_flag`,
177 /// recomputed every `end_cycle`). Mesen `_runIrq`.
178 pub(crate) mc_run_irq: bool,
179 /// One-cycle-delayed copy of `mc_run_irq` (the dispatch gate). Mesen
180 /// `_prevRunIrq`.
181 pub(crate) mc_prev_run_irq: bool,
182 /// Previous /NMI line level, for the φ2 rising-edge detector in
183 /// `handle_interrupts`. Mesen `_prevNmiFlag`.
184 pub(crate) mc_prev_nmi_line: bool,
185 /// v2.0.0 beta.2 (A2 scoping diagnostic): per-opcode count of cycles the
186 /// trailing burn-loop had to fill (`cycles - cycles_emitted`) — the exact
187 /// remaining busless-cycle surface the every-cycle-bus-access conversion
188 /// must turn into dummy reads of the held address. Read by the harness
189 /// `burn_probe` bin; never consulted by emulation.
190 #[cfg(feature = "cpu-instr-cycle-trace")]
191 pub burn_histogram: [u64; 256],
192}
193
194impl Default for Cpu {
195 fn default() -> Self {
196 Self::new()
197 }
198}
199
200/// Effective address + page-crossed flag, returned by addressing-mode resolvers.
201#[derive(Clone, Copy)]
202struct Operand {
203 addr: u16,
204 page_crossed: bool,
205}
206
207impl Cpu {
208 /// New CPU in "post-reset" state. Caller must invoke [`Cpu::reset`] with a
209 /// real bus before stepping (PC is undefined until reset reads `$FFFC/D`).
210 ///
211 /// This constructor is the convenience entry-point used by unit tests and
212 /// nestest fixtures that drive the CPU without going through a full
213 /// power-on path: `S=$FD`, `P=$24` (`UNUSED` + `INTERRUPT_DISABLE`). If the
214 /// caller subsequently invokes [`Cpu::reset`] the stack pointer will be
215 /// decremented by 3 (per the reset sequence), landing on `$FA` — that is
216 /// the input shape several `tests/opcodes.rs` fixtures expect.
217 ///
218 /// **For the real cold-boot path** (`Nes::from_rom`, `Nes::power_cycle`),
219 /// use [`Cpu::power_on`] instead, which seeds `S=$00`. After the 3-decrement
220 /// reset sequence that lands `S=$FD`, matching Mesen2's power-up state.
221 /// See `docs/audit/session-13-cpu-boot-fix-2026-05-21.md` for the reference
222 /// behaviour from `Core/NES/NesCpu.cpp::NesCpu::Reset(softReset=false)`.
223 #[must_use]
224 pub const fn new() -> Self {
225 Self {
226 a: 0,
227 x: 0,
228 y: 0,
229 pc: 0,
230 s: 0xFD,
231 p: Status::power_on(),
232 cycles: 0,
233 jammed: false,
234 pending_nmi: false,
235 armed_nmi: false,
236 pending_irq: false,
237 armed_irq: false,
238 nmi_first_tick: u8::MAX,
239 irq_first_tick: u8::MAX,
240 irq_sample_i_flag: true,
241 cycles_emitted: 0,
242 skip_irq_sample: false,
243 master_clock: 0,
244 mc_need_nmi: false,
245 mc_prev_need_nmi: false,
246 mc_run_irq: false,
247 mc_prev_run_irq: false,
248 mc_prev_nmi_line: false,
249 #[cfg(feature = "cpu-instr-cycle-trace")]
250 burn_histogram: [0; 256],
251 }
252 }
253
254 /// New CPU in real-hardware cold-boot state (`S=$00`).
255 ///
256 /// Real silicon comes up with the stack pointer in an undefined state;
257 /// the convention used by Mesen2 (and adopted here for trace parity) is
258 /// to treat power-up as `S=$00` and rely on the reset sequence's three
259 /// "phantom" decrements to wrap into `S=$FD`. See Mesen2
260 /// `Core/NES/NesCpu.cpp::Reset(softReset=false)`:
261 ///
262 /// ```cpp
263 /// if(softReset) {
264 /// _state.SP -= 0x03; // soft reset path
265 /// } else {
266 /// _state.SP = 0xFD; // power-up: direct assignment
267 /// }
268 /// ```
269 ///
270 /// `RustyNES` models that two-path behaviour by gating the SP delta through
271 /// the constructor: `Cpu::power_on() + reset()` ⇒ `$00 - 3 = $FD` (cold);
272 /// `cpu.reset()` again ⇒ `$FD - 3 = $FA` (subsequent soft reset).
273 ///
274 /// `P` is left at `$24` (`INTERRUPT_DISABLE` | `UNUSED`). Mesen2's trace
275 /// surface shows `P = $04` because it masks `UNUSED` out of the displayed
276 /// byte, but the bit is conventionally always set on a 6502 internally
277 /// (nesdev: "Bit 5: Always 1, the so-called 'unused' bit"); the trace
278 /// divergence on P is cosmetic.
279 #[must_use]
280 pub const fn power_on() -> Self {
281 let mut cpu = Self::new();
282 cpu.s = 0x00;
283 cpu
284 }
285
286 /// Returns `true` when the CPU has executed a JAM/KIL/STP.
287 #[must_use]
288 pub const fn is_jammed(&self) -> bool {
289 self.jammed
290 }
291
292 /// Read-only accessor for the CPU's authoritative master clock
293 /// (v2.0.0-beta.1 one-clock instrumentation).
294 ///
295 /// `master_clock` counts master-clock units (NTSC: 12 per CPU cycle,
296 /// PAL: 16, Dendy: 15) and is advanced only by `start_cycle` /
297 /// `end_cycle` (the asymmetric read 5/7 vs write 7/5 φ1/φ2 split on
298 /// NTSC) plus the bus-side DMA coherence fold
299 /// (`Bus::take_dma_mc_consumed`). It is the counter the v2.0.0
300 /// "Timebase" rewrite (ADR 0002) promotes to the ONE canonical
301 /// timebase; the test harness asserts the affine relation
302 /// `master_clock == seed + cpu_divider * cycles` against the other
303 /// cycle counters (`one_clock_invariants.rs`) as the gate for the
304 /// beta.1 counter collapse.
305 #[must_use]
306 pub const fn master_clock(&self) -> u64 {
307 self.master_clock
308 }
309
310 /// Reset (warm boot).
311 ///
312 /// Real hardware: 8-cycle sequence with suppressed pushes, then PC loads
313 /// from the reset vector and the I flag is set. We model the cycle count
314 /// (advances `cycles` by 8 and fires `on_cpu_cycle` 8 times) without
315 /// mutating registers other than P (set I), S (decrement by 3), and PC.
316 ///
317 /// Matches Mesen2's `NesCpu::Reset()` 8-cycle post-power-up loop ("CPU
318 /// takes 8 cycles before it starts executing the ROM's code"). Combined
319 /// with the PPU power-up at (scanline=-1, dot=340) (see `Ppu::new`),
320 /// this closes the +344-dot PPU offset identified empirically in
321 /// Session-13 (docs/audit/session-13-cpu-boot-fix-2026-05-21.md).
322 pub fn reset<B: Bus>(&mut self, bus: &mut B) {
323 // Real hardware decrements S three times during reset (no actual
324 // pushes occur, but the decrements happen).
325 self.s = self.s.wrapping_sub(3);
326 self.p.insert(Status::INTERRUPT_DISABLE);
327 self.jammed = false;
328 self.pending_nmi = false;
329 self.armed_nmi = false;
330 self.pending_irq = false;
331 self.armed_irq = false;
332 self.nmi_first_tick = u8::MAX;
333 self.irq_first_tick = u8::MAX;
334 // R1/R3 cold-boot: advance master_clock by one CPU divider BEFORE the
335 // 8-cycle reset loop (Mesen `NesCpu::Reset()` `_masterClock += cpuDivider
336 // + cpuOffset`). Without it the first start_cycle's `run_ppu_to(mc-1)`
337 // would leave the PPU 3 dots behind. See R1 port plan / branch `acddd22`.
338 {
339 self.master_clock = self.master_clock.wrapping_add(bus.cpu_divider());
340 }
341 // V-axis: Mesen adds `cpuDivider + cpuOffset` (13), not just cpuDivider
342 // (12). The +PPU_OFFSET corrects R1's power-up CPU/PPU sub-cycle
343 // alignment to the reference, shifting every $2002 poll-exit to match
344 // hardware (nesdev `PPU_frame_timing`: the read sees the flag change iff
345 // it starts at/after the set tick). Default-off; A/B against Y/C1/6-10.
346 // 8-cycle reset sequence: 6 idle/internal cycles + 2 vector reads.
347 self.cycles_emitted = 0;
348 for _ in 0..6 {
349 self.idle_tick(bus);
350 }
351 let lo = self.read1(bus, RESET_VECTOR);
352 let hi = self.read1(bus, RESET_VECTOR + 1);
353 self.pc = u16::from(lo) | (u16::from(hi) << 8);
354 }
355
356 /// Force PC to `addr`. Used by the nestest harness which enters at
357 /// `$C000` rather than the reset vector.
358 pub const fn set_pc(&mut self, addr: u16) {
359 self.pc = addr;
360 }
361
362 /// Step one instruction (or service an interrupt). Returns the number of
363 /// CPU cycles consumed.
364 ///
365 /// On a JAM-state CPU this is a no-op returning 0.
366 ///
367 /// # Interrupt timing model
368 ///
369 /// Real 6502 hardware samples the NMI / IRQ lines at the *second-to-last*
370 /// cycle of every instruction and, if asserted there, queues the
371 /// interrupt to be serviced *after* the current instruction completes.
372 /// Our model dispatches all bus operations atomically before ticking
373 /// cycles, so a write that itself raises NMI (e.g. `STA $2000` enabling
374 /// NMI while VBL is set) appears to the bus's edge detector during the
375 /// FIRST cycle of the tally loop — earlier than hardware would observe
376 /// it. Hardware places the actual write at the *last* cycle of the
377 /// instruction, so the second-to-last sample point would NOT see the new
378 /// line state; only the NEXT instruction's sample sees it. We model
379 /// that by introducing a one-instruction promotion delay: edges captured
380 /// at end-of-step land in `pending_*` and, after the following step,
381 /// promote to `armed_*` which is the gate that actually triggers
382 /// service. This passes `04-nmi_control` test 11 ("Immediate occurence
383 /// should be after NEXT instruction") without regressing the
384 /// instruction-count-insensitive tests like `02-vbl_set_time`,
385 /// `09-even_odd_frames`, or any `instr_test_v5` ROM (which never raise
386 /// NMI from within a single instruction).
387 #[allow(clippy::too_many_lines, clippy::missing_panics_doc)]
388 pub fn step<B: Bus>(&mut self, bus: &mut B) -> u8 {
389 if self.jammed {
390 return 0;
391 }
392 // v2.0 master-clock R1 UNIFIED interrupt dispatch (`mc-r1-substrate`):
393 // a SINGLE service sequence gated on the one-cycle-delayed `prev_*`
394 // copies (Mesen `_prevRunIrq || _prevNeedNmi`). The vector is chosen
395 // INSIDE `service_interrupt` by the live `mc_need_nmi` at cycle 5 (the
396 // NMI hijack); NMI priority is resolved there, not here. Setting
397 // `irq_sample_i_flag = true` BEFORE the service masks the φ2 sampler
398 // for the 7-cycle sequence (no re-entry). Clears `skip_irq_sample`
399 // (a prior taken-branch could have left it set, freezing the recompute)
400 // and defers any still-pending NMI by one instruction.
401 if self.mc_prev_run_irq || self.mc_prev_need_nmi {
402 self.armed_irq = false;
403 self.irq_sample_i_flag = true;
404 self.skip_irq_sample = false;
405 self.service_interrupt(bus, IRQ_VECTOR, false);
406 self.mc_prev_need_nmi = false;
407 self.promote_post_step_interrupts(7);
408 return 7;
409 }
410 // Service an armed interrupt before the next instruction. NMI has
411 // priority over IRQ; both are mutually exclusive for a single
412 // service window.
413 // Once armed, the IRQ services unconditionally — the I-flag
414 // gating already happened at the sample point (second-to-last
415 // cycle of the prior instruction). This is what produces the
416 // "CLI SEI should still allow one IRQ to fire" behavior:
417 // SEI's I=1 takes effect at end-of-SEI but the sample at SEI's
418 // second-to-last cycle saw I=0 (CLI cleared it) and queued the
419 // IRQ, which now fires regardless of the current I-flag.
420
421 // Per-instruction state for the per-cycle helpers
422 // (`read1`/`write1`/`idle_tick`). These track the FIRST tick at
423 // which each interrupt line was seen high; hardware samples at the
424 // second-to-last cycle so seen < last_tick = arm now;
425 // seen == last_tick = defer one instruction (the next instruction's
426 // sample window catches it instead).
427 self.nmi_first_tick = u8::MAX;
428 self.irq_first_tick = u8::MAX;
429 self.cycles_emitted = 0;
430 // Cleared every instruction; set inside the branch dispatch arms
431 // (after the operand fetch / canonical IRQ poll) to suppress
432 // further IRQ sampling on the additional taken / page-cross
433 // branch cycles.
434 self.skip_irq_sample = false;
435 // Snapshot the I flag for this instruction. CLI / SEI / PLP /
436 // RTI mutate `self.p` *during* the instruction, but the hardware
437 // IRQ sample reads the I value as it was at the start. This is
438 // what produces the documented "exactly one instruction after
439 // CLI executes before IRQ is taken" delay.
440 self.irq_sample_i_flag = self.p.contains(Status::INTERRUPT_DISABLE);
441
442 #[cfg(feature = "cpu-instr-cycle-trace")]
443 bus.trace_instr(self.pc, self.cycles);
444
445 let opcode = self.fetch_pc(bus);
446 let mut cycles = 0u8;
447 self.dispatch(bus, opcode, &mut cycles);
448 // Burn whichever cycles the dispatch did NOT emit through helpers.
449 // As opcodes migrate to fully per-cycle emission, this loop runs
450 // for fewer iterations; eventually it can be removed entirely.
451 //
452 // v2.0.0 beta.2 (A2 scoping): the diagnostic histogram below records,
453 // per opcode, how many cycles the burn-loop had to fill — the exact
454 // empirical work list for the every-cycle-bus-access conversion (the
455 // remaining busless cycles that must become dummy reads of the held
456 // address). Default-off; the `burn_probe` harness bin prints it.
457 #[cfg(feature = "cpu-instr-cycle-trace")]
458 {
459 let burned = cycles.saturating_sub(self.cycles_emitted);
460 if burned > 0 {
461 self.burn_histogram[opcode as usize] =
462 self.burn_histogram[opcode as usize].saturating_add(u64::from(burned));
463 }
464 }
465 // v2.0.0 beta.2 (A2, promoted to the only path in beta.4): every
466 // instruction cycle is a bus access — the resolvers + RMW arms emit
467 // the canonical dummy reads, so the burn-loop must never fire.
468 // Proven empirically at zero across AccuracyCoin, nestest, both
469 // blargg_nes_cpu_test5 suites, and cpu_timing_test6 (the full
470 // official + unofficial opcode space); this assert makes any future
471 // under-emitting dispatch arm fail loud in dev-profile runs instead
472 // of silently reintroducing a busless cycle.
473 debug_assert!(
474 self.cycles_emitted >= cycles,
475 "opcode ${opcode:02X} under-emitted: declared {cycles} cycles but emitted \
476 only {} — a busless burn-loop cycle would fill the gap (A2 regression; \
477 see the v2.0.0 plan Workstream A2)",
478 self.cycles_emitted
479 );
480 while self.cycles_emitted < cycles {
481 self.idle_tick(bus);
482 }
483 self.promote_post_step_interrupts(cycles);
484 cycles
485 }
486
487 /// Promote any per-instruction interrupt edges captured by the
488 /// per-cycle helpers into the `armed_*` / `pending_*` latches the
489 /// next [`Cpu::step`] consults. Hardware samples interrupts at the
490 /// second-to-last cycle of an instruction; we approximate that with
491 /// "first sampled tick strictly before the last cycle = arm now,
492 /// else defer one instruction."
493 const fn promote_post_step_interrupts(&mut self, cycles: u8) {
494 // Promote any previously-pending interrupt (latched at the very
495 // last cycle of the prior instruction).
496 if self.pending_nmi {
497 self.armed_nmi = true;
498 self.pending_nmi = false;
499 }
500 if self.pending_irq {
501 self.armed_irq = true;
502 self.pending_irq = false;
503 }
504 let last_tick = cycles.saturating_sub(1);
505 if self.nmi_first_tick != u8::MAX {
506 if self.nmi_first_tick < last_tick {
507 self.armed_nmi = true;
508 } else {
509 self.pending_nmi = true;
510 }
511 }
512 if self.irq_first_tick != u8::MAX {
513 // IRQ is masked by the I-flag value as it was at the START of
514 // this instruction; CLI / SEI / PLP / RTI mutations take effect
515 // at end-of-instruction. If IRQ was already disabled when we
516 // entered, the second-to-last-cycle sample sees I=1 and the
517 // edge is dropped (the next instruction's sample will pick it
518 // up if I has since cleared).
519 if !self.irq_sample_i_flag {
520 if self.irq_first_tick < last_tick {
521 self.armed_irq = true;
522 } else {
523 self.pending_irq = true;
524 }
525 }
526 }
527 }
528
529 fn fetch_pc<B: Bus>(&mut self, bus: &mut B) -> u8 {
530 let v = self.read1(bus, self.pc);
531 self.pc = self.pc.wrapping_add(1);
532 v
533 }
534
535 fn fetch_pc_u16<B: Bus>(&mut self, bus: &mut B) -> u16 {
536 let lo = self.fetch_pc(bus);
537 let hi = self.fetch_pc(bus);
538 u16::from(lo) | (u16::from(hi) << 8)
539 }
540
541 fn read_u16_with_wrap<B: Bus>(&mut self, bus: &mut B, addr: u16) -> u16 {
542 // Used by indirect modes to honor the 6502 page-wrap quirk.
543 let lo = self.read1(bus, addr);
544 let hi_addr = (addr & 0xFF00) | u16::from((addr as u8).wrapping_add(1));
545 let hi = self.read1(bus, hi_addr);
546 u16::from(lo) | (u16::from(hi) << 8)
547 }
548
549 fn push<B: Bus>(&mut self, bus: &mut B, value: u8) {
550 self.write1(bus, STACK_BASE | u16::from(self.s), value);
551 self.s = self.s.wrapping_sub(1);
552 }
553
554 fn pull<B: Bus>(&mut self, bus: &mut B) -> u8 {
555 self.s = self.s.wrapping_add(1);
556 self.read1(bus, STACK_BASE | u16::from(self.s))
557 }
558
559 fn push_u16<B: Bus>(&mut self, bus: &mut B, value: u16) {
560 self.push(bus, (value >> 8) as u8);
561 self.push(bus, (value & 0xFF) as u8);
562 }
563
564 fn pull_u16<B: Bus>(&mut self, bus: &mut B) -> u16 {
565 let lo = self.pull(bus);
566 let hi = self.pull(bus);
567 u16::from(lo) | (u16::from(hi) << 8)
568 }
569
570 // ------------------------------------------------------------------
571 // Per-cycle bus interleaving primitives.
572 //
573 // Real 6502 hardware reads or writes the bus *exactly once per CPU
574 // cycle*; "internal" cycles (ALU work, stack pointer increment, etc.)
575 // still tick the system clock without driving the address bus.
576 // `read1` / `write1` / `idle_tick` model that one-cycle granularity:
577 // each one ticks the bus exactly once and samples the NMI/IRQ lines
578 // at the end of the cycle, mirroring what the existing trailing
579 // tally loop in `step` did but with the polling now coupled to the
580 // *actual* memory access ordering.
581 //
582 // `step()` resets `cycles_emitted` to 0; each call here increments
583 // it. The trailing burn-loop in `step` consumes whatever cycles
584 // the opcode declared but didn't emit through these helpers.
585 // ------------------------------------------------------------------
586
587 // === v2.0 master-clock R1 substrate core (Phase 2; `mc-r1-substrate`) ===
588
589 /// Start half of one CPU cycle: advance `master_clock` by the PRE split,
590 /// catch the PPU up to `master_clock - PPU_OFFSET`, then fire the bus's
591 /// per-cycle work (`cpu_clock`). After this the PPU is at the access's
592 /// exact master clock (so a `$2002`/`$2007` read sees on-time state).
593 fn start_cycle<B: Bus>(&mut self, bus: &mut B, for_read: bool) {
594 let div = bus.cpu_divider();
595 let pre = if for_read {
596 read_split(div).0
597 } else {
598 write_split(div).0
599 };
600 self.master_clock = self.master_clock.wrapping_add(pre);
601 bus.run_ppu_to(self.master_clock.saturating_sub(ppu_sample_offset()), false);
602 bus.cpu_clock();
603 // v2.0.0 beta.1 (A1 one-clock collapse, promoted to the only path in
604 // beta.4): `cycles` is ASSIGNED from the canonical bus cycle counter
605 // at this single per-cycle site instead of being independently
606 // incremented by every `read1`/`write1`/`idle_tick`/DMA-loop caller.
607 // `bus.cpu_clock()` above advanced the canonical counter for THIS
608 // cycle, so the assignment lands on the same post-increment value the
609 // legacy caller-side `+= 1` produced (the `one_clock_invariants`
610 // harness test pins the residue at zero).
611 self.cycles = bus.cycle_count();
612 }
613
614 /// End half of one CPU cycle: fold any bus-side DMA span into
615 /// `master_clock` (coherence — keeps the CPU<->PPU phase aligned across a
616 /// DMA), advance by the POST split, catch the PPU up again (the double
617 /// catch-up), then sample interrupts (φ2, the T_last-1 rule).
618 fn end_cycle<B: Bus>(&mut self, bus: &mut B, for_read: bool) {
619 // v2.0.0 beta.1 (A1 one-clock collapse, promoted to the only path in
620 // beta.4): the `dma_mc_consumed` coherence fold is RETIRED. On the
621 // live unified-DMA path every DMA cycle is a first-class
622 // `start_cycle`/`end_cycle` (advancing `master_clock` directly), so
623 // the bus-side accumulator is structurally zero — the fold only ever
624 // mattered for the legacy bus-side burst engine, which is dead code.
625 // The accumulator is drained UNCONDITIONALLY (identical dev/release
626 // behavior — clippy's `debug_assert_with_mut_call` rightly forbids
627 // the take inside the assertion) and the structural-zero claim is
628 // asserted in dev profiles; the byte-identity gate (AccuracyCoin
629 // 139/139 + nestest 0-diff) proves it for release.
630 let folded = bus.take_dma_mc_consumed();
631 debug_assert_eq!(
632 folded, 0,
633 "dma_mc_consumed accumulated on the live path — a legacy \
634 bus-side DMA cycle ran outside the unified engine (see the \
635 v2.0.0 plan A1)"
636 );
637 let _ = folded;
638 let div = bus.cpu_divider();
639 let post = if for_read {
640 read_split(div).1
641 } else {
642 write_split(div).1
643 };
644 self.master_clock = self.master_clock.wrapping_add(post);
645 bus.run_ppu_to(self.master_clock.saturating_sub(ppu_sample_offset()), true);
646 // F-2: tick the DMC byte-timer at END of cycle (after the access),
647 // matching main's DMC fire-phase for DMASync, BEFORE the φ2 interrupt
648 // sample so handle_interrupts sees the post-tick DMC IRQ line.
649 bus.cpu_clock_apu_dmc();
650 self.handle_interrupts(bus);
651 // Diagnostic trace hook (no-op unless the bus enables irq-timing-trace).
652 bus.trace_end_cycle();
653 }
654
655 /// φ2 interrupt sampler (Mesen `EndCpuCycle`): edge-detect /NMI into
656 /// `mc_need_nmi` (after copying the one-cycle-delayed `mc_prev_need_nmi`),
657 /// and recompute `mc_run_irq = irq_level && !irq_sample_i_flag` (after the
658 /// `mc_prev_run_irq` copy). The `step()`-top dispatch reads the `prev_*`
659 /// copies — i.e. second-to-last-cycle recognition. The I-mask uses the
660 /// start-of-instruction snapshot (`irq_sample_i_flag`), not live `self.p`,
661 /// so CLI/SEI/PLP delay their I-change one instruction.
662 #[allow(clippy::needless_pass_by_ref_mut)] // &mut B for signature parity
663 fn handle_interrupts<B: Bus>(&mut self, bus: &mut B) {
664 self.mc_prev_need_nmi = self.mc_need_nmi;
665 let nmi_level = bus.nmi_level();
666 if !self.mc_prev_nmi_line && nmi_level {
667 self.mc_need_nmi = true;
668 }
669 self.mc_prev_nmi_line = nmi_level;
670 self.mc_prev_run_irq = self.mc_run_irq;
671 // W1 (`mc-r1-branch-poll-points`): a taken branch polls IRQ ONCE —
672 // before C2 — so while `skip_irq_sample` is set (the branch dispatch
673 // arms set it after the C1 opcode fetch, before the C2 operand fetch)
674 // the recognition latch is FROZEN at its end-of-C1 value instead of
675 // recomputed from the live line every cycle. DMC-DMA halt cycles
676 // drained inside the branch's own `read1`/`idle_tick` therefore
677 // cannot make a freshly-asserted IRQ visible to THIS instruction
678 // (AccuracyCoin `Interrupt flag latency` Test A; TriCNES polls at
679 // C2-start only, plus a can-set poll at C4-start handled in
680 // `branch()`). The `mc_prev_run_irq` copy above still runs, so the
681 // held end-of-C1 value is what the next `step()` dispatch reads. NMI
682 // edge detection above is untouched (sampled every cycle, per
683 // hardware — the quirk is IRQ-only).
684 if self.skip_irq_sample {
685 return;
686 }
687 let irq_level = bus.irq_level();
688 self.mc_run_irq = irq_level && !self.irq_sample_i_flag;
689 }
690
691 /// Tick the bus once and sample interrupt lines, *without* a bus
692 /// access. Models a 6502 internal cycle.
693 fn idle_tick<B: Bus>(&mut self, bus: &mut B) {
694 {
695 // F-2 re-coupling (`mc-r1-dmc-idle-halt`): a DMC DMA can halt the CPU
696 // on a 6502 INTERNAL cycle too — on hardware every cycle is a bus
697 // read, and Mesen's `ProcessPendingDma` runs on every `MemoryRead`
698 // (incl. dummy reads), NOT only instruction/operand reads. R1's
699 // `read1` loop only services on real reads, so the DMA waits through
700 // internal cycles (the `lat=4` idle-runs the per-fetch trace pinned
701 // as the period-jitter source). Service it here too, on the held
702 // (last-read) bus address. Default-off; the banked 6/10 path skips it.
703 // W3-Stage-1 (`mc-r1-dma-unified`): the unified-engine replacement
704 // for the idle DMC drain above — same loop shape, ONE engine. The
705 // bus supplies the held (last-read) address for the parked 6502
706 // address bus. Same budget accounting as the loop it replaces.
707 while bus.unified_dma_pending() {
708 self.cycles_emitted = self.cycles_emitted.saturating_add(1);
709 self.start_cycle(bus, true);
710 bus.unified_dma_cycle_idle();
711 self.end_cycle(bus, true);
712 }
713 // R1: a pure internal cycle — busless (idle_tick stays busless).
714 self.cycles_emitted = self.cycles_emitted.saturating_add(1);
715 self.start_cycle(bus, true);
716 self.end_cycle(bus, true);
717 }
718 }
719
720 /// Canonical cycle-2 PC dummy read for implied / accumulator /
721 /// transfer / flag instructions (per nesdev `6502_cpu.txt` + MOS
722 /// 6502 datasheet). Real silicon fetches the byte AFTER the opcode
723 /// during cycle 2 of these single-byte instructions and discards
724 /// it (the would-be operand). Without this dummy read, our emulator
725 /// instead "burns an idle cycle" via `idle_tick` for the second
726 /// cycle, which counts the cycle for time but produces no bus
727 /// access — diverging from real silicon's bus-access pattern.
728 ///
729 /// Wired into 22 dispatch arms (ASL/LSR/ROL/ROR A; CLC/SEC/CLI/SEI/
730 /// CLV/CLD/SED; TAX/TAY/TSX/TXA/TXS/TYA; INX/DEX/INY/DEY; NOP;
731 /// 6 unofficial 1-byte NOPs) under the `cpu-implied-dummy-reads`
732 /// cargo feature. Default-off pending the coordinated DMC scheduler
733 /// audit per `docs/audit/sprint-2.3-implied-dummy-dmc-recon-2026-05-25.md`
734 /// — Session-19 documented that this fix alone (Step 1+2 of the
735 /// recipe) cascades into `Implicit DMA Abort [error 2]`. Step 3
736 /// (DMC scheduler awareness of cycle-2 bus-active reads) is the
737 /// next-session attack.
738 ///
739 /// When the feature flag is OFF, this helper compiles to a no-op
740 /// (the `bus` parameter is silenced via `_ = bus`), and the
741 /// existing `*cycles = 2` + caller's idle-tick burn loop preserves
742 /// pre-Sprint-2.3 behavior byte-identically.
743 // `&mut self` + `&mut bus` are required for the feature-ON branch;
744 // when the feature is off the helper is a no-op (cfg-gated). The
745 // lint suppressions cover the OFF branch's "unused argument /
746 // could be const fn / inline(always) is suspicious" complaints.
747 #[inline(always)]
748 #[allow(
749 clippy::inline_always,
750 clippy::needless_pass_by_ref_mut,
751 clippy::unused_self,
752 clippy::missing_const_for_fn
753 )]
754 fn implied_dummy_read<B: Bus>(&mut self, bus: &mut B) {
755 {
756 let _ = self.read1(bus, self.pc);
757 }
758 }
759
760 /// Read a byte at `addr` *and* consume one CPU cycle (with bus tick
761 /// + interrupt sampling).
762 #[allow(clippy::too_many_lines)] // mc-r1 DMA-interleave arms push this past 100
763 fn read1<B: Bus>(&mut self, bus: &mut B, addr: u16) -> u8 {
764 {
765 // accuracycoin-100 Phase 2 (`mc-r1-dmc-abort-cancel`): a 1-byte
766 // non-looping implicit abort that matured during the prior APU tick
767 // is serviced HERE, before the (cancelled) reload could run. On a
768 // GET (read) cycle the abort is a 1-cycle DMA (one halt re-read) →
769 // CalculateDMADuration Y=1; on a PUT cycle it does NOT occur → Y=0
770 // ("the 1-cycle abort will not land on a write cycle"). Both clear
771 // the pending reload so the `dmc_dma_pending` loop below skips it.
772 if bus.dmc_abort_pending() {
773 if bus.dmc_abort_is_get_cycle() {
774 self.cycles_emitted = self.cycles_emitted.saturating_add(1);
775 self.start_cycle(bus, true);
776 bus.dmc_abort_halt_step(addr);
777 self.end_cycle(bus, true);
778 } else {
779 bus.dmc_abort_cancel();
780 }
781 }
782 // W3-Stage-1 (`mc-r1-dma-unified`): ONE DMA loop replacing the
783 // three loops below (the standalone DMC drain, the sequential
784 // Stage-D OAM loop, and the Program-M overlap loop). Each
785 // iteration is one full R1 cycle (start_cycle -> the bus's
786 // unified TriCNES-dispatch cycle -> end_cycle), so every DMA
787 // cycle keeps the φ2 IRQ sample — the C1-safe shape. The
788 // engine itself (ONE driver for standalone DMC, standalone OAM,
789 // and the overlap) lives bus-side in `unified_dma_cycle`; the
790 // load-get-entry defer is folded into `unified_dma_pending`
791 // (pre-cycle, like the floor's while-gate) AND the engine's
792 // in-cycle entry gate (post-flip parity).
793 while bus.unified_dma_pending() {
794 // DMA halt cycles count against `cycles_emitted`.
795 self.cycles_emitted = self.cycles_emitted.saturating_add(1);
796 self.start_cycle(bus, true);
797 bus.unified_dma_cycle(addr);
798 self.end_cycle(bus, true);
799 }
800 // Phase B (interleaved DMC DMA): a DMC DMA halts the CPU only on a
801 // READ cycle (TriCNES `CPU_Read`). While one is pending, consume R1
802 // cycles ONE AT A TIME — each a full R1 cycle (PPU caught up,
803 // `tick_dmc` advances the DMC timer once = the span↔fire feedback,
804 // arm gated by `in_dmc_dma` = no cascade) — BEFORE the CPU's own
805 // read. `dmc_dma_step` re-reads `addr` on halt/align cycles and
806 // fetches the sample on the get cycle (`!put_cycle`).
807 // W3-Stage-0: when an OAM DMA can overlap this DMC
808 // (`oam_dma_overlap_ready` — under `mc-r1-counter-collapse` that
809 // includes a `$4014` write still pending its first cycle), do NOT
810 // drain the DMC standalone here: the combined overlap loop below
811 // services both engines as ONE shared-cycle event. Draining it here
812 // first pays a full unshared reload span = the DMC+OAM idx[7]
813 // regime-transition `03`. Without the overlap feature this bus query
814 // is the trait default (`oam_dma_in_flight` = `false` at read1 entry),
815 // so the floor path is unchanged by construction.
816 // W3-Stage-1: replaced by the unified engine loop above under
817 // `mc-r1-dma-unified` (cfg'd out, not deleted).
818 // Stage-D (`mc-r1-full-cpu`): OAM DMA runs CPU-driven, one cycle at a
819 // time through start_cycle/end_cycle (so each OAM cycle samples
820 // IRQ/NMI via the φ2 `_prev*` pipeline in end_cycle — the surface the
821 // bus burst bypassed and RW-2 regressed). A pending DMC DMA preempts
822 // (the DMC loop above already drained first = DMC-get-before-OAM-get).
823 //
824 // NOTE: this SEQUENTIAL nested form drains a mid-OAM DMC DMA fully
825 // before resuming OAM (no overlap), so the DMC+OAM test's `02/01`
826 // shared-cycle entries never appear. `mc-r1-dmc-oam-overlap` replaces
827 // it with the overlap model below.
828 // Program M (M-2, `mc-r1-dmc-oam-overlap`): the DMC-DMA-during-OAM-DMA
829 // overlap model. A single combined loop services BOTH engines per
830 // cycle: when a DMC DMA is pending while an OAM DMA is IN FLIGHT, the
831 // DMC halt/dummy/align cycles SHARE an OAM cycle (the 6502 is
832 // RDY-halted but the OAM engine keeps consuming its bus slot), and
833 // only the DMC GET steals an OAM slot. This is the per-cycle analogue
834 // of lockstep `service_dmc_dma_during_oam`, which produces the test's
835 // canonical `04,03,...,02,01` sweep (nesdev `DMA#DMC_DMA_during_OAM`).
836 // R1 clean access shape: start_cycle (PPU caught up to the
837 // access's exact mc + bus cpu_clock) → bus.read → end_cycle
838 // (double catch-up + φ2 interrupt sample). Mesen `MemoryRead`.
839 self.cycles_emitted = self.cycles_emitted.saturating_add(1);
840 self.start_cycle(bus, true);
841 let v = bus.read(addr);
842 self.end_cycle(bus, true);
843 v
844 }
845 }
846
847 /// Write `value` to `addr` *and* consume one CPU cycle (with bus
848 /// tick + interrupt sampling).
849 fn write1<B: Bus>(&mut self, bus: &mut B, addr: u16, value: u8) {
850 {
851 // accuracycoin-100 Phase 2: a CPU write cycle cannot be RDY-halted,
852 // so a 1-byte implicit abort matured before a write does NOT occur
853 // (Y=0). Cancel it with no halt cycle — this is the "will not land on
854 // a write cycle" half of the sweep that the read-only `read1` path
855 // can't reach.
856 if bus.dmc_abort_pending() {
857 bus.dmc_abort_cancel();
858 }
859 // R1 clean write shape (symmetric split — writes commit 2 mc
860 // later than reads). No interrupt sample latches here; end_cycle's
861 // handle_interrupts does the φ2 sample.
862 self.cycles_emitted = self.cycles_emitted.saturating_add(1);
863 self.start_cycle(bus, false);
864 bus.write(addr, value);
865 self.end_cycle(bus, false);
866 }
867 }
868
869 /// SH* unstable-store family helper (`SHA / SHX / SHY / SHS / TAS`,
870 /// opcodes `$9F / $93 / $9E / $9C / $9B`).
871 ///
872 /// Implements the 6502 unstable-store (SH*) algorithm — the
873 /// "unstable"/"highbyte" store opcodes (`value AND (high_byte + 1)`, with the
874 /// RDY/DMA quirk), pinned bit-for-bit by `AccuracyCoin`'s "Unofficial
875 /// Instructions: SH*" sub-test.
876 ///
877 /// Provenance: **derived from Mesen2's `SyaSxaAxa`** (`Core/NES/NesCpu.h`),
878 /// `GPL-3.0-or-later`. The `NESdev` community documents this behavior, but this
879 /// implementation was ported from Mesen2's — not written independently from
880 /// the documentation. The surrounding DMC-DMA interruption detection uses the
881 /// emulator's own bus cycle-count machinery. See NOTICE and
882 /// docs/originality-and-provenance.md (Section 1).
883 /// The algorithm:
884 ///
885 /// 1. Compute the page-crossed flag against `base + index_reg`.
886 /// 2. Perform a dummy read at the **unfixed** address
887 /// (`base + index_reg - 0x100` if page-crossed, else
888 /// `base + index_reg`). This is the cycle DMC DMA can
889 /// interrupt.
890 /// 3. Detect DMC-DMA interruption via `bus.cycle_count()`
891 /// before/after the dummy read — if more than 1 bus cycle
892 /// elapsed, a DMA fired.
893 /// 4. On page-cross, the address-high-byte is corrupted to
894 /// `original_addr_high AND value_reg`.
895 /// 5. Compute the store value:
896 /// - With DMA: just `value_reg` (the H+1 AND is suppressed
897 /// because the DMC pulled the bus low).
898 /// - Without DMA: `value_reg AND ((base >> 8) + 1)`.
899 /// 6. Write to the (possibly corrupted) final address.
900 ///
901 /// This shape is what `AccuracyCoin Unofficial Instructions: SH*`
902 /// sub-test 7 ("the cycle before the write had a DMA") brackets.
903 /// Pre-2026-05-23 `RustyNES` skipped the dummy read entirely and
904 /// always wrote `value_reg & (H+1)`, failing sub-test 7 across
905 /// all 5 SH* opcodes (error code 7).
906 fn sh_store<B: Bus>(&mut self, bus: &mut B, base: u16, index_reg: u8, value_reg: u8) {
907 let addr = base.wrapping_add(u16::from(index_reg));
908 let page_crossed = (base & 0xFF00) != (addr & 0xFF00);
909
910 // Dummy read at the unfixed address (this is the cycle DMC
911 // DMA can halt). We sample the bus-side cycle count before
912 // and after to detect interruption — DMC DMA service path
913 // advances `bus.cycle` by 3+ extra ticks while the CPU's
914 // own `Cpu::cycles` (which `idle_tick` increments) only goes
915 // up by 1 for the read itself.
916 let cyc_before = bus.cycle_count();
917 let dummy_addr = if page_crossed {
918 addr.wrapping_sub(0x100)
919 } else {
920 addr
921 };
922 let _dummy = self.read1(bus, dummy_addr);
923 let had_dma = bus.cycle_count().wrapping_sub(cyc_before) > 1;
924
925 let addr_high = (addr >> 8) as u8;
926 let addr_low = (addr & 0xFF) as u8;
927 let final_high = if page_crossed {
928 addr_high & value_reg
929 } else {
930 addr_high
931 };
932
933 let write_value = if had_dma {
934 // DMC DMA interrupted the dummy read — bus latch was
935 // overwritten by the DMC fetch, so the store value loses
936 // its AND-with-(H+1) component. Per Mesen2 `SyaSxaAxa`.
937 value_reg
938 } else {
939 // Canonical "documented behavior 1" path: AND with
940 // (base_high + 1).
941 value_reg & ((base >> 8) as u8).wrapping_add(1)
942 };
943
944 let final_addr = (u16::from(final_high) << 8) | u16::from(addr_low);
945 self.write1(bus, final_addr, write_value);
946 }
947
948 fn service_interrupt<B: Bus>(&mut self, bus: &mut B, vector: u16, brk: bool) {
949 // Per-cycle interrupt sequence (7 cycles total when entered from
950 // an interrupt edge, 6 from BRK because its opcode fetch already
951 // burned cycle 1):
952 // C1: opcode fetch (BRK only — IRQ/NMI skip this and instead
953 // perform an extra dummy read in C2).
954 // C2: dummy read of PC+1 (BRK) / filler/internal read (IRQ/NMI).
955 // C3-C5: push PCH, PCL, P.
956 // C6-C7: read vector lo, hi.
957 // `cycles_emitted` is reset here so the caller's accounting starts
958 // from this routine's first tick (the opcode-fetch tick from a BRK
959 // is harmless — the BRK arm sets *cycles = 0 to suppress the
960 // trailing burn loop).
961 self.cycles_emitted = 0;
962 // Reset the per-instruction interrupt sample latches so any NMI
963 // edge during the push sequence below is captured here.
964 self.nmi_first_tick = u8::MAX;
965 self.irq_first_tick = u8::MAX;
966 // Two filler reads for IRQ/NMI; one for BRK (the opcode fetch
967 // counted as the other).
968 //
969 // W3-Stage-3 Part B (`mc-r1-brk-padding-read`): BRK's C2 is the
970 // canonical PADDING-BYTE read at PC+1 — a REAL bus access on
971 // silicon, not an internal cycle. AccuracyCoin `Implied Dummy
972 // Reads` error 31 brackets exactly this: the test choreographs a
973 // BRK whose padding read lands on `$4015`, which must clear the
974 // frame-counter IRQ flag (RTI/RTS already emit their canonical
975 // reads under `cpu-stack-dummy-reads`; BRK was the one gap — with
976 // it the whole test PASSES, one sub-check beyond Mesen2's error
977 // 34). The dispatch arm has already advanced PC past the padding
978 // byte, so it sits at `pc - 1`.
979 if brk {
980 let _ = self.read1(bus, self.pc.wrapping_sub(1));
981 } else {
982 // v2.0.0 beta.2 (A2 every-cycle-bus-access, promoted to the only
983 // path in beta.4): canonical hardware IRQ/NMI cycles 1-2 are
984 // DUMMY READS of the interrupted PC (the suppressed opcode fetch
985 // + suppressed operand fetch — nesdev `6502_cpu.txt`; Mesen2
986 // `NesCpu::IRQ` issues two `DummyRead`s). This is the C1-trio
987 // canary path: the conversion keeps the exact same two-cycle
988 // start/end structure (φ2 samples unchanged) and only adds the
989 // bus access + held-address update; the cpu_interrupts_v2 5/5
990 // strict gate + AccuracyCoin 139/139 hold (verified at the
991 // beta.2 gate).
992 let _ = self.read1(bus, self.pc);
993 let _ = self.read1(bus, self.pc);
994 }
995 self.push_u16(bus, self.pc);
996 let mut p = self.p | Status::UNUSED;
997 if brk {
998 p.insert(Status::BREAK);
999 } else {
1000 p.remove(Status::BREAK);
1001 }
1002 self.push(bus, p.bits());
1003 self.p.insert(Status::INTERRUPT_DISABLE);
1004 // NMI hijacking: real 6502 latches the vector to read on the
1005 // CYCLE just before the vector reads; if NMI is asserted at that
1006 // point, BRK / IRQ both read $FFFA / $FFFB instead of the
1007 // declared vector. We approximate "NMI asserted by now" with
1008 // "the NMI sample latch was hit during cycles 1..=5 of this
1009 // sequence."
1010 // R1: the hijack reads the DELAYED `mc_prev_need_nmi`, NOT the live
1011 // `mc_need_nmi` — an NMI edge latched ON the P-push cycle's φ2 sampler
1012 // must NOT hijack (the BRK/IRQ completes to its own vector and the NMI
1013 // is taken after one handler instruction). `mc_prev_need_nmi` is the
1014 // pre-edge value: 1 only if the NMI was pending BEFORE this cycle
1015 // (oracle-derived, cpu_interrupts_v2/2). Legacy uses `nmi_first_tick`.
1016 let effective_vector = if self.mc_prev_need_nmi && vector != NMI_VECTOR {
1017 self.mc_need_nmi = false;
1018 self.mc_prev_need_nmi = false;
1019 NMI_VECTOR
1020 } else {
1021 vector
1022 };
1023 // Phase 1.2 of Track C1 attempt 14: notify the bus of the vector
1024 // fetch BEFORE the low-byte read so the trace records the cycle
1025 // at which the CPU enters its vector-fetch micro-op (C6 of the
1026 // 7-cycle service sequence). `is_nmi` distinguishes a clean NMI
1027 // service entry from an IRQ/BRK service entry that an NMI edge
1028 // has hijacked to `$FFFA` — both fetch from `$FFFA` but only one
1029 // has `vector == NMI_VECTOR` at this call site.
1030 bus.notify_irq_service(effective_vector, vector == NMI_VECTOR);
1031 let lo = self.read1(bus, effective_vector);
1032 let hi = self.read1(bus, effective_vector + 1);
1033 self.pc = u16::from(lo) | (u16::from(hi) << 8);
1034 }
1035
1036 // ------------------------------------------------------------------
1037 // Addressing-mode resolvers. Each returns the effective address plus a
1038 // page-crossed flag; the caller decides whether to add a cycle.
1039 // ------------------------------------------------------------------
1040
1041 fn addr_zp<B: Bus>(&mut self, bus: &mut B) -> Operand {
1042 Operand {
1043 addr: u16::from(self.fetch_pc(bus)),
1044 page_crossed: false,
1045 }
1046 }
1047
1048 fn addr_zp_x<B: Bus>(&mut self, bus: &mut B) -> Operand {
1049 let base = self.fetch_pc(bus);
1050 // v2.0.0 beta.2 (A2 every-cycle-bus-access): canonical 6502 cycle 3
1051 // reads the UN-indexed zero-page address while the index add
1052 // completes, then discards it (nesdev `6502_cpu.txt`; Mesen2 models
1053 // it as a real `MemoryRead`). Zero-page addresses are always RAM
1054 // ($0000-$00FF), so the read is register-side-effect-free — but it
1055 // parks a real address on the bus (the held address a DMA halt
1056 // re-reads) instead of leaving the cycle busless in the burn-loop.
1057 // The burn-probe histogram pinned this family as 99% of the
1058 // remaining busless surface ($95 STA zp,X alone = 8,955 of 9,795
1059 // burned cycles over the AccuracyCoin battery). Promoted to the only
1060 // path in v2.0.0 beta.4.
1061 let _ = self.read1(bus, u16::from(base));
1062 Operand {
1063 addr: u16::from(base.wrapping_add(self.x)),
1064 page_crossed: false,
1065 }
1066 }
1067
1068 fn addr_zp_y<B: Bus>(&mut self, bus: &mut B) -> Operand {
1069 let base = self.fetch_pc(bus);
1070 // A2: same canonical un-indexed dummy read as `addr_zp_x` (cycle 3
1071 // of LDX/STX zp,Y and the unofficial LAX/SAX zp,Y arms).
1072 let _ = self.read1(bus, u16::from(base));
1073 Operand {
1074 addr: u16::from(base.wrapping_add(self.y)),
1075 page_crossed: false,
1076 }
1077 }
1078
1079 fn addr_abs<B: Bus>(&mut self, bus: &mut B) -> Operand {
1080 Operand {
1081 addr: self.fetch_pc_u16(bus),
1082 page_crossed: false,
1083 }
1084 }
1085
1086 fn addr_abs_x<B: Bus>(&mut self, bus: &mut B) -> Operand {
1087 let base = self.fetch_pc_u16(bus);
1088 let addr = base.wrapping_add(u16::from(self.x));
1089 let page_crossed = (base & 0xFF00) != (addr & 0xFF00);
1090 if page_crossed {
1091 // Canonical 6502 page-cross dummy read at the unfixed
1092 // address: (base_hi << 8) | ((base_lo + X) & 0xFF). The
1093 // high byte hasn't been incremented yet. This read has
1094 // side effects on PPU registers (`$2002` clears VBlank,
1095 // `$2007` advances the buffer) and is the hardware oracle
1096 // AccuracyCoin's `CPU Behavior :: Dummy read cycles`
1097 // Test 1 brackets via `LDA $20F2, X` with X=$10 reading
1098 // $2002 through the mirror.
1099 let dummy = (base & 0xFF00) | (addr & 0x00FF);
1100 let _ = self.read1(bus, dummy);
1101 }
1102 Operand { addr, page_crossed }
1103 }
1104
1105 fn addr_abs_y<B: Bus>(&mut self, bus: &mut B) -> Operand {
1106 let base = self.fetch_pc_u16(bus);
1107 let addr = base.wrapping_add(u16::from(self.y));
1108 let page_crossed = (base & 0xFF00) != (addr & 0xFF00);
1109 if page_crossed {
1110 // See addr_abs_x for the page-cross dummy-read rationale.
1111 let dummy = (base & 0xFF00) | (addr & 0x00FF);
1112 let _ = self.read1(bus, dummy);
1113 }
1114 Operand { addr, page_crossed }
1115 }
1116
1117 // ABS,X / ABS,Y operands for read-modify-write opcodes (ASL, LSR, ROL,
1118 // ROR, INC, DEC, and the unofficial SLO/RLA/SRE/RRA/DCP/ISC). Canonical
1119 // 6502: the unfixed-address dummy read happens UNCONDITIONALLY at
1120 // cycle 4 (not just on page cross) because the CPU has 7 cycles to
1121 // fill and cannot know the fixed address until the high-byte add
1122 // completes. Reads with side effects (`$2002` clears VBlank, `$4015`
1123 // clears frame-IRQ, `$2007` advances buffer) therefore fire twice on
1124 // RMW ABS,X. Bracketed by AccuracyCoin's `Implied Dummy Reads`
1125 // test 2: `SLO $4015,X` with X=0 expects the dummy read to clear the
1126 // frame-IRQ flag so the subsequent real read returns 0.
1127 fn addr_abs_x_rmw<B: Bus>(&mut self, bus: &mut B) -> u16 {
1128 let base = self.fetch_pc_u16(bus);
1129 let addr = base.wrapping_add(u16::from(self.x));
1130 let dummy = (base & 0xFF00) | (addr & 0x00FF);
1131 let _ = self.read1(bus, dummy);
1132 addr
1133 }
1134
1135 fn addr_abs_y_rmw<B: Bus>(&mut self, bus: &mut B) -> u16 {
1136 let base = self.fetch_pc_u16(bus);
1137 let addr = base.wrapping_add(u16::from(self.y));
1138 let dummy = (base & 0xFF00) | (addr & 0x00FF);
1139 let _ = self.read1(bus, dummy);
1140 addr
1141 }
1142
1143 /// (zp),Y operand for the unofficial read-modify-write opcodes
1144 /// (SLO/RLA/SRE/RRA/DCP/ISB `(zp),Y` — `$13/$33/$53/$73/$D3/$F3`).
1145 ///
1146 /// v2.0.0 beta.2 (A2 every-cycle-bus-access, promoted to the only path
1147 /// in beta.4): canonical 6502 8-cycle (zp),Y RMW performs the
1148 /// unfixed-address dummy read UNCONDITIONALLY at cycle 5 (like RMW
1149 /// ABS,X/Y above — the CPU cannot know the fixed address until the
1150 /// high-byte add completes), not only on page cross. The burn-probe
1151 /// histogram pinned these six arms as the last instruction-dispatch
1152 /// busless cycles (25 of the original 9,795).
1153 fn addr_ind_y_rmw<B: Bus>(&mut self, bus: &mut B) -> u16 {
1154 // Delegate to the plain resolver (which already emits the
1155 // unfixed-address dummy read on a page cross), then emit the
1156 // RMW's unconditional cycle-5 dummy for the non-crossing case.
1157 // On a non-crossing access the canonical unfixed address
1158 // `(base & 0xFF00) | (addr & 0xFF)` EQUALS the final address
1159 // (the high byte needed no fix-up), so reading `o.addr` here is
1160 // the silicon-exact target — do not "fix" this to a separate
1161 // unfixed computation, they are identical by construction.
1162 let o = self.addr_ind_y(bus);
1163 if !o.page_crossed {
1164 let _ = self.read1(bus, o.addr);
1165 }
1166 o.addr
1167 }
1168
1169 fn addr_ind_x<B: Bus>(&mut self, bus: &mut B) -> Operand {
1170 let base = self.fetch_pc(bus);
1171 // A2: canonical (zp,X) cycle 3 — dummy read of the UN-indexed
1172 // pointer address while the X add completes (same silicon behavior
1173 // as `addr_zp_x`; zero-page, so register-side-effect-free).
1174 let _ = self.read1(bus, u16::from(base));
1175 let ptr = base.wrapping_add(self.x);
1176 let lo = self.read1(bus, u16::from(ptr));
1177 let hi = self.read1(bus, u16::from(ptr.wrapping_add(1)));
1178 Operand {
1179 addr: u16::from(lo) | (u16::from(hi) << 8),
1180 page_crossed: false,
1181 }
1182 }
1183
1184 fn addr_ind_y<B: Bus>(&mut self, bus: &mut B) -> Operand {
1185 let ptr = self.fetch_pc(bus);
1186 let lo = self.read1(bus, u16::from(ptr));
1187 let hi = self.read1(bus, u16::from(ptr.wrapping_add(1)));
1188 let base = u16::from(lo) | (u16::from(hi) << 8);
1189 let addr = base.wrapping_add(u16::from(self.y));
1190 let page_crossed = (base & 0xFF00) != (addr & 0xFF00);
1191 if page_crossed {
1192 // Page-cross dummy read at the unfixed address — same as
1193 // addr_abs_x/y. Canonical 6502 behavior for LDA (zp),Y on
1194 // page crossing.
1195 let dummy = (base & 0xFF00) | (addr & 0x00FF);
1196 let _ = self.read1(bus, dummy);
1197 }
1198 Operand { addr, page_crossed }
1199 }
1200
1201 // ------------------------------------------------------------------
1202 // Top-level dispatch.
1203 //
1204 // The 256-way match is the cleanest way to express the entire opcode
1205 // table; the doc-comments are intentionally absent at the arm level
1206 // because each one is a single line of the standard 6502 reference and
1207 // adding individual arm comments would overwhelm the readability of the
1208 // table.
1209 // ------------------------------------------------------------------
1210
1211 #[allow(
1212 clippy::cognitive_complexity,
1213 clippy::too_many_lines,
1214 clippy::match_same_arms
1215 )]
1216 fn dispatch<B: Bus>(&mut self, bus: &mut B, op: u8, cycles: &mut u8) {
1217 match op {
1218 // === Loads ===
1219 0xA9 => {
1220 let v = self.fetch_pc(bus);
1221 self.lda(v);
1222 *cycles = 2;
1223 }
1224 0xA5 => {
1225 let o = self.addr_zp(bus);
1226 self.lda_addr(bus, o.addr);
1227 *cycles = 3;
1228 }
1229 0xB5 => {
1230 let o = self.addr_zp_x(bus);
1231 self.lda_addr(bus, o.addr);
1232 *cycles = 4;
1233 }
1234 0xAD => {
1235 let o = self.addr_abs(bus);
1236 self.lda_addr(bus, o.addr);
1237 *cycles = 4;
1238 }
1239 0xBD => {
1240 let o = self.addr_abs_x(bus);
1241 self.lda_addr(bus, o.addr);
1242 *cycles = 4 + u8::from(o.page_crossed);
1243 }
1244 0xB9 => {
1245 let o = self.addr_abs_y(bus);
1246 self.lda_addr(bus, o.addr);
1247 *cycles = 4 + u8::from(o.page_crossed);
1248 }
1249 0xA1 => {
1250 let o = self.addr_ind_x(bus);
1251 self.lda_addr(bus, o.addr);
1252 *cycles = 6;
1253 }
1254 0xB1 => {
1255 let o = self.addr_ind_y(bus);
1256 self.lda_addr(bus, o.addr);
1257 *cycles = 5 + u8::from(o.page_crossed);
1258 }
1259
1260 0xA2 => {
1261 let v = self.fetch_pc(bus);
1262 self.ldx(v);
1263 *cycles = 2;
1264 }
1265 0xA6 => {
1266 let o = self.addr_zp(bus);
1267 let v = self.read1(bus, o.addr);
1268 self.ldx(v);
1269 *cycles = 3;
1270 }
1271 0xB6 => {
1272 let o = self.addr_zp_y(bus);
1273 let v = self.read1(bus, o.addr);
1274 self.ldx(v);
1275 *cycles = 4;
1276 }
1277 0xAE => {
1278 let o = self.addr_abs(bus);
1279 let v = self.read1(bus, o.addr);
1280 self.ldx(v);
1281 *cycles = 4;
1282 }
1283 0xBE => {
1284 let o = self.addr_abs_y(bus);
1285 let v = self.read1(bus, o.addr);
1286 self.ldx(v);
1287 *cycles = 4 + u8::from(o.page_crossed);
1288 }
1289
1290 0xA0 => {
1291 let v = self.fetch_pc(bus);
1292 self.ldy(v);
1293 *cycles = 2;
1294 }
1295 0xA4 => {
1296 let o = self.addr_zp(bus);
1297 let v = self.read1(bus, o.addr);
1298 self.ldy(v);
1299 *cycles = 3;
1300 }
1301 0xB4 => {
1302 let o = self.addr_zp_x(bus);
1303 let v = self.read1(bus, o.addr);
1304 self.ldy(v);
1305 *cycles = 4;
1306 }
1307 0xAC => {
1308 let o = self.addr_abs(bus);
1309 let v = self.read1(bus, o.addr);
1310 self.ldy(v);
1311 *cycles = 4;
1312 }
1313 0xBC => {
1314 let o = self.addr_abs_x(bus);
1315 let v = self.read1(bus, o.addr);
1316 self.ldy(v);
1317 *cycles = 4 + u8::from(o.page_crossed);
1318 }
1319
1320 // === Stores ===
1321 0x85 => {
1322 let o = self.addr_zp(bus);
1323 self.write1(bus, o.addr, self.a);
1324 *cycles = 3;
1325 }
1326 0x95 => {
1327 let o = self.addr_zp_x(bus);
1328 self.write1(bus, o.addr, self.a);
1329 *cycles = 4;
1330 }
1331 0x8D => {
1332 let o = self.addr_abs(bus);
1333 self.write1(bus, o.addr, self.a);
1334 *cycles = 4;
1335 }
1336 0x9D => {
1337 let o = self.addr_abs_x(bus);
1338 // Canonical 6502: STA absolute,X performs a dummy
1339 // read at cycle 4 even when no page is crossed (unlike
1340 // LDA where cycle 4 is the real read). `addr_abs_x`
1341 // already issues the dummy read at the unfixed address
1342 // when page-crossed; for the no-page-cross case we add
1343 // it here at the final address.
1344 if !o.page_crossed {
1345 let _ = self.read1(bus, o.addr);
1346 }
1347 self.write1(bus, o.addr, self.a);
1348 *cycles = 5;
1349 }
1350 0x99 => {
1351 let o = self.addr_abs_y(bus);
1352 if !o.page_crossed {
1353 let _ = self.read1(bus, o.addr);
1354 }
1355 self.write1(bus, o.addr, self.a);
1356 *cycles = 5;
1357 }
1358 0x81 => {
1359 let o = self.addr_ind_x(bus);
1360 self.write1(bus, o.addr, self.a);
1361 *cycles = 6;
1362 }
1363 0x91 => {
1364 let o = self.addr_ind_y(bus);
1365 // Canonical STA (zp),Y always dummy-reads at cycle 5
1366 // even when no page is crossed. `addr_ind_y` already
1367 // handles the page-cross dummy at the unfixed address;
1368 // add the no-page-cross dummy here at the final address.
1369 if !o.page_crossed {
1370 let _ = self.read1(bus, o.addr);
1371 }
1372 self.write1(bus, o.addr, self.a);
1373 *cycles = 6;
1374 }
1375
1376 0x86 => {
1377 let o = self.addr_zp(bus);
1378 self.write1(bus, o.addr, self.x);
1379 *cycles = 3;
1380 }
1381 0x96 => {
1382 let o = self.addr_zp_y(bus);
1383 self.write1(bus, o.addr, self.x);
1384 *cycles = 4;
1385 }
1386 0x8E => {
1387 let o = self.addr_abs(bus);
1388 self.write1(bus, o.addr, self.x);
1389 *cycles = 4;
1390 }
1391
1392 0x84 => {
1393 let o = self.addr_zp(bus);
1394 self.write1(bus, o.addr, self.y);
1395 *cycles = 3;
1396 }
1397 0x94 => {
1398 let o = self.addr_zp_x(bus);
1399 self.write1(bus, o.addr, self.y);
1400 *cycles = 4;
1401 }
1402 0x8C => {
1403 let o = self.addr_abs(bus);
1404 self.write1(bus, o.addr, self.y);
1405 *cycles = 4;
1406 }
1407
1408 // === Transfers ===
1409 0xAA => {
1410 self.implied_dummy_read(bus);
1411 self.x = self.a;
1412 self.p.set_nz(self.x);
1413 *cycles = 2;
1414 }
1415 0xA8 => {
1416 self.implied_dummy_read(bus);
1417 self.y = self.a;
1418 self.p.set_nz(self.y);
1419 *cycles = 2;
1420 }
1421 0xBA => {
1422 self.implied_dummy_read(bus);
1423 self.x = self.s;
1424 self.p.set_nz(self.x);
1425 *cycles = 2;
1426 }
1427 0x8A => {
1428 self.implied_dummy_read(bus);
1429 self.a = self.x;
1430 self.p.set_nz(self.a);
1431 *cycles = 2;
1432 }
1433 0x9A => {
1434 self.implied_dummy_read(bus);
1435 self.s = self.x;
1436 *cycles = 2;
1437 }
1438 0x98 => {
1439 self.implied_dummy_read(bus);
1440 self.a = self.y;
1441 self.p.set_nz(self.a);
1442 *cycles = 2;
1443 }
1444
1445 // === Stack ===
1446 0x48 => {
1447 // PHA: C2 dummy read PC (the 6502 always reads the next byte on
1448 // the second cycle of a stack push), then the push.
1449 let _ = self.read1(bus, self.pc);
1450 self.push(bus, self.a);
1451 *cycles = 3;
1452 }
1453 0x08 => {
1454 let _ = self.read1(bus, self.pc);
1455 self.push(bus, (self.p | Status::BREAK | Status::UNUSED).bits());
1456 *cycles = 3;
1457 }
1458 0x68 => {
1459 // PLA: C2 dummy read PC, C3 dummy stack read (pre-increment),
1460 // then the pull.
1461 {
1462 let _ = self.read1(bus, self.pc);
1463 let _ = self.read1(bus, STACK_BASE | u16::from(self.s));
1464 }
1465 self.a = self.pull(bus);
1466 self.p.set_nz(self.a);
1467 *cycles = 4;
1468 }
1469 0x28 => {
1470 {
1471 let _ = self.read1(bus, self.pc);
1472 let _ = self.read1(bus, STACK_BASE | u16::from(self.s));
1473 }
1474 let v = self.pull(bus);
1475 let mut new_p = Status::from_bits_truncate(v);
1476 new_p.remove(Status::BREAK);
1477 new_p.insert(Status::UNUSED);
1478 self.p = new_p;
1479 *cycles = 4;
1480 }
1481
1482 // === Logical ===
1483 0x29 => {
1484 let v = self.fetch_pc(bus);
1485 self.and(v);
1486 *cycles = 2;
1487 }
1488 0x25 => {
1489 let o = self.addr_zp(bus);
1490 let v = self.read1(bus, o.addr);
1491 self.and(v);
1492 *cycles = 3;
1493 }
1494 0x35 => {
1495 let o = self.addr_zp_x(bus);
1496 let v = self.read1(bus, o.addr);
1497 self.and(v);
1498 *cycles = 4;
1499 }
1500 0x2D => {
1501 let o = self.addr_abs(bus);
1502 let v = self.read1(bus, o.addr);
1503 self.and(v);
1504 *cycles = 4;
1505 }
1506 0x3D => {
1507 let o = self.addr_abs_x(bus);
1508 let v = self.read1(bus, o.addr);
1509 self.and(v);
1510 *cycles = 4 + u8::from(o.page_crossed);
1511 }
1512 0x39 => {
1513 let o = self.addr_abs_y(bus);
1514 let v = self.read1(bus, o.addr);
1515 self.and(v);
1516 *cycles = 4 + u8::from(o.page_crossed);
1517 }
1518 0x21 => {
1519 let o = self.addr_ind_x(bus);
1520 let v = self.read1(bus, o.addr);
1521 self.and(v);
1522 *cycles = 6;
1523 }
1524 0x31 => {
1525 let o = self.addr_ind_y(bus);
1526 let v = self.read1(bus, o.addr);
1527 self.and(v);
1528 *cycles = 5 + u8::from(o.page_crossed);
1529 }
1530
1531 0x09 => {
1532 let v = self.fetch_pc(bus);
1533 self.ora(v);
1534 *cycles = 2;
1535 }
1536 0x05 => {
1537 let o = self.addr_zp(bus);
1538 let v = self.read1(bus, o.addr);
1539 self.ora(v);
1540 *cycles = 3;
1541 }
1542 0x15 => {
1543 let o = self.addr_zp_x(bus);
1544 let v = self.read1(bus, o.addr);
1545 self.ora(v);
1546 *cycles = 4;
1547 }
1548 0x0D => {
1549 let o = self.addr_abs(bus);
1550 let v = self.read1(bus, o.addr);
1551 self.ora(v);
1552 *cycles = 4;
1553 }
1554 0x1D => {
1555 let o = self.addr_abs_x(bus);
1556 let v = self.read1(bus, o.addr);
1557 self.ora(v);
1558 *cycles = 4 + u8::from(o.page_crossed);
1559 }
1560 0x19 => {
1561 let o = self.addr_abs_y(bus);
1562 let v = self.read1(bus, o.addr);
1563 self.ora(v);
1564 *cycles = 4 + u8::from(o.page_crossed);
1565 }
1566 0x01 => {
1567 let o = self.addr_ind_x(bus);
1568 let v = self.read1(bus, o.addr);
1569 self.ora(v);
1570 *cycles = 6;
1571 }
1572 0x11 => {
1573 let o = self.addr_ind_y(bus);
1574 let v = self.read1(bus, o.addr);
1575 self.ora(v);
1576 *cycles = 5 + u8::from(o.page_crossed);
1577 }
1578
1579 0x49 => {
1580 let v = self.fetch_pc(bus);
1581 self.eor(v);
1582 *cycles = 2;
1583 }
1584 0x45 => {
1585 let o = self.addr_zp(bus);
1586 let v = self.read1(bus, o.addr);
1587 self.eor(v);
1588 *cycles = 3;
1589 }
1590 0x55 => {
1591 let o = self.addr_zp_x(bus);
1592 let v = self.read1(bus, o.addr);
1593 self.eor(v);
1594 *cycles = 4;
1595 }
1596 0x4D => {
1597 let o = self.addr_abs(bus);
1598 let v = self.read1(bus, o.addr);
1599 self.eor(v);
1600 *cycles = 4;
1601 }
1602 0x5D => {
1603 let o = self.addr_abs_x(bus);
1604 let v = self.read1(bus, o.addr);
1605 self.eor(v);
1606 *cycles = 4 + u8::from(o.page_crossed);
1607 }
1608 0x59 => {
1609 let o = self.addr_abs_y(bus);
1610 let v = self.read1(bus, o.addr);
1611 self.eor(v);
1612 *cycles = 4 + u8::from(o.page_crossed);
1613 }
1614 0x41 => {
1615 let o = self.addr_ind_x(bus);
1616 let v = self.read1(bus, o.addr);
1617 self.eor(v);
1618 *cycles = 6;
1619 }
1620 0x51 => {
1621 let o = self.addr_ind_y(bus);
1622 let v = self.read1(bus, o.addr);
1623 self.eor(v);
1624 *cycles = 5 + u8::from(o.page_crossed);
1625 }
1626
1627 0x24 => {
1628 let o = self.addr_zp(bus);
1629 let v = self.read1(bus, o.addr);
1630 self.bit(v);
1631 *cycles = 3;
1632 }
1633 0x2C => {
1634 let o = self.addr_abs(bus);
1635 let v = self.read1(bus, o.addr);
1636 self.bit(v);
1637 *cycles = 4;
1638 }
1639
1640 // === Arithmetic ===
1641 0x69 => {
1642 let v = self.fetch_pc(bus);
1643 self.adc(v);
1644 *cycles = 2;
1645 }
1646 0x65 => {
1647 let o = self.addr_zp(bus);
1648 let v = self.read1(bus, o.addr);
1649 self.adc(v);
1650 *cycles = 3;
1651 }
1652 0x75 => {
1653 let o = self.addr_zp_x(bus);
1654 let v = self.read1(bus, o.addr);
1655 self.adc(v);
1656 *cycles = 4;
1657 }
1658 0x6D => {
1659 let o = self.addr_abs(bus);
1660 let v = self.read1(bus, o.addr);
1661 self.adc(v);
1662 *cycles = 4;
1663 }
1664 0x7D => {
1665 let o = self.addr_abs_x(bus);
1666 let v = self.read1(bus, o.addr);
1667 self.adc(v);
1668 *cycles = 4 + u8::from(o.page_crossed);
1669 }
1670 0x79 => {
1671 let o = self.addr_abs_y(bus);
1672 let v = self.read1(bus, o.addr);
1673 self.adc(v);
1674 *cycles = 4 + u8::from(o.page_crossed);
1675 }
1676 0x61 => {
1677 let o = self.addr_ind_x(bus);
1678 let v = self.read1(bus, o.addr);
1679 self.adc(v);
1680 *cycles = 6;
1681 }
1682 0x71 => {
1683 let o = self.addr_ind_y(bus);
1684 let v = self.read1(bus, o.addr);
1685 self.adc(v);
1686 *cycles = 5 + u8::from(o.page_crossed);
1687 }
1688
1689 0xE9 | 0xEB => {
1690 let v = self.fetch_pc(bus);
1691 self.sbc(v);
1692 *cycles = 2;
1693 }
1694 0xE5 => {
1695 let o = self.addr_zp(bus);
1696 let v = self.read1(bus, o.addr);
1697 self.sbc(v);
1698 *cycles = 3;
1699 }
1700 0xF5 => {
1701 let o = self.addr_zp_x(bus);
1702 let v = self.read1(bus, o.addr);
1703 self.sbc(v);
1704 *cycles = 4;
1705 }
1706 0xED => {
1707 let o = self.addr_abs(bus);
1708 let v = self.read1(bus, o.addr);
1709 self.sbc(v);
1710 *cycles = 4;
1711 }
1712 0xFD => {
1713 let o = self.addr_abs_x(bus);
1714 let v = self.read1(bus, o.addr);
1715 self.sbc(v);
1716 *cycles = 4 + u8::from(o.page_crossed);
1717 }
1718 0xF9 => {
1719 let o = self.addr_abs_y(bus);
1720 let v = self.read1(bus, o.addr);
1721 self.sbc(v);
1722 *cycles = 4 + u8::from(o.page_crossed);
1723 }
1724 0xE1 => {
1725 let o = self.addr_ind_x(bus);
1726 let v = self.read1(bus, o.addr);
1727 self.sbc(v);
1728 *cycles = 6;
1729 }
1730 0xF1 => {
1731 let o = self.addr_ind_y(bus);
1732 let v = self.read1(bus, o.addr);
1733 self.sbc(v);
1734 *cycles = 5 + u8::from(o.page_crossed);
1735 }
1736
1737 // === Compare ===
1738 0xC9 => {
1739 let v = self.fetch_pc(bus);
1740 self.cmp_with(self.a, v);
1741 *cycles = 2;
1742 }
1743 0xC5 => {
1744 let o = self.addr_zp(bus);
1745 let v = self.read1(bus, o.addr);
1746 self.cmp_with(self.a, v);
1747 *cycles = 3;
1748 }
1749 0xD5 => {
1750 let o = self.addr_zp_x(bus);
1751 let v = self.read1(bus, o.addr);
1752 self.cmp_with(self.a, v);
1753 *cycles = 4;
1754 }
1755 0xCD => {
1756 let o = self.addr_abs(bus);
1757 let v = self.read1(bus, o.addr);
1758 self.cmp_with(self.a, v);
1759 *cycles = 4;
1760 }
1761 0xDD => {
1762 let o = self.addr_abs_x(bus);
1763 let v = self.read1(bus, o.addr);
1764 self.cmp_with(self.a, v);
1765 *cycles = 4 + u8::from(o.page_crossed);
1766 }
1767 0xD9 => {
1768 let o = self.addr_abs_y(bus);
1769 let v = self.read1(bus, o.addr);
1770 self.cmp_with(self.a, v);
1771 *cycles = 4 + u8::from(o.page_crossed);
1772 }
1773 0xC1 => {
1774 let o = self.addr_ind_x(bus);
1775 let v = self.read1(bus, o.addr);
1776 self.cmp_with(self.a, v);
1777 *cycles = 6;
1778 }
1779 0xD1 => {
1780 let o = self.addr_ind_y(bus);
1781 let v = self.read1(bus, o.addr);
1782 self.cmp_with(self.a, v);
1783 *cycles = 5 + u8::from(o.page_crossed);
1784 }
1785
1786 0xE0 => {
1787 let v = self.fetch_pc(bus);
1788 self.cmp_with(self.x, v);
1789 *cycles = 2;
1790 }
1791 0xE4 => {
1792 let o = self.addr_zp(bus);
1793 let v = self.read1(bus, o.addr);
1794 self.cmp_with(self.x, v);
1795 *cycles = 3;
1796 }
1797 0xEC => {
1798 let o = self.addr_abs(bus);
1799 let v = self.read1(bus, o.addr);
1800 self.cmp_with(self.x, v);
1801 *cycles = 4;
1802 }
1803
1804 0xC0 => {
1805 let v = self.fetch_pc(bus);
1806 self.cmp_with(self.y, v);
1807 *cycles = 2;
1808 }
1809 0xC4 => {
1810 let o = self.addr_zp(bus);
1811 let v = self.read1(bus, o.addr);
1812 self.cmp_with(self.y, v);
1813 *cycles = 3;
1814 }
1815 0xCC => {
1816 let o = self.addr_abs(bus);
1817 let v = self.read1(bus, o.addr);
1818 self.cmp_with(self.y, v);
1819 *cycles = 4;
1820 }
1821
1822 // === Increments / decrements ===
1823 0xE6 => {
1824 let o = self.addr_zp(bus);
1825 self.inc_addr(bus, o.addr);
1826 *cycles = 5;
1827 }
1828 0xF6 => {
1829 let o = self.addr_zp_x(bus);
1830 self.inc_addr(bus, o.addr);
1831 *cycles = 6;
1832 }
1833 0xEE => {
1834 let o = self.addr_abs(bus);
1835 self.inc_addr(bus, o.addr);
1836 *cycles = 6;
1837 }
1838 0xFE => {
1839 let addr = self.addr_abs_x_rmw(bus);
1840 self.inc_addr(bus, addr);
1841 *cycles = 7;
1842 }
1843 0xC6 => {
1844 let o = self.addr_zp(bus);
1845 self.dec_addr(bus, o.addr);
1846 *cycles = 5;
1847 }
1848 0xD6 => {
1849 let o = self.addr_zp_x(bus);
1850 self.dec_addr(bus, o.addr);
1851 *cycles = 6;
1852 }
1853 0xCE => {
1854 let o = self.addr_abs(bus);
1855 self.dec_addr(bus, o.addr);
1856 *cycles = 6;
1857 }
1858 0xDE => {
1859 let addr = self.addr_abs_x_rmw(bus);
1860 self.dec_addr(bus, addr);
1861 *cycles = 7;
1862 }
1863 0xE8 => {
1864 self.implied_dummy_read(bus);
1865 self.x = self.x.wrapping_add(1);
1866 self.p.set_nz(self.x);
1867 *cycles = 2;
1868 }
1869 0xCA => {
1870 self.implied_dummy_read(bus);
1871 self.x = self.x.wrapping_sub(1);
1872 self.p.set_nz(self.x);
1873 *cycles = 2;
1874 }
1875 0xC8 => {
1876 self.implied_dummy_read(bus);
1877 self.y = self.y.wrapping_add(1);
1878 self.p.set_nz(self.y);
1879 *cycles = 2;
1880 }
1881 0x88 => {
1882 self.implied_dummy_read(bus);
1883 self.y = self.y.wrapping_sub(1);
1884 self.p.set_nz(self.y);
1885 *cycles = 2;
1886 }
1887
1888 // === Shifts ===
1889 0x0A => {
1890 self.implied_dummy_read(bus);
1891 self.a = self.asl_value(self.a);
1892 *cycles = 2;
1893 }
1894 0x06 => {
1895 let o = self.addr_zp(bus);
1896 self.asl_addr(bus, o.addr);
1897 *cycles = 5;
1898 }
1899 0x16 => {
1900 let o = self.addr_zp_x(bus);
1901 self.asl_addr(bus, o.addr);
1902 *cycles = 6;
1903 }
1904 0x0E => {
1905 let o = self.addr_abs(bus);
1906 self.asl_addr(bus, o.addr);
1907 *cycles = 6;
1908 }
1909 0x1E => {
1910 let addr = self.addr_abs_x_rmw(bus);
1911 self.asl_addr(bus, addr);
1912 *cycles = 7;
1913 }
1914
1915 0x4A => {
1916 self.implied_dummy_read(bus);
1917 self.a = self.lsr_value(self.a);
1918 *cycles = 2;
1919 }
1920 0x46 => {
1921 let o = self.addr_zp(bus);
1922 self.lsr_addr(bus, o.addr);
1923 *cycles = 5;
1924 }
1925 0x56 => {
1926 let o = self.addr_zp_x(bus);
1927 self.lsr_addr(bus, o.addr);
1928 *cycles = 6;
1929 }
1930 0x4E => {
1931 let o = self.addr_abs(bus);
1932 self.lsr_addr(bus, o.addr);
1933 *cycles = 6;
1934 }
1935 0x5E => {
1936 let addr = self.addr_abs_x_rmw(bus);
1937 self.lsr_addr(bus, addr);
1938 *cycles = 7;
1939 }
1940
1941 0x2A => {
1942 self.implied_dummy_read(bus);
1943 self.a = self.rol_value(self.a);
1944 *cycles = 2;
1945 }
1946 0x26 => {
1947 let o = self.addr_zp(bus);
1948 self.rol_addr(bus, o.addr);
1949 *cycles = 5;
1950 }
1951 0x36 => {
1952 let o = self.addr_zp_x(bus);
1953 self.rol_addr(bus, o.addr);
1954 *cycles = 6;
1955 }
1956 0x2E => {
1957 let o = self.addr_abs(bus);
1958 self.rol_addr(bus, o.addr);
1959 *cycles = 6;
1960 }
1961 0x3E => {
1962 let addr = self.addr_abs_x_rmw(bus);
1963 self.rol_addr(bus, addr);
1964 *cycles = 7;
1965 }
1966
1967 0x6A => {
1968 self.implied_dummy_read(bus);
1969 self.a = self.ror_value(self.a);
1970 *cycles = 2;
1971 }
1972 0x66 => {
1973 let o = self.addr_zp(bus);
1974 self.ror_addr(bus, o.addr);
1975 *cycles = 5;
1976 }
1977 0x76 => {
1978 let o = self.addr_zp_x(bus);
1979 self.ror_addr(bus, o.addr);
1980 *cycles = 6;
1981 }
1982 0x6E => {
1983 let o = self.addr_abs(bus);
1984 self.ror_addr(bus, o.addr);
1985 *cycles = 6;
1986 }
1987 0x7E => {
1988 let addr = self.addr_abs_x_rmw(bus);
1989 self.ror_addr(bus, addr);
1990 *cycles = 7;
1991 }
1992
1993 // === Branches ===
1994 //
1995 // The `branch_delays_irq` quirk: real 6502 branches poll IRQ
1996 // at the same point a 2-cycle untaken branch would — at the
1997 // opcode-fetch cycle (the canonical 2-cycle "second-to-last"
1998 // poll). The operand-fetch cycle and any extra taken /
1999 // page-cross cycles do NOT re-sample IRQ. We suppress IRQ
2000 // sampling for the remaining cycles of the instruction
2001 // immediately *before* the operand fetch — the opcode-fetch
2002 // sample (in `step()`) has already happened by this point.
2003 // See `docs/cpu-6502.md` §Interrupt logic and
2004 // <https://www.nesdev.org/wiki/CPU_interrupts>.
2005 0x10 => {
2006 self.skip_irq_sample = true;
2007 let off = self.fetch_pc(bus);
2008 *cycles = self.branch(bus, off, !self.p.contains(Status::NEGATIVE));
2009 }
2010 0x30 => {
2011 self.skip_irq_sample = true;
2012 let off = self.fetch_pc(bus);
2013 *cycles = self.branch(bus, off, self.p.contains(Status::NEGATIVE));
2014 }
2015 0x50 => {
2016 self.skip_irq_sample = true;
2017 let off = self.fetch_pc(bus);
2018 *cycles = self.branch(bus, off, !self.p.contains(Status::OVERFLOW));
2019 }
2020 0x70 => {
2021 self.skip_irq_sample = true;
2022 let off = self.fetch_pc(bus);
2023 *cycles = self.branch(bus, off, self.p.contains(Status::OVERFLOW));
2024 }
2025 0x90 => {
2026 self.skip_irq_sample = true;
2027 let off = self.fetch_pc(bus);
2028 *cycles = self.branch(bus, off, !self.p.contains(Status::CARRY));
2029 }
2030 0xB0 => {
2031 self.skip_irq_sample = true;
2032 let off = self.fetch_pc(bus);
2033 *cycles = self.branch(bus, off, self.p.contains(Status::CARRY));
2034 }
2035 0xD0 => {
2036 self.skip_irq_sample = true;
2037 let off = self.fetch_pc(bus);
2038 *cycles = self.branch(bus, off, !self.p.contains(Status::ZERO));
2039 }
2040 0xF0 => {
2041 self.skip_irq_sample = true;
2042 let off = self.fetch_pc(bus);
2043 *cycles = self.branch(bus, off, self.p.contains(Status::ZERO));
2044 }
2045
2046 // === Jumps / subroutine ===
2047 0x4C => {
2048 self.pc = self.fetch_pc_u16(bus);
2049 *cycles = 3;
2050 }
2051 0x6C => {
2052 let ptr = self.fetch_pc_u16(bus);
2053 self.pc = self.read_u16_with_wrap(bus, ptr);
2054 *cycles = 5;
2055 }
2056 0x20 => {
2057 // Canonical 6502 JSR cycle sequence — the high byte of
2058 // the target is read AFTER PC is pushed to the stack.
2059 // Wrong order is observable when JSR overwrites its own
2060 // operand via the pushed return address (AccuracyCoin
2061 // `CPU Behavior 2 :: JSR Edge Cases` Test 2 brackets
2062 // this exactly):
2063 // C1: opcode fetch (already done by `tick` dispatcher)
2064 // C2: fetch low byte of target → advances PC
2065 // C3: dummy read from stack at $0100|S (no-op)
2066 // C4: push PC high (PC is currently at the high-byte
2067 // operand address, which is exactly the return
2068 // address minus one)
2069 // C5: push PC low
2070 // C6: fetch high byte of target → PC = target
2071 let lo = self.fetch_pc(bus);
2072 let _ = self.read1(bus, STACK_BASE | u16::from(self.s));
2073 // self.pc now points at the high-byte operand; this is
2074 // the "return - 1" address JSR canonically pushes.
2075 let return_minus_one = self.pc;
2076 self.push(bus, (return_minus_one >> 8) as u8);
2077 self.push(bus, (return_minus_one & 0xFF) as u8);
2078 let hi = self.fetch_pc(bus);
2079 self.pc = u16::from(lo) | (u16::from(hi) << 8);
2080 *cycles = 6;
2081 }
2082 0x60 => {
2083 // Canonical 6502 RTS bus pattern (every cycle is a bus access):
2084 // C1 opcode fetch (dispatcher) | C2 dummy read PC |
2085 // C3 dummy stack read (pre-increment) |
2086 // C4 pull PCL | C5 pull PCH | C6 dummy read at the return addr.
2087 // Default build burns C2/C3/C6 as `idle_tick` (no bus access);
2088 // `cpu-stack-dummy-reads` emits the canonical dummy reads — the
2089 // DC-6 Y=3-vs-4 fix. See the cell-trace cross-diff.
2090 {
2091 let _ = self.read1(bus, self.pc);
2092 let _ = self.read1(bus, STACK_BASE | u16::from(self.s));
2093 let v = self.pull_u16(bus);
2094 let _ = self.read1(bus, v);
2095 self.pc = v.wrapping_add(1);
2096 }
2097 *cycles = 6;
2098 }
2099 0x40 => {
2100 // Canonical RTI bus pattern: C2 dummy read PC, C3 dummy stack
2101 // read (pre-increment) before the pulls. Default-off helper.
2102 {
2103 let _ = self.read1(bus, self.pc);
2104 let _ = self.read1(bus, STACK_BASE | u16::from(self.s));
2105 }
2106 let p = self.pull(bus);
2107 let mut new_p = Status::from_bits_truncate(p);
2108 new_p.remove(Status::BREAK);
2109 new_p.insert(Status::UNUSED);
2110 self.p = new_p;
2111 // RTI's I-flag change is observed by the IRQ sample
2112 // (unlike PLP / CLI / SEI which delay one instruction).
2113 self.irq_sample_i_flag = self.p.contains(Status::INTERRUPT_DISABLE);
2114 self.pc = self.pull_u16(bus);
2115 *cycles = 6;
2116 }
2117 0x00 => {
2118 // BRK is a 7-cycle interrupt with PC+2 pushed (PC already
2119 // advanced by fetch; advance one more for the padding byte).
2120 self.pc = self.pc.wrapping_add(1);
2121 self.service_interrupt(bus, IRQ_VECTOR, true);
2122 // R1/A2: suppress an NMI that became pending during/just-after
2123 // the BRK sequence so the FIRST instruction of the IRQ handler
2124 // runs before the NMI is taken (Mesen2 `NesCpu::BRK`
2125 // `_prevNeedNmi = false`; "needed for nmi_and_brk"). The NMI is
2126 // not lost — `mc_need_nmi` stays set and re-arms next cycle.
2127 {
2128 self.mc_prev_need_nmi = false;
2129 }
2130 // service_interrupt already burned 7 cycles; do NOT double-count.
2131 *cycles = 0;
2132 }
2133 0xEA => {
2134 self.implied_dummy_read(bus);
2135 *cycles = 2;
2136 }
2137
2138 // === Flag manipulation ===
2139 0x18 => {
2140 self.implied_dummy_read(bus);
2141 self.p.remove(Status::CARRY);
2142 *cycles = 2;
2143 }
2144 0x38 => {
2145 self.implied_dummy_read(bus);
2146 self.p.insert(Status::CARRY);
2147 *cycles = 2;
2148 }
2149 0x58 => {
2150 self.implied_dummy_read(bus);
2151 self.p.remove(Status::INTERRUPT_DISABLE);
2152 *cycles = 2;
2153 }
2154 0x78 => {
2155 self.implied_dummy_read(bus);
2156 self.p.insert(Status::INTERRUPT_DISABLE);
2157 *cycles = 2;
2158 }
2159 0xB8 => {
2160 self.implied_dummy_read(bus);
2161 self.p.remove(Status::OVERFLOW);
2162 *cycles = 2;
2163 }
2164 0xD8 => {
2165 self.implied_dummy_read(bus);
2166 self.p.remove(Status::DECIMAL);
2167 *cycles = 2;
2168 }
2169 0xF8 => {
2170 self.implied_dummy_read(bus);
2171 self.p.insert(Status::DECIMAL);
2172 *cycles = 2;
2173 }
2174
2175 // === Unofficial NOP variants ===
2176 // Implied / 1-byte NOPs
2177 0x1A | 0x3A | 0x5A | 0x7A | 0xDA | 0xFA => {
2178 self.implied_dummy_read(bus);
2179 *cycles = 2;
2180 }
2181 // Immediate / zero-page DOP (double NOP) variants: skip 1 byte.
2182 0x80 | 0x82 | 0x89 | 0xC2 | 0xE2 => {
2183 let _ = self.fetch_pc(bus);
2184 *cycles = 2;
2185 }
2186 0x04 | 0x44 | 0x64 => {
2187 let o = self.addr_zp(bus);
2188 let _ = self.read1(bus, o.addr); // unofficial DOP dummy read
2189 *cycles = 3;
2190 }
2191 0x14 | 0x34 | 0x54 | 0x74 | 0xD4 | 0xF4 => {
2192 let o = self.addr_zp_x(bus);
2193 let _ = self.read1(bus, o.addr); // unofficial DOP dummy read
2194 *cycles = 4;
2195 }
2196 // Absolute "TOP" (triple NOP) — must dummy-read the target so
2197 // that PPU-mirror side-effects (e.g. clearing $2002.7) fire,
2198 // matching real silicon and AccuracyCoin's All-NOPs Test 2.
2199 0x0C => {
2200 let o = self.addr_abs(bus);
2201 let _ = self.read1(bus, o.addr);
2202 *cycles = 4;
2203 }
2204 0x1C | 0x3C | 0x5C | 0x7C | 0xDC | 0xFC => {
2205 let o = self.addr_abs_x(bus);
2206 let _ = self.read1(bus, o.addr); // dummy read on TOP
2207 *cycles = 4 + u8::from(o.page_crossed);
2208 }
2209
2210 // === Stable unofficial: LAX, SAX ===
2211 0xA7 => {
2212 let o = self.addr_zp(bus);
2213 let v = self.read1(bus, o.addr);
2214 self.lax(v);
2215 *cycles = 3;
2216 }
2217 0xB7 => {
2218 let o = self.addr_zp_y(bus);
2219 let v = self.read1(bus, o.addr);
2220 self.lax(v);
2221 *cycles = 4;
2222 }
2223 0xAF => {
2224 let o = self.addr_abs(bus);
2225 let v = self.read1(bus, o.addr);
2226 self.lax(v);
2227 *cycles = 4;
2228 }
2229 0xBF => {
2230 let o = self.addr_abs_y(bus);
2231 let v = self.read1(bus, o.addr);
2232 self.lax(v);
2233 *cycles = 4 + u8::from(o.page_crossed);
2234 }
2235 0xA3 => {
2236 let o = self.addr_ind_x(bus);
2237 let v = self.read1(bus, o.addr);
2238 self.lax(v);
2239 *cycles = 6;
2240 }
2241 0xB3 => {
2242 let o = self.addr_ind_y(bus);
2243 let v = self.read1(bus, o.addr);
2244 self.lax(v);
2245 *cycles = 5 + u8::from(o.page_crossed);
2246 }
2247 0xAB => {
2248 let v = self.fetch_pc(bus);
2249 self.lax(v);
2250 *cycles = 2;
2251 } // LAX immediate (often listed as ATX). We follow nestest behavior.
2252
2253 0x87 => {
2254 let o = self.addr_zp(bus);
2255 self.write1(bus, o.addr, self.a & self.x);
2256 *cycles = 3;
2257 }
2258 0x97 => {
2259 let o = self.addr_zp_y(bus);
2260 self.write1(bus, o.addr, self.a & self.x);
2261 *cycles = 4;
2262 }
2263 0x8F => {
2264 let o = self.addr_abs(bus);
2265 self.write1(bus, o.addr, self.a & self.x);
2266 *cycles = 4;
2267 }
2268 0x83 => {
2269 let o = self.addr_ind_x(bus);
2270 self.write1(bus, o.addr, self.a & self.x);
2271 *cycles = 6;
2272 }
2273
2274 // === DCP (DEC + CMP) ===
2275 0xC7 => {
2276 let o = self.addr_zp(bus);
2277 self.dcp_addr(bus, o.addr);
2278 *cycles = 5;
2279 }
2280 0xD7 => {
2281 let o = self.addr_zp_x(bus);
2282 self.dcp_addr(bus, o.addr);
2283 *cycles = 6;
2284 }
2285 0xCF => {
2286 let o = self.addr_abs(bus);
2287 self.dcp_addr(bus, o.addr);
2288 *cycles = 6;
2289 }
2290 0xDF => {
2291 let addr = self.addr_abs_x_rmw(bus);
2292 self.dcp_addr(bus, addr);
2293 *cycles = 7;
2294 }
2295 0xDB => {
2296 let addr = self.addr_abs_y_rmw(bus);
2297 self.dcp_addr(bus, addr);
2298 *cycles = 7;
2299 }
2300 0xC3 => {
2301 let o = self.addr_ind_x(bus);
2302 self.dcp_addr(bus, o.addr);
2303 *cycles = 8;
2304 }
2305 0xD3 => {
2306 let addr = self.addr_ind_y_rmw(bus);
2307 self.dcp_addr(bus, addr);
2308 *cycles = 8;
2309 }
2310
2311 // === ISC (INC + SBC) ===
2312 0xE7 => {
2313 let o = self.addr_zp(bus);
2314 self.isc_addr(bus, o.addr);
2315 *cycles = 5;
2316 }
2317 0xF7 => {
2318 let o = self.addr_zp_x(bus);
2319 self.isc_addr(bus, o.addr);
2320 *cycles = 6;
2321 }
2322 0xEF => {
2323 let o = self.addr_abs(bus);
2324 self.isc_addr(bus, o.addr);
2325 *cycles = 6;
2326 }
2327 0xFF => {
2328 let addr = self.addr_abs_x_rmw(bus);
2329 self.isc_addr(bus, addr);
2330 *cycles = 7;
2331 }
2332 0xFB => {
2333 let addr = self.addr_abs_y_rmw(bus);
2334 self.isc_addr(bus, addr);
2335 *cycles = 7;
2336 }
2337 0xE3 => {
2338 let o = self.addr_ind_x(bus);
2339 self.isc_addr(bus, o.addr);
2340 *cycles = 8;
2341 }
2342 0xF3 => {
2343 let addr = self.addr_ind_y_rmw(bus);
2344 self.isc_addr(bus, addr);
2345 *cycles = 8;
2346 }
2347
2348 // === SLO (ASL + ORA) ===
2349 0x07 => {
2350 let o = self.addr_zp(bus);
2351 self.slo_addr(bus, o.addr);
2352 *cycles = 5;
2353 }
2354 0x17 => {
2355 let o = self.addr_zp_x(bus);
2356 self.slo_addr(bus, o.addr);
2357 *cycles = 6;
2358 }
2359 0x0F => {
2360 let o = self.addr_abs(bus);
2361 self.slo_addr(bus, o.addr);
2362 *cycles = 6;
2363 }
2364 0x1F => {
2365 let addr = self.addr_abs_x_rmw(bus);
2366 self.slo_addr(bus, addr);
2367 *cycles = 7;
2368 }
2369 0x1B => {
2370 let addr = self.addr_abs_y_rmw(bus);
2371 self.slo_addr(bus, addr);
2372 *cycles = 7;
2373 }
2374 0x03 => {
2375 let o = self.addr_ind_x(bus);
2376 self.slo_addr(bus, o.addr);
2377 *cycles = 8;
2378 }
2379 0x13 => {
2380 let addr = self.addr_ind_y_rmw(bus);
2381 self.slo_addr(bus, addr);
2382 *cycles = 8;
2383 }
2384
2385 // === RLA (ROL + AND) ===
2386 0x27 => {
2387 let o = self.addr_zp(bus);
2388 self.rla_addr(bus, o.addr);
2389 *cycles = 5;
2390 }
2391 0x37 => {
2392 let o = self.addr_zp_x(bus);
2393 self.rla_addr(bus, o.addr);
2394 *cycles = 6;
2395 }
2396 0x2F => {
2397 let o = self.addr_abs(bus);
2398 self.rla_addr(bus, o.addr);
2399 *cycles = 6;
2400 }
2401 0x3F => {
2402 let addr = self.addr_abs_x_rmw(bus);
2403 self.rla_addr(bus, addr);
2404 *cycles = 7;
2405 }
2406 0x3B => {
2407 let addr = self.addr_abs_y_rmw(bus);
2408 self.rla_addr(bus, addr);
2409 *cycles = 7;
2410 }
2411 0x23 => {
2412 let o = self.addr_ind_x(bus);
2413 self.rla_addr(bus, o.addr);
2414 *cycles = 8;
2415 }
2416 0x33 => {
2417 let addr = self.addr_ind_y_rmw(bus);
2418 self.rla_addr(bus, addr);
2419 *cycles = 8;
2420 }
2421
2422 // === SRE (LSR + EOR) ===
2423 0x47 => {
2424 let o = self.addr_zp(bus);
2425 self.sre_addr(bus, o.addr);
2426 *cycles = 5;
2427 }
2428 0x57 => {
2429 let o = self.addr_zp_x(bus);
2430 self.sre_addr(bus, o.addr);
2431 *cycles = 6;
2432 }
2433 0x4F => {
2434 let o = self.addr_abs(bus);
2435 self.sre_addr(bus, o.addr);
2436 *cycles = 6;
2437 }
2438 0x5F => {
2439 let addr = self.addr_abs_x_rmw(bus);
2440 self.sre_addr(bus, addr);
2441 *cycles = 7;
2442 }
2443 0x5B => {
2444 let addr = self.addr_abs_y_rmw(bus);
2445 self.sre_addr(bus, addr);
2446 *cycles = 7;
2447 }
2448 0x43 => {
2449 let o = self.addr_ind_x(bus);
2450 self.sre_addr(bus, o.addr);
2451 *cycles = 8;
2452 }
2453 0x53 => {
2454 let addr = self.addr_ind_y_rmw(bus);
2455 self.sre_addr(bus, addr);
2456 *cycles = 8;
2457 }
2458
2459 // === RRA (ROR + ADC) ===
2460 0x67 => {
2461 let o = self.addr_zp(bus);
2462 self.rra_addr(bus, o.addr);
2463 *cycles = 5;
2464 }
2465 0x77 => {
2466 let o = self.addr_zp_x(bus);
2467 self.rra_addr(bus, o.addr);
2468 *cycles = 6;
2469 }
2470 0x6F => {
2471 let o = self.addr_abs(bus);
2472 self.rra_addr(bus, o.addr);
2473 *cycles = 6;
2474 }
2475 0x7F => {
2476 let addr = self.addr_abs_x_rmw(bus);
2477 self.rra_addr(bus, addr);
2478 *cycles = 7;
2479 }
2480 0x7B => {
2481 let addr = self.addr_abs_y_rmw(bus);
2482 self.rra_addr(bus, addr);
2483 *cycles = 7;
2484 }
2485 0x63 => {
2486 let o = self.addr_ind_x(bus);
2487 self.rra_addr(bus, o.addr);
2488 *cycles = 8;
2489 }
2490 0x73 => {
2491 let addr = self.addr_ind_y_rmw(bus);
2492 self.rra_addr(bus, addr);
2493 *cycles = 8;
2494 }
2495
2496 // === ANC, ALR, ARR, AXS ===
2497 0x0B | 0x2B => {
2498 let v = self.fetch_pc(bus);
2499 self.a &= v;
2500 self.p.set_nz(self.a);
2501 self.p.set(Status::CARRY, self.a & 0x80 != 0);
2502 *cycles = 2;
2503 }
2504 0x4B => {
2505 let v = self.fetch_pc(bus);
2506 self.a &= v;
2507 let new_carry = self.a & 0x01 != 0;
2508 self.a >>= 1;
2509 self.p.set_nz(self.a);
2510 self.p.set(Status::CARRY, new_carry);
2511 *cycles = 2;
2512 }
2513 0x6B => {
2514 let v = self.fetch_pc(bus);
2515 self.a &= v;
2516 let carry_in = self.p.contains(Status::CARRY);
2517 self.a = (self.a >> 1) | (u8::from(carry_in) << 7);
2518 self.p.set_nz(self.a);
2519 let bit6 = self.a & 0x40 != 0;
2520 let bit5 = self.a & 0x20 != 0;
2521 self.p.set(Status::CARRY, bit6);
2522 self.p.set(Status::OVERFLOW, bit6 ^ bit5);
2523 *cycles = 2;
2524 }
2525 0xCB => {
2526 let v = self.fetch_pc(bus);
2527 let ax = self.a & self.x;
2528 let (res, overflow) = ax.overflowing_sub(v);
2529 self.x = res;
2530 self.p.set(Status::CARRY, !overflow);
2531 self.p.set_nz(res);
2532 *cycles = 2;
2533 }
2534
2535 // === Unstable: XAA, LAS, TAS, SHA, SHX, SHY ===
2536 0x8B => {
2537 // XAA / ANE: A = (A | const) & X & operand. nestest expects this.
2538 let v = self.fetch_pc(bus);
2539 self.a = (self.a | 0xFF) & self.x & v;
2540 self.p.set_nz(self.a);
2541 *cycles = 2;
2542 }
2543 0xBB => {
2544 let o = self.addr_abs_y(bus);
2545 let v = self.read1(bus, o.addr);
2546 let res = self.s & v;
2547 self.a = res;
2548 self.x = res;
2549 self.s = res;
2550 self.p.set_nz(res);
2551 *cycles = 4 + u8::from(o.page_crossed);
2552 }
2553 0x9B => {
2554 // TAS / SHS / XAS abs,Y: S = A & X; then SHA-style write
2555 // using `S` as the value register.
2556 let base = self.fetch_pc_u16(bus);
2557 self.s = self.a & self.x;
2558 self.sh_store(bus, base, self.y, self.s);
2559 *cycles = 5;
2560 }
2561 0x9F => {
2562 // SHA abs,Y. value_reg = A & X.
2563 let base = self.fetch_pc_u16(bus);
2564 self.sh_store(bus, base, self.y, self.a & self.x);
2565 *cycles = 5;
2566 }
2567 0x93 => {
2568 // SHA (zp),Y. Indirect; base from zp-pointer-resolved
2569 // low/high bytes. value_reg = A & X.
2570 let zp = self.fetch_pc(bus);
2571 let lo = self.read1(bus, u16::from(zp));
2572 let hi_byte = self.read1(bus, u16::from(zp.wrapping_add(1)));
2573 let base = u16::from(lo) | (u16::from(hi_byte) << 8);
2574 self.sh_store(bus, base, self.y, self.a & self.x);
2575 *cycles = 6;
2576 }
2577 0x9E => {
2578 // SHX abs,Y. value_reg = X.
2579 let base = self.fetch_pc_u16(bus);
2580 self.sh_store(bus, base, self.y, self.x);
2581 *cycles = 5;
2582 }
2583 0x9C => {
2584 // SHY abs,X. value_reg = Y. Index register is X here.
2585 let base = self.fetch_pc_u16(bus);
2586 self.sh_store(bus, base, self.x, self.y);
2587 *cycles = 5;
2588 }
2589
2590 // === JAM / KIL / STP ===
2591 0x02 | 0x12 | 0x22 | 0x32 | 0x42 | 0x52 | 0x62 | 0x72 | 0x92 | 0xB2 | 0xD2 | 0xF2 => {
2592 // v2.0.0 (every-cycle-bus-access): cycle 2 is a real dummy
2593 // read of the byte after the opcode before the CPU wedges —
2594 // the same silicon shape as the implied-opcode cycle-2 dummy
2595 // read. Caught post-promote by the burn-loop fail-loud
2596 // assert (this arm declared 2 cycles but emitted only the
2597 // opcode fetch — invisible to every probe workload, since no
2598 // test ROM executes a JAM).
2599 let _ = self.read1(bus, self.pc);
2600 self.jammed = true;
2601 *cycles = 2;
2602 }
2603 }
2604 }
2605
2606 // ------------------------------------------------------------------
2607 // Helpers / micro-ops.
2608 // ------------------------------------------------------------------
2609
2610 fn lda(&mut self, value: u8) {
2611 self.a = value;
2612 self.p.set_nz(value);
2613 }
2614
2615 fn lda_addr<B: Bus>(&mut self, bus: &mut B, addr: u16) {
2616 let v = self.read1(bus, addr);
2617 self.lda(v);
2618 }
2619
2620 fn ldx(&mut self, value: u8) {
2621 self.x = value;
2622 self.p.set_nz(value);
2623 }
2624
2625 fn ldy(&mut self, value: u8) {
2626 self.y = value;
2627 self.p.set_nz(value);
2628 }
2629
2630 fn and(&mut self, value: u8) {
2631 self.a &= value;
2632 self.p.set_nz(self.a);
2633 }
2634
2635 fn ora(&mut self, value: u8) {
2636 self.a |= value;
2637 self.p.set_nz(self.a);
2638 }
2639
2640 fn eor(&mut self, value: u8) {
2641 self.a ^= value;
2642 self.p.set_nz(self.a);
2643 }
2644
2645 fn bit(&mut self, value: u8) {
2646 let result = self.a & value;
2647 self.p.set(Status::ZERO, result == 0);
2648 self.p.set(Status::NEGATIVE, value & 0x80 != 0);
2649 self.p.set(Status::OVERFLOW, value & 0x40 != 0);
2650 }
2651
2652 fn adc(&mut self, value: u8) {
2653 let carry = u16::from(self.p.contains(Status::CARRY));
2654 let sum = u16::from(self.a) + u16::from(value) + carry;
2655 let result = sum as u8;
2656 self.p.set(Status::CARRY, sum > 0xFF);
2657 let overflow = ((self.a ^ result) & (value ^ result) & 0x80) != 0;
2658 self.p.set(Status::OVERFLOW, overflow);
2659 self.a = result;
2660 self.p.set_nz(self.a);
2661 }
2662
2663 fn sbc(&mut self, value: u8) {
2664 // SBC = ADC of inverted value.
2665 self.adc(value ^ 0xFF);
2666 }
2667
2668 fn cmp_with(&mut self, lhs: u8, rhs: u8) {
2669 let (r, borrow) = lhs.overflowing_sub(rhs);
2670 self.p.set(Status::CARRY, !borrow);
2671 self.p.set_nz(r);
2672 }
2673
2674 fn inc_addr<B: Bus>(&mut self, bus: &mut B, addr: u16) {
2675 let original = self.read1(bus, addr);
2676 // RMW dummy write: real 6502 writes the original byte back to the
2677 // same address before writing the modified value (visible at memory-
2678 // mapped registers like $4014 and $2007). See `docs/cpu-6502.md` and
2679 // nesdev wiki "Dummy writes".
2680 self.write1(bus, addr, original);
2681 let v = original.wrapping_add(1);
2682 self.write1(bus, addr, v);
2683 self.p.set_nz(v);
2684 }
2685
2686 fn dec_addr<B: Bus>(&mut self, bus: &mut B, addr: u16) {
2687 let original = self.read1(bus, addr);
2688 self.write1(bus, addr, original);
2689 let v = original.wrapping_sub(1);
2690 self.write1(bus, addr, v);
2691 self.p.set_nz(v);
2692 }
2693
2694 fn asl_value(&mut self, value: u8) -> u8 {
2695 self.p.set(Status::CARRY, value & 0x80 != 0);
2696 let r = value << 1;
2697 self.p.set_nz(r);
2698 r
2699 }
2700
2701 fn asl_addr<B: Bus>(&mut self, bus: &mut B, addr: u16) {
2702 let v = self.read1(bus, addr);
2703 // RMW dummy write — see `inc_addr`.
2704 self.write1(bus, addr, v);
2705 let r = self.asl_value(v);
2706 self.write1(bus, addr, r);
2707 }
2708
2709 fn lsr_value(&mut self, value: u8) -> u8 {
2710 self.p.set(Status::CARRY, value & 0x01 != 0);
2711 let r = value >> 1;
2712 self.p.set_nz(r);
2713 r
2714 }
2715
2716 fn lsr_addr<B: Bus>(&mut self, bus: &mut B, addr: u16) {
2717 let v = self.read1(bus, addr);
2718 self.write1(bus, addr, v);
2719 let r = self.lsr_value(v);
2720 self.write1(bus, addr, r);
2721 }
2722
2723 fn rol_value(&mut self, value: u8) -> u8 {
2724 let carry_in = u8::from(self.p.contains(Status::CARRY));
2725 self.p.set(Status::CARRY, value & 0x80 != 0);
2726 let r = (value << 1) | carry_in;
2727 self.p.set_nz(r);
2728 r
2729 }
2730
2731 fn rol_addr<B: Bus>(&mut self, bus: &mut B, addr: u16) {
2732 let v = self.read1(bus, addr);
2733 self.write1(bus, addr, v);
2734 let r = self.rol_value(v);
2735 self.write1(bus, addr, r);
2736 }
2737
2738 fn ror_value(&mut self, value: u8) -> u8 {
2739 let carry_in = u8::from(self.p.contains(Status::CARRY)) << 7;
2740 self.p.set(Status::CARRY, value & 0x01 != 0);
2741 let r = (value >> 1) | carry_in;
2742 self.p.set_nz(r);
2743 r
2744 }
2745
2746 fn ror_addr<B: Bus>(&mut self, bus: &mut B, addr: u16) {
2747 let v = self.read1(bus, addr);
2748 self.write1(bus, addr, v);
2749 let r = self.ror_value(v);
2750 self.write1(bus, addr, r);
2751 }
2752
2753 fn branch<B: Bus>(&mut self, bus: &mut B, offset: u8, condition: bool) -> u8 {
2754 if !condition {
2755 return 2;
2756 }
2757 // T-60-001 (2026-05-17 — 9th C1 attempt, branch axis): per
2758 // nesdev wiki §"CPU interrupts" §"Branch instructions", TAKEN
2759 // branches DELAY IRQ detection. Our `step()` samples IRQ at
2760 // the opcode-fetch (cycle 1 = tick 0) via `idle_tick`, then
2761 // each branch opcode sets `skip_irq_sample = true` before the
2762 // operand fetch to suppress sampling on the extra cycles. But
2763 // the cycle-1 sample is still recorded in `irq_first_tick`
2764 // and `promote_post_step_interrupts` will ARM the IRQ at the
2765 // end of this instruction (cycle-1 sample < last_tick on a
2766 // 3- or 4-cycle taken branch). That contradicts the
2767 // "branches delay IRQ" rule — IRQ should be deferred to the
2768 // NEXT instruction's poll. Drop the cycle-1 sample here on
2769 // taken branches; the next instruction's opcode fetch will
2770 // re-sample the (still-asserted, level-triggered) IRQ line
2771 // and arm it normally. NMI is edge-triggered and sampled on
2772 // every cycle the CPU is alive (per nesdev) — its first-tick
2773 // latch is intentionally NOT dropped.
2774 self.irq_first_tick = u8::MAX;
2775 // Canonical 6502 branch cycle sequence per nesdev wiki and
2776 // AccuracyCoin `CPU Behavior 2 :: Branch Dummy Reads` Test 4:
2777 // C1: opcode fetch (done by `tick` dispatcher)
2778 // C2: operand fetch (done by per-opcode `fetch_pc` before call)
2779 // C3: dummy read of PC (the byte after the operand) — this is
2780 // cycle 3 of the taken branch, and is observable as a
2781 // second consecutive read of `$2002` mirror through which
2782 // AccuracyCoin brackets the dummy.
2783 // C4: (only if page-crossed) dummy read of (old_pch | new_pcl)
2784 // — the unfixed-high-byte address before the high-byte
2785 // carry propagates.
2786 let _ = self.read1(bus, self.pc); // C3 dummy
2787 let signed = offset as i8 as i16;
2788 let old_pc = self.pc;
2789 let new_pc = (self.pc as i32 + i32::from(signed)) as u16;
2790 let crossed = (old_pc & 0xFF00) != (new_pc & 0xFF00);
2791 if crossed {
2792 // W1 (`mc-r1-branch-poll-points`): a page-cross taken branch
2793 // polls a SECOND time at C4-start — TriCNES's
2794 // `PollInterrupts_CantDisableIRQ` in the BPL microcode
2795 // (`golden/tricnes/tricnes-full-src/Emulator.cs`): if the C2-start
2796 // poll already saw the IRQ this one cannot un-see it (can-SET-
2797 // not-clear). `mc_run_irq` is frozen across the branch's
2798 // remaining cycles by the `handle_interrupts` early-return, so
2799 // sample the live line here (state as of end-of-C3) and OR it in;
2800 // the end-of-C4 `mc_prev_run_irq` copy then exposes it to the
2801 // next `step()` dispatch.
2802 if !self.mc_run_irq {
2803 self.mc_run_irq = bus.irq_level() && !self.irq_sample_i_flag;
2804 }
2805 // C4 page-cross dummy read at the unfixed address.
2806 let dummy = (old_pc & 0xFF00) | (new_pc & 0x00FF);
2807 let _ = self.read1(bus, dummy);
2808 }
2809 self.pc = new_pc;
2810 if crossed { 4 } else { 3 }
2811 }
2812
2813 fn lax(&mut self, value: u8) {
2814 self.a = value;
2815 self.x = value;
2816 self.p.set_nz(value);
2817 }
2818
2819 fn dcp_addr<B: Bus>(&mut self, bus: &mut B, addr: u16) {
2820 let original = self.read1(bus, addr);
2821 // RMW dummy write.
2822 self.write1(bus, addr, original);
2823 let v = original.wrapping_sub(1);
2824 self.write1(bus, addr, v);
2825 self.cmp_with(self.a, v);
2826 }
2827
2828 fn isc_addr<B: Bus>(&mut self, bus: &mut B, addr: u16) {
2829 let original = self.read1(bus, addr);
2830 self.write1(bus, addr, original);
2831 let v = original.wrapping_add(1);
2832 self.write1(bus, addr, v);
2833 self.sbc(v);
2834 }
2835
2836 fn slo_addr<B: Bus>(&mut self, bus: &mut B, addr: u16) {
2837 let v = self.read1(bus, addr);
2838 self.write1(bus, addr, v);
2839 let r = self.asl_value(v);
2840 self.write1(bus, addr, r);
2841 self.a |= r;
2842 self.p.set_nz(self.a);
2843 }
2844
2845 fn rla_addr<B: Bus>(&mut self, bus: &mut B, addr: u16) {
2846 let v = self.read1(bus, addr);
2847 self.write1(bus, addr, v);
2848 let r = self.rol_value(v);
2849 self.write1(bus, addr, r);
2850 self.a &= r;
2851 self.p.set_nz(self.a);
2852 }
2853
2854 fn sre_addr<B: Bus>(&mut self, bus: &mut B, addr: u16) {
2855 let v = self.read1(bus, addr);
2856 self.write1(bus, addr, v);
2857 let r = self.lsr_value(v);
2858 self.write1(bus, addr, r);
2859 self.a ^= r;
2860 self.p.set_nz(self.a);
2861 }
2862
2863 fn rra_addr<B: Bus>(&mut self, bus: &mut B, addr: u16) {
2864 let v = self.read1(bus, addr);
2865 self.write1(bus, addr, v);
2866 let r = self.ror_value(v);
2867 self.write1(bus, addr, r);
2868 self.adc(r);
2869 }
2870}