rustynes_cpu/scheduler.rs
1//! Scheduler-facing types the CPU crate exposes to its bus host.
2//!
3//! Currently exposes only [`M2Phase`], the canonical reference enum for
4//! "which half of the 6502 cycle the host bus is currently in". The
5//! enum lives in `rustynes-cpu` rather than `rustynes-core` because the [`Bus`]
6//! trait method [`Bus::poll_irq_at_phase`] is parameterised by it; the
7//! CPU crate stays at the top of the workspace dep graph (`rustynes-core`
8//! already depends on `rustynes-cpu`, not the other way round) and any
9//! `rustynes-core` consumer continues to import `M2Phase` from
10//! `rustynes_core::scheduler` via re-export.
11//!
12//! See `docs/scheduler.md` and `docs/adr/0002-irq-timing-coordination.md`
13//! for the surrounding design.
14//!
15//! [`Bus`]: crate::Bus
16//! [`Bus::poll_irq_at_phase`]: crate::Bus::poll_irq_at_phase
17
18/// Convention for the M2-phase reference relative to the CPU cycle's 3
19/// PPU dots.
20///
21/// In silicon the 6502 cycle has two halves — φ1 (M2 low; address valid;
22/// memory access) and φ2 (M2 high; data latch; interrupt sample). The
23/// host scheduler ticks the PPU 3 dots per CPU cycle. The convention
24/// this crate adopts:
25///
26/// * [`M2Phase::Low`] — the **first** half of the cycle: from the start
27/// of the bus's per-cycle tick through the end of PPU sub-dot 1
28/// (corresponds to silicon's φ1).
29/// * [`M2Phase::High`] — the **second** half of the cycle: from the end
30/// of PPU sub-dot 1 through end-of-cycle (corresponds to silicon's
31/// φ2). The M2-rising boundary lives between sub-dot 1 and sub-dot 2.
32///
33/// At end-of-cycle the bus advances its cycle counter and the phase
34/// resets to [`M2Phase::Low`] for the next cycle.
35///
36/// This is the canonical reference enum used by the docs/ADR, by the
37/// IRQ-timing tracing fixture (`rustynes_core::irq_trace`), and by
38/// [`Bus::poll_irq_at_phase`].
39///
40/// [`Bus::poll_irq_at_phase`]: crate::Bus::poll_irq_at_phase
41#[derive(Clone, Copy, Debug, Eq, PartialEq)]
42pub enum M2Phase {
43 /// M2 low (φ1): memory access window.
44 Low,
45 /// M2 high (φ2): IRQ/NMI sample window.
46 High,
47}
48
49impl M2Phase {
50 /// CSV-friendly single-letter abbreviation.
51 #[must_use]
52 pub const fn as_str(self) -> &'static str {
53 match self {
54 Self::Low => "L",
55 Self::High => "H",
56 }
57 }
58}
59
60#[cfg(test)]
61mod tests {
62 use super::*;
63
64 #[test]
65 fn m2_phase_as_str_round_trips() {
66 assert_eq!(M2Phase::Low.as_str(), "L");
67 assert_eq!(M2Phase::High.as_str(), "H");
68 }
69}