Skip to main content

Cpu

Struct Cpu 

Source
pub struct Cpu {
    pub a: u8,
    pub x: u8,
    pub y: u8,
    pub pc: u16,
    pub s: u8,
    pub p: Status,
    pub cycles: u64,
    pub jammed: bool,
    /* private fields */
}
Expand description

6502 CPU core.

Fields§

§a: u8

Accumulator.

§x: u8

X index register.

§y: u8

Y index register.

§pc: u16

Program counter.

§s: u8

Stack pointer (low byte; effective address 0x0100 | s).

§p: Status

Processor status.

§cycles: u64

Cumulative CPU cycle count.

§jammed: bool

true when the CPU has executed a JAM/KIL/STP and is waiting for reset.

Implementations§

Source§

impl Cpu

Source

pub const fn new() -> Self

New CPU in “post-reset” state. Caller must invoke Cpu::reset with a real bus before stepping (PC is undefined until reset reads $FFFC/D).

This constructor is the convenience entry-point used by unit tests and nestest fixtures that drive the CPU without going through a full power-on path: S=$FD, P=$24 (UNUSED + INTERRUPT_DISABLE). If the caller subsequently invokes Cpu::reset the stack pointer will be decremented by 3 (per the reset sequence), landing on $FA — that is the input shape several tests/opcodes.rs fixtures expect.

For the real cold-boot path (Nes::from_rom, Nes::power_cycle), use Cpu::power_on instead, which seeds S=$00. After the 3-decrement reset sequence that lands S=$FD, matching Mesen2’s power-up state. See docs/audit/session-13-cpu-boot-fix-2026-05-21.md for the reference behaviour from Core/NES/NesCpu.cpp::NesCpu::Reset(softReset=false).

Source

pub const fn power_on() -> Self

New CPU in real-hardware cold-boot state (S=$00).

Real silicon comes up with the stack pointer in an undefined state; the convention used by Mesen2 (and adopted here for trace parity) is to treat power-up as S=$00 and rely on the reset sequence’s three “phantom” decrements to wrap into S=$FD. See Mesen2 Core/NES/NesCpu.cpp::Reset(softReset=false):

if(softReset) {
    _state.SP -= 0x03;          // soft reset path
} else {
    _state.SP = 0xFD;           // power-up: direct assignment
}

RustyNES models that two-path behaviour by gating the SP delta through the constructor: Cpu::power_on() + reset()$00 - 3 = $FD (cold); cpu.reset() again ⇒ $FD - 3 = $FA (subsequent soft reset).

P is left at $24 (INTERRUPT_DISABLE | UNUSED). Mesen2’s trace surface shows P = $04 because it masks UNUSED out of the displayed byte, but the bit is conventionally always set on a 6502 internally (nesdev: “Bit 5: Always 1, the so-called ‘unused’ bit”); the trace divergence on P is cosmetic.

Source

pub const fn is_jammed(&self) -> bool

Returns true when the CPU has executed a JAM/KIL/STP.

Source

pub const fn master_clock(&self) -> u64

Read-only accessor for the CPU’s authoritative master clock (v2.0.0-beta.1 one-clock instrumentation).

master_clock counts master-clock units (NTSC: 12 per CPU cycle, PAL: 16, Dendy: 15) and is advanced only by start_cycle / end_cycle (the asymmetric read 5/7 vs write 7/5 φ1/φ2 split on NTSC) plus the bus-side DMA coherence fold (Bus::take_dma_mc_consumed). It is the counter the v2.0.0 “Timebase” rewrite (ADR 0002) promotes to the ONE canonical timebase; the test harness asserts the affine relation master_clock == seed + cpu_divider * cycles against the other cycle counters (one_clock_invariants.rs) as the gate for the beta.1 counter collapse.

Source

pub fn reset<B: Bus>(&mut self, bus: &mut B)

Reset (warm boot).

Real hardware: 8-cycle sequence with suppressed pushes, then PC loads from the reset vector and the I flag is set. We model the cycle count (advances cycles by 8 and fires on_cpu_cycle 8 times) without mutating registers other than P (set I), S (decrement by 3), and PC.

Matches Mesen2’s NesCpu::Reset() 8-cycle post-power-up loop (“CPU takes 8 cycles before it starts executing the ROM’s code”). Combined with the PPU power-up at (scanline=-1, dot=340) (see Ppu::new), this closes the +344-dot PPU offset identified empirically in Session-13 (docs/audit/session-13-cpu-boot-fix-2026-05-21.md).

Source

pub const fn set_pc(&mut self, addr: u16)

Force PC to addr. Used by the nestest harness which enters at $C000 rather than the reset vector.

Source

pub fn step<B: Bus>(&mut self, bus: &mut B) -> u8

Step one instruction (or service an interrupt). Returns the number of CPU cycles consumed.

On a JAM-state CPU this is a no-op returning 0.

§Interrupt timing model

Real 6502 hardware samples the NMI / IRQ lines at the second-to-last cycle of every instruction and, if asserted there, queues the interrupt to be serviced after the current instruction completes. Our model dispatches all bus operations atomically before ticking cycles, so a write that itself raises NMI (e.g. STA $2000 enabling NMI while VBL is set) appears to the bus’s edge detector during the FIRST cycle of the tally loop — earlier than hardware would observe it. Hardware places the actual write at the last cycle of the instruction, so the second-to-last sample point would NOT see the new line state; only the NEXT instruction’s sample sees it. We model that by introducing a one-instruction promotion delay: edges captured at end-of-step land in pending_* and, after the following step, promote to armed_* which is the gate that actually triggers service. This passes 04-nmi_control test 11 (“Immediate occurence should be after NEXT instruction”) without regressing the instruction-count-insensitive tests like 02-vbl_set_time, 09-even_odd_frames, or any instr_test_v5 ROM (which never raise NMI from within a single instruction).

Source§

impl Cpu

Source

pub fn snapshot(&self) -> Vec<u8>

Encode the CPU’s mutable state into a versioned binary blob.

Format is little-endian, version-tagged at offset 0. See CPU_SNAPSHOT_VERSION for the current schema number.

Source

pub fn restore(&mut self, data: &[u8]) -> Result<(), CpuSnapshotError>

Decode a previously Cpu::snapshoted blob back into self.

§Errors

Returns CpuSnapshotError if the blob is the wrong length or carries an unrecognized version.

Trait Implementations§

Source§

impl Clone for Cpu

Source§

fn clone(&self) -> Cpu

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Cpu

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Cpu

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

§

impl Freeze for Cpu

§

impl RefUnwindSafe for Cpu

§

impl Send for Cpu

§

impl Sync for Cpu

§

impl Unpin for Cpu

§

impl UnsafeUnpin for Cpu

§

impl UnwindSafe for Cpu

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.