rustynes_cpu/
status.rs

1//! CPU Status Register (P) implementation.
2//!
3//! The 6502 status register contains 8 flags that track the processor state.
4//! Bit layout: NV-BDIZC (where - is the unused bit, always set to 1)
5
6use bitflags::bitflags;
7
8bitflags! {
9    /// CPU Status Register Flags (P register)
10    ///
11    /// The status register contains flags that reflect the state of the CPU
12    /// and control certain behaviors like interrupt handling.
13    ///
14    /// # Flag Bits (NV-BDIZC)
15    ///
16    /// - **N (Negative)**: Set if result is negative (bit 7 = 1)
17    /// - **V (Overflow)**: Set if signed overflow occurred
18    /// - **U (Unused)**: Always 1 when pushed to stack
19    /// - **B (Break)**: Distinguishes BRK from IRQ (stack only)
20    /// - **D (Decimal)**: Decimal mode flag (ignored on NES)
21    /// - **I (Interrupt Disable)**: When set, IRQ interrupts are masked
22    /// - **Z (Zero)**: Set if result is zero
23    /// - **C (Carry)**: Set if carry/borrow occurred
24    ///
25    /// # Example
26    ///
27    /// ```
28    /// use rustynes_cpu::StatusFlags;
29    ///
30    /// let mut flags = StatusFlags::default();
31    /// flags.insert(StatusFlags::CARRY);
32    /// assert!(flags.contains(StatusFlags::CARRY));
33    ///
34    /// // Set N and Z based on a value
35    /// let value = 0x00;
36    /// flags.set_zn(value);
37    /// assert!(flags.contains(StatusFlags::ZERO));
38    /// assert!(!flags.contains(StatusFlags::NEGATIVE));
39    /// ```
40    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
41    pub struct StatusFlags: u8 {
42        /// Carry flag (bit 0)
43        const CARRY             = 0b0000_0001;
44        /// Zero flag (bit 1)
45        const ZERO              = 0b0000_0010;
46        /// Interrupt Disable flag (bit 2)
47        const INTERRUPT_DISABLE = 0b0000_0100;
48        /// Decimal Mode flag (bit 3) - ignored on NES but still functional
49        const DECIMAL           = 0b0000_1000;
50        /// Break flag (bit 4) - only set when pushed to stack from PHP/BRK
51        const BREAK             = 0b0001_0000;
52        /// Unused flag (bit 5) - always 1 when pushed to stack
53        const UNUSED            = 0b0010_0000;
54        /// Overflow flag (bit 6)
55        const OVERFLOW          = 0b0100_0000;
56        /// Negative flag (bit 7)
57        const NEGATIVE          = 0b1000_0000;
58    }
59}
60
61impl Default for StatusFlags {
62    /// Creates status register with default power-on state
63    ///
64    /// Default state: Interrupt Disable = 1, Unused = 1
65    fn default() -> Self {
66        Self::INTERRUPT_DISABLE | Self::UNUSED
67    }
68}
69
70impl StatusFlags {
71    /// Update Zero and Negative flags based on a value
72    ///
73    /// # Arguments
74    ///
75    /// * `value` - The 8-bit value to test
76    ///
77    /// # Flag Behavior
78    ///
79    /// - Z is set if `value == 0`
80    /// - N is set if `value & 0x80 != 0` (bit 7 set)
81    ///
82    /// # Example
83    ///
84    /// ```
85    /// use rustynes_cpu::StatusFlags;
86    ///
87    /// let mut flags = StatusFlags::default();
88    ///
89    /// flags.set_zn(0x00);
90    /// assert!(flags.contains(StatusFlags::ZERO));
91    /// assert!(!flags.contains(StatusFlags::NEGATIVE));
92    ///
93    /// flags.set_zn(0x80);
94    /// assert!(!flags.contains(StatusFlags::ZERO));
95    /// assert!(flags.contains(StatusFlags::NEGATIVE));
96    /// ```
97    #[inline]
98    pub fn set_zn(&mut self, value: u8) {
99        self.set(Self::ZERO, value == 0);
100        self.set(Self::NEGATIVE, value & 0x80 != 0);
101    }
102
103    /// Convert status to byte for pushing to stack
104    ///
105    /// When status is pushed to stack (PHP, BRK, interrupts), the U bit
106    /// is always set and the B bit depends on the source.
107    ///
108    /// # Arguments
109    ///
110    /// * `brk` - If true, set B flag (from BRK/PHP). If false, clear B flag (from IRQ/NMI)
111    ///
112    /// # Returns
113    ///
114    /// 8-bit value with U=1 and B set according to `brk`
115    ///
116    /// # Example
117    ///
118    /// ```
119    /// use rustynes_cpu::StatusFlags;
120    ///
121    /// let flags = StatusFlags::CARRY | StatusFlags::ZERO;
122    ///
123    /// // From BRK/PHP: B=1, U=1
124    /// let stack_byte_brk = flags.to_stack_byte(true);
125    /// assert_eq!(stack_byte_brk & 0b0011_0000, 0b0011_0000);
126    ///
127    /// // From IRQ/NMI: B=0, U=1
128    /// let stack_byte_int = flags.to_stack_byte(false);
129    /// assert_eq!(stack_byte_int & 0b0011_0000, 0b0010_0000);
130    /// ```
131    #[inline]
132    #[must_use]
133    pub fn to_stack_byte(&self, brk: bool) -> u8 {
134        let mut byte = self.bits();
135        byte |= Self::UNUSED.bits();
136        if brk {
137            byte |= Self::BREAK.bits();
138        } else {
139            byte &= !Self::BREAK.bits();
140        }
141        byte
142    }
143
144    /// Convert byte from stack to status flags
145    ///
146    /// When pulling status from stack (PLP, RTI), the B flag is ignored
147    /// and U is always set.
148    ///
149    /// # Arguments
150    ///
151    /// * `byte` - The 8-bit value pulled from stack
152    ///
153    /// # Returns
154    ///
155    /// StatusFlags with U=1 and other flags set from `byte`
156    ///
157    /// # Example
158    ///
159    /// ```
160    /// use rustynes_cpu::StatusFlags;
161    ///
162    /// // Stack byte with B=1 (from BRK)
163    /// let stack_byte = 0b0011_0011; // B=1, U=1, C=1, Z=1
164    /// let flags = StatusFlags::from_stack_byte(stack_byte);
165    ///
166    /// // B flag is ignored when pulling from stack
167    /// assert!(!flags.contains(StatusFlags::BREAK));
168    /// // U is always set
169    /// assert!(flags.contains(StatusFlags::UNUSED));
170    /// // Other flags preserved
171    /// assert!(flags.contains(StatusFlags::CARRY));
172    /// assert!(flags.contains(StatusFlags::ZERO));
173    /// ```
174    #[inline]
175    #[must_use]
176    pub fn from_stack_byte(byte: u8) -> Self {
177        (Self::from_bits_truncate(byte) & !Self::BREAK) | Self::UNUSED
178    }
179}
180
181#[cfg(test)]
182mod tests {
183    use super::*;
184
185    #[test]
186    fn test_default_status() {
187        let status = StatusFlags::default();
188        assert!(status.contains(StatusFlags::INTERRUPT_DISABLE));
189        assert!(status.contains(StatusFlags::UNUSED));
190        assert!(!status.contains(StatusFlags::CARRY));
191        assert!(!status.contains(StatusFlags::ZERO));
192    }
193
194    #[test]
195    fn test_set_zn_zero() {
196        let mut status = StatusFlags::default();
197        status.set_zn(0x00);
198        assert!(status.contains(StatusFlags::ZERO));
199        assert!(!status.contains(StatusFlags::NEGATIVE));
200    }
201
202    #[test]
203    fn test_set_zn_negative() {
204        let mut status = StatusFlags::default();
205        status.set_zn(0x80);
206        assert!(!status.contains(StatusFlags::ZERO));
207        assert!(status.contains(StatusFlags::NEGATIVE));
208    }
209
210    #[test]
211    fn test_set_zn_positive_nonzero() {
212        let mut status = StatusFlags::default();
213        status.set_zn(0x42);
214        assert!(!status.contains(StatusFlags::ZERO));
215        assert!(!status.contains(StatusFlags::NEGATIVE));
216    }
217
218    #[test]
219    fn test_to_stack_byte_brk() {
220        let status = StatusFlags::CARRY | StatusFlags::ZERO;
221        let byte = status.to_stack_byte(true);
222
223        // B and U should be set
224        assert_eq!(byte & 0b0011_0000, 0b0011_0000);
225        // C and Z should be preserved
226        assert_eq!(byte & 0b0000_0011, 0b0000_0011);
227    }
228
229    #[test]
230    fn test_to_stack_byte_interrupt() {
231        let status = StatusFlags::CARRY | StatusFlags::ZERO;
232        let byte = status.to_stack_byte(false);
233
234        // Only U should be set, not B
235        assert_eq!(byte & 0b0011_0000, 0b0010_0000);
236        // C and Z should be preserved
237        assert_eq!(byte & 0b0000_0011, 0b0000_0011);
238    }
239
240    #[test]
241    fn test_from_stack_byte() {
242        // Byte with B=1, U=1, C=1, Z=1
243        let byte = 0b0011_0011;
244        let status = StatusFlags::from_stack_byte(byte);
245
246        // B should be ignored (cleared)
247        assert!(!status.contains(StatusFlags::BREAK));
248        // U should always be set
249        assert!(status.contains(StatusFlags::UNUSED));
250        // Other flags preserved
251        assert!(status.contains(StatusFlags::CARRY));
252        assert!(status.contains(StatusFlags::ZERO));
253    }
254
255    #[test]
256    fn test_all_flags() {
257        let all = StatusFlags::CARRY
258            | StatusFlags::ZERO
259            | StatusFlags::INTERRUPT_DISABLE
260            | StatusFlags::DECIMAL
261            | StatusFlags::BREAK
262            | StatusFlags::UNUSED
263            | StatusFlags::OVERFLOW
264            | StatusFlags::NEGATIVE;
265
266        assert_eq!(all.bits(), 0xFF);
267    }
268}