rustynes_cpu/
state.rs

1//! CPU execution state machine for cycle-accurate emulation.
2//!
3//! This module defines the state machine that enables cycle-by-cycle execution
4//! of 6502 instructions. Each state represents a single bus access cycle.
5
6/// CPU execution state for cycle-by-cycle execution.
7///
8/// Each state represents one CPU cycle with one bus access.
9/// The state machine transitions through these states to execute
10/// instructions with perfect cycle accuracy.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
12pub enum CpuState {
13    /// Fetch opcode from PC (cycle 1 of every instruction)
14    #[default]
15    FetchOpcode,
16
17    /// Fetch low byte of operand
18    FetchOperandLo,
19
20    /// Fetch high byte of operand
21    FetchOperandHi,
22
23    /// Resolve effective address (internal operation or dummy read)
24    /// Used for indexed addressing modes with page crossing
25    ResolveAddress,
26
27    /// Read data from effective address
28    ReadData,
29
30    /// Write data to effective address
31    WriteData,
32
33    /// Read-Modify-Write: Read phase
34    RmwRead,
35
36    /// Read-Modify-Write: Dummy write old value (hardware behavior)
37    RmwDummyWrite,
38
39    /// Read-Modify-Write: Write new value
40    RmwWrite,
41
42    /// Execute internal operation (no bus access, register-only)
43    Execute,
44
45    /// Fetch indirect address low byte (for indirect addressing)
46    FetchIndirectLo,
47
48    /// Fetch indirect address high byte (for indirect addressing)
49    FetchIndirectHi,
50
51    /// Add index to indirect address (indexed indirect)
52    AddIndex,
53
54    /// Push high byte to stack
55    PushHi,
56
57    /// Push low byte to stack
58    PushLo,
59
60    /// Push status to stack
61    PushStatus,
62
63    /// Pop low byte from stack (with internal cycle)
64    PopLo,
65
66    /// Pop high byte from stack
67    PopHi,
68
69    /// Pop status from stack
70    PopStatus,
71
72    /// Internal cycle (dummy stack read)
73    InternalCycle,
74
75    /// Branch taken - calculate new PC
76    BranchTaken,
77
78    /// Branch page cross - extra cycle for crossing page
79    BranchPageCross,
80
81    /// Interrupt: Push PC high
82    InterruptPushPcHi,
83
84    /// Interrupt: Push PC low
85    InterruptPushPcLo,
86
87    /// Interrupt: Push status
88    InterruptPushStatus,
89
90    /// Interrupt: Fetch vector low
91    InterruptFetchVectorLo,
92
93    /// Interrupt: Fetch vector high
94    InterruptFetchVectorHi,
95}
96
97/// Instruction execution pattern classification.
98///
99/// Different instruction types follow different state sequences:
100/// - Read: Fetch operand, read from effective address
101/// - Write: Fetch operand, write to effective address
102/// - ReadModifyWrite: Read, dummy write old, write new
103/// - etc.
104#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
105pub enum InstructionType {
106    /// Read instructions (LDA, LDX, LDY, AND, ORA, EOR, CMP, ADC, SBC, BIT, LAX, etc.)
107    #[default]
108    Read,
109
110    /// Write instructions (STA, STX, STY, SAX, SHA, SHX, SHY, TAS)
111    Write,
112
113    /// Read-Modify-Write (ASL, LSR, ROL, ROR, INC, DEC, SLO, RLA, SRE, RRA, DCP, ISC)
114    ReadModifyWrite,
115
116    /// Implied/Register operations (TAX, INX, CLC, NOP, etc.)
117    /// Single-byte instructions with internal operation
118    Implied,
119
120    /// Accumulator operations (ASL A, LSR A, ROL A, ROR A)
121    /// Two-cycle, single-byte
122    Accumulator,
123
124    /// Branch instructions (BEQ, BNE, BCC, BCS, BPL, BMI, BVC, BVS)
125    /// 2/3/4 cycles depending on condition and page crossing
126    Branch,
127
128    /// Jump Absolute (JMP $NNNN)
129    /// 3 cycles
130    JumpAbsolute,
131
132    /// Jump Indirect (JMP ($NNNN))
133    /// 5 cycles
134    JumpIndirect,
135
136    /// Jump to Subroutine (JSR $NNNN)
137    /// 6 cycles
138    JumpSubroutine,
139
140    /// Return from Subroutine (RTS)
141    /// 6 cycles
142    ReturnSubroutine,
143
144    /// Return from Interrupt (RTI)
145    /// 6 cycles
146    ReturnInterrupt,
147
148    /// Push to Stack (PHA, PHP)
149    /// 3 cycles
150    Push,
151
152    /// Pull from Stack (PLA, PLP)
153    /// 4 cycles
154    Pull,
155
156    /// Software Interrupt (BRK)
157    /// 7 cycles
158    Break,
159
160    /// JAM/KIL - Halt CPU
161    Jam,
162}
163
164impl CpuState {
165    /// Returns true if this state requires a bus read.
166    #[inline]
167    pub const fn is_read(&self) -> bool {
168        matches!(
169            self,
170            Self::FetchOpcode
171                | Self::FetchOperandLo
172                | Self::FetchOperandHi
173                | Self::ReadData
174                | Self::RmwRead
175                | Self::FetchIndirectLo
176                | Self::FetchIndirectHi
177                | Self::ResolveAddress
178                | Self::PopLo
179                | Self::PopHi
180                | Self::PopStatus
181                | Self::InterruptFetchVectorLo
182                | Self::InterruptFetchVectorHi
183        )
184    }
185
186    /// Returns true if this state requires a bus write.
187    #[inline]
188    pub const fn is_write(&self) -> bool {
189        matches!(
190            self,
191            Self::WriteData
192                | Self::RmwDummyWrite
193                | Self::RmwWrite
194                | Self::PushHi
195                | Self::PushLo
196                | Self::PushStatus
197                | Self::InterruptPushPcHi
198                | Self::InterruptPushPcLo
199                | Self::InterruptPushStatus
200        )
201    }
202
203    /// Returns true if this is an internal operation (no bus access visible).
204    #[inline]
205    pub const fn is_internal(&self) -> bool {
206        matches!(
207            self,
208            Self::Execute
209                | Self::AddIndex
210                | Self::InternalCycle
211                | Self::BranchTaken
212                | Self::BranchPageCross
213        )
214    }
215}
216
217impl InstructionType {
218    /// Returns the base number of cycles for this instruction type.
219    /// This does not include addressing mode cycles or penalties.
220    #[inline]
221    pub const fn base_cycles(&self) -> u8 {
222        match self {
223            Self::Implied => 2,
224            Self::Accumulator => 2,
225            Self::Read => 2,            // Base + addressing mode cycles
226            Self::Write => 2,           // Base + addressing mode cycles
227            Self::ReadModifyWrite => 2, // Base + addressing mode cycles + 2 (read/dummy/write)
228            Self::Branch => 2,          // +1 if taken, +1 if page cross
229            Self::JumpAbsolute => 3,
230            Self::JumpIndirect => 5,
231            Self::JumpSubroutine => 6,
232            Self::ReturnSubroutine => 6,
233            Self::ReturnInterrupt => 6,
234            Self::Push => 3,
235            Self::Pull => 4,
236            Self::Break => 7,
237            Self::Jam => 2,
238        }
239    }
240
241    /// Returns true if this instruction type can have page crossing penalty.
242    #[inline]
243    pub const fn has_page_cross_penalty(&self) -> bool {
244        matches!(self, Self::Read | Self::Branch)
245    }
246
247    /// Returns true if this is a read-modify-write instruction.
248    #[inline]
249    pub const fn is_rmw(&self) -> bool {
250        matches!(self, Self::ReadModifyWrite)
251    }
252
253    /// Classify an opcode into its instruction type.
254    ///
255    /// This function maps all 256 opcodes (official and unofficial) to their
256    /// execution pattern type, enabling proper cycle-by-cycle state machine
257    /// dispatch.
258    #[inline]
259    pub const fn from_opcode(opcode: u8) -> Self {
260        match opcode {
261            // ===== Branch instructions (2/3/4 cycles) =====
262            0x10 | 0x30 | 0x50 | 0x70 | 0x90 | 0xB0 | 0xD0 | 0xF0 => Self::Branch,
263
264            // ===== Jump/Subroutine/Return =====
265            0x4C => Self::JumpAbsolute,     // JMP abs
266            0x6C => Self::JumpIndirect,     // JMP (ind)
267            0x20 => Self::JumpSubroutine,   // JSR
268            0x60 => Self::ReturnSubroutine, // RTS
269            0x40 => Self::ReturnInterrupt,  // RTI
270            0x00 => Self::Break,            // BRK
271
272            // ===== Stack: Push (3 cycles) =====
273            0x48 | 0x08 => Self::Push, // PHA, PHP
274
275            // ===== Stack: Pull (4 cycles) =====
276            0x68 | 0x28 => Self::Pull, // PLA, PLP
277
278            // ===== Accumulator mode (2 cycles) =====
279            0x0A | 0x2A | 0x4A | 0x6A => Self::Accumulator, // ASL A, ROL A, LSR A, ROR A
280
281            // ===== Implied mode (2 cycles) =====
282            // Transfers
283            0xAA | 0xA8 | 0x8A | 0x98 | 0xBA | 0x9A => Self::Implied,
284            // Increment/Decrement registers
285            0xE8 | 0xC8 | 0xCA | 0x88 => Self::Implied,
286            // Flag operations
287            0x18 | 0x38 | 0x58 | 0x78 | 0xB8 | 0xD8 | 0xF8 => Self::Implied,
288            // Official NOP
289            0xEA => Self::Implied,
290            // Unofficial NOPs (implied, 2 cycles)
291            0x1A | 0x3A | 0x5A | 0x7A | 0xDA | 0xFA => Self::Implied,
292
293            // ===== Store instructions (Write) =====
294            // STA
295            0x85 | 0x95 | 0x8D | 0x9D | 0x99 | 0x81 | 0x91 => Self::Write,
296            // STX
297            0x86 | 0x96 | 0x8E => Self::Write,
298            // STY
299            0x84 | 0x94 | 0x8C => Self::Write,
300            // SAX (unofficial)
301            0x87 | 0x97 | 0x8F | 0x83 => Self::Write,
302            // SHA, SHX, SHY, TAS (unofficial)
303            0x93 | 0x9F | 0x9C | 0x9E | 0x9B => Self::Write,
304
305            // ===== Read-Modify-Write (memory) =====
306            // ASL
307            0x06 | 0x16 | 0x0E | 0x1E => Self::ReadModifyWrite,
308            // LSR
309            0x46 | 0x56 | 0x4E | 0x5E => Self::ReadModifyWrite,
310            // ROL
311            0x26 | 0x36 | 0x2E | 0x3E => Self::ReadModifyWrite,
312            // ROR
313            0x66 | 0x76 | 0x6E | 0x7E => Self::ReadModifyWrite,
314            // INC
315            0xE6 | 0xF6 | 0xEE | 0xFE => Self::ReadModifyWrite,
316            // DEC
317            0xC6 | 0xD6 | 0xCE | 0xDE => Self::ReadModifyWrite,
318            // SLO (unofficial: ASL + ORA)
319            0x07 | 0x17 | 0x0F | 0x1F | 0x1B | 0x03 | 0x13 => Self::ReadModifyWrite,
320            // RLA (unofficial: ROL + AND)
321            0x27 | 0x37 | 0x2F | 0x3F | 0x3B | 0x23 | 0x33 => Self::ReadModifyWrite,
322            // SRE (unofficial: LSR + EOR)
323            0x47 | 0x57 | 0x4F | 0x5F | 0x5B | 0x43 | 0x53 => Self::ReadModifyWrite,
324            // RRA (unofficial: ROR + ADC)
325            0x67 | 0x77 | 0x6F | 0x7F | 0x7B | 0x63 | 0x73 => Self::ReadModifyWrite,
326            // DCP (unofficial: DEC + CMP)
327            0xC7 | 0xD7 | 0xCF | 0xDF | 0xDB | 0xC3 | 0xD3 => Self::ReadModifyWrite,
328            // ISC (unofficial: INC + SBC)
329            0xE7 | 0xF7 | 0xEF | 0xFF | 0xFB | 0xE3 | 0xF3 => Self::ReadModifyWrite,
330
331            // ===== JAM/KIL opcodes =====
332            0x02 | 0x12 | 0x22 | 0x32 | 0x42 | 0x52 | 0x62 | 0x72 | 0x92 | 0xB2 | 0xD2 | 0xF2 => {
333                Self::Jam
334            }
335
336            // ===== All remaining opcodes are Read instructions =====
337            // LDA (all addressing modes)
338            0xA9 | 0xA5 | 0xB5 | 0xAD | 0xBD | 0xB9 | 0xA1 | 0xB1 => Self::Read,
339            // LDX
340            0xA2 | 0xA6 | 0xB6 | 0xAE | 0xBE => Self::Read,
341            // LDY
342            0xA0 | 0xA4 | 0xB4 | 0xAC | 0xBC => Self::Read,
343            // ADC
344            0x69 | 0x65 | 0x75 | 0x6D | 0x7D | 0x79 | 0x61 | 0x71 => Self::Read,
345            // SBC (including unofficial 0xEB)
346            0xE9 | 0xE5 | 0xF5 | 0xED | 0xFD | 0xF9 | 0xE1 | 0xF1 | 0xEB => Self::Read,
347            // AND
348            0x29 | 0x25 | 0x35 | 0x2D | 0x3D | 0x39 | 0x21 | 0x31 => Self::Read,
349            // ORA
350            0x09 | 0x05 | 0x15 | 0x0D | 0x1D | 0x19 | 0x01 | 0x11 => Self::Read,
351            // EOR
352            0x49 | 0x45 | 0x55 | 0x4D | 0x5D | 0x59 | 0x41 | 0x51 => Self::Read,
353            // CMP
354            0xC9 | 0xC5 | 0xD5 | 0xCD | 0xDD | 0xD9 | 0xC1 | 0xD1 => Self::Read,
355            // CPX
356            0xE0 | 0xE4 | 0xEC => Self::Read,
357            // CPY
358            0xC0 | 0xC4 | 0xCC => Self::Read,
359            // BIT
360            0x24 | 0x2C => Self::Read,
361            // LAX (unofficial)
362            0xA7 | 0xB7 | 0xAF | 0xBF | 0xA3 | 0xB3 => Self::Read,
363            // LAS (unofficial)
364            0xBB => Self::Read,
365            // Unofficial immediate operations (reads)
366            0x0B | 0x2B => Self::Read, // ANC
367            0x4B => Self::Read,        // ALR
368            0x6B => Self::Read,        // ARR
369            0x8B => Self::Read,        // XAA
370            0xAB => Self::Read,        // LXA
371            0xCB => Self::Read,        // AXS
372            // Unofficial NOPs with reads (immediate/zp/abs)
373            0x80 | 0x82 | 0x89 | 0xC2 | 0xE2 => Self::Read,
374            0x04 | 0x44 | 0x64 | 0x14 | 0x34 | 0x54 | 0x74 | 0xD4 | 0xF4 => Self::Read,
375            0x0C | 0x1C | 0x3C | 0x5C | 0x7C | 0xDC | 0xFC => Self::Read,
376        }
377    }
378}
379
380#[cfg(test)]
381mod tests {
382    use super::*;
383
384    #[test]
385    fn test_cpu_state_default() {
386        assert_eq!(CpuState::default(), CpuState::FetchOpcode);
387    }
388
389    #[test]
390    fn test_instruction_type_default() {
391        assert_eq!(InstructionType::default(), InstructionType::Read);
392    }
393
394    #[test]
395    fn test_state_is_read() {
396        assert!(CpuState::FetchOpcode.is_read());
397        assert!(CpuState::ReadData.is_read());
398        assert!(!CpuState::WriteData.is_read());
399        assert!(!CpuState::Execute.is_read());
400    }
401
402    #[test]
403    fn test_state_is_write() {
404        assert!(CpuState::WriteData.is_write());
405        assert!(CpuState::RmwWrite.is_write());
406        assert!(!CpuState::ReadData.is_write());
407        assert!(!CpuState::Execute.is_write());
408    }
409
410    #[test]
411    fn test_state_is_internal() {
412        assert!(CpuState::Execute.is_internal());
413        assert!(CpuState::AddIndex.is_internal());
414        assert!(!CpuState::FetchOpcode.is_internal());
415        assert!(!CpuState::WriteData.is_internal());
416    }
417
418    #[test]
419    fn test_instruction_type_base_cycles() {
420        assert_eq!(InstructionType::Implied.base_cycles(), 2);
421        assert_eq!(InstructionType::Push.base_cycles(), 3);
422        assert_eq!(InstructionType::Pull.base_cycles(), 4);
423        assert_eq!(InstructionType::JumpIndirect.base_cycles(), 5);
424        assert_eq!(InstructionType::JumpSubroutine.base_cycles(), 6);
425        assert_eq!(InstructionType::Break.base_cycles(), 7);
426    }
427
428    #[test]
429    fn test_page_cross_penalty() {
430        assert!(InstructionType::Read.has_page_cross_penalty());
431        assert!(InstructionType::Branch.has_page_cross_penalty());
432        assert!(!InstructionType::Write.has_page_cross_penalty());
433        assert!(!InstructionType::ReadModifyWrite.has_page_cross_penalty());
434    }
435
436    #[test]
437    fn test_from_opcode_branches() {
438        // All branch instructions
439        assert_eq!(InstructionType::from_opcode(0x10), InstructionType::Branch); // BPL
440        assert_eq!(InstructionType::from_opcode(0x30), InstructionType::Branch); // BMI
441        assert_eq!(InstructionType::from_opcode(0x50), InstructionType::Branch); // BVC
442        assert_eq!(InstructionType::from_opcode(0x70), InstructionType::Branch); // BVS
443        assert_eq!(InstructionType::from_opcode(0x90), InstructionType::Branch); // BCC
444        assert_eq!(InstructionType::from_opcode(0xB0), InstructionType::Branch); // BCS
445        assert_eq!(InstructionType::from_opcode(0xD0), InstructionType::Branch); // BNE
446        assert_eq!(InstructionType::from_opcode(0xF0), InstructionType::Branch);
447        // BEQ
448    }
449
450    #[test]
451    fn test_from_opcode_jumps() {
452        assert_eq!(
453            InstructionType::from_opcode(0x4C),
454            InstructionType::JumpAbsolute
455        );
456        assert_eq!(
457            InstructionType::from_opcode(0x6C),
458            InstructionType::JumpIndirect
459        );
460        assert_eq!(
461            InstructionType::from_opcode(0x20),
462            InstructionType::JumpSubroutine
463        );
464        assert_eq!(
465            InstructionType::from_opcode(0x60),
466            InstructionType::ReturnSubroutine
467        );
468        assert_eq!(
469            InstructionType::from_opcode(0x40),
470            InstructionType::ReturnInterrupt
471        );
472        assert_eq!(InstructionType::from_opcode(0x00), InstructionType::Break);
473    }
474
475    #[test]
476    fn test_from_opcode_stack() {
477        assert_eq!(InstructionType::from_opcode(0x48), InstructionType::Push); // PHA
478        assert_eq!(InstructionType::from_opcode(0x08), InstructionType::Push); // PHP
479        assert_eq!(InstructionType::from_opcode(0x68), InstructionType::Pull); // PLA
480        assert_eq!(InstructionType::from_opcode(0x28), InstructionType::Pull); // PLP
481    }
482
483    #[test]
484    fn test_from_opcode_accumulator() {
485        assert_eq!(
486            InstructionType::from_opcode(0x0A),
487            InstructionType::Accumulator
488        ); // ASL A
489        assert_eq!(
490            InstructionType::from_opcode(0x2A),
491            InstructionType::Accumulator
492        ); // ROL A
493        assert_eq!(
494            InstructionType::from_opcode(0x4A),
495            InstructionType::Accumulator
496        ); // LSR A
497        assert_eq!(
498            InstructionType::from_opcode(0x6A),
499            InstructionType::Accumulator
500        ); // ROR A
501    }
502
503    #[test]
504    fn test_from_opcode_implied() {
505        // Transfers
506        assert_eq!(InstructionType::from_opcode(0xAA), InstructionType::Implied); // TAX
507        assert_eq!(InstructionType::from_opcode(0xA8), InstructionType::Implied); // TAY
508        // Inc/Dec registers
509        assert_eq!(InstructionType::from_opcode(0xE8), InstructionType::Implied); // INX
510        assert_eq!(InstructionType::from_opcode(0xC8), InstructionType::Implied); // INY
511        // Flags
512        assert_eq!(InstructionType::from_opcode(0x18), InstructionType::Implied); // CLC
513        // NOP
514        assert_eq!(InstructionType::from_opcode(0xEA), InstructionType::Implied);
515    }
516
517    #[test]
518    fn test_from_opcode_write() {
519        // STA
520        assert_eq!(InstructionType::from_opcode(0x85), InstructionType::Write); // zp
521        assert_eq!(InstructionType::from_opcode(0x8D), InstructionType::Write); // abs
522        // STX
523        assert_eq!(InstructionType::from_opcode(0x86), InstructionType::Write);
524        // STY
525        assert_eq!(InstructionType::from_opcode(0x84), InstructionType::Write);
526    }
527
528    #[test]
529    fn test_from_opcode_rmw() {
530        // ASL memory
531        assert_eq!(
532            InstructionType::from_opcode(0x06),
533            InstructionType::ReadModifyWrite
534        );
535        assert_eq!(
536            InstructionType::from_opcode(0x0E),
537            InstructionType::ReadModifyWrite
538        );
539        // INC
540        assert_eq!(
541            InstructionType::from_opcode(0xE6),
542            InstructionType::ReadModifyWrite
543        );
544        // DEC
545        assert_eq!(
546            InstructionType::from_opcode(0xC6),
547            InstructionType::ReadModifyWrite
548        );
549        // Unofficial RMW (SLO, RLA, etc.)
550        assert_eq!(
551            InstructionType::from_opcode(0x07),
552            InstructionType::ReadModifyWrite
553        ); // SLO zp
554    }
555
556    #[test]
557    fn test_from_opcode_read() {
558        // LDA
559        assert_eq!(InstructionType::from_opcode(0xA9), InstructionType::Read); // imm
560        assert_eq!(InstructionType::from_opcode(0xA5), InstructionType::Read); // zp
561        // LDX
562        assert_eq!(InstructionType::from_opcode(0xA2), InstructionType::Read);
563        // LDY
564        assert_eq!(InstructionType::from_opcode(0xA0), InstructionType::Read);
565        // ADC
566        assert_eq!(InstructionType::from_opcode(0x69), InstructionType::Read);
567        // CMP
568        assert_eq!(InstructionType::from_opcode(0xC9), InstructionType::Read);
569    }
570
571    #[test]
572    fn test_from_opcode_jam() {
573        assert_eq!(InstructionType::from_opcode(0x02), InstructionType::Jam);
574        assert_eq!(InstructionType::from_opcode(0x12), InstructionType::Jam);
575        assert_eq!(InstructionType::from_opcode(0x22), InstructionType::Jam);
576    }
577}