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}