Skip to main content

rustysnes_cpu/
regs.rs

1//! WDC 65C816 register file and processor-status flags.
2//!
3//! The 65C816 has variable-width data paths: the accumulator `A` is 8- or 16-bit per the
4//! `M` status flag, and the index registers `X`/`Y` are 8- or 16-bit per the `X` status
5//! flag. In 8-bit index mode the high byte of `X`/`Y` is forced to zero. The hidden `E`
6//! (emulation) latch forces `M`/`X` to the 8-bit width and confines the stack to page `$01`.
7//!
8//! See `docs/cpu.md` ("Registers and state", "Emulation vs native mode").
9
10// Taking the low byte of a 16-bit register to set 8-bit-width flags is a deliberate, ubiquitous
11// truncation in the width model; the cast-precision lints would flag every such narrowing.
12#![allow(clippy::cast_possible_truncation)]
13
14use bitflags::bitflags;
15
16bitflags! {
17    /// Processor status register `P` (8 bits) plus the hidden emulation latch is tracked
18    /// separately on [`Regs`]. Bit layout matches the hardware `P` register so `PHP`/`PLP`
19    /// round-trip exactly.
20    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
21    pub struct Status: u8 {
22        /// Carry.
23        const C = 0b0000_0001;
24        /// Zero.
25        const Z = 0b0000_0010;
26        /// IRQ disable (IRQ honored only when clear).
27        const I = 0b0000_0100;
28        /// Decimal mode (BCD arithmetic for `ADC`/`SBC`).
29        const D = 0b0000_1000;
30        /// Index-register width: set ⇒ 8-bit `X`/`Y`. In emulation mode this also doubles as
31        /// the 6502 `B` (break) flag on the stacked status byte.
32        const X = 0b0001_0000;
33        /// Memory/accumulator width: set ⇒ 8-bit `A`/memory.
34        const M = 0b0010_0000;
35        /// Overflow.
36        const V = 0b0100_0000;
37        /// Negative.
38        const N = 0b1000_0000;
39    }
40}
41
42/// The complete architectural register file of the 65C816.
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub struct Regs {
45    /// Accumulator (full 16 bits stored; the active width is the `M` flag).
46    pub a: u16,
47    /// `X` index register (full 16 bits stored; the active width is the `X` flag).
48    pub x: u16,
49    /// `Y` index register (full 16 bits stored; the active width is the `X` flag).
50    pub y: u16,
51    /// Stack pointer (16-bit in native mode; forced to page `$01` in emulation mode).
52    pub s: u16,
53    /// Direct-page register `D`.
54    pub d: u16,
55    /// Data bank register `DBR`.
56    pub dbr: u8,
57    /// Program bank register `PBR` / `K`.
58    pub pbr: u8,
59    /// Program counter (16-bit; bank is `pbr`).
60    pub pc: u16,
61    /// Processor status flags `P`.
62    pub p: Status,
63    /// Hidden emulation-mode latch `E` (`true` at power-on).
64    pub emulation: bool,
65}
66
67impl Default for Regs {
68    fn default() -> Self {
69        Self::new()
70    }
71}
72
73impl Regs {
74    /// Power-on register file: emulation mode (`E=1`), `M=1`, `X=1`, `I=1`, `D=0`, with the
75    /// stack pointer parked at `$01FF`. `PC` is loaded from the reset vector by
76    /// [`crate::Cpu::reset`].
77    #[must_use]
78    pub const fn new() -> Self {
79        Self {
80            a: 0,
81            x: 0,
82            y: 0,
83            s: 0x01FF,
84            d: 0,
85            dbr: 0,
86            pbr: 0,
87            pc: 0,
88            // I and the width flags are set at reset; emulation forces M/X high.
89            p: Status::from_bits_truncate(Status::M.bits() | Status::X.bits() | Status::I.bits()),
90            emulation: true,
91        }
92    }
93
94    /// Whether the accumulator / memory width is 8-bit (`M` flag set, or emulation mode).
95    #[must_use]
96    pub const fn m8(&self) -> bool {
97        self.emulation || self.p.contains(Status::M)
98    }
99
100    /// Whether the index registers are 8-bit (`X` flag set, or emulation mode).
101    #[must_use]
102    pub const fn x8(&self) -> bool {
103        self.emulation || self.p.contains(Status::X)
104    }
105
106    /// Read the accumulator at the active width (high byte masked off when 8-bit).
107    #[must_use]
108    pub const fn a_val(&self) -> u16 {
109        if self.m8() { self.a & 0x00FF } else { self.a }
110    }
111
112    /// Write the accumulator at the active width, preserving the hidden high byte (`B`) when
113    /// in 8-bit mode — matching hardware, where `A` and `B` are distinct halves.
114    pub const fn set_a(&mut self, val: u16) {
115        if self.m8() {
116            self.a = (self.a & 0xFF00) | (val & 0x00FF);
117        } else {
118            self.a = val;
119        }
120    }
121
122    /// Read `X` at the active index width.
123    #[must_use]
124    pub const fn x_val(&self) -> u16 {
125        if self.x8() { self.x & 0x00FF } else { self.x }
126    }
127
128    /// Read `Y` at the active index width.
129    #[must_use]
130    pub const fn y_val(&self) -> u16 {
131        if self.x8() { self.y & 0x00FF } else { self.y }
132    }
133
134    /// Write `X`; in 8-bit index mode the high byte is forced to zero (hardware behavior).
135    pub const fn set_x(&mut self, val: u16) {
136        self.x = if self.x8() { val & 0x00FF } else { val };
137    }
138
139    /// Write `Y`; in 8-bit index mode the high byte is forced to zero (hardware behavior).
140    pub const fn set_y(&mut self, val: u16) {
141        self.y = if self.x8() { val & 0x00FF } else { val };
142    }
143
144    /// Update `N` and `Z` from an accumulator-width result.
145    pub const fn set_nz_m(&mut self, val: u16) {
146        if self.m8() {
147            self.set_nz8(val as u8);
148        } else {
149            self.set_nz16(val);
150        }
151    }
152
153    /// Update `N` and `Z` from an index-width result.
154    pub const fn set_nz_x(&mut self, val: u16) {
155        if self.x8() {
156            self.set_nz8(val as u8);
157        } else {
158            self.set_nz16(val);
159        }
160    }
161
162    /// Update `N`/`Z` from an explicit 8-bit value.
163    pub const fn set_nz8(&mut self, val: u8) {
164        self.set_flag(Status::Z, val == 0);
165        self.set_flag(Status::N, val & 0x80 != 0);
166    }
167
168    /// Update `N`/`Z` from an explicit 16-bit value.
169    pub const fn set_nz16(&mut self, val: u16) {
170        self.set_flag(Status::Z, val == 0);
171        self.set_flag(Status::N, val & 0x8000 != 0);
172    }
173
174    /// Set or clear a single status flag.
175    pub const fn set_flag(&mut self, flag: Status, on: bool) {
176        if on {
177            self.p = Status::from_bits_truncate(self.p.bits() | flag.bits());
178        } else {
179            self.p = Status::from_bits_truncate(self.p.bits() & !flag.bits());
180        }
181    }
182}