pub struct FrameCounter {
pub mode: Mode,
pub irq_inhibit: bool,
pub irq_flag: bool,
pub irq_line_active: bool,
pub apu_aligned: bool,
/* private fields */
}Expand description
Frame counter state.
Fields§
§mode: ModeCurrent mode.
irq_inhibit: boolIRQ inhibit flag.
irq_flag: bool$4015 bit 6 visibility flag (cleared by reading $4015 or by
writing $4017 with bit 6 set). Independent of the CPU IRQ
line (see irq_line_active) since
Session-26 Sprint 2 iter 5 (2026-05-23) — the AccuracyCoin
APU Tests :: Frame Counter IRQ Tests I/J/K specifically test
that with inhibit SET, $4015 bit 6 is still visible for 2
CPU cycles (29828, 29829) before clearing at cycle 29830.
Mesen2 separates these two concepts: _irqFlag (this field)
vs IRQSource::FrameCounter registration on the CPU’s
_irqSource list. RustyNES previously conflated them into a
single field, which broke the J/K axis. See
docs/audit/session-26-sprint2-iter5-frame-counter-irq-split-2026-05-23.md.
irq_line_active: boolCPU IRQ line driver — true iff the frame counter is currently
asserting an IRQ on the CPU’s _irqSource list (Mesen2’s
IRQSource::FrameCounter registration). Set at FC steps 3, 4,
5 (cycles 29828, 29829, 29830) ONLY when not inhibited;
cleared by $4015 read or $4017 inhibit-set. The CPU’s
IRQ-poll path reads via Apu::irq_line,
which ORs this with dmc.irq_flag. Distinct from
irq_flag: when inhibited, irq_flag may
be transiently set at cycles 29828-29829 to make $4015 bit 6
visible per Tests I/J/K, but irq_line_active stays false so
no spurious IRQ fires on the CPU.
apu_aligned: boolapu_phase: false on cycle that aligns with APU clock, true otherwise.
Used to time the $4017 reset delay (3 vs 4 cycles).
Implementations§
Source§impl FrameCounter
impl FrameCounter
Sourcepub fn reset_rewrite_4017(&mut self) -> u8
pub fn reset_rewrite_4017(&mut self) -> u8
v2.0.0 beta.3 (A4 cycle-accurate reset): warm-reset with the
hardware $4017 re-write. Per blargg’s apu_reset spec, the 2A03
reset sequence behaves as if the LAST value written to $4017 were
written again: the retained last_4017 is re-issued through
the normal write path (pending mode + the 3/4-cycle aligned delay,
and — for a mode-1 value — the immediate quarter+half clock), then
the sequencer restarts. The CPU’s subsequent 8-cycle reset delay
(real clocked cycles on the master clock since Workstream A2) ages
the re-armed counter so execution resumes ~9-12 cycles after the
effective write — the window blargg’s 4017_timing brackets.
Two prior frame-granular re-arm attempts (see
tests/apu_reset.rs’s history preamble) failed precisely because the
reset was a function call with no clocked delay; this variant exists
on the one-clock sequence path (promoted to the only path in beta.4).
Sourcepub fn write(&mut self, value: u8, apu_aligned: bool)
pub fn write(&mut self, value: u8, apu_aligned: bool)
$4017 write. apu_aligned is true if the current CPU cycle
is also an APU cycle (i.e., even CPU-cycle alignment). The reset
happens 3 or 4 CPU cycles later depending on alignment.
Sourcepub fn read_status(&mut self, cpu_cycle: u64, apu_aligned: bool) -> bool
pub fn read_status(&mut self, cpu_cycle: u64, apu_aligned: bool) -> bool
Reading $4015 returns the current frame IRQ flag value and
SCHEDULES a future clear that matures one or two CPU cycles
later, mirroring Mesen2’s ApuFrameCounter::GetIrqFlag lazy
algorithm (Core/NES/APU/ApuFrameCounter.h lines 214-227).
Semantics:
- If
irq_flagis true and no clear is scheduled, scheduleirq_flag_clear_cycle = cpu_cycle + deltawheredelta = 1on a RustyNES “get” cycle (apu_phase=true) anddelta = 2on a “put” cycle (apu_phase=false). Return the OLD flag value (true). - If a schedule is already pending and
cpu_cycle >= irq_flag_clear_cycle, perform the clear NOW (the silicon observed enough APU clocks since the read) and return the freshly-cleared flag (false). - If no flag is set, return false (no schedule needed).
The delta polarity is INVERTED vs Mesen2’s (clock & 0x01) ? 2 : 1
because RustyNES’s apu_phase polarity at the $4015 read site
is opposite to Mesen2’s master-clock parity. Verified against
the frame-counter-irq.nes oracle pair (Session-25,
2026-05-23).
cpu_cycle is the bus’s CPU-cycle counter at the moment of
the read (passed in from apu.rs::read_status).
apu_aligned is self.apu_phase of the APU at the same
moment (also passed in from apu.rs::read_status).
Sourcepub fn tick(&mut self, cpu_cycle: u64, apu_aligned: bool) -> FrameEvents
pub fn tick(&mut self, cpu_cycle: u64, apu_aligned: bool) -> FrameEvents
One CPU clock — return any frame-counter events fired by this cycle.
cpu_cycle is the bus’s CPU-cycle counter for the cycle
being ticked (the post-increment value, since
apu.tick_with_external advances apu.cpu_cycle BEFORE
invoking this). apu_aligned: true iff this CPU cycle is
also an APU “get” cycle. The lazy $4015 IRQ clear matures
here if cpu_cycle >= irq_flag_clear_cycle; the per-frame
step events fire as before.
Trait Implementations§
Source§impl Clone for FrameCounter
impl Clone for FrameCounter
Source§fn clone(&self) -> FrameCounter
fn clone(&self) -> FrameCounter
1.0.0 · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more