├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ └── ci.yml ├── assets └── 6502.jpg ├── examples ├── asm │ ├── euclid │ │ ├── euclid.bin │ │ └── euclid.a65 │ └── linker.cfg ├── euclid.rs ├── euclid_bytes.rs └── mos6502.rs ├── AUTHORS.txt ├── notes ├── Notes.org ├── 6502Arch.org ├── NOTES.md ├── Notes.html └── 6502Arch.html ├── .gitattributes ├── .gitignore ├── COPYRIGHT ├── LICENSE ├── Cargo.toml ├── src ├── lib.rs ├── memory.rs ├── registers.rs ├── instruction.rs └── cpu.rs └── README.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: mre 2 | -------------------------------------------------------------------------------- /assets/6502.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mre/mos6502/HEAD/assets/6502.jpg -------------------------------------------------------------------------------- /examples/asm/euclid/euclid.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mre/mos6502/HEAD/examples/asm/euclid/euclid.bin -------------------------------------------------------------------------------- /AUTHORS.txt: -------------------------------------------------------------------------------- 1 | 6502-rs was written by: 2 | 3 | Alex Weisberger 4 | Andrew Keeton 5 | Johannes Muenzel 6 | Matthias Endler -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Enable version updates for Cargo 4 | - package-ecosystem: "cargo" 5 | directory: "/" 6 | schedule: 7 | interval: "weekly" 8 | open-pull-requests-limit: 10 9 | commit-message: 10 | prefix: "deps" 11 | include: "scope" 12 | -------------------------------------------------------------------------------- /examples/asm/linker.cfg: -------------------------------------------------------------------------------- 1 | MEMORY { 2 | ZP: start = $0000, size = $0100, type = rw, define = yes; 3 | RAM: start = $0100, size = $0200, type = rw, define = yes; 4 | ROM: start = $8000, size = $8000, type = ro; 5 | } 6 | 7 | SEGMENTS { 8 | ZEROPAGE: load = ZP, type = zp; 9 | DATA: load = RAM, type = rw; 10 | CODE: load = ROM, type = ro; 11 | } 12 | -------------------------------------------------------------------------------- /notes/Notes.org: -------------------------------------------------------------------------------- 1 | * Information 2 | 3 | ** 6502.org 4 | 5 | http://6502.org/ 6 | 7 | 8 | ** Easy 6502 tutorial 9 | 10 | http://skilldrick.github.io/easy6502/ 11 | 12 | 13 | ** Opcodes 14 | 15 | http://6502.org/tutorials/6502opcodes.html 16 | 17 | * Tools 18 | 19 | ** Web Compiler 20 | 21 | http://www.6502asm.com/ 22 | 23 | * Work 24 | 25 | ** aweisberger 26 | 27 | *** TODO Add Controller 28 | 29 | Must fetch program instruction from memory, decode and execute it. 30 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.a 2 | *.aux 3 | *.bc 4 | *.boot 5 | *.bz2 6 | *.cmi 7 | *.cmo 8 | *.cmx 9 | *.cp 10 | *.cps 11 | *.d 12 | *.dSYM 13 | *.def 14 | *.diff 15 | *.dll 16 | *.dylib 17 | *.elc 18 | *.epub 19 | *.exe 20 | *.fn 21 | *.html 22 | *.ky 23 | *.ll 24 | *.llvm 25 | *.log 26 | *.o 27 | *.orig 28 | *.out 29 | *.patch 30 | *.pdf 31 | *.pg 32 | *.pot 33 | *.pyc 34 | *.rej 35 | *.rlib 36 | *.rustc 37 | *.so 38 | *.swo 39 | *.swp 40 | *.tmp 41 | *.toc 42 | *.tp 43 | *.vr 44 | *.x86 45 | *~ 46 | .#* 47 | .DS_Store 48 | .cproject 49 | .hg/ 50 | .hgignore 51 | .project 52 | .settings/ 53 | .valgrindrc 54 | /*-*-*-*/ 55 | /*-*-*/ 56 | /Makefile 57 | /doc 58 | target/ 59 | /test/ 60 | /tmp/ 61 | TAGS 62 | TAGS.emacs 63 | TAGS.vi 64 | \#* 65 | \#*\# 66 | src/.DS_Store 67 | tmp.*.rs 68 | .vscode 69 | Cargo.lock -------------------------------------------------------------------------------- /examples/asm/euclid/euclid.a65: -------------------------------------------------------------------------------- 1 | ; euclid.a65 2 | ; A program to find the greatest common divisor of two numbers 3 | 4 | .ORG $1000 5 | 6 | ; .algo 7 | LDA $00 ; Load from F to A 8 | ; .algo_ 9 | sec ; Set carry flag 10 | SBC $01 ; Subtract S from the number in A (from F) 11 | BEQ end ; Jump to .end if the difference is zero 12 | BMI swap ; Jump to .swap if the difference is negative 13 | STA $00 ; Load A to F 14 | JMP algo_ ; Jump to .algo_ 15 | 16 | ; .end 17 | end: 18 | LDA $00 ; Load from F to A 19 | BRK ; Break (end program) 20 | 21 | ; .swap 22 | swap: 23 | LDX $00 ; Load F to X 24 | LDY $01 ; Load S to Y 25 | STX $01 ; Store X to S 26 | STY $00 ; Store Y to F 27 | JMP algo ; Jump to .algo 28 | 29 | algo: 30 | JMP algo ; Infinite loop to prevent program from ending 31 | 32 | algo_: 33 | JMP algo_ ; Infinite loop to prevent program from ending 34 | -------------------------------------------------------------------------------- /COPYRIGHT: -------------------------------------------------------------------------------- 1 | 6502-rs is copyright (C) 2014 The 6502-rs Developers (listed in the file 2 | AUTHORS.txt). 3 | 4 | Licensed under the license reproduced in the file LICENSE at the root of this 5 | repository. All files in the project carrying such notice may not be copied, 6 | modified, or distributed except according to those terms. 7 | 8 | 9 | Additional copyright may be retained by contributors other than The 6502-rs 10 | Developers or the parties enumerated in this file. Such copyright can be 11 | determined on a case-by-case basis by examining the author of each portion of a 12 | file in the revision-control commit records of the project, or by consulting 13 | representative comments claiming copyright ownership for a file. 14 | 15 | In all such cases, the absence of explicit licensing text indicates that the 16 | contributor chose to license their work for distribution under identical terms 17 | to those The 6502-rs Developers have chosen for the collective work, enumerated 18 | at the top of this file. The only difference is the retention of copyright 19 | itself, held by the contributor. 20 | -------------------------------------------------------------------------------- /examples/euclid.rs: -------------------------------------------------------------------------------- 1 | use mos6502::cpu; 2 | use mos6502::instruction::Nmos6502; 3 | use mos6502::memory::Bus; 4 | use mos6502::memory::Memory; 5 | use std::fs::read; 6 | 7 | fn main() { 8 | println!("Enter two numbers (< 128) separated by a space to know their GCD."); 9 | let mut input = String::new(); 10 | std::io::stdin().read_line(&mut input).unwrap(); 11 | 12 | let zero_page_data = input 13 | .split_whitespace() 14 | .map(|s| s.parse::().unwrap()) 15 | .collect::>(); 16 | 17 | // Load the binary file from disk 18 | let program = match read("examples/asm/euclid/euclid.bin") { 19 | Ok(data) => data, 20 | Err(err) => { 21 | println!("Error reading euclid.bin: {err}"); 22 | return; 23 | } 24 | }; 25 | 26 | let mut cpu = cpu::CPU::new(Memory::new(), Nmos6502); 27 | 28 | cpu.memory.set_bytes(0x00, &zero_page_data); 29 | cpu.memory.set_bytes(0x10, &program); 30 | cpu.registers.program_counter = 0x10; 31 | 32 | cpu.run(); 33 | 34 | println!("GCD is {}", cpu.registers.accumulator); 35 | } 36 | -------------------------------------------------------------------------------- /notes/6502Arch.org: -------------------------------------------------------------------------------- 1 | * Addressing modes 2 | 3 | has a 16-byte address bus. 4 | 5 | 6 | ** Absolute 7 | 8 | - Full memory location specified, i.e. $c000 9 | - 65536 bytes of addressable memory (2^16 duyyy) 10 | 11 | 12 | ** Zero-page 13 | 14 | - 1 byte adress, i.e. $c0, 256 bytes adressable 15 | - faster, takes less program space 16 | 17 | 18 | ** Zero-page, X 19 | 20 | - Relative adressing _within_ the zero page. 21 | - Adds the value in reg. x with a 1-byte adress 22 | - i.e. STA $a0, X 23 | - Address wraps around if the addition is larger than 1 byte 24 | 25 | 26 | ** Zero-page, Y 27 | 28 | - equiv to zero-page, X but can only be used with LDX and STX 29 | 30 | 31 | 32 | 33 | 34 | ** Immediate 35 | 36 | - i.e. #$c0 37 | - loads immedate number into register 38 | 39 | 40 | ** Relative 41 | 42 | - i.e. $c0, or label 43 | 44 | 45 | ** Implicit 46 | 47 | - when operation doesn't deal with memory 48 | 49 | 50 | ** Indirect 51 | 52 | - Uses absolute address to get another address 53 | - first address is LSB of address, following byte is MSB 54 | 55 | 56 | ** Indexed Indirect 57 | 58 | - i.e. LDA ($c0, X) 59 | - Take a zero page adress and add the value in reg. x to it, look up 2 byte address 60 | 61 | 62 | 63 | ** Indirect Indexed 64 | 65 | - zero page address dereferenced then Y is added to it 66 | 67 | * Stack 68 | 69 | - lives in memory between $0100 and $01ff 70 | 71 | * Jumping 72 | 73 | - JSR/RTS: Jump to subroutine and return from subroutine 74 | 75 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2014 The 6502-rs Developers 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 3. Neither the names of the copyright holders nor the names of any 13 | contributors may be used to endorse or promote products derived from this 14 | software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 | POSSIBILITY OF SUCH DAMAGE. 27 | 28 | -------------------------------------------------------------------------------- /examples/euclid_bytes.rs: -------------------------------------------------------------------------------- 1 | extern crate mos6502; 2 | 3 | use mos6502::cpu; 4 | use mos6502::instruction::Nmos6502; 5 | use mos6502::memory::Bus; 6 | use mos6502::memory::Memory; 7 | 8 | fn main() { 9 | println!("Enter two numbers (< 128) separated by a space to know their GCD."); 10 | let mut input = String::new(); 11 | std::io::stdin().read_line(&mut input).unwrap(); 12 | 13 | let zero_page_data = input 14 | .split_whitespace() 15 | .map(|s| s.parse::().unwrap()) 16 | .collect::>(); 17 | 18 | let program = [ 19 | // (F)irst | (S)econd 20 | // .algo 21 | 0xa5, 0x00, // Load from F to A 22 | // .algo_ 23 | 0x38, // Set carry flag 24 | 0xe5, 0x01, // Substract S from number in A (from F) 25 | 0xf0, 0x07, // Jump to .end if diff is zero 26 | 0x30, 0x08, // Jump to .swap if diff is negative 27 | 0x85, 0x00, // Load A to F 28 | 0x4c, 0x12, 0x00, // Jump to .algo_ 29 | // .end 30 | 0xa5, 0x00, // Load from S to A 31 | 0xff, // .swap 32 | 0xa6, 0x00, // load F to X 33 | 0xa4, 0x01, // load S to Y 34 | 0x86, 0x01, // Store X to F 35 | 0x84, 0x00, // Store Y to S 36 | 0x4c, 0x10, 0x00, // Jump to .algo 37 | ]; 38 | 39 | let mut cpu = cpu::CPU::new(Memory::new(), Nmos6502); 40 | 41 | cpu.memory.set_bytes(0x00, &zero_page_data); 42 | cpu.memory.set_bytes(0x10, &program); 43 | cpu.registers.program_counter = 0x10; 44 | 45 | cpu.run(); 46 | 47 | println!("GCD is {}", cpu.registers.accumulator); 48 | } 49 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2014-2021 The 6502-rs Developers 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions 6 | # are met: 7 | # 1. Redistributions of source code must retain the above copyright 8 | # notice, this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 3. Neither the names of the copyright holders nor the names of any 13 | # contributors may be used to endorse or promote products derived from this 14 | # software without specific prior written permission. 15 | # 16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 | # POSSIBILITY OF SUCH DAMAGE. 27 | 28 | 29 | [package] 30 | name = "mos6502" 31 | description = "A MOS 6502 Emulator" 32 | license = "BSD-3-Clause" 33 | version = "0.6.2" 34 | authors = ["The 6502-rs Developers"] 35 | exclude = ["examples/**"] 36 | edition = "2024" 37 | 38 | [lib] 39 | # This will look in src/lib.rs 40 | name = "mos6502" 41 | 42 | [dependencies] 43 | bitflags = "2.9.1" 44 | log = "0.4.27" 45 | 46 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | repository_dispatch: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - master 8 | tags: 9 | - "*.*.*" 10 | pull_request: 11 | types: 12 | - opened 13 | - synchronize 14 | 15 | env: 16 | CARGO_TERM_COLOR: always 17 | 18 | jobs: 19 | test: 20 | name: test 21 | strategy: 22 | matrix: 23 | os: [ubuntu-latest, macOS-latest, windows-latest] 24 | runs-on: ${{ matrix.os }} 25 | steps: 26 | - uses: actions/checkout@v3 27 | - uses: actions-rs/toolchain@v1 28 | with: 29 | toolchain: stable 30 | - uses: Swatinem/rust-cache@v2 31 | - name: Run tests 32 | run: cargo test 33 | 34 | lint: 35 | runs-on: ubuntu-latest 36 | steps: 37 | - uses: actions/checkout@v3 38 | - uses: actions-rs/toolchain@v1 39 | with: 40 | toolchain: stable 41 | components: clippy 42 | - uses: Swatinem/rust-cache@v2 43 | - name: Run cargo fmt (check if all code is rustfmt-ed) 44 | uses: actions-rs/cargo@v1 45 | with: 46 | command: fmt 47 | args: --all -- --check 48 | - name: Run cargo clippy (deny warnings) 49 | uses: actions-rs/cargo@v1 50 | with: 51 | command: clippy 52 | # --all-targets makes it lint tests too 53 | args: --all-targets --all-features -- -D warnings 54 | 55 | audit: 56 | runs-on: ubuntu-latest 57 | steps: 58 | - uses: actions/checkout@v3 59 | - uses: actions-rs/toolchain@v1 60 | with: 61 | toolchain: stable 62 | - name: Install cargo-audit 63 | uses: actions-rs/install@v0.1 64 | with: 65 | crate: cargo-audit 66 | version: latest 67 | use-tool-cache: true 68 | - name: Run cargo-audit 69 | run: cargo audit --deny yanked 70 | 71 | publish-check: 72 | runs-on: ubuntu-latest 73 | steps: 74 | - uses: actions/checkout@v3 75 | - name: cargo fetch 76 | uses: actions-rs/cargo@v1 77 | with: 78 | command: fetch 79 | - name: cargo publish 80 | uses: actions-rs/cargo@v1 81 | env: 82 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 83 | with: 84 | command: publish 85 | args: --dry-run 86 | 87 | publish: 88 | if: startsWith(github.ref, 'refs/tags/') 89 | needs: 90 | - test 91 | - lint 92 | - audit 93 | - publish-check 94 | runs-on: ubuntu-latest 95 | steps: 96 | - uses: actions/checkout@v3 97 | - name: cargo fetch 98 | uses: actions-rs/cargo@v1 99 | with: 100 | command: fetch 101 | - name: cargo publish 102 | uses: actions-rs/cargo@v1 103 | env: 104 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 105 | with: 106 | command: publish -------------------------------------------------------------------------------- /notes/NOTES.md: -------------------------------------------------------------------------------- 1 | Here are the resources about the 6502 CPU and its history. 2 | 3 | * [MOS Technology 6502 - Wikipedia](https://en.wikipedia.org/wiki/MOS_Technology_6502) - Comprehensive overview of original 6502, ROR instruction timeline, development history 4 | * [WDC 65C02 - Wikipedia](https://en.wikipedia.org/wiki/WDC_65C02) - Details on 65C02 development timeline (1981-1983), technical improvements 5 | * [Ricoh 2A03 - Wikipedia](https://en.wikipedia.org/wiki/Ricoh_2A03) - Nintendo NES processor details, decimal mode removal, sound generation 6 | * [Nintendo Entertainment System - Wikipedia](https://en.wikipedia.org/wiki/Nintendo_Entertainment_System) - Famicom release date confirmation (July 15, 1983) 7 | * [MOS Technology 6502 - Computer History Wiki](https://gunkies.org/wiki/MOS_Technology_6502) - Early ROR bug details, pre-June 1976 CPUs, KIM-1 information 8 | * [2A03 - NESdev Wiki](https://www.nesdev.org/wiki/2A03) - Technical specifications of Nintendo's 2A03 processor 9 | * [6502 Instruction Set - Masswerk](https://www.masswerk.at/6502/6502_instruction_set.html) - Rev. A details, ROR instruction analysis 10 | * [Development of the MOS Technology 6502: A Historical Perspective](https://www.embeddedrelated.com/showarticle/1453.php) - Detailed development timeline, transistor counts, process information 11 | * [The Rise of MOS Technology & The 6502](https://www.commodore.ca/commodore-history/the-rise-of-mos-technology-the-6502/) - Chuck Peddle interviews, WESCON 1975 launch details 12 | * [Chip Hall of Fame: MOS Technology 6502 Microprocessor - IEEE Spectrum](https://spectrum.ieee.org/chip-hall-of-fame-mos-technology-6502-microprocessor) - 1975 release confirmation, Apple computer usage 13 | * [When did the 65C02 become available? - Retrocomputing Stack Exchange](https://retrocomputing.stackexchange.com/questions/11497/when-did-the-65c02-become-available) - 1983 release date evidence, Softalk magazine references 14 | * [Were there 6502 revisions B and C? - Retrocomputing Stack Exchange](https://retrocomputing.stackexchange.com/questions/27529/were-there-6502-revisions-b-and-c-and-what-were-they-like) - Revision A vs later revisions, ROR instruction timeline 15 | * [Interesting Video about 6502 "broken" ROR - nesdev.org](https://forums.nesdev.org/viewtopic.php?t=24484) - Modern research on ROR "bug" vs intentional omission debate 16 | * [How to test a ceramic 6502 for the ROR bug? - Applefritter](https://www.applefritter.com/content/how-test-ceramic-6502-ror-bug) - Expert community discussion on ROR implementation theories 17 | * [Measuring the ROR Bug in the Early MOS 6502](https://www.pagetable.com/?p=406) - Actual testing of early 6502 chips, KIM-1 from week 51 of 1975 18 | * [Were any Apple 1s sold with non-ROR 6502s? - Retrocomputing Stack Exchange](https://retrocomputing.stackexchange.com/questions/14005/were-any-apple-1s-sold-with-non-ror-6502s) - Timeline correlation between Apple-1 production and ROR fix 19 | * [27c3: Reverse Engineering the MOS 6502 CPU](https://www.youtube.com/watch?v=fWqBmmPQP40) - Technical presentation on 6502 die analysis and reverse engineering 20 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2014 The 6502-rs Developers 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions 6 | // are met: 7 | // 1. Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // 2. Redistributions in binary form must reproduce the above copyright 10 | // notice, this list of conditions and the following disclaimer in the 11 | // documentation and/or other materials provided with the distribution. 12 | // 3. Neither the names of the copyright holders nor the names of any 13 | // contributors may be used to endorse or promote products derived from this 14 | // software without specific prior written permission. 15 | // 16 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 | // POSSIBILITY OF SUCH DAMAGE. 27 | 28 | #![warn(clippy::all, clippy::pedantic)] 29 | #![warn( 30 | absolute_paths_not_starting_with_crate, 31 | rustdoc::invalid_html_tags, 32 | missing_copy_implementations, 33 | missing_debug_implementations, 34 | semicolon_in_expressions_from_macros, 35 | unreachable_pub, 36 | unused_crate_dependencies, 37 | unused_extern_crates, 38 | variant_size_differences, 39 | clippy::missing_const_for_fn 40 | )] 41 | #![deny(anonymous_parameters, macro_use_extern_crate)] 42 | #![allow(clippy::module_name_repetitions, clippy::needless_doctest_main)] 43 | // Registers and ops follow the 6502 naming convention and have similar names at 44 | // times 45 | #![allow(clippy::similar_names)] 46 | #![allow(clippy::match_same_arms)] 47 | #![allow(clippy::too_many_lines)] 48 | #![no_std] 49 | 50 | #[doc = include_str!("../README.md")] 51 | pub mod cpu; 52 | pub mod instruction; 53 | pub mod memory; 54 | pub mod registers; 55 | 56 | /// Output of the ADC instruction 57 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 58 | #[allow(clippy::struct_excessive_bools)] 59 | pub struct AdcOutput { 60 | result: u8, 61 | did_carry: bool, 62 | overflow: bool, 63 | negative: bool, 64 | zero: bool, 65 | } 66 | 67 | /// Trait for 6502 variant. This is the mechanism allowing the different 6502-like CPUs to be 68 | /// emulated. It allows a struct to decode an opcode into its instruction and addressing mode, 69 | /// and implements variant-specific instruction behavior. 70 | pub trait Variant { 71 | fn decode( 72 | opcode: u8, 73 | ) -> Option<( 74 | crate::instruction::Instruction, 75 | crate::instruction::AddressingMode, 76 | )>; 77 | 78 | /// Execute Add with Carry (ADC) in binary mode 79 | /// 80 | /// # Arguments 81 | /// * `accumulator` - Current accumulator value 82 | /// * `value` - Value to add 83 | /// * `carry_set` - Carry flag set at the time of execution (0 or 1) 84 | /// 85 | /// # Returns 86 | /// Tuple of (result, `carry_out`, overflow, negative, zero) 87 | fn adc_binary(accumulator: u8, value: u8, carry_set: u8) -> AdcOutput; 88 | 89 | /// Execute Add with Carry (ADC) in decimal mode (BCD) 90 | /// 91 | /// # Arguments 92 | /// * `accumulator` - Current accumulator value 93 | /// * `value` - Value to add 94 | /// * `carry_set` - Carry flag set at the time of execution (0 or 1) 95 | /// 96 | /// # Returns 97 | /// Tuple of (result, `carry_out`, overflow, negative, zero) 98 | fn adc_decimal(accumulator: u8, value: u8, carry_set: u8) -> AdcOutput; 99 | } 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mos6502 2 | 3 | ![MOS6502](assets/6502.jpg) 4 | 5 | ![](https://github.com/mre/mos6502/workflows/test/badge.svg) 6 | [![docs.rs](https://docs.rs/mos6502/badge.svg)](https://docs.rs/mos6502) 7 | 8 | An emulator for the [MOS 6502 CPU](https://en.wikipedia.org/wiki/MOS_Technology_6502) written in Rust.\ 9 | Tested and validated by [solid65](https://github.com/omarandlorraine/solid65).\ 10 | It builds on stable Rust and supports `#[no_std]` targets. 11 | 12 | ## What is the MOS 6502? 13 | 14 | > The MOS Technology 6502 (typically pronounced "sixty-five-oh-two" or "six-five-oh-two") is an 8-bit microprocessor that was designed by a small team led by Chuck Peddle for MOS Technology. [...] 15 | > 16 | > When it was introduced in 1975, the 6502 was the **least expensive microprocessor on the market** by a considerable margin. It initially sold for less than one-sixth the cost of competing designs from larger companies, such as the 6800 or Intel 8080. Its introduction caused rapid decreases in pricing across the entire processor market. **Along with the Zilog Z80, it sparked a series of projects that resulted in the home computer revolution of the early 1980s.** 17 | 18 | Source: [Wikipedia](https://en.wikipedia.org/wiki/MOS_Technology_6502) 19 | 20 | 21 | ## How to use this library 22 | 23 | ```rust 24 | use mos6502::memory::Bus; 25 | use mos6502::memory::Memory; 26 | use mos6502::instruction::Nmos6502; 27 | use mos6502::cpu; 28 | 29 | fn main() { 30 | // Calculate the greatest common divisor of 56 and 49 31 | // using Euclid's algorithm. 32 | let zero_page_data = [56, 49]; 33 | 34 | let program = [ 35 | // (F)irst | (S)econd 36 | // .algo 37 | 0xa5, 0x00, // Load from F to A 38 | // .algo_ 39 | 0x38, // Set carry flag 40 | 0xe5, 0x01, // Substract S from number in A (from F) 41 | 0xf0, 0x07, // Jump to .end if diff is zero 42 | 0x30, 0x08, // Jump to .swap if diff is negative 43 | 0x85, 0x00, // Load A to F 44 | 0x4c, 0x12, 0x00, // Jump to .algo_ 45 | // .end 46 | 0xa5, 0x00, // Load from S to A 47 | 0xff, 48 | // .swap 49 | 0xa6, 0x00, // load F to X 50 | 0xa4, 0x01, // load S to Y 51 | 0x86, 0x01, // Store X to F 52 | 0x84, 0x00, // Store Y to S 53 | 0x4c, 0x10, 0x00, // Jump to .algo 54 | ]; 55 | 56 | let mut cpu = cpu::CPU::new(Memory::new(), Nmos6502); 57 | 58 | cpu.memory.set_bytes(0x00, &zero_page_data); 59 | cpu.memory.set_bytes(0x10, &program); 60 | cpu.registers.program_counter = 0x10; 61 | 62 | cpu.run(); 63 | 64 | // The expected GCD is 7 65 | assert_eq!(7, cpu.registers.accumulator); 66 | } 67 | ``` 68 | 69 | The same can be achieved, by compiling the euclid example yourself. 70 | 71 | First install a 6502 assembler and linker, e.g. [cc65](https://cc65.github.io/cc65/). 72 | 73 | ```sh 74 | brew install cc65 75 | ``` 76 | 77 | Then compile and link the assembly file: 78 | 79 | ```sh 80 | cd examples/asm/euclid 81 | ca65 euclid.a65 82 | ld65 -C ../linker.cfg -o euclid.bin euclid.o 83 | ``` 84 | 85 | This will create a binary file `euclid.bin` that you can load into the emulator: 86 | 87 | ```rust 88 | use mos6502::memory::Bus; 89 | use mos6502::memory::Memory; 90 | use mos6502::instruction::Nmos6502; 91 | use mos6502::cpu; 92 | use std::fs::read; 93 | 94 | fn main() { 95 | // Calculate the greatest common divisor of 56 and 49 96 | // using Euclid's algorithm. 97 | let zero_page_data = [56, 49]; 98 | 99 | // Load the binary file from disk 100 | let program = match read("examples/asm/euclid/euclid.bin") { 101 | Ok(data) => data, 102 | Err(err) => { 103 | println!("Error reading euclid.bin: {}", err); 104 | return; 105 | } 106 | }; 107 | 108 | let mut cpu = cpu::CPU::new(Memory::new(), Nmos6502); 109 | 110 | cpu.memory.set_bytes(0x00, &zero_page_data); 111 | cpu.memory.set_bytes(0x10, &program); 112 | cpu.registers.program_counter = 0x10; 113 | 114 | cpu.run(); 115 | 116 | // The expected GCD is 7 117 | assert_eq!(7, cpu.registers.accumulator); 118 | } 119 | ``` 120 | 121 | ## Credits 122 | 123 | This started off as a fork of [amw-zero/6502-rs](https://github.com/amw-zero/6502-rs), 124 | which seems to be [unmaintained](https://github.com/amw-zero/6502-rs/pull/36) at this point. 125 | -------------------------------------------------------------------------------- /examples/mos6502.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2014 The 6502-rs Developers 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions 6 | // are met: 7 | // 1. Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // 2. Redistributions in binary form must reproduce the above copyright 10 | // notice, this list of conditions and the following disclaimer in the 11 | // documentation and/or other materials provided with the distribution. 12 | // 3. Neither the names of the copyright holders nor the names of any 13 | // contributors may be used to endorse or promote products derived from this 14 | // software without specific prior written permission. 15 | // 16 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 | // POSSIBILITY OF SUCH DAMAGE. 27 | 28 | extern crate mos6502; 29 | 30 | #[cfg(not(test))] 31 | use mos6502::cpu; 32 | use mos6502::instruction::Nmos6502; 33 | use mos6502::memory::Bus; 34 | use mos6502::memory::Memory; 35 | 36 | #[cfg(not(test))] 37 | fn main() { 38 | let mut cpu = cpu::CPU::new(Memory::new(), Nmos6502); 39 | 40 | // "Load" a program 41 | 42 | let zero_page_data = [ 43 | // ZeroPage data start 44 | 0x00, 0x02, // ADC ZeroPage target 45 | 0x00, 0x04, // ADC ZeroPageX target 46 | 0x00, 0x00, 0x00, 0x00, 0x10, // ADC IndexedIndirectX address 47 | 0x80, // ADC IndexedIndirectX address 48 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, // ADC IndirectIndexedY address 49 | 0x80, // ADC IndirectIndexedY address 50 | ]; 51 | 52 | let program = [ 53 | // Code start 54 | 0xA9, // LDA Immediate 55 | 0x01, // Immediate operand 56 | 0x69, // ADC Immediate 57 | 0x07, // Immediate operand 58 | 0x65, // ADC ZeroPage 59 | 0x01, // ZeroPage operand 60 | 0xA2, // LDX Immediate 61 | 0x01, // Immediate operand 62 | 0x75, // ADC ZeroPageX 63 | 0x02, // ZeroPageX operand 64 | 0x6D, // ADC Absolute 65 | 0x01, // Absolute operand 66 | 0x80, // Absolute operand 67 | 0xA2, // LDX immediate 68 | 0x08, // Immediate operand 69 | 0x7D, // ADC AbsoluteX 70 | 0x00, // AbsoluteX operand 71 | 0x80, // AbsoluteX operand 72 | 0xA0, // LDY immediate 73 | 0x04, // Immediate operand 74 | 0x79, // ADC AbsoluteY 75 | 0x00, // AbsoluteY operand 76 | 0x80, // AbsoluteY operand 77 | 0xA2, // LDX immediate 78 | 0x05, // Immediate operand 79 | 0x61, // ADC IndexedIndirectX 80 | 0x03, // IndexedIndirectX operand 81 | 0xA0, // LDY immediate 82 | 0x10, // Immediate operand 83 | 0x71, // ADC IndirectIndexedY 84 | 0x0F, // IndirectIndexedY operand 85 | 0xEA, // NOP :) 86 | 0xFF, // Something invalid -- the end! 87 | ]; 88 | 89 | let data = [ 90 | 0x00, 0x09, // ADC Absolute target 91 | 0x00, 0x00, 0x40, // ADC AbsoluteY target 92 | 0x00, 0x00, 0x00, 0x11, // ADC AbsoluteX target 93 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, // ADC IndexedIndirectX target 94 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, // ADC IndirectIndexedY target 95 | ]; 96 | 97 | cpu.memory.set_bytes(0x0000, &zero_page_data); 98 | cpu.memory.set_bytes(0x4000, &program); 99 | cpu.memory.set_bytes(0x8000, &data); 100 | 101 | cpu.registers.program_counter = 0x4000; 102 | 103 | cpu.run(); 104 | 105 | println!("{cpu:?}"); 106 | } 107 | -------------------------------------------------------------------------------- /src/memory.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2014 The 6502-rs Developers 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions 6 | // are met: 7 | // 1. Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // 2. Redistributions in binary form must reproduce the above copyright 10 | // notice, this list of conditions and the following disclaimer in the 11 | // documentation and/or other materials provided with the distribution. 12 | // 3. Neither the names of the copyright holders nor the names of any 13 | // contributors may be used to endorse or promote products derived from this 14 | // software without specific prior written permission. 15 | // 16 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 | // POSSIBILITY OF SUCH DAMAGE. 27 | 28 | // JAM: We can probably come up with a better way to represent address ranges. 29 | // Address range type? 30 | // 31 | // // Address range -- inclusive on both sides 32 | // pub struct AddressRangeIncl { 33 | // begin: Address, 34 | // end: Address, 35 | // } 36 | 37 | const ADDR_LO_BARE: u16 = 0x0000; 38 | const ADDR_HI_BARE: u16 = 0xFFFF; 39 | 40 | pub const MEMORY_ADDRESS_LO: u16 = ADDR_LO_BARE; 41 | pub const MEMORY_ADDRESS_HI: u16 = ADDR_HI_BARE; 42 | pub const STACK_ADDRESS_LO: u16 = 0x0100; 43 | pub const STACK_ADDRESS_HI: u16 = 0x01FF; 44 | pub const IRQ_INTERRUPT_VECTOR_LO: u16 = 0xFFFE; 45 | pub const IRQ_INTERRUPT_VECTOR_HI: u16 = 0xFFFF; 46 | 47 | const MEMORY_SIZE: usize = (ADDR_HI_BARE - ADDR_LO_BARE) as usize + 1usize; 48 | 49 | /// 64KB memory implementation 50 | #[derive(Copy, Clone, Debug)] 51 | pub struct Memory { 52 | #[allow(clippy::large_stack_arrays)] 53 | bytes: [u8; MEMORY_SIZE], 54 | } 55 | 56 | impl Default for Memory { 57 | fn default() -> Self { 58 | Self::new() 59 | } 60 | } 61 | 62 | /// Trait for a bus that can read and write bytes. 63 | /// 64 | /// This is used to abstract the memory and I/O operations of the CPU. 65 | /// 66 | /// # Examples 67 | /// 68 | /// ``` 69 | /// use mos6502::memory::{Bus, Memory}; 70 | /// 71 | /// let mut memory = Memory::new(); 72 | /// memory.set_byte(0x0000, 0x12); 73 | /// assert_eq!(memory.get_byte(0x0000), 0x12); 74 | /// ``` 75 | pub trait Bus { 76 | /// Returns the byte at the given address. 77 | fn get_byte(&mut self, address: u16) -> u8; 78 | 79 | /// Sets the byte at the given address to the given value. 80 | fn set_byte(&mut self, address: u16, value: u8); 81 | 82 | /// Sets the bytes starting at the given address to the given values. 83 | /// 84 | /// This is a default implementation that calls `set_byte` for each byte. 85 | /// 86 | /// # Note 87 | /// 88 | /// This assumes that the length of `values` is less than or equal to 89 | /// [`u16::MAX`] (65535). If the length of `values` is greater than `u16::MAX`, 90 | /// this will truncate the length. This assumption is made because the 91 | /// maximum addressable memory for the 6502 is 64KB. 92 | #[allow(clippy::cast_possible_truncation)] 93 | fn set_bytes(&mut self, start: u16, values: &[u8]) { 94 | for i in 0..values.len() as u16 { 95 | self.set_byte(start + i, values[i as usize]); 96 | } 97 | } 98 | } 99 | 100 | impl Memory { 101 | #[must_use] 102 | #[allow(clippy::large_stack_arrays)] 103 | pub const fn new() -> Memory { 104 | Memory { 105 | #[allow(clippy::large_stack_arrays)] 106 | bytes: [0; MEMORY_SIZE], 107 | } 108 | } 109 | } 110 | 111 | impl Bus for Memory { 112 | fn get_byte(&mut self, address: u16) -> u8 { 113 | self.bytes[address as usize] 114 | } 115 | 116 | /// Sets the byte at the given address to the given value and returns the 117 | /// previous value at the address. 118 | fn set_byte(&mut self, address: u16, value: u8) { 119 | self.bytes[address as usize] = value; 120 | } 121 | 122 | /// Fast way to set multiple bytes in memory when the underlying memory is a 123 | /// consecutive block of bytes. 124 | fn set_bytes(&mut self, start: u16, values: &[u8]) { 125 | let start = start as usize; 126 | 127 | // This panics if the range is invalid 128 | let end = start + values.len(); 129 | 130 | self.bytes[start..end].copy_from_slice(values); 131 | } 132 | } 133 | 134 | #[cfg(test)] 135 | mod tests { 136 | use super::*; 137 | 138 | #[test] 139 | #[should_panic(expected = "range end index 65537 out of range for slice of length 65536")] 140 | fn test_memory_overflow_panic() { 141 | let mut memory = Memory::new(); 142 | memory.set_bytes(0xFFFE, &[1, 2, 3]); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/registers.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2014 The 6502-rs Developers 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions 6 | // are met: 7 | // 1. Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // 2. Redistributions in binary form must reproduce the above copyright 10 | // notice, this list of conditions and the following disclaimer in the 11 | // documentation and/or other materials provided with the distribution. 12 | // 3. Neither the names of the copyright holders nor the names of any 13 | // contributors may be used to endorse or promote products derived from this 14 | // software without specific prior written permission. 15 | // 16 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 | // POSSIBILITY OF SUCH DAMAGE. 27 | 28 | use bitflags::bitflags; 29 | 30 | // Useful for constructing Status instances 31 | #[derive(Copy, Clone, Debug)] 32 | #[allow(clippy::struct_excessive_bools)] 33 | pub struct StatusArgs { 34 | pub negative: bool, 35 | pub overflow: bool, 36 | pub unused: bool, 37 | pub brk: bool, 38 | pub decimal_mode: bool, 39 | pub disable_interrupts: bool, 40 | pub zero: bool, 41 | pub carry: bool, 42 | } 43 | 44 | impl StatusArgs { 45 | #[must_use] 46 | pub const fn none() -> StatusArgs { 47 | StatusArgs { 48 | negative: false, 49 | overflow: false, 50 | unused: false, 51 | brk: false, 52 | decimal_mode: false, 53 | disable_interrupts: false, 54 | zero: false, 55 | carry: false, 56 | } 57 | } 58 | } 59 | 60 | bitflags! { 61 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 62 | pub struct Status: u8 { 63 | const PS_NEGATIVE = 0b1000_0000; 64 | const PS_OVERFLOW = 0b0100_0000; 65 | const PS_UNUSED = 0b0010_0000; // JAM: Should this exist? 66 | // (note that it affects the 67 | // behavior of things like 68 | // from_bits_truncate) 69 | const PS_BRK = 0b0001_0000; 70 | const PS_DECIMAL_MODE = 0b0000_1000; 71 | const PS_DISABLE_INTERRUPTS = 0b0000_0100; 72 | const PS_ZERO = 0b0000_0010; 73 | const PS_CARRY = 0b0000_0001; 74 | } 75 | } 76 | 77 | impl Status { 78 | #[must_use] 79 | pub fn new( 80 | StatusArgs { 81 | negative, 82 | overflow, 83 | unused, 84 | brk, 85 | decimal_mode, 86 | disable_interrupts, 87 | zero, 88 | carry, 89 | }: StatusArgs, 90 | ) -> Status { 91 | let mut out = Status::empty(); 92 | 93 | if negative { 94 | out |= Status::PS_NEGATIVE; 95 | } 96 | if overflow { 97 | out |= Status::PS_OVERFLOW; 98 | } 99 | if unused { 100 | out |= Status::PS_UNUSED; 101 | } 102 | if brk { 103 | out |= Status::PS_BRK; 104 | } 105 | if decimal_mode { 106 | out |= Status::PS_DECIMAL_MODE; 107 | } 108 | if disable_interrupts { 109 | out |= Status::PS_DISABLE_INTERRUPTS; 110 | } 111 | if zero { 112 | out |= Status::PS_ZERO; 113 | } 114 | if carry { 115 | out |= Status::PS_CARRY; 116 | } 117 | 118 | out 119 | } 120 | 121 | pub fn and(&mut self, rhs: Status) { 122 | *self &= rhs; 123 | } 124 | 125 | pub fn or(&mut self, rhs: Status) { 126 | *self |= rhs; 127 | } 128 | 129 | pub fn set_with_mask(&mut self, mask: Status, rhs: Status) { 130 | *self = (*self & !mask) | rhs; 131 | } 132 | } 133 | 134 | impl Default for Status { 135 | fn default() -> Self { 136 | // Safe emulator defaults chosen for reliability across all 6502 variants. 137 | // Real hardware varies: NMOS has undefined decimal flag on reset, 65C02 clears it. 138 | // We could implement variant-specific defaults, but given that the flags 139 | // are randomly set on real hardware, it's fair to use a safe default. 140 | Status::new(StatusArgs { 141 | negative: false, // N: Negative result flag 142 | overflow: false, // V: Overflow flag, not set on reset 143 | unused: true, // -: Bit 5 typically set on all variants 144 | brk: false, // B: Not stored in register, only during stack operations 145 | decimal_mode: false, // D: Matches 65C02 behavior, safe for NMOS (software uses CLD anyway) 146 | disable_interrupts: true, // I: Interrupts disabled on reset for all variants 147 | zero: false, // Z: Flag for zero result 148 | carry: false, // C: Flag for carry 149 | }) 150 | } 151 | } 152 | 153 | #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] 154 | pub struct StackPointer(pub u8); 155 | 156 | impl StackPointer { 157 | #[must_use] 158 | pub const fn to_u16(self) -> u16 { 159 | let StackPointer(val) = self; 160 | u16::from_le_bytes([val, 0x01]) 161 | } 162 | 163 | pub const fn decrement(&mut self) { 164 | self.0 = self.0.wrapping_sub(1); 165 | } 166 | 167 | pub const fn increment(&mut self) { 168 | self.0 = self.0.wrapping_add(1); 169 | } 170 | } 171 | 172 | #[derive(Copy, Clone, PartialEq, Eq, Debug)] 173 | pub struct Registers { 174 | pub accumulator: u8, 175 | pub index_x: u8, 176 | pub index_y: u8, 177 | pub stack_pointer: StackPointer, 178 | pub program_counter: u16, 179 | pub status: Status, 180 | } 181 | 182 | impl Default for Registers { 183 | fn default() -> Self { 184 | Self::new() 185 | } 186 | } 187 | 188 | impl Registers { 189 | #[must_use] 190 | pub fn new() -> Registers { 191 | // Zero initialization for emulator predictability. 192 | // Real hardware has undefined register states on power-on. 193 | Registers { 194 | accumulator: 0, 195 | index_x: 0, 196 | index_y: 0, 197 | stack_pointer: StackPointer(0), // Real hardware: random value on power-on 198 | program_counter: 0, // Set by reset vector in practice 199 | status: Status::default(), 200 | } 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /notes/Notes.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | Notes 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 63 | 109 | 110 | 111 | 112 | 113 |
114 | 115 |
116 | 117 |
118 |

Notes

119 | 120 | 121 |
122 |

Table of Contents

123 |
124 | 147 |
148 |
149 | 150 |
151 |

1 Information

152 |
153 | 154 | 155 | 156 |
157 | 158 |
159 |

1.1 6502.org

160 |
161 | 162 | 163 |

164 | http://6502.org/ 165 |

166 | 167 |
168 | 169 |
170 | 171 |
172 |

1.2 Easy 6502 tutorial

173 |
174 | 175 | 176 |

177 | http://skilldrick.github.io/easy6502/ 178 |

179 | 180 |
181 | 182 |
183 | 184 |
185 |

1.3 Opcodes

186 |
187 | 188 | 189 |

190 | http://6502.org/tutorials/6502opcodes.html 191 |

192 |
193 |
194 | 195 |
196 | 197 |
198 |

2 Tools

199 |
200 | 201 | 202 | 203 |
204 | 205 |
206 |

2.1 Web Compiler

207 |
208 | 209 | 210 |

211 | http://www.6502asm.com/ 212 |

213 |
214 |
215 | 216 |
217 | 218 |
219 |

3 Work

220 |
221 | 222 | 223 | 224 |
225 | 226 |
227 |

3.1 aweisberger

228 |
229 | 230 | 231 | 232 |
233 | 234 |
235 |

3.1.1 TODO Add Controller

236 |
237 | 238 | 239 |

240 | Must fetch program instruction from memory, decode and execute it. 241 |

242 |
243 |
244 |
245 |
246 | 247 |
248 |

Date: 2014-10-01T17:38-0400

249 |

Author: Alex Weisberger

250 |

Org version 7.9.3f with Emacs version 24

251 | Validate XHTML 1.0 252 | 253 |
254 | 255 | 256 | -------------------------------------------------------------------------------- /notes/6502Arch.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 6502Arch 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 63 | 109 | 110 | 111 | 112 | 113 |
114 | 115 |
116 | 117 |
118 |

6502Arch

119 | 120 | 121 |
122 |

Table of Contents

123 |
124 | 142 |
143 |
144 | 145 |
146 |

1 Addressing modes

147 |
148 | 149 | 150 |

151 | has a 16-byte address bus. 152 |

153 | 154 | 155 |
156 | 157 |
158 |

1.1 Absolute

159 |
160 | 161 | 162 |
    163 |
  • Full memory location specified, i.e. $c000 164 |
  • 165 |
  • 65536 bytes of addressable memory (216 duyyy) 166 |
  • 167 |
168 | 169 | 170 | 171 |
172 | 173 |
174 | 175 |
176 |

1.2 Zero-page

177 |
178 | 179 | 180 |
    181 |
  • 1 byte adress, i.e. $c0, 256 bytes adressable 182 |
  • 183 |
  • faster, takes less program space 184 |
  • 185 |
186 | 187 | 188 | 189 |
190 | 191 |
192 | 193 |
194 |

1.3 Zero-page, X

195 |
196 | 197 | 198 |
    199 |
  • Relative adressing within the zero page. 200 |
  • 201 |
  • Adds the value in reg. x with a 1-byte adress 202 |
  • 203 |
  • i.e. STA $a0, X 204 |
  • 205 |
  • Address wraps around if the addition is larger than 1 byte 206 |
  • 207 |
208 | 209 | 210 | 211 |
212 | 213 |
214 | 215 |
216 |

1.4 Zero-page, Y

217 |
218 | 219 | 220 |
    221 |
  • equiv to zero-page, X but can only be used with LDX and STX 222 |
  • 223 |
224 | 225 | 226 | 227 | 228 | 229 | 230 |
231 | 232 |
233 | 234 |
235 |

1.5 Immediate

236 |
237 | 238 | 239 |
    240 |
  • i.e. #$c0 241 |
  • 242 |
  • loads immedate number into register 243 |
  • 244 |
245 | 246 | 247 | 248 |
249 | 250 |
251 | 252 |
253 |

1.6 Relative

254 |
255 | 256 | 257 |
    258 |
  • i.e. $c0, or label 259 |
  • 260 |
261 | 262 | 263 | 264 |
265 | 266 |
267 | 268 |
269 |

1.7 Implicit

270 |
271 | 272 | 273 |
    274 |
  • when operation doesn't deal with memory 275 |
  • 276 |
277 | 278 | 279 | 280 |
281 | 282 |
283 | 284 |
285 |

1.8 Indirect

286 |
287 | 288 | 289 |
    290 |
  • Uses absolute address to get another address 291 |
  • 292 |
  • first address is LSB of address, following byte is MSB 293 |
  • 294 |
295 | 296 | 297 | 298 |
299 | 300 |
301 | 302 |
303 |

1.9 Indexed Indirect

304 |
305 | 306 | 307 |
    308 |
  • i.e. LDA ($c0, X) 309 |
  • 310 |
  • Take a zero page adress and add the value in reg. x to it, look up 2 byte address 311 |
  • 312 |
313 | 314 | 315 | 316 | 317 |
318 | 319 |
320 | 321 |
322 |

1.10 Indirect Indexed

323 |
324 | 325 | 326 |
    327 |
  • zero page address dereferenced then Y is added to it 328 |
  • 329 |
330 | 331 | 332 |
333 |
334 | 335 |
336 | 337 |
338 |

2 Stack

339 |
340 | 341 | 342 |
    343 |
  • lives in memory between $0100 and $01ff 344 |
  • 345 |
346 | 347 | 348 |
349 | 350 |
351 | 352 |
353 |

3 Jumping

354 |
355 | 356 | 357 |
    358 |
  • JSR/RTS: Jump to subroutine and return from subroutine 359 |
  • 360 |
361 | 362 | 363 |
364 |
365 |
366 | 367 |
368 |

Date: 2014-09-25T21:09-0400

369 |

Author: Alex Weisberger

370 |

Org version 7.9.3f with Emacs version 24

371 | Validate XHTML 1.0 372 | 373 |
374 | 375 | 376 | -------------------------------------------------------------------------------- /src/instruction.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2014 The 6502-rs Developers 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions 6 | // are met: 7 | // 1. Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // 2. Redistributions in binary form must reproduce the above copyright 10 | // notice, this list of conditions and the following disclaimer in the 11 | // documentation and/or other materials provided with the distribution. 12 | // 3. Neither the names of the copyright holders nor the names of any 13 | // contributors may be used to endorse or promote products derived from this 14 | // software without specific prior written permission. 15 | // 16 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 | // POSSIBILITY OF SUCH DAMAGE. 27 | 28 | use crate::AdcOutput; 29 | 30 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 31 | pub enum Instruction { 32 | // ADd with Carry 33 | ADC, 34 | 35 | // ADd with Carry. This one has no decimal mode. 36 | ADCnd, 37 | 38 | // logical AND (bitwise) 39 | AND, 40 | 41 | // Arithmetic Shift Left 42 | ASL, 43 | 44 | // Branch if Carry Clear 45 | BCC, 46 | 47 | // Branch if Carry Set 48 | BCS, 49 | 50 | // Branch if Equal (to zero?) 51 | BEQ, 52 | 53 | // BIT test 54 | BIT, 55 | 56 | // Branch if Minus 57 | BMI, 58 | 59 | // Branch if Not Equal 60 | BNE, 61 | 62 | // Branch if Positive 63 | BPL, 64 | 65 | // Unconditional BRAnch 66 | BRA, 67 | 68 | // BReaK 69 | BRK, 70 | 71 | // BReaK, clearing decimal flag 72 | BRKcld, 73 | 74 | // Branch if oVerflow Clear 75 | BVC, 76 | 77 | // Branch if oVerflow Set 78 | BVS, 79 | 80 | // CLear Carry flag 81 | CLC, 82 | 83 | // Clear Decimal Mode 84 | CLD, 85 | 86 | // Clear Interrupt Disable 87 | CLI, 88 | 89 | // Clear oVerflow flag 90 | CLV, 91 | 92 | // Compare 93 | CMP, 94 | 95 | // Compare X register 96 | CPX, 97 | 98 | // Compare Y register 99 | CPY, 100 | 101 | // DECrement memory 102 | DEC, 103 | 104 | // DEcrement X register 105 | DEX, 106 | 107 | // DEcrement Y register 108 | DEY, 109 | 110 | // Exclusive OR (bitwise) 111 | EOR, 112 | 113 | // INCrement memory 114 | INC, 115 | 116 | // INcrement X register 117 | INX, 118 | 119 | // INcrement Y register 120 | INY, 121 | 122 | // JuMP 123 | JMP, 124 | 125 | // Jump to SubRoutine 126 | JSR, 127 | 128 | // LoaD Accumulator 129 | LDA, 130 | 131 | // LoaD X register 132 | LDX, 133 | 134 | // LoaD Y register 135 | LDY, 136 | 137 | // Logical Shift Right 138 | LSR, 139 | 140 | // No OPeration 141 | NOP, 142 | 143 | // inclusive OR (bitwise) 144 | ORA, 145 | 146 | // PusH Accumulator 147 | PHA, 148 | 149 | // PusH X 150 | PHX, 151 | 152 | // PusH Y 153 | PHY, 154 | 155 | // PusH Processor status 156 | PHP, 157 | 158 | // PuLl Accumulator 159 | PLA, 160 | 161 | // PuLl X 162 | PLX, 163 | 164 | // PuLl Y 165 | PLY, 166 | 167 | // PuLl Processor status 168 | PLP, 169 | 170 | // ROtate Left 171 | ROL, 172 | 173 | // ROtate Right 174 | ROR, 175 | 176 | // ReTurn from Interrupt 177 | RTI, 178 | 179 | // ReTurn from Subroutine 180 | RTS, 181 | 182 | // SuBtract with Carry 183 | SBC, 184 | 185 | // SuBtract with Carry. This one has no decimal mode. 186 | SBCnd, 187 | 188 | // SEt Carry flag 189 | SEC, 190 | 191 | // SEt Decimal flag 192 | SED, 193 | 194 | // SEt Interrupt disable 195 | SEI, 196 | 197 | // STore Accumulator 198 | STA, 199 | 200 | // STore X register 201 | STX, 202 | 203 | // STore Y register 204 | STY, 205 | 206 | // STore Zero 207 | STZ, 208 | 209 | // Transfer Accumulator to X 210 | TAX, 211 | 212 | // Transfer Accumulator to Y 213 | TAY, 214 | 215 | // Test and Reset Bits 216 | TRB, 217 | 218 | // Test and Set Bits 219 | TSB, 220 | 221 | // Transfer Stack pointer to X 222 | TSX, 223 | 224 | // Transfer X to Accumulator 225 | TXA, 226 | 227 | // Transfer X to Stack pointer 228 | TXS, 229 | 230 | // Transfer Y to Accumulator 231 | TYA, 232 | } 233 | 234 | #[derive(Copy, Clone, Debug)] 235 | pub enum OpInput { 236 | UseImplied, 237 | UseImmediate(u8), 238 | UseRelative(u16), 239 | UseAddress(u16), 240 | } 241 | 242 | #[derive(Copy, Clone, Debug)] 243 | pub enum AddressingMode { 244 | // work directly on accumulator, e. g. `lsr a`. 245 | Accumulator, 246 | 247 | // BRK 248 | Implied, 249 | 250 | // 8-bit constant in instruction, e. g. `lda #10`. 251 | Immediate, 252 | 253 | // zero-page address, e. g. `lda $00`. 254 | ZeroPage, 255 | 256 | // address is X register + 8-bit constant, e. g. `lda $80,x`. 257 | ZeroPageX, 258 | 259 | // address is Y register + 8-bit constant, e. g. `ldx $10,y`. 260 | ZeroPageY, 261 | 262 | // branch target as signed relative offset, e. g. `bne label`. 263 | Relative, 264 | 265 | // full 16-bit address, e. g. `jmp $1000`. 266 | Absolute, 267 | 268 | // full 16-bit address plus X register, e. g. `sta $1000,X`. 269 | AbsoluteX, 270 | 271 | // full 16-bit address plus Y register, e. g. `sta $1000,Y`. 272 | AbsoluteY, 273 | 274 | // jump to address stored at address, with the page-crossing bug found in NMOS chips, e. g. `jmp ($1000)`. 275 | BuggyIndirect, 276 | 277 | // jump to address stored at address, e. g. `jmp ($1000)`. 278 | Indirect, 279 | 280 | // load from address stored at (constant zero page address plus X register), e. g. `lda ($10,X)`. 281 | IndexedIndirectX, 282 | 283 | // load from (address stored at constant zero page address) plus Y register, e. g. `lda ($10),Y`. 284 | IndirectIndexedY, 285 | 286 | // Address stored at constant zero page address 287 | ZeroPageIndirect, 288 | } 289 | 290 | impl AddressingMode { 291 | #[must_use] 292 | pub const fn extra_bytes(self) -> u16 { 293 | match self { 294 | AddressingMode::Accumulator => 0, 295 | AddressingMode::Implied => 0, 296 | AddressingMode::Immediate => 1, 297 | AddressingMode::ZeroPage => 1, 298 | AddressingMode::ZeroPageX => 1, 299 | AddressingMode::ZeroPageY => 1, 300 | AddressingMode::Relative => 1, 301 | AddressingMode::Absolute => 2, 302 | AddressingMode::AbsoluteX => 2, 303 | AddressingMode::AbsoluteY => 2, 304 | AddressingMode::Indirect => 2, 305 | AddressingMode::BuggyIndirect => 2, 306 | AddressingMode::IndexedIndirectX => 1, 307 | AddressingMode::IndirectIndexedY => 1, 308 | AddressingMode::ZeroPageIndirect => 1, 309 | } 310 | } 311 | } 312 | 313 | pub type DecodedInstr = (Instruction, OpInput); 314 | 315 | /// The NMOS 6502 variant. This one is present in the Commodore 64, early Apple IIs, etc. 316 | #[derive(Copy, Clone, Debug, Default)] 317 | pub struct Nmos6502; 318 | 319 | impl crate::Variant for Nmos6502 { 320 | fn decode(opcode: u8) -> Option<(Instruction, AddressingMode)> { 321 | match opcode { 322 | 0x00 => Some((Instruction::BRK, AddressingMode::Implied)), 323 | 0x01 => Some((Instruction::ORA, AddressingMode::IndexedIndirectX)), 324 | 0x02 => None, 325 | 0x03 => None, 326 | 0x04 => None, 327 | 0x05 => Some((Instruction::ORA, AddressingMode::ZeroPage)), 328 | 0x06 => Some((Instruction::ASL, AddressingMode::ZeroPage)), 329 | 0x07 => None, 330 | 0x08 => Some((Instruction::PHP, AddressingMode::Implied)), 331 | 0x09 => Some((Instruction::ORA, AddressingMode::Immediate)), 332 | 0x0a => Some((Instruction::ASL, AddressingMode::Accumulator)), 333 | 0x0b => None, 334 | 0x0c => None, 335 | 0x0d => Some((Instruction::ORA, AddressingMode::Absolute)), 336 | 0x0e => Some((Instruction::ASL, AddressingMode::Absolute)), 337 | 0x0f => None, 338 | 0x10 => Some((Instruction::BPL, AddressingMode::Relative)), 339 | 0x11 => Some((Instruction::ORA, AddressingMode::IndirectIndexedY)), 340 | 0x12 => None, 341 | 0x13 => None, 342 | 0x14 => None, 343 | 0x15 => Some((Instruction::ORA, AddressingMode::ZeroPageX)), 344 | 0x16 => Some((Instruction::ASL, AddressingMode::ZeroPageX)), 345 | 0x17 => None, 346 | 0x18 => Some((Instruction::CLC, AddressingMode::Implied)), 347 | 0x19 => Some((Instruction::ORA, AddressingMode::AbsoluteY)), 348 | 0x1a => None, 349 | 0x1b => None, 350 | 0x1c => None, 351 | 0x1d => Some((Instruction::ORA, AddressingMode::AbsoluteX)), 352 | 0x1e => Some((Instruction::ASL, AddressingMode::AbsoluteX)), 353 | 0x1f => None, 354 | 0x20 => Some((Instruction::JSR, AddressingMode::Absolute)), 355 | 0x21 => Some((Instruction::AND, AddressingMode::IndexedIndirectX)), 356 | 0x22 => None, 357 | 0x23 => None, 358 | 0x24 => Some((Instruction::BIT, AddressingMode::ZeroPage)), 359 | 0x25 => Some((Instruction::AND, AddressingMode::ZeroPage)), 360 | 0x26 => Some((Instruction::ROL, AddressingMode::ZeroPage)), 361 | 0x27 => None, 362 | 0x28 => Some((Instruction::PLP, AddressingMode::Implied)), 363 | 0x29 => Some((Instruction::AND, AddressingMode::Immediate)), 364 | 0x2a => Some((Instruction::ROL, AddressingMode::Accumulator)), 365 | 0x2b => None, 366 | 0x2c => Some((Instruction::BIT, AddressingMode::Absolute)), 367 | 0x2d => Some((Instruction::AND, AddressingMode::Absolute)), 368 | 0x2e => Some((Instruction::ROL, AddressingMode::Absolute)), 369 | 0x2f => None, 370 | 0x30 => Some((Instruction::BMI, AddressingMode::Relative)), 371 | 0x31 => Some((Instruction::AND, AddressingMode::IndirectIndexedY)), 372 | 0x32 => None, 373 | 0x33 => None, 374 | 0x34 => None, 375 | 0x35 => Some((Instruction::AND, AddressingMode::ZeroPageX)), 376 | 0x36 => Some((Instruction::ROL, AddressingMode::ZeroPageX)), 377 | 0x37 => None, 378 | 0x38 => Some((Instruction::SEC, AddressingMode::Implied)), 379 | 0x39 => Some((Instruction::AND, AddressingMode::AbsoluteY)), 380 | 0x3a => None, 381 | 0x3b => None, 382 | 0x3c => None, 383 | 0x3d => Some((Instruction::AND, AddressingMode::AbsoluteX)), 384 | 0x3e => Some((Instruction::ROL, AddressingMode::AbsoluteX)), 385 | 0x3f => None, 386 | 0x40 => Some((Instruction::RTI, AddressingMode::Implied)), 387 | 0x41 => Some((Instruction::EOR, AddressingMode::IndexedIndirectX)), 388 | 0x42 => None, 389 | 0x43 => None, 390 | 0x44 => None, 391 | 0x45 => Some((Instruction::EOR, AddressingMode::ZeroPage)), 392 | 0x46 => Some((Instruction::LSR, AddressingMode::ZeroPage)), 393 | 0x47 => None, 394 | 0x48 => Some((Instruction::PHA, AddressingMode::Implied)), 395 | 0x49 => Some((Instruction::EOR, AddressingMode::Immediate)), 396 | 0x4a => Some((Instruction::LSR, AddressingMode::Accumulator)), 397 | 0x4b => None, 398 | 0x4c => Some((Instruction::JMP, AddressingMode::Absolute)), 399 | 0x4d => Some((Instruction::EOR, AddressingMode::Absolute)), 400 | 0x4e => Some((Instruction::LSR, AddressingMode::Absolute)), 401 | 0x4f => None, 402 | 0x50 => Some((Instruction::BVC, AddressingMode::Relative)), 403 | 0x51 => Some((Instruction::EOR, AddressingMode::IndirectIndexedY)), 404 | 0x52 => None, 405 | 0x53 => None, 406 | 0x54 => None, 407 | 0x55 => Some((Instruction::EOR, AddressingMode::ZeroPageX)), 408 | 0x56 => Some((Instruction::LSR, AddressingMode::ZeroPageX)), 409 | 0x57 => None, 410 | 0x58 => Some((Instruction::CLI, AddressingMode::Implied)), 411 | 0x59 => Some((Instruction::EOR, AddressingMode::AbsoluteY)), 412 | 0x5a => None, 413 | 0x5b => None, 414 | 0x5c => None, 415 | 0x5d => Some((Instruction::EOR, AddressingMode::AbsoluteX)), 416 | 0x5e => Some((Instruction::LSR, AddressingMode::AbsoluteX)), 417 | 0x5f => None, 418 | 0x60 => Some((Instruction::RTS, AddressingMode::Implied)), 419 | 0x61 => Some((Instruction::ADC, AddressingMode::IndexedIndirectX)), 420 | 0x62 => None, 421 | 0x63 => None, 422 | 0x64 => None, 423 | 0x65 => Some((Instruction::ADC, AddressingMode::ZeroPage)), 424 | 0x66 => Some((Instruction::ROR, AddressingMode::ZeroPage)), 425 | 0x67 => None, 426 | 0x68 => Some((Instruction::PLA, AddressingMode::Implied)), 427 | 0x69 => Some((Instruction::ADC, AddressingMode::Immediate)), 428 | 0x6a => Some((Instruction::ROR, AddressingMode::Accumulator)), 429 | 0x6b => None, 430 | 0x6c => Some((Instruction::JMP, AddressingMode::BuggyIndirect)), 431 | 0x6d => Some((Instruction::ADC, AddressingMode::Absolute)), 432 | 0x6e => Some((Instruction::ROR, AddressingMode::Absolute)), 433 | 0x6f => None, 434 | 0x70 => Some((Instruction::BVS, AddressingMode::Relative)), 435 | 0x71 => Some((Instruction::ADC, AddressingMode::IndirectIndexedY)), 436 | 0x72 => None, 437 | 0x73 => None, 438 | 0x74 => None, 439 | 0x75 => Some((Instruction::ADC, AddressingMode::ZeroPageX)), 440 | 0x76 => Some((Instruction::ROR, AddressingMode::ZeroPageX)), 441 | 0x77 => None, 442 | 0x78 => Some((Instruction::SEI, AddressingMode::Implied)), 443 | 0x79 => Some((Instruction::ADC, AddressingMode::AbsoluteY)), 444 | 0x7a => None, 445 | 0x7b => None, 446 | 0x7c => None, 447 | 0x7d => Some((Instruction::ADC, AddressingMode::AbsoluteX)), 448 | 0x7e => Some((Instruction::ROR, AddressingMode::AbsoluteX)), 449 | 0x7f => None, 450 | 0x80 => None, 451 | 0x81 => Some((Instruction::STA, AddressingMode::IndexedIndirectX)), 452 | 0x82 => None, 453 | 0x83 => None, 454 | 0x84 => Some((Instruction::STY, AddressingMode::ZeroPage)), 455 | 0x85 => Some((Instruction::STA, AddressingMode::ZeroPage)), 456 | 0x86 => Some((Instruction::STX, AddressingMode::ZeroPage)), 457 | 0x87 => None, 458 | 0x88 => Some((Instruction::DEY, AddressingMode::Implied)), 459 | 0x89 => None, 460 | 0x8a => Some((Instruction::TXA, AddressingMode::Implied)), 461 | 0x8b => None, 462 | 0x8c => Some((Instruction::STY, AddressingMode::Absolute)), 463 | 0x8d => Some((Instruction::STA, AddressingMode::Absolute)), 464 | 0x8e => Some((Instruction::STX, AddressingMode::Absolute)), 465 | 0x8f => None, 466 | 0x90 => Some((Instruction::BCC, AddressingMode::Relative)), 467 | 0x91 => Some((Instruction::STA, AddressingMode::IndirectIndexedY)), 468 | 0x92 => None, 469 | 0x93 => None, 470 | 0x94 => Some((Instruction::STY, AddressingMode::ZeroPageX)), 471 | 0x95 => Some((Instruction::STA, AddressingMode::ZeroPageX)), 472 | 0x96 => Some((Instruction::STX, AddressingMode::ZeroPageY)), 473 | 0x97 => None, 474 | 0x98 => Some((Instruction::TYA, AddressingMode::Implied)), 475 | 0x99 => Some((Instruction::STA, AddressingMode::AbsoluteY)), 476 | 0x9a => Some((Instruction::TXS, AddressingMode::Implied)), 477 | 0x9b => None, 478 | 0x9c => None, 479 | 0x9d => Some((Instruction::STA, AddressingMode::AbsoluteX)), 480 | 0x9e => None, 481 | 0x9f => None, 482 | 0xa0 => Some((Instruction::LDY, AddressingMode::Immediate)), 483 | 0xa1 => Some((Instruction::LDA, AddressingMode::IndexedIndirectX)), 484 | 0xa2 => Some((Instruction::LDX, AddressingMode::Immediate)), 485 | 0xa3 => None, 486 | 0xa4 => Some((Instruction::LDY, AddressingMode::ZeroPage)), 487 | 0xa5 => Some((Instruction::LDA, AddressingMode::ZeroPage)), 488 | 0xa6 => Some((Instruction::LDX, AddressingMode::ZeroPage)), 489 | 0xa7 => None, 490 | 0xa8 => Some((Instruction::TAY, AddressingMode::Implied)), 491 | 0xa9 => Some((Instruction::LDA, AddressingMode::Immediate)), 492 | 0xaa => Some((Instruction::TAX, AddressingMode::Implied)), 493 | 0xab => None, 494 | 0xac => Some((Instruction::LDY, AddressingMode::Absolute)), 495 | 0xad => Some((Instruction::LDA, AddressingMode::Absolute)), 496 | 0xae => Some((Instruction::LDX, AddressingMode::Absolute)), 497 | 0xaf => None, 498 | 0xb0 => Some((Instruction::BCS, AddressingMode::Relative)), 499 | 0xb1 => Some((Instruction::LDA, AddressingMode::IndirectIndexedY)), 500 | 0xb2 => None, 501 | 0xb3 => None, 502 | 0xb4 => Some((Instruction::LDY, AddressingMode::ZeroPageX)), 503 | 0xb5 => Some((Instruction::LDA, AddressingMode::ZeroPageX)), 504 | 0xb6 => Some((Instruction::LDX, AddressingMode::ZeroPageY)), 505 | 0xb7 => None, 506 | 0xb8 => Some((Instruction::CLV, AddressingMode::Implied)), 507 | 0xb9 => Some((Instruction::LDA, AddressingMode::AbsoluteY)), 508 | 0xba => Some((Instruction::TSX, AddressingMode::Implied)), 509 | 0xbb => None, 510 | 0xbc => Some((Instruction::LDY, AddressingMode::AbsoluteX)), 511 | 0xbd => Some((Instruction::LDA, AddressingMode::AbsoluteX)), 512 | 0xbe => Some((Instruction::LDX, AddressingMode::AbsoluteY)), 513 | 0xbf => None, 514 | 0xc0 => Some((Instruction::CPY, AddressingMode::Immediate)), 515 | 0xc1 => Some((Instruction::CMP, AddressingMode::IndexedIndirectX)), 516 | 0xc2 => None, 517 | 0xc3 => None, 518 | 0xc4 => Some((Instruction::CPY, AddressingMode::ZeroPage)), 519 | 0xc5 => Some((Instruction::CMP, AddressingMode::ZeroPage)), 520 | 0xc6 => Some((Instruction::DEC, AddressingMode::ZeroPage)), 521 | 0xc7 => None, 522 | 0xc8 => Some((Instruction::INY, AddressingMode::Implied)), 523 | 0xc9 => Some((Instruction::CMP, AddressingMode::Immediate)), 524 | 0xca => Some((Instruction::DEX, AddressingMode::Implied)), 525 | 0xcb => None, 526 | 0xcc => Some((Instruction::CPY, AddressingMode::Absolute)), 527 | 0xcd => Some((Instruction::CMP, AddressingMode::Absolute)), 528 | 0xce => Some((Instruction::DEC, AddressingMode::Absolute)), 529 | 0xcf => None, 530 | 0xd0 => Some((Instruction::BNE, AddressingMode::Relative)), 531 | 0xd1 => Some((Instruction::CMP, AddressingMode::IndirectIndexedY)), 532 | 0xd2 => None, 533 | 0xd3 => None, 534 | 0xd4 => None, 535 | 0xd5 => Some((Instruction::CMP, AddressingMode::ZeroPageX)), 536 | 0xd6 => Some((Instruction::DEC, AddressingMode::ZeroPageX)), 537 | 0xd7 => None, 538 | 0xd8 => Some((Instruction::CLD, AddressingMode::Implied)), 539 | 0xd9 => Some((Instruction::CMP, AddressingMode::AbsoluteY)), 540 | 0xda => None, 541 | 0xdb => None, 542 | 0xdc => None, 543 | 0xdd => Some((Instruction::CMP, AddressingMode::AbsoluteX)), 544 | 0xde => Some((Instruction::DEC, AddressingMode::AbsoluteX)), 545 | 0xdf => None, 546 | 0xe0 => Some((Instruction::CPX, AddressingMode::Immediate)), 547 | 0xe1 => Some((Instruction::SBC, AddressingMode::IndexedIndirectX)), 548 | 0xe2 => None, 549 | 0xe3 => None, 550 | 0xe4 => Some((Instruction::CPX, AddressingMode::ZeroPage)), 551 | 0xe5 => Some((Instruction::SBC, AddressingMode::ZeroPage)), 552 | 0xe6 => Some((Instruction::INC, AddressingMode::ZeroPage)), 553 | 0xe7 => None, 554 | 0xe8 => Some((Instruction::INX, AddressingMode::Implied)), 555 | 0xe9 => Some((Instruction::SBC, AddressingMode::Immediate)), 556 | 0xea => Some((Instruction::NOP, AddressingMode::Implied)), 557 | 0xeb => None, 558 | 0xec => Some((Instruction::CPX, AddressingMode::Absolute)), 559 | 0xed => Some((Instruction::SBC, AddressingMode::Absolute)), 560 | 0xee => Some((Instruction::INC, AddressingMode::Absolute)), 561 | 0xef => None, 562 | 0xf0 => Some((Instruction::BEQ, AddressingMode::Relative)), 563 | 0xf1 => Some((Instruction::SBC, AddressingMode::IndirectIndexedY)), 564 | 0xf2 => None, 565 | 0xf3 => None, 566 | 0xf4 => None, 567 | 0xf5 => Some((Instruction::SBC, AddressingMode::ZeroPageX)), 568 | 0xf6 => Some((Instruction::INC, AddressingMode::ZeroPageX)), 569 | 0xf7 => None, 570 | 0xf8 => Some((Instruction::SED, AddressingMode::Implied)), 571 | 0xf9 => Some((Instruction::SBC, AddressingMode::AbsoluteY)), 572 | 0xfa => None, 573 | 0xfb => None, 574 | 0xfc => None, 575 | 0xfd => Some((Instruction::SBC, AddressingMode::AbsoluteX)), 576 | 0xfe => Some((Instruction::INC, AddressingMode::AbsoluteX)), 577 | 0xff => None, 578 | } 579 | } 580 | 581 | /// NMOS 6502 ADC implementation in binary mode 582 | /// 583 | /// - Standard binary arithmetic 584 | /// - All flags calculated from binary result 585 | /// 586 | /// # References 587 | /// 588 | /// - [6502.org ADC documentation](http://www.6502.org/tutorials/decimal_mode.html) 589 | /// - [NESdev 6502 reference](https://www.nesdev.org/obelisk-6502-guide/reference.html#ADC) 590 | fn adc_binary(accumulator: u8, value: u8, carry_set: u8) -> AdcOutput { 591 | // Binary addition with carry detection 592 | let result_16 = u16::from(accumulator) + u16::from(value) + u16::from(carry_set); 593 | let did_carry = result_16 > 0xFF; 594 | // Convert to 8-bit result using little-endian byte conversion 595 | let result = result_16.to_le_bytes()[0]; 596 | 597 | // Calculate overflow from binary result 598 | let overflow = (!(accumulator ^ value) & (accumulator ^ result)) & 0x80 != 0; 599 | 600 | // Negative flag is set if the result's sign bit is set, which is the 8th bit. 601 | let negative = (result & 0x80) != 0; 602 | 603 | let zero = result == 0; 604 | 605 | AdcOutput { 606 | result, 607 | did_carry, 608 | overflow, 609 | negative, 610 | zero, 611 | } 612 | } 613 | 614 | /// NMOS 6502 ADC implementation in decimal mode (BCD) 615 | /// 616 | /// - Binary Coded Decimal (BCD) arithmetic using 4-bit decimal digits 617 | /// - Each nibble represents a digit from 0-9 618 | /// - In decimal mode: N and Z flags are unreliable/undefined 619 | /// - In decimal mode: V flag calculated from binary result (documented behavior) 620 | /// 621 | /// # References 622 | /// 623 | /// - [6502.org ADC documentation](http://www.6502.org/tutorials/decimal_mode.html) 624 | /// - [NESdev 6502 reference](https://www.nesdev.org/obelisk-6502-guide/reference.html#ADC) 625 | fn adc_decimal(accumulator: u8, value: u8, carry_set: u8) -> AdcOutput { 626 | // Perform binary addition first for overflow calculation 627 | let temp_result = accumulator.wrapping_add(value).wrapping_add(carry_set); 628 | 629 | // Decimal mode: treat each nibble as a decimal digit (0-9) 630 | let mut low_nibble = (accumulator & 0x0f) 631 | .wrapping_add(value & 0x0f) 632 | .wrapping_add(carry_set); 633 | 634 | let mut high_nibble = (accumulator >> 4).wrapping_add(value >> 4); 635 | let mut carry_to_high = false; 636 | 637 | // If low nibble is > 9, adjust it and carry to high nibble 638 | if low_nibble > 9 { 639 | low_nibble = low_nibble.wrapping_sub(10); 640 | carry_to_high = true; 641 | } 642 | 643 | high_nibble = high_nibble.wrapping_add(u8::from(carry_to_high)); 644 | 645 | // Adjust high nibble if it's > 9 and set final carry flag 646 | let (adjusted_high, did_carry) = if high_nibble > 9 { 647 | (high_nibble.wrapping_sub(10), true) 648 | } else { 649 | (high_nibble, false) 650 | }; 651 | 652 | let result = (adjusted_high << 4) | (low_nibble & 0x0f); 653 | 654 | // Calculate overflow from binary result (even in decimal mode) 655 | let overflow = (!(accumulator ^ value) & (accumulator ^ temp_result)) & 0x80 != 0; 656 | 657 | // Calculate other flags from final result 658 | let negative = (result & 0x80) != 0; 659 | let zero = result == 0; 660 | 661 | AdcOutput { 662 | result, 663 | did_carry, 664 | overflow, 665 | negative, 666 | zero, 667 | } 668 | } 669 | } 670 | 671 | /// The Ricoh variant which has no decimal mode. This is what to use if you want 672 | /// to emulate the NES. 673 | #[derive(Copy, Clone, Debug, Default)] 674 | pub struct Ricoh2a03; 675 | 676 | impl crate::Variant for Ricoh2a03 { 677 | fn decode(opcode: u8) -> Option<(Instruction, AddressingMode)> { 678 | // It's the same as on NMOS, but doesn't support decimal mode. 679 | match Nmos6502::decode(opcode) { 680 | Some((Instruction::ADC, addressing_mode)) => { 681 | Some((Instruction::ADCnd, addressing_mode)) 682 | } 683 | Some((Instruction::SBC, addressing_mode)) => { 684 | Some((Instruction::SBCnd, addressing_mode)) 685 | } 686 | other_instruction => other_instruction, 687 | } 688 | } 689 | 690 | /// `Ricoh2A03` (NES) ADC implementation in binary mode 691 | /// 692 | /// - Always performs binary arithmetic 693 | /// - All flags (N, Z, V, C) behave consistently like binary mode 694 | /// - Used in Nintendo Entertainment System (NES) and Famicom 695 | /// 696 | /// # References 697 | /// - [NESdev Ricoh2A03 reference](https://www.nesdev.org/wiki/CPU) 698 | fn adc_binary(accumulator: u8, value: u8, carry_set: u8) -> AdcOutput { 699 | let result_16 = u16::from(accumulator) + u16::from(value) + u16::from(carry_set); 700 | let did_carry = result_16 > 0xFF; 701 | #[allow(clippy::cast_possible_truncation)] 702 | let result = result_16 as u8; 703 | 704 | // Calculate overflow from binary result 705 | let overflow = (!(accumulator ^ value) & (accumulator ^ result)) & 0x80 != 0; 706 | 707 | // Calculate other flags 708 | let negative = (result & 0x80) != 0; 709 | let zero = result == 0; 710 | 711 | AdcOutput { 712 | result, 713 | did_carry, 714 | overflow, 715 | negative, 716 | zero, 717 | } 718 | } 719 | 720 | /// `Ricoh2A03` (NES) ADC implementation - decimal mode not supported 721 | /// 722 | /// The `Ricoh2A03` removed the decimal mode circuitry entirely to save cost, 723 | /// so BCD operations are not supported. This method calls `adc_binary` instead. 724 | /// 725 | /// # References 726 | /// - [NESdev Ricoh2A03 reference](https://www.nesdev.org/wiki/CPU) 727 | fn adc_decimal(accumulator: u8, value: u8, carry_set: u8) -> AdcOutput { 728 | Self::adc_binary(accumulator, value, carry_set) 729 | } 730 | } 731 | 732 | /// Emulates some very early 6502s which have no ROR instruction. This one is used in very early 733 | /// KIM-1s. 734 | #[derive(Copy, Clone, Debug, Default)] 735 | pub struct RevisionA; 736 | 737 | impl crate::Variant for RevisionA { 738 | fn decode(opcode: u8) -> Option<(Instruction, AddressingMode)> { 739 | // It's the same as on NMOS, but has no ROR instruction. 740 | match Nmos6502::decode(opcode) { 741 | Some((Instruction::ROR, _)) => None, 742 | other_instruction => other_instruction, 743 | } 744 | } 745 | 746 | /// Revision A 6502 ADC implementation in binary mode 747 | /// 748 | /// - Identical ADC behavior to NMOS 6502 749 | /// - Found in very early 6502 processors (KIM-1, etc.) 750 | /// 751 | /// # References: 752 | /// 753 | /// - [Rev. A 6502 (Pre-June 1976) "ROR Bug"](https://www.masswerk.at/6502/6502_instruction_set.html#ror-bug) 754 | fn adc_binary(accumulator: u8, value: u8, carry_set: u8) -> AdcOutput { 755 | // RevisionA behaves the same as NMOS 6502 for ADC 756 | Nmos6502::adc_binary(accumulator, value, carry_set) 757 | } 758 | 759 | /// Revision A 6502 ADC implementation in decimal mode (BCD) 760 | /// 761 | /// - Identical ADC behavior to NMOS 6502 762 | /// - Supports decimal (BCD) mode with same flag behavior as NMOS 763 | /// - Found in very early 6502 processors (KIM-1, etc.) 764 | /// 765 | /// # Difference from NMOS 6502 766 | /// 767 | /// `RevisionA` lacks the ROR (Rotate Right) instruction entirely, but ADC 768 | /// behavior is identical to the standard NMOS 6502. 769 | /// 770 | /// # References: 771 | /// 772 | /// - [Rev. A 6502 (Pre-June 1976) "ROR Bug"](https://www.masswerk.at/6502/6502_instruction_set.html#ror-bug) 773 | fn adc_decimal(accumulator: u8, value: u8, carry_set: u8) -> AdcOutput { 774 | // RevisionA behaves the same as NMOS 6502 for ADC 775 | Nmos6502::adc_decimal(accumulator, value, carry_set) 776 | } 777 | } 778 | 779 | /// Emulates the 65C02, which has a few bugfixes, and another addressing mode 780 | #[derive(Copy, Clone, Debug, Default)] 781 | pub struct Cmos6502; 782 | 783 | impl crate::Variant for Cmos6502 { 784 | fn decode(opcode: u8) -> Option<(Instruction, AddressingMode)> { 785 | // TODO: We obviously need to add the other CMOS instructions here. 786 | match opcode { 787 | 0x00 => Some((Instruction::BRKcld, AddressingMode::Implied)), 788 | 0x1a => Some((Instruction::INC, AddressingMode::Accumulator)), 789 | 0x3a => Some((Instruction::DEC, AddressingMode::Accumulator)), 790 | 0x6c => Some((Instruction::JMP, AddressingMode::Indirect)), 791 | 0x80 => Some((Instruction::BRA, AddressingMode::Relative)), 792 | 0x64 => Some((Instruction::STZ, AddressingMode::ZeroPage)), 793 | 0x74 => Some((Instruction::STZ, AddressingMode::ZeroPageX)), 794 | 0x9c => Some((Instruction::STZ, AddressingMode::Absolute)), 795 | 0x9e => Some((Instruction::STZ, AddressingMode::AbsoluteX)), 796 | 0x7a => Some((Instruction::PLY, AddressingMode::Implied)), 797 | 0xfa => Some((Instruction::PLX, AddressingMode::Implied)), 798 | 0x5a => Some((Instruction::PHY, AddressingMode::Implied)), 799 | 0xda => Some((Instruction::PHX, AddressingMode::Implied)), 800 | 0x04 => Some((Instruction::TSB, AddressingMode::ZeroPage)), 801 | 0x14 => Some((Instruction::TRB, AddressingMode::ZeroPage)), 802 | 0x0c => Some((Instruction::TSB, AddressingMode::Absolute)), 803 | 0x1c => Some((Instruction::TRB, AddressingMode::Absolute)), 804 | 0x12 => Some((Instruction::ORA, AddressingMode::ZeroPageIndirect)), 805 | 0x32 => Some((Instruction::AND, AddressingMode::ZeroPageIndirect)), 806 | 0x52 => Some((Instruction::EOR, AddressingMode::ZeroPageIndirect)), 807 | 0x72 => Some((Instruction::ADC, AddressingMode::ZeroPageIndirect)), 808 | 0x92 => Some((Instruction::STA, AddressingMode::ZeroPageIndirect)), 809 | 0xb2 => Some((Instruction::LDA, AddressingMode::ZeroPageIndirect)), 810 | 0xd2 => Some((Instruction::CMP, AddressingMode::ZeroPageIndirect)), 811 | 0xf2 => Some((Instruction::SBC, AddressingMode::ZeroPageIndirect)), 812 | 0x89 => Some((Instruction::BIT, AddressingMode::Immediate)), 813 | _ => Nmos6502::decode(opcode), 814 | } 815 | } 816 | 817 | /// 65C02 (CMOS) ADC implementation in binary mode 818 | /// 819 | /// - Standard binary arithmetic 820 | /// - All flags calculated from binary result 821 | /// 822 | /// # References 823 | /// 824 | /// - [65C02 Programming Manual](http://www.westerndesigncenter.com/wdc/documentation/w65c02s.pdf) 825 | /// - [6502.org CMOS differences](http://www.6502.org/tutorials/65c02opcodes.html) 826 | fn adc_binary(accumulator: u8, value: u8, carry_set: u8) -> AdcOutput { 827 | // Binary addition with carry detection 828 | let result_16 = u16::from(accumulator) + u16::from(value) + u16::from(carry_set); 829 | let did_carry = result_16 > 0xFF; 830 | #[allow(clippy::cast_possible_truncation)] 831 | let result = result_16 as u8; 832 | 833 | // Calculate overflow from binary result 834 | let overflow = (!(accumulator ^ value) & (accumulator ^ result)) & 0x80 != 0; 835 | 836 | // Calculate other flags 837 | let negative = (result & 0x80) != 0; 838 | let zero = result == 0; 839 | 840 | AdcOutput { 841 | result, 842 | did_carry, 843 | overflow, 844 | negative, 845 | zero, 846 | } 847 | } 848 | 849 | /// 65C02 (CMOS) ADC implementation in decimal mode (BCD) 850 | /// 851 | /// - Supports decimal (BCD) mode with **corrected flag behavior** 852 | /// - In decimal mode: N and Z flags are **reliable** (calculated from BCD result) 853 | /// - In decimal mode: V flag behavior still undefined but calculated from binary result 854 | /// - In decimal mode: **Extra cycle** consumed (not implemented in this emulator yet) 855 | /// 856 | /// # Difference from NMOS 6502 857 | /// 858 | /// The 65C02 fixed the unreliable N and Z flags in decimal mode. These flags 859 | /// now correctly reflect the BCD result, making decimal arithmetic more predictable. 860 | /// 861 | /// # References 862 | /// 863 | /// - [65C02 Programming Manual](http://www.westerndesigncenter.com/wdc/documentation/w65c02s.pdf) 864 | /// - [6502.org CMOS differences](http://www.6502.org/tutorials/65c02opcodes.html) 865 | fn adc_decimal(accumulator: u8, value: u8, carry_set: u8) -> AdcOutput { 866 | // Perform binary addition first for overflow calculation 867 | let temp_result = accumulator.wrapping_add(value).wrapping_add(carry_set); 868 | 869 | // Decimal mode: treat each nibble as a decimal digit (0-9) 870 | let mut low_nibble = (accumulator & 0x0f) 871 | .wrapping_add(value & 0x0f) 872 | .wrapping_add(carry_set); 873 | 874 | let mut high_nibble = (accumulator >> 4).wrapping_add(value >> 4); 875 | let mut carry_to_high = false; 876 | 877 | // If low nibble is > 9, adjust it and carry to high nibble 878 | if low_nibble > 9 { 879 | low_nibble = low_nibble.wrapping_sub(10); 880 | carry_to_high = true; 881 | } 882 | 883 | high_nibble = high_nibble.wrapping_add(u8::from(carry_to_high)); 884 | 885 | // Adjust high nibble if it's > 9 and set final carry flag 886 | let (adjusted_high, did_carry) = if high_nibble > 9 { 887 | (high_nibble.wrapping_sub(10), true) 888 | } else { 889 | (high_nibble, false) 890 | }; 891 | 892 | let result = (adjusted_high << 4) | (low_nibble & 0x0f); 893 | 894 | // Calculate overflow from binary result (even in decimal mode) 895 | let overflow = (!(accumulator ^ value) & (accumulator ^ temp_result)) & 0x80 != 0; 896 | 897 | // On 65C02, N and Z flags are valid in decimal mode (calculated from BCD result) 898 | // V flag behavior is still undocumented but calculated from binary result 899 | let negative = (result & 0x80) != 0; 900 | let zero = result == 0; 901 | 902 | AdcOutput { 903 | result, 904 | did_carry, 905 | overflow, 906 | negative, 907 | zero, 908 | } 909 | } 910 | } 911 | -------------------------------------------------------------------------------- /src/cpu.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2014 The 6502-rs Developers 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions 6 | // are met: 7 | // 1. Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // 2. Redistributions in binary form must reproduce the above copyright 10 | // notice, this list of conditions and the following disclaimer in the 11 | // documentation and/or other materials provided with the distribution. 12 | // 3. Neither the names of the copyright holders nor the names of any 13 | // contributors may be used to endorse or promote products derived from this 14 | // software without specific prior written permission. 15 | // 16 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 | // POSSIBILITY OF SUCH DAMAGE. 27 | 28 | //! ## Decimal Mode Implementation Notes 29 | //! 30 | //! This emulator implements decimal mode (BCD arithmetic) for ADC and SBC instructions 31 | //! following the documented behavior of the original NMOS 6502 processor. 32 | //! 33 | //! ### Key Implementation Details: 34 | //! 35 | //! - **Target Processor**: NMOS 6502 (as used in Commodore 64, Apple II, etc.) 36 | //! - **Flag Behavior**: Only the Carry flag is reliable in decimal mode; N, V, Z flags 37 | //! are set but may not correspond to expected decimal arithmetic results 38 | //! - **Invalid BCD**: Values A-F in nibbles produce undefined but deterministic results 39 | //! - **Overflow Flag**: Calculated from binary addition result, not BCD result 40 | //! 41 | //! ### Authoritative References: 42 | //! 43 | //! - [6502.org Decimal Mode Tutorial](http://www.6502.org/tutorials/decimal_mode.html) 44 | //! - [NESdev Wiki 6502 Decimal Mode](https://www.nesdev.org/wiki/Visual6502wiki/6502DecimalMode) 45 | //! - Bruce Clark's comprehensive decimal mode test suite 46 | //! 47 | //! For other 6502 variants (65C02, RP2A03), see variant-specific instruction handling. 48 | 49 | use crate::Variant; 50 | use crate::instruction::{AddressingMode, DecodedInstr, Instruction, OpInput}; 51 | use crate::memory::Bus; 52 | 53 | use crate::registers::{Registers, StackPointer, Status, StatusArgs}; 54 | 55 | fn address_from_bytes(lo: u8, hi: u8) -> u16 { 56 | u16::from(lo) + (u16::from(hi) << 8usize) 57 | } 58 | 59 | #[derive(Clone, Default)] 60 | pub struct CPU 61 | where 62 | M: Bus, 63 | V: Variant, 64 | { 65 | pub registers: Registers, 66 | pub memory: M, 67 | variant: core::marker::PhantomData, 68 | } 69 | 70 | impl CPU { 71 | // Allowing `needless_pass_by_value` to simplify construction. Passing by 72 | // value avoids the borrow and improves readability when constructing the 73 | // CPU. 74 | #[allow(clippy::needless_pass_by_value)] 75 | pub fn new(memory: M, _variant: V) -> CPU { 76 | CPU { 77 | registers: Registers::new(), 78 | memory, 79 | variant: core::marker::PhantomData::, 80 | } 81 | } 82 | 83 | /// Perform the 6502 reset sequence 84 | /// 85 | /// The reset sequence on a 6502 is an 8-cycle process that simulates the same sequence 86 | /// as BRK/IRQ/NMI but with reads instead of writes for the stack operations: 87 | /// 88 | /// 1. Cycle 0-2: Initial cycles with IR=0 89 | /// 2. Cycle 3-5: Three "fake" stack pushes (reads instead of writes): 90 | /// - Read from $0100 + SP (would be PC high byte) 91 | /// - Read from $01FF + SP (would be PC low byte) 92 | /// - Read from $01FE + SP (would be status register) 93 | /// - SP decrements 3 times to 0xFD 94 | /// 3. Cycle 6: Read reset vector low byte from $FFFC 95 | /// 4. Cycle 7: Read reset vector high byte from $FFFD 96 | /// 5. Cycle 8: First instruction fetch from new PC 97 | /// 98 | /// The interrupt disable flag is set, and on 65C02 the decimal flag is cleared. 99 | /// 100 | /// For detailed cycle-by-cycle analysis, see: 101 | pub fn reset(&mut self) { 102 | // Simulate the 3 fake stack operations that decrement SP from 0x00 to 0xFD 103 | // Real hardware performs reads from $0100, $01FF, $01FE but discards the results 104 | // This matches cycles 3-5 of the reset sequence described at pagetable.com 105 | self.registers.stack_pointer.decrement(); 106 | self.registers.stack_pointer.decrement(); 107 | self.registers.stack_pointer.decrement(); 108 | 109 | // Set interrupt disable flag (all variants) 110 | self.registers.status.insert(Status::PS_DISABLE_INTERRUPTS); 111 | 112 | // Read reset vector: low byte at $FFFC, high byte at $FFFD 113 | let reset_vector_low = self.memory.get_byte(0xFFFC); 114 | let reset_vector_high = self.memory.get_byte(0xFFFD); 115 | self.registers.program_counter = u16::from_le_bytes([reset_vector_low, reset_vector_high]); 116 | } 117 | 118 | /// Get the next byte from memory and decode it into an instruction and addressing mode. 119 | /// 120 | /// # Panics 121 | /// 122 | /// This function will panic if the instruction is not recognized 123 | /// (i.e. the opcode is invalid or has not been implemented). 124 | pub fn fetch_next_and_decode(&mut self) -> Option { 125 | // Helper function to read a 16-bit address from memory 126 | fn read_address(mem: &mut M, addr: u16) -> [u8; 2] { 127 | let lo = mem.get_byte(addr); 128 | let hi = mem.get_byte(addr.wrapping_add(1)); 129 | [lo, hi] 130 | } 131 | 132 | let x: u8 = self.memory.get_byte(self.registers.program_counter); 133 | 134 | match V::decode(x) { 135 | Some((instr, am)) => { 136 | let extra_bytes = am.extra_bytes(); 137 | let num_bytes = extra_bytes + 1; 138 | 139 | let data_start = self.registers.program_counter.wrapping_add(1); 140 | 141 | let slice = if extra_bytes == 0 { 142 | [0, 0] 143 | } else if extra_bytes == 1 { 144 | [self.memory.get_byte(data_start), 0] 145 | } else if extra_bytes == 2 { 146 | [ 147 | self.memory.get_byte(data_start), 148 | self.memory.get_byte(data_start.wrapping_add(1)), 149 | ] 150 | } else { 151 | panic!() 152 | }; 153 | 154 | let x = self.registers.index_x; 155 | let y = self.registers.index_y; 156 | 157 | let memory = &mut self.memory; 158 | 159 | let am_out = match am { 160 | AddressingMode::Accumulator | AddressingMode::Implied => { 161 | // Always the same -- no input 162 | OpInput::UseImplied 163 | } 164 | AddressingMode::Immediate => { 165 | // Use [u8, ..1] specified in instruction as input 166 | OpInput::UseImmediate(slice[0]) 167 | } 168 | AddressingMode::ZeroPage => { 169 | // Use [u8, ..1] from instruction 170 | // Interpret as zero page address 171 | // (Output: an 8-bit zero-page address) 172 | OpInput::UseAddress(u16::from(slice[0])) 173 | } 174 | AddressingMode::ZeroPageX => { 175 | // Use [u8, ..1] from instruction 176 | // Add to X register (as u8 -- the final address is in 0-page) 177 | // (Output: an 8-bit zero-page address) 178 | OpInput::UseAddress(u16::from(slice[0].wrapping_add(x))) 179 | } 180 | AddressingMode::ZeroPageY => { 181 | // Use [u8, ..1] from instruction 182 | // Add to Y register (as u8 -- the final address is in 0-page) 183 | // (Output: an 8-bit zero-page address) 184 | OpInput::UseAddress(u16::from(slice[0].wrapping_add(y))) 185 | } 186 | 187 | AddressingMode::Relative => { 188 | // Use [u8, ..1] from instruction 189 | // (interpret as relative...) 190 | // (This is sign extended to a 16-but data type, but an unsigned one: u16. It's a 191 | // little weird, but it's so we can add the PC and the offset easily) 192 | let offset = slice[0]; 193 | let sign_extend = if offset & 0x80 == 0x80 { 0xffu8 } else { 0x0 }; 194 | let rel = u16::from_le_bytes([offset, sign_extend]); 195 | OpInput::UseRelative(rel) 196 | } 197 | AddressingMode::Absolute => { 198 | // Use [u8, ..2] from instruction as address 199 | // (Output: a 16-bit address) 200 | OpInput::UseAddress(address_from_bytes(slice[0], slice[1])) 201 | } 202 | AddressingMode::AbsoluteX => { 203 | // Use [u8, ..2] from instruction as address, add X 204 | // (Output: a 16-bit address) 205 | OpInput::UseAddress( 206 | address_from_bytes(slice[0], slice[1]).wrapping_add(x.into()), 207 | ) 208 | } 209 | AddressingMode::AbsoluteY => { 210 | // Use [u8, ..2] from instruction as address, add Y 211 | // (Output: a 16-bit address) 212 | OpInput::UseAddress( 213 | address_from_bytes(slice[0], slice[1]).wrapping_add(y.into()), 214 | ) 215 | } 216 | AddressingMode::Indirect => { 217 | // Use [u8, ..2] from instruction as an address. Interpret the 218 | // two bytes starting at that address as an address. 219 | // (Output: a 16-bit address) 220 | // TODO: If the pointer ends in 0xff, then incrementing it would propagate 221 | // the carry to the high byte of the pointer. This incurs a cost of one 222 | // machine cycle on the real 65C02, which is not implemented here. 223 | let slice = read_address(memory, address_from_bytes(slice[0], slice[1])); 224 | OpInput::UseAddress(address_from_bytes(slice[0], slice[1])) 225 | } 226 | AddressingMode::BuggyIndirect => { 227 | // Use [u8, ..2] from instruction as an address. Interpret the 228 | // two bytes starting at that address as an address. 229 | // (Output: a 16-bit address) 230 | let pointer = address_from_bytes(slice[0], slice[1]); 231 | 232 | let low_byte_of_target = memory.get_byte(pointer); 233 | 234 | let low_byte_of_incremented_pointer = 235 | pointer.to_le_bytes()[0].wrapping_add(1); 236 | let incremented_pointer = u16::from_le_bytes([ 237 | low_byte_of_incremented_pointer, 238 | pointer.to_le_bytes()[1], 239 | ]); 240 | 241 | let high_byte_of_target = memory.get_byte(incremented_pointer); 242 | OpInput::UseAddress(address_from_bytes( 243 | low_byte_of_target, 244 | high_byte_of_target, 245 | )) 246 | } 247 | AddressingMode::IndexedIndirectX => { 248 | // Use [u8, ..1] from instruction 249 | // Add to X register with 0-page wraparound, like ZeroPageX. 250 | // This is where the absolute (16-bit) target address is stored. 251 | // (Output: a 16-bit address) 252 | let start = slice[0].wrapping_add(x); 253 | let slice = read_address(memory, u16::from(start)); 254 | OpInput::UseAddress(address_from_bytes(slice[0], slice[1])) 255 | } 256 | AddressingMode::IndirectIndexedY => { 257 | // Use [u8, ..1] from instruction 258 | // This is where the absolute (16-bit) target address is stored. 259 | // Add Y register to this address to get the final address 260 | // (Output: a 16-bit address) 261 | let start = slice[0]; 262 | let slice = read_address(memory, u16::from(start)); 263 | OpInput::UseAddress( 264 | address_from_bytes(slice[0], slice[1]).wrapping_add(y.into()), 265 | ) 266 | } 267 | AddressingMode::ZeroPageIndirect => { 268 | // Use [u8, ..1] from instruction 269 | // This is where the absolute (16-bit) target address is stored. 270 | // (Output: a 16-bit address) 271 | let start = slice[0]; 272 | let slice = read_address(memory, u16::from(start)); 273 | OpInput::UseAddress(address_from_bytes(slice[0], slice[1])) 274 | } 275 | }; 276 | 277 | // Increment program counter 278 | self.registers.program_counter = 279 | self.registers.program_counter.wrapping_add(num_bytes); 280 | 281 | Some((instr, am_out)) 282 | } 283 | _ => None, 284 | } 285 | } 286 | 287 | #[allow(clippy::too_many_lines)] 288 | pub fn execute_instruction(&mut self, decoded_instr: DecodedInstr) { 289 | match decoded_instr { 290 | (Instruction::ADC, OpInput::UseImmediate(val)) => { 291 | log::debug!("add with carry immediate: {val}"); 292 | self.add_with_carry(val); 293 | } 294 | (Instruction::ADC, OpInput::UseAddress(addr)) => { 295 | let val = self.memory.get_byte(addr); 296 | log::debug!("add with carry. address: {addr:?}. value: {val}"); 297 | self.add_with_carry(val); 298 | } 299 | (Instruction::ADCnd, OpInput::UseImmediate(val)) => { 300 | log::debug!("add with carry immediate: {val}"); 301 | self.add_with_no_decimal(val); 302 | } 303 | (Instruction::ADCnd, OpInput::UseAddress(addr)) => { 304 | let val = self.memory.get_byte(addr); 305 | log::debug!("add with carry. address: {addr:?}. value: {val}"); 306 | self.add_with_no_decimal(val); 307 | } 308 | 309 | (Instruction::AND, OpInput::UseImmediate(val)) => { 310 | self.and(val); 311 | } 312 | (Instruction::AND, OpInput::UseAddress(addr)) => { 313 | let val = self.memory.get_byte(addr); 314 | self.and(val); 315 | } 316 | 317 | (Instruction::ASL, OpInput::UseImplied) => { 318 | // Accumulator mode 319 | let mut val = self.registers.accumulator; 320 | CPU::::shift_left_with_flags(&mut val, &mut self.registers.status); 321 | self.registers.accumulator = val; 322 | } 323 | (Instruction::ASL, OpInput::UseAddress(addr)) => { 324 | let mut operand: u8 = self.memory.get_byte(addr); 325 | CPU::::shift_left_with_flags(&mut operand, &mut self.registers.status); 326 | self.memory.set_byte(addr, operand); 327 | } 328 | 329 | (Instruction::BCC, OpInput::UseRelative(rel)) => { 330 | let addr = self.registers.program_counter.wrapping_add(rel); 331 | self.branch_if_carry_clear(addr); 332 | } 333 | 334 | (Instruction::BCS, OpInput::UseRelative(rel)) => { 335 | let addr = self.registers.program_counter.wrapping_add(rel); 336 | self.branch_if_carry_set(addr); 337 | } 338 | 339 | (Instruction::BEQ, OpInput::UseRelative(rel)) => { 340 | let addr = self.registers.program_counter.wrapping_add(rel); 341 | self.branch_if_equal(addr); 342 | } 343 | 344 | (Instruction::BNE, OpInput::UseRelative(rel)) => { 345 | let addr = self.registers.program_counter.wrapping_add(rel); 346 | self.branch_if_not_equal(addr); 347 | } 348 | 349 | (Instruction::BIT, OpInput::UseImmediate(val)) => { 350 | self.registers.status.set_with_mask( 351 | Status::PS_ZERO, 352 | Status::new(StatusArgs { 353 | zero: 0 == (self.registers.accumulator & val), 354 | ..StatusArgs::none() 355 | }), 356 | ); 357 | } 358 | 359 | (Instruction::BIT, OpInput::UseAddress(addr)) => { 360 | let a: u8 = self.registers.accumulator; 361 | let m: u8 = self.memory.get_byte(addr); 362 | let res = a & m; 363 | 364 | // The zero flag is set based on the result of the 'and'. 365 | let is_zero = 0 == res; 366 | 367 | // The N flag is set to bit 7 of the byte from memory. 368 | let bit7 = 0 != (0x80 & m); 369 | 370 | // The V flag is set to bit 6 of the byte from memory. 371 | let bit6 = 0 != (0x40 & m); 372 | 373 | self.registers.status.set_with_mask( 374 | Status::PS_ZERO | Status::PS_NEGATIVE | Status::PS_OVERFLOW, 375 | Status::new(StatusArgs { 376 | zero: is_zero, 377 | negative: bit7, 378 | overflow: bit6, 379 | ..StatusArgs::none() 380 | }), 381 | ); 382 | } 383 | 384 | (Instruction::BMI, OpInput::UseRelative(rel)) => { 385 | let addr = self.registers.program_counter.wrapping_add(rel); 386 | log::debug!("branch if minus relative. address: {addr:?}"); 387 | self.branch_if_minus(addr); 388 | } 389 | 390 | (Instruction::BPL, OpInput::UseRelative(rel)) => { 391 | let addr = self.registers.program_counter.wrapping_add(rel); 392 | self.branch_if_positive(addr); 393 | } 394 | 395 | (Instruction::BRA, OpInput::UseRelative(rel)) => { 396 | let addr = self.registers.program_counter.wrapping_add(rel); 397 | self.branch(addr); 398 | } 399 | 400 | (Instruction::BRK, OpInput::UseImplied) => { 401 | for b in self.registers.program_counter.wrapping_sub(1).to_be_bytes() { 402 | self.push_on_stack(b); 403 | } 404 | self.push_on_stack(self.registers.status.bits()); 405 | let pcl = self.memory.get_byte(0xfffe); 406 | let pch = self.memory.get_byte(0xffff); 407 | self.jump((u16::from(pch) << 8) | u16::from(pcl)); 408 | self.set_flag(Status::PS_DISABLE_INTERRUPTS); 409 | } 410 | 411 | (Instruction::BRKcld, OpInput::UseImplied) => { 412 | for b in self.registers.program_counter.wrapping_sub(1).to_be_bytes() { 413 | self.push_on_stack(b); 414 | } 415 | self.push_on_stack(self.registers.status.bits()); 416 | let pcl = self.memory.get_byte(0xfffe); 417 | let pch = self.memory.get_byte(0xffff); 418 | self.jump((u16::from(pch) << 8) | u16::from(pcl)); 419 | self.set_flag(Status::PS_DISABLE_INTERRUPTS); 420 | self.unset_flag(Status::PS_DECIMAL_MODE); 421 | } 422 | 423 | (Instruction::BVC, OpInput::UseRelative(rel)) => { 424 | let addr = self.registers.program_counter.wrapping_add(rel); 425 | self.branch_if_overflow_clear(addr); 426 | } 427 | 428 | (Instruction::BVS, OpInput::UseRelative(rel)) => { 429 | let addr = self.registers.program_counter.wrapping_add(rel); 430 | self.branch_if_overflow_set(addr); 431 | } 432 | 433 | (Instruction::CLC, OpInput::UseImplied) => { 434 | self.unset_flag(Status::PS_CARRY); 435 | } 436 | (Instruction::CLD, OpInput::UseImplied) => { 437 | self.unset_flag(Status::PS_DECIMAL_MODE); 438 | } 439 | (Instruction::CLI, OpInput::UseImplied) => { 440 | self.unset_flag(Status::PS_DISABLE_INTERRUPTS); 441 | } 442 | (Instruction::CLV, OpInput::UseImplied) => { 443 | self.unset_flag(Status::PS_OVERFLOW); 444 | } 445 | 446 | (Instruction::CMP, OpInput::UseImmediate(val)) => { 447 | self.compare_with_a_register(val); 448 | } 449 | (Instruction::CMP, OpInput::UseAddress(addr)) => { 450 | let val = self.memory.get_byte(addr); 451 | self.compare_with_a_register(val); 452 | } 453 | 454 | (Instruction::CPX, OpInput::UseImmediate(val)) => { 455 | self.compare_with_x_register(val); 456 | } 457 | (Instruction::CPX, OpInput::UseAddress(addr)) => { 458 | let val = self.memory.get_byte(addr); 459 | self.compare_with_x_register(val); 460 | } 461 | 462 | (Instruction::CPY, OpInput::UseImmediate(val)) => { 463 | self.compare_with_y_register(val); 464 | } 465 | (Instruction::CPY, OpInput::UseAddress(addr)) => { 466 | let val = self.memory.get_byte(addr); 467 | self.compare_with_y_register(val); 468 | } 469 | 470 | (Instruction::DEC, OpInput::UseAddress(addr)) => { 471 | let mut operand: u8 = self.memory.get_byte(addr); 472 | CPU::::decrement(&mut operand, &mut self.registers.status); 473 | self.memory.set_byte(addr, operand); 474 | } 475 | 476 | (Instruction::DEY, OpInput::UseImplied) => { 477 | CPU::::decrement(&mut self.registers.index_y, &mut self.registers.status); 478 | } 479 | 480 | (Instruction::DEX, OpInput::UseImplied) => { 481 | CPU::::decrement(&mut self.registers.index_x, &mut self.registers.status); 482 | } 483 | 484 | (Instruction::EOR, OpInput::UseImmediate(val)) => { 485 | self.exclusive_or(val); 486 | } 487 | (Instruction::EOR, OpInput::UseAddress(addr)) => { 488 | let val = self.memory.get_byte(addr); 489 | self.exclusive_or(val); 490 | } 491 | 492 | (Instruction::INC, OpInput::UseAddress(addr)) => { 493 | let mut operand: u8 = self.memory.get_byte(addr); 494 | CPU::::increment(&mut operand, &mut self.registers.status); 495 | self.memory.set_byte(addr, operand); 496 | } 497 | (Instruction::INX, OpInput::UseImplied) => { 498 | CPU::::increment(&mut self.registers.index_x, &mut self.registers.status); 499 | } 500 | (Instruction::INY, OpInput::UseImplied) => { 501 | CPU::::increment(&mut self.registers.index_y, &mut self.registers.status); 502 | } 503 | 504 | (Instruction::JMP, OpInput::UseAddress(addr)) => self.jump(addr), 505 | 506 | (Instruction::JSR, OpInput::UseAddress(addr)) => { 507 | for b in self.registers.program_counter.wrapping_sub(1).to_be_bytes() { 508 | self.push_on_stack(b); 509 | } 510 | self.jump(addr); 511 | } 512 | 513 | (Instruction::LDA, OpInput::UseImmediate(val)) => { 514 | log::debug!("load A immediate: {val}"); 515 | self.load_accumulator(val); 516 | } 517 | (Instruction::LDA, OpInput::UseAddress(addr)) => { 518 | let val = self.memory.get_byte(addr); 519 | log::debug!("load A. address: {addr:?}. value: {val}"); 520 | self.load_accumulator(val); 521 | } 522 | 523 | (Instruction::LDX, OpInput::UseImmediate(val)) => { 524 | log::debug!("load X immediate: {val}"); 525 | self.load_x_register(val); 526 | } 527 | (Instruction::LDX, OpInput::UseAddress(addr)) => { 528 | let val = self.memory.get_byte(addr); 529 | log::debug!("load X. address: {addr:?}. value: {val}"); 530 | self.load_x_register(val); 531 | } 532 | 533 | (Instruction::LDY, OpInput::UseImmediate(val)) => { 534 | log::debug!("load Y immediate: {val}"); 535 | self.load_y_register(val); 536 | } 537 | (Instruction::LDY, OpInput::UseAddress(addr)) => { 538 | let val = self.memory.get_byte(addr); 539 | log::debug!("load Y. address: {addr:?}. value: {val}"); 540 | self.load_y_register(val); 541 | } 542 | 543 | (Instruction::LSR, OpInput::UseImplied) => { 544 | // Accumulator mode 545 | let mut val = self.registers.accumulator; 546 | CPU::::shift_right_with_flags(&mut val, &mut self.registers.status); 547 | self.registers.accumulator = val; 548 | } 549 | (Instruction::LSR, OpInput::UseAddress(addr)) => { 550 | let mut operand: u8 = self.memory.get_byte(addr); 551 | CPU::::shift_right_with_flags(&mut operand, &mut self.registers.status); 552 | self.memory.set_byte(addr, operand); 553 | } 554 | 555 | (Instruction::ORA, OpInput::UseImmediate(val)) => { 556 | self.inclusive_or(val); 557 | } 558 | (Instruction::ORA, OpInput::UseAddress(addr)) => { 559 | let val = self.memory.get_byte(addr); 560 | self.inclusive_or(val); 561 | } 562 | 563 | (Instruction::PHA, OpInput::UseImplied) => { 564 | // Push accumulator 565 | let val = self.registers.accumulator; 566 | self.push_on_stack(val); 567 | } 568 | (Instruction::PHX, OpInput::UseImplied) => { 569 | // Push X 570 | self.push_on_stack(self.registers.index_x); 571 | } 572 | (Instruction::PHY, OpInput::UseImplied) => { 573 | // Push Y 574 | self.push_on_stack(self.registers.index_y); 575 | } 576 | (Instruction::PHP, OpInput::UseImplied) => { 577 | // Push status 578 | let val = self.registers.status.bits() | 0x30; 579 | self.push_on_stack(val); 580 | } 581 | (Instruction::PLX, OpInput::UseImplied) => { 582 | // Pull accumulator 583 | self.pull_from_stack(); 584 | let val: u8 = self.fetch_from_stack(); 585 | self.registers.index_x = val; 586 | self.registers.status.set_with_mask( 587 | Status::PS_ZERO | Status::PS_NEGATIVE, 588 | Status::new(StatusArgs { 589 | zero: val == 0, 590 | negative: self.registers.accumulator > 127, 591 | ..StatusArgs::none() 592 | }), 593 | ); 594 | } 595 | (Instruction::PLY, OpInput::UseImplied) => { 596 | // Pull accumulator 597 | self.pull_from_stack(); 598 | let val: u8 = self.fetch_from_stack(); 599 | self.registers.index_y = val; 600 | self.registers.status.set_with_mask( 601 | Status::PS_ZERO | Status::PS_NEGATIVE, 602 | Status::new(StatusArgs { 603 | zero: val == 0, 604 | negative: self.registers.accumulator > 127, 605 | ..StatusArgs::none() 606 | }), 607 | ); 608 | } 609 | (Instruction::PLA, OpInput::UseImplied) => { 610 | // Pull accumulator 611 | self.pull_from_stack(); 612 | let val: u8 = self.fetch_from_stack(); 613 | self.registers.accumulator = val; 614 | self.registers.status.set_with_mask( 615 | Status::PS_ZERO | Status::PS_NEGATIVE, 616 | Status::new(StatusArgs { 617 | zero: val == 0, 618 | negative: self.registers.accumulator > 127, 619 | ..StatusArgs::none() 620 | }), 621 | ); 622 | } 623 | (Instruction::PLP, OpInput::UseImplied) => { 624 | // Pull status 625 | self.pull_from_stack(); 626 | let val: u8 = self.fetch_from_stack(); 627 | // The `truncate` here won't do anything because we have a 628 | // constant for the single unused flags bit. This probably 629 | // corresponds to the behavior of the 6502...? FIXME: verify 630 | self.registers.status = Status::from_bits_truncate(val); 631 | } 632 | 633 | (Instruction::ROL, OpInput::UseImplied) => { 634 | // Accumulator mode 635 | let mut val = self.registers.accumulator; 636 | CPU::::rotate_left_with_flags(&mut val, &mut self.registers.status); 637 | self.registers.accumulator = val; 638 | } 639 | (Instruction::ROL, OpInput::UseAddress(addr)) => { 640 | let mut operand: u8 = self.memory.get_byte(addr); 641 | CPU::::rotate_left_with_flags(&mut operand, &mut self.registers.status); 642 | self.memory.set_byte(addr, operand); 643 | } 644 | (Instruction::ROR, OpInput::UseImplied) => { 645 | // Accumulator mode 646 | let mut val = self.registers.accumulator; 647 | CPU::::rotate_right_with_flags(&mut val, &mut self.registers.status); 648 | self.registers.accumulator = val; 649 | } 650 | (Instruction::ROR, OpInput::UseAddress(addr)) => { 651 | let mut operand: u8 = self.memory.get_byte(addr); 652 | CPU::::rotate_right_with_flags(&mut operand, &mut self.registers.status); 653 | self.memory.set_byte(addr, operand); 654 | } 655 | (Instruction::RTI, OpInput::UseImplied) => { 656 | // Pull status 657 | self.pull_from_stack(); 658 | let val: u8 = self.pull_from_stack(); 659 | // The `truncate` here won't do anything because we have a 660 | // constant for the single unused flags bit. This probably 661 | // corresponds to the behavior of the 6502...? FIXME: verify 662 | self.registers.status = Status::from_bits_truncate(val); 663 | let pcl: u8 = self.pull_from_stack(); 664 | let pch: u8 = self.fetch_from_stack(); 665 | self.registers.program_counter = (u16::from(pch) << 8) | u16::from(pcl); 666 | } 667 | (Instruction::RTS, OpInput::UseImplied) => { 668 | self.pull_from_stack(); 669 | let pcl: u8 = self.pull_from_stack(); 670 | let pch: u8 = self.fetch_from_stack(); 671 | self.registers.program_counter = 672 | ((u16::from(pch) << 8) | u16::from(pcl)).wrapping_add(1); 673 | } 674 | 675 | (Instruction::SBC, OpInput::UseImmediate(val)) => { 676 | log::debug!("subtract with carry immediate: {val}"); 677 | self.subtract_with_carry(val); 678 | } 679 | (Instruction::SBC, OpInput::UseAddress(addr)) => { 680 | let val = self.memory.get_byte(addr); 681 | log::debug!("subtract with carry. address: {addr:?}. value: {val}"); 682 | self.subtract_with_carry(val); 683 | } 684 | 685 | (Instruction::SBCnd, OpInput::UseImmediate(val)) => { 686 | log::debug!("subtract with carry immediate: {val}"); 687 | self.subtract_with_no_decimal(val); 688 | } 689 | (Instruction::SBCnd, OpInput::UseAddress(addr)) => { 690 | let val = self.memory.get_byte(addr); 691 | log::debug!("subtract with carry. address: {addr:?}. value: {val}"); 692 | self.subtract_with_no_decimal(val); 693 | } 694 | 695 | (Instruction::SEC, OpInput::UseImplied) => { 696 | self.set_flag(Status::PS_CARRY); 697 | } 698 | (Instruction::SED, OpInput::UseImplied) => { 699 | self.set_flag(Status::PS_DECIMAL_MODE); 700 | } 701 | (Instruction::SEI, OpInput::UseImplied) => { 702 | self.set_flag(Status::PS_DISABLE_INTERRUPTS); 703 | } 704 | 705 | (Instruction::STA, OpInput::UseAddress(addr)) => { 706 | self.memory.set_byte(addr, self.registers.accumulator); 707 | } 708 | (Instruction::STX, OpInput::UseAddress(addr)) => { 709 | self.memory.set_byte(addr, self.registers.index_x); 710 | } 711 | (Instruction::STY, OpInput::UseAddress(addr)) => { 712 | self.memory.set_byte(addr, self.registers.index_y); 713 | } 714 | (Instruction::STZ, OpInput::UseAddress(addr)) => { 715 | self.memory.set_byte(addr, 0); 716 | } 717 | 718 | (Instruction::TAX, OpInput::UseImplied) => { 719 | let val = self.registers.accumulator; 720 | self.load_x_register(val); 721 | } 722 | (Instruction::TAY, OpInput::UseImplied) => { 723 | let val = self.registers.accumulator; 724 | self.load_y_register(val); 725 | } 726 | (Instruction::TRB, OpInput::UseAddress(addr)) => { 727 | let val = self.memory.get_byte(addr); 728 | 729 | // The zero flag is set based on the result of the 'and'. 730 | self.registers.status.set_with_mask( 731 | Status::PS_ZERO, 732 | Status::new(StatusArgs { 733 | zero: 0 == (self.registers.accumulator & val), 734 | ..StatusArgs::none() 735 | }), 736 | ); 737 | 738 | // The 1's in the accumulator set the corresponding bits in the operand 739 | let res = self.registers.accumulator | val; 740 | self.memory.set_byte(addr, res); 741 | } 742 | (Instruction::TSB, OpInput::UseAddress(addr)) => { 743 | let val = self.memory.get_byte(addr); 744 | 745 | // The zero flag is set based on the result of the 'and'. 746 | self.registers.status.set_with_mask( 747 | Status::PS_ZERO, 748 | Status::new(StatusArgs { 749 | zero: 0 == (self.registers.accumulator & val), 750 | ..StatusArgs::none() 751 | }), 752 | ); 753 | 754 | // The 1's in the accumulator clear the corresponding bits in the operand 755 | let res = (self.registers.accumulator ^ 0xff) & val; 756 | self.memory.set_byte(addr, res); 757 | } 758 | (Instruction::TSX, OpInput::UseImplied) => { 759 | let StackPointer(val) = self.registers.stack_pointer; 760 | self.load_x_register(val); 761 | } 762 | (Instruction::TXA, OpInput::UseImplied) => { 763 | let val = self.registers.index_x; 764 | self.load_accumulator(val); 765 | } 766 | (Instruction::TXS, OpInput::UseImplied) => { 767 | // Note that this is the only 'transfer' instruction that does 768 | // NOT set the zero and negative flags. (Because the target 769 | // is the stack pointer) 770 | let val = self.registers.index_x; 771 | self.registers.stack_pointer = StackPointer(val); 772 | } 773 | (Instruction::TYA, OpInput::UseImplied) => { 774 | let val = self.registers.index_y; 775 | self.load_accumulator(val); 776 | } 777 | 778 | (Instruction::NOP, OpInput::UseImplied) => { 779 | log::debug!("NOP instruction"); 780 | } 781 | (_, _) => { 782 | log::debug!( 783 | "attempting to execute unimplemented or invalid \ 784 | instruction" 785 | ); 786 | } 787 | } 788 | } 789 | 790 | pub fn single_step(&mut self) { 791 | if let Some(decoded_instr) = self.fetch_next_and_decode() { 792 | self.execute_instruction(decoded_instr); 793 | } 794 | } 795 | 796 | pub fn run(&mut self) { 797 | while let Some(decoded_instr) = self.fetch_next_and_decode() { 798 | self.execute_instruction(decoded_instr); 799 | } 800 | } 801 | 802 | /// Checks if a given `u8` value should be interpreted as negative when 803 | /// considered as `i8`. 804 | /// 805 | /// In an 8-bit unsigned integer (`u8`), values range from 0 to 255. When 806 | /// these values are interpreted as signed integers (`i8`), values from 128 807 | /// to 255 are considered negative, corresponding to the signed range -128 808 | /// to -1. This function checks if the provided `u8` value falls within that 809 | /// range, effectively determining if the most significant bit is set, which 810 | /// indicates a negative number in two's complement form. 811 | /// ``` 812 | const fn value_is_negative(value: u8) -> bool { 813 | value > 127 814 | } 815 | 816 | fn set_flags_from_u8(status: &mut Status, value: u8) { 817 | let is_zero = value == 0; 818 | let is_negative = Self::value_is_negative(value); 819 | 820 | status.set_with_mask( 821 | Status::PS_ZERO | Status::PS_NEGATIVE, 822 | Status::new(StatusArgs { 823 | zero: is_zero, 824 | negative: is_negative, 825 | ..StatusArgs::none() 826 | }), 827 | ); 828 | } 829 | 830 | fn shift_left_with_flags(p_val: &mut u8, status: &mut Status) { 831 | let mask = 1 << 7; 832 | let is_bit_7_set = (*p_val & mask) == mask; 833 | let shifted = (*p_val & !(1 << 7)) << 1; 834 | *p_val = shifted; 835 | status.set_with_mask( 836 | Status::PS_CARRY, 837 | Status::new(StatusArgs { 838 | carry: is_bit_7_set, 839 | ..StatusArgs::none() 840 | }), 841 | ); 842 | CPU::::set_flags_from_u8(status, *p_val); 843 | } 844 | 845 | fn shift_right_with_flags(p_val: &mut u8, status: &mut Status) { 846 | let mask = 1; 847 | let is_bit_0_set = (*p_val & mask) == mask; 848 | *p_val >>= 1; 849 | status.set_with_mask( 850 | Status::PS_CARRY, 851 | Status::new(StatusArgs { 852 | carry: is_bit_0_set, 853 | ..StatusArgs::none() 854 | }), 855 | ); 856 | CPU::::set_flags_from_u8(status, *p_val); 857 | } 858 | 859 | fn rotate_left_with_flags(p_val: &mut u8, status: &mut Status) { 860 | let is_carry_set = status.contains(Status::PS_CARRY); 861 | let mask = 1 << 7; 862 | let is_bit_7_set = (*p_val & mask) == mask; 863 | let shifted = (*p_val & !(1 << 7)) << 1; 864 | *p_val = shifted + u8::from(is_carry_set); 865 | status.set_with_mask( 866 | Status::PS_CARRY, 867 | Status::new(StatusArgs { 868 | carry: is_bit_7_set, 869 | ..StatusArgs::none() 870 | }), 871 | ); 872 | CPU::::set_flags_from_u8(status, *p_val); 873 | } 874 | 875 | fn rotate_right_with_flags(p_val: &mut u8, status: &mut Status) { 876 | let is_carry_set = status.contains(Status::PS_CARRY); 877 | let mask = 1; 878 | let is_bit_0_set = (*p_val & mask) == mask; 879 | let shifted = *p_val >> 1; 880 | *p_val = shifted + if is_carry_set { 1 << 7 } else { 0 }; 881 | status.set_with_mask( 882 | Status::PS_CARRY, 883 | Status::new(StatusArgs { 884 | carry: is_bit_0_set, 885 | ..StatusArgs::none() 886 | }), 887 | ); 888 | CPU::::set_flags_from_u8(status, *p_val); 889 | } 890 | 891 | fn set_u8_with_flags(mem: &mut u8, status: &mut Status, value: u8) { 892 | *mem = value; 893 | CPU::::set_flags_from_u8(status, value); 894 | } 895 | 896 | fn load_x_register(&mut self, value: u8) { 897 | CPU::::set_u8_with_flags( 898 | &mut self.registers.index_x, 899 | &mut self.registers.status, 900 | value, 901 | ); 902 | } 903 | 904 | fn load_y_register(&mut self, value: u8) { 905 | CPU::::set_u8_with_flags( 906 | &mut self.registers.index_y, 907 | &mut self.registers.status, 908 | value, 909 | ); 910 | } 911 | 912 | fn load_accumulator(&mut self, value: u8) { 913 | CPU::::set_u8_with_flags( 914 | &mut self.registers.accumulator, 915 | &mut self.registers.status, 916 | value, 917 | ); 918 | } 919 | 920 | /// Shorthand for checking if a specific flag is set in the status register 921 | #[inline] 922 | const fn get_flag(&self, flag: Status) -> bool { 923 | self.registers.status.contains(flag) 924 | } 925 | 926 | /// Shorthand for setting a specific flag in the status register 927 | #[inline] 928 | fn set_flag(&mut self, flag: Status) { 929 | self.registers.status.or(flag); 930 | } 931 | 932 | /// Shorthand for clearing a specific flag in the status register 933 | #[inline] 934 | fn unset_flag(&mut self, flag: Status) { 935 | self.registers.status.and(!flag); 936 | } 937 | 938 | /// Executes the following calculation: A + M + C (Add with Carry) 939 | /// 940 | /// This implementation follows the NMOS 6502 behavior as documented in authoritative sources. 941 | /// 942 | /// ## Decimal Mode Behavior (NMOS 6502) 943 | /// 944 | /// In decimal mode, this instruction performs Binary Coded Decimal (BCD) arithmetic 945 | /// where each nibble represents a decimal digit (0-9). However, flag behavior differs 946 | /// significantly from binary mode: 947 | /// 948 | /// - **Carry Flag (C)**: Correctly set when BCD addition overflows (> 99) 949 | /// - **Overflow Flag (V)**: Calculated from the binary addition result, not the BCD result. 950 | /// The meaning is effectively undocumented in decimal mode since BCD is unsigned arithmetic. 951 | /// - **Negative Flag (N)**: Set from the BCD result but may not match expected 10's complement behavior 952 | /// - **Zero Flag (Z)**: Set from the BCD result but may not always be correct on NMOS 6502 953 | /// 954 | /// ## Invalid BCD Values 955 | /// 956 | /// When either nibble contains values A-F (invalid BCD), the NMOS 6502 behavior is 957 | /// undefined. This implementation treats them as valid binary values, which produces 958 | /// deterministic results but may not match all real hardware variants. 959 | /// 960 | /// ## References 961 | /// 962 | /// - [6502.org Decimal Mode Tutorial](http://www.6502.org/tutorials/decimal_mode.html) 963 | /// - [NESdev Wiki 6502 Decimal Mode](https://www.nesdev.org/wiki/Visual6502wiki/6502DecimalMode) 964 | /// - Bruce Clark's comprehensive decimal mode test programs 965 | /// 966 | /// ## Variant Differences 967 | /// 968 | /// - **NMOS 6502**: Only carry flag is reliable in decimal mode 969 | /// - **65C02**: N and Z flags are valid, V flag still undocumented, +1 cycle in decimal mode 970 | /// - **RP2A03** (NES): Decimal mode completely disabled in hardware 971 | fn add_with_carry(&mut self, value: u8) { 972 | let carry_set = u8::from(self.get_flag(Status::PS_CARRY)); 973 | let decimal_mode = self.get_flag(Status::PS_DECIMAL_MODE); 974 | 975 | // Use variant-specific ADC implementation 976 | let output = if decimal_mode { 977 | V::adc_decimal(self.registers.accumulator, value, carry_set) 978 | } else { 979 | V::adc_binary(self.registers.accumulator, value, carry_set) 980 | }; 981 | 982 | // Update processor status flags 983 | self.registers.status.set_with_mask( 984 | Status::PS_CARRY | Status::PS_OVERFLOW | Status::PS_ZERO | Status::PS_NEGATIVE, 985 | Status::new(StatusArgs { 986 | carry: output.did_carry, 987 | overflow: output.overflow, 988 | zero: output.zero, 989 | negative: output.negative, 990 | ..StatusArgs::none() 991 | }), 992 | ); 993 | 994 | // Update accumulator 995 | self.registers.accumulator = output.result; 996 | } 997 | 998 | fn add_with_no_decimal(&mut self, value: u8) { 999 | let carry_set = u8::from(self.get_flag(Status::PS_CARRY)); 1000 | 1001 | // Use variant-specific binary ADC implementation 1002 | let output = V::adc_binary(self.registers.accumulator, value, carry_set); 1003 | 1004 | // Update processor status flags 1005 | self.registers.status.set_with_mask( 1006 | Status::PS_CARRY | Status::PS_OVERFLOW | Status::PS_ZERO | Status::PS_NEGATIVE, 1007 | Status::new(StatusArgs { 1008 | carry: output.did_carry, 1009 | overflow: output.overflow, 1010 | zero: output.zero, 1011 | negative: output.negative, 1012 | ..StatusArgs::none() 1013 | }), 1014 | ); 1015 | 1016 | // Update accumulator 1017 | self.registers.accumulator = output.result; 1018 | } 1019 | 1020 | fn and(&mut self, value: u8) { 1021 | let a_after = self.registers.accumulator & value; 1022 | self.load_accumulator(a_after); 1023 | } 1024 | 1025 | fn subtract_with_no_decimal(&mut self, value: u8) { 1026 | // A - M - (1 - C) 1027 | 1028 | // nc -- 'not carry' 1029 | let nc: u8 = u8::from(!self.registers.status.contains(Status::PS_CARRY)); 1030 | 1031 | let a_before = self.registers.accumulator; 1032 | 1033 | let a_after = a_before.wrapping_sub(value).wrapping_sub(nc); 1034 | 1035 | // The overflow flag is set on two's-complement overflow. 1036 | // 1037 | // range of A is -128 to 127 1038 | // range of - M - (1 - C) is -128 to 128 1039 | // -(127 + 1) to -(-128 + 0) 1040 | // 1041 | let over = (nc == 0 && value > 127) && a_before < 128 && a_after > 127; 1042 | 1043 | let under = 1044 | (a_before > 127) && (0u8.wrapping_sub(value).wrapping_sub(nc) > 127) && a_after < 128; 1045 | 1046 | let did_overflow = over || under; 1047 | 1048 | let mask = Status::PS_CARRY | Status::PS_OVERFLOW; 1049 | 1050 | let result = a_after; 1051 | 1052 | // The carry flag is set on unsigned overflow. 1053 | let did_carry = (result) > (a_before); 1054 | 1055 | self.registers.status.set_with_mask( 1056 | mask, 1057 | Status::new(StatusArgs { 1058 | carry: did_carry, 1059 | overflow: did_overflow, 1060 | ..StatusArgs::none() 1061 | }), 1062 | ); 1063 | 1064 | self.load_accumulator(result); 1065 | } 1066 | 1067 | /// Executes the following calculation: A - M - (1 - C) (Subtract with Carry) 1068 | /// 1069 | /// This implementation follows the NMOS 6502 behavior as documented in authoritative sources. 1070 | /// 1071 | /// ## Decimal Mode Behavior (NMOS 6502) 1072 | /// 1073 | /// In decimal mode, this instruction performs Binary Coded Decimal (BCD) arithmetic 1074 | /// where each nibble represents a decimal digit (0-9). Flag behavior matches ADC: 1075 | /// 1076 | /// - **Carry Flag (C)**: Correctly set (inverse of borrow) for BCD subtraction 1077 | /// - **Overflow Flag (V)**: Disabled in decimal mode (always false) to match real hardware 1078 | /// - **Negative Flag (N)**: Set from the BCD result but behavior is undocumented 1079 | /// - **Zero Flag (Z)**: Set from the BCD result but may not always be correct on NMOS 6502 1080 | /// 1081 | /// ## Invalid BCD Values 1082 | /// 1083 | /// When either nibble contains values A-F (invalid BCD), the NMOS 6502 behavior is 1084 | /// undefined. This implementation handles them deterministically but results may vary 1085 | /// from real hardware. 1086 | /// 1087 | /// ## References 1088 | /// 1089 | /// - [6502.org Decimal Mode Tutorial](http://www.6502.org/tutorials/decimal_mode.html) 1090 | /// - [NESdev Wiki 6502 Decimal Mode](https://www.nesdev.org/wiki/Visual6502wiki/6502DecimalMode) 1091 | /// 1092 | /// ## Variant Differences 1093 | /// 1094 | /// - **NMOS 6502**: Only carry flag is reliable in decimal mode 1095 | /// - **65C02**: N and Z flags are valid, V flag still undocumented 1096 | /// - **RP2A03** (NES): Decimal mode completely disabled in hardware 1097 | fn subtract_with_carry(&mut self, value: u8) { 1098 | // First, convert the carry flag to a 0 or 1 1099 | let carry: u8 = u8::from(self.registers.status.contains(Status::PS_CARRY)); 1100 | let accumulator_before = self.registers.accumulator; 1101 | 1102 | // Calculate a temporary result using wrapping subtraction to handle potential underflow. 1103 | let temp_result = accumulator_before 1104 | .wrapping_sub(value) 1105 | .wrapping_sub(1 - carry); 1106 | 1107 | // Branch on whether we're in decimal mode 1108 | let (final_result, did_borrow) = if self.registers.status.contains(Status::PS_DECIMAL_MODE) 1109 | { 1110 | // In decimal mode, each byte is treated as two 4-bit BCD digits (nibbles). 1111 | // 1112 | // The algorithm for BCD subtraction is as follows: 1113 | // 1114 | // 1. Separate the lower and upper nibbles (4 bits each) of both 1115 | // the accumulator and the value to subtract. 1116 | // 2. Perform subtraction on each nibble separately, considering the initial carry. 1117 | // 3. If a nibble subtraction results in a negative value 1118 | // (indicated by bit 4 being set), add 10 to correct it and set a borrow flag. 1119 | // 4. Propagate the borrow from the low nibble to the high nibble. 1120 | // 5. Combine the corrected nibbles to form the final result. 1121 | 1122 | let mut low_nibble = (accumulator_before & 0x0f) 1123 | .wrapping_sub(value & 0x0f) 1124 | .wrapping_sub(1 - carry); 1125 | let mut high_nibble = (accumulator_before >> 4).wrapping_sub(value >> 4); 1126 | let mut borrow = false; 1127 | 1128 | if (low_nibble & 0x10) != 0 { 1129 | low_nibble = (low_nibble.wrapping_add(10)) & 0x0f; 1130 | borrow = true; 1131 | } 1132 | 1133 | high_nibble = high_nibble.wrapping_sub(u8::from(borrow)); 1134 | 1135 | if (high_nibble & 0x10) != 0 { 1136 | high_nibble = (high_nibble.wrapping_add(10)) & 0x0f; 1137 | borrow = true; 1138 | } else { 1139 | borrow = false; 1140 | } 1141 | 1142 | let result = (high_nibble << 4) | low_nibble; 1143 | (result, borrow) 1144 | } else { 1145 | // In non-decimal mode, use the temporary result calculated earlier 1146 | // and determine if a borrow occurred (which is the case if the 1147 | // result is greater than the initial accumulator value due to 1148 | // unsigned underflow). 1149 | (temp_result, temp_result > accumulator_before) 1150 | }; 1151 | 1152 | // Carry flag is the inverse of borrow 1153 | let did_carry = !did_borrow; 1154 | 1155 | // Overflow flag: Set if the sign of the result is different from the sign of A 1156 | // and the sign of the result is different from the sign of the negation of M 1157 | let did_overflow = if self.registers.status.contains(Status::PS_DECIMAL_MODE) { 1158 | // Overflow flag is not affected in decimal mode 1159 | false 1160 | } else { 1161 | // In non-decimal mode, the overflow flag is set if the subtraction 1162 | // caused a sign change that shouldn't have occurred 1163 | // (i.e., pos - neg = neg, or neg - pos = pos). 1164 | // 1165 | // Overflow occurs in subtraction when: 1166 | // 1167 | // A positive number minus a negative number results in a negative number, or 1168 | // A negative number minus a positive number results in a positive number 1169 | // 1170 | // In two's complement representation, the sign of a number is 1171 | // determined by its most significant bit (MSB). For 8-bit numbers, 1172 | // this is bit 7 (0x80 in hexadecimal). 1173 | // 1174 | // The following expression is true if 1175 | // (A and M have different signs) AND (A and result have different signs) 1176 | (accumulator_before ^ value) & (accumulator_before ^ final_result) & 0x80 != 0 1177 | }; 1178 | 1179 | // Update Status Register and Accumulator 1180 | let mask = Status::PS_CARRY | Status::PS_OVERFLOW; 1181 | 1182 | // Update the carry and overflow flags in the status register. 1183 | self.registers.status.set_with_mask( 1184 | mask, 1185 | Status::new(StatusArgs { 1186 | carry: did_carry, 1187 | overflow: did_overflow, 1188 | ..StatusArgs::none() 1189 | }), 1190 | ); 1191 | 1192 | // Load the final result into the accumulator, which will also update 1193 | // the zero and negative flags. 1194 | self.load_accumulator(final_result); 1195 | } 1196 | 1197 | fn increment(val: &mut u8, flags: &mut Status) { 1198 | let value_new = val.wrapping_add(1); 1199 | *val = value_new; 1200 | 1201 | let is_zero = value_new == 0; 1202 | 1203 | flags.set_with_mask( 1204 | Status::PS_NEGATIVE | Status::PS_ZERO, 1205 | Status::new(StatusArgs { 1206 | negative: Self::value_is_negative(value_new), 1207 | zero: is_zero, 1208 | ..StatusArgs::none() 1209 | }), 1210 | ); 1211 | } 1212 | 1213 | fn decrement(val: &mut u8, flags: &mut Status) { 1214 | let value_new = val.wrapping_sub(1); 1215 | *val = value_new; 1216 | 1217 | let is_zero = value_new == 0; 1218 | let is_negative = Self::value_is_negative(value_new); 1219 | 1220 | flags.set_with_mask( 1221 | Status::PS_NEGATIVE | Status::PS_ZERO, 1222 | Status::new(StatusArgs { 1223 | zero: is_zero, 1224 | negative: is_negative, 1225 | ..StatusArgs::none() 1226 | }), 1227 | ); 1228 | } 1229 | 1230 | const fn jump(&mut self, addr: u16) { 1231 | self.registers.program_counter = addr; 1232 | } 1233 | 1234 | const fn branch_if_carry_clear(&mut self, addr: u16) { 1235 | if !self.registers.status.contains(Status::PS_CARRY) { 1236 | self.registers.program_counter = addr; 1237 | } 1238 | } 1239 | 1240 | const fn branch_if_carry_set(&mut self, addr: u16) { 1241 | if self.registers.status.contains(Status::PS_CARRY) { 1242 | self.registers.program_counter = addr; 1243 | } 1244 | } 1245 | 1246 | const fn branch_if_equal(&mut self, addr: u16) { 1247 | if self.registers.status.contains(Status::PS_ZERO) { 1248 | self.registers.program_counter = addr; 1249 | } 1250 | } 1251 | 1252 | const fn branch_if_not_equal(&mut self, addr: u16) { 1253 | if !self.registers.status.contains(Status::PS_ZERO) { 1254 | self.registers.program_counter = addr; 1255 | } 1256 | } 1257 | 1258 | const fn branch_if_minus(&mut self, addr: u16) { 1259 | if self.registers.status.contains(Status::PS_NEGATIVE) { 1260 | self.registers.program_counter = addr; 1261 | } 1262 | } 1263 | 1264 | const fn branch(&mut self, addr: u16) { 1265 | self.registers.program_counter = addr; 1266 | } 1267 | 1268 | const fn branch_if_positive(&mut self, addr: u16) { 1269 | if !self.registers.status.contains(Status::PS_NEGATIVE) { 1270 | self.registers.program_counter = addr; 1271 | } 1272 | } 1273 | 1274 | const fn branch_if_overflow_clear(&mut self, addr: u16) { 1275 | if !self.registers.status.contains(Status::PS_OVERFLOW) { 1276 | self.registers.program_counter = addr; 1277 | } 1278 | } 1279 | 1280 | const fn branch_if_overflow_set(&mut self, addr: u16) { 1281 | if self.registers.status.contains(Status::PS_OVERFLOW) { 1282 | self.registers.program_counter = addr; 1283 | } 1284 | } 1285 | 1286 | // From http://www.6502.org/tutorials/compare_beyond.html: 1287 | // If the Z flag is 0, then A <> NUM and BNE will branch 1288 | // If the Z flag is 1, then A = NUM and BEQ will branch 1289 | // If the C flag is 0, then A (unsigned) < NUM (unsigned) and BCC will branch 1290 | // If the C flag is 1, then A (unsigned) >= NUM (unsigned) and BCS will branch 1291 | // ... 1292 | // The N flag contains most significant bit of the subtraction result. 1293 | fn compare(&mut self, r: u8, val: u8) { 1294 | // Setting the CARRY flag: A (unsigned) >= NUM (unsigned) 1295 | if r >= val { 1296 | self.registers.status.insert(Status::PS_CARRY); 1297 | } else { 1298 | self.registers.status.remove(Status::PS_CARRY); 1299 | } 1300 | 1301 | // Setting the ZERO flag: A = NUM 1302 | if r == val { 1303 | self.registers.status.insert(Status::PS_ZERO); 1304 | } else { 1305 | self.registers.status.remove(Status::PS_ZERO); 1306 | } 1307 | 1308 | // Set the NEGATIVE flag based on the MSB of the result of subtraction 1309 | // This checks if the 8th bit is set (0x80 in hex is 128 in decimal, which is the 8th bit in a byte) 1310 | let diff = r.wrapping_sub(val); 1311 | if Self::value_is_negative(diff) { 1312 | self.registers.status.insert(Status::PS_NEGATIVE); 1313 | } else { 1314 | self.registers.status.remove(Status::PS_NEGATIVE); 1315 | } 1316 | } 1317 | 1318 | fn compare_with_a_register(&mut self, val: u8) { 1319 | let a = self.registers.accumulator; 1320 | self.compare(a, val); 1321 | } 1322 | 1323 | fn compare_with_x_register(&mut self, val: u8) { 1324 | log::debug!("compare_with_x_register"); 1325 | 1326 | let x = self.registers.index_x; 1327 | self.compare(x, val); 1328 | } 1329 | 1330 | fn compare_with_y_register(&mut self, val: u8) { 1331 | let y = self.registers.index_y; 1332 | self.compare(y, val); 1333 | } 1334 | 1335 | fn exclusive_or(&mut self, val: u8) { 1336 | let a_after = self.registers.accumulator ^ val; 1337 | self.load_accumulator(a_after); 1338 | } 1339 | 1340 | fn inclusive_or(&mut self, val: u8) { 1341 | let a_after = self.registers.accumulator | val; 1342 | self.load_accumulator(a_after); 1343 | } 1344 | 1345 | fn push_on_stack(&mut self, val: u8) { 1346 | let addr = self.registers.stack_pointer.to_u16(); 1347 | self.memory.set_byte(addr, val); 1348 | self.registers.stack_pointer.decrement(); 1349 | } 1350 | 1351 | fn pull_from_stack(&mut self) -> u8 { 1352 | let addr = self.registers.stack_pointer.to_u16(); 1353 | let out = self.memory.get_byte(addr); 1354 | self.registers.stack_pointer.increment(); 1355 | out 1356 | } 1357 | 1358 | fn fetch_from_stack(&mut self) -> u8 { 1359 | // gets the next value on the stack but does not update the stack pointer 1360 | let addr = self.registers.stack_pointer.to_u16(); 1361 | self.memory.get_byte(addr) 1362 | } 1363 | } 1364 | 1365 | impl core::fmt::Debug for CPU { 1366 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { 1367 | write!( 1368 | f, 1369 | "CPU Dump:\n\nAccumulator: {}", 1370 | self.registers.accumulator 1371 | ) 1372 | } 1373 | } 1374 | 1375 | #[cfg(test)] 1376 | mod tests { 1377 | // Casting from signed to unsigned integers is intentional in these tests 1378 | #![allow(clippy::cast_sign_loss)] 1379 | // Operations may intentionally wrap due to emulation of 8-bit unsigned 1380 | // integer arithmetic. We do this to test wrap-around conditions. 1381 | #![allow(clippy::cast_possible_wrap)] 1382 | 1383 | use super::*; 1384 | use crate::instruction::Nmos6502; 1385 | use crate::memory::Memory as Ram; 1386 | 1387 | #[test] 1388 | fn dont_panic_for_overflow() { 1389 | let mut cpu = CPU::new(Ram::new(), Nmos6502); 1390 | cpu.add_with_carry(0x80); 1391 | assert_eq!(cpu.registers.accumulator, 0x80); 1392 | cpu.add_with_carry(0x80); 1393 | assert_eq!(cpu.registers.accumulator, 0); 1394 | 1395 | cpu.registers.status.insert(Status::PS_CARRY); 1396 | cpu.subtract_with_carry(0x80); 1397 | assert_eq!(cpu.registers.accumulator, 0x80); 1398 | cpu.registers.status.insert(Status::PS_CARRY); 1399 | cpu.subtract_with_carry(0x80); 1400 | assert_eq!(cpu.registers.accumulator, 0); 1401 | } 1402 | 1403 | #[test] 1404 | fn decimal_add_test() { 1405 | let mut cpu = CPU::new(Ram::new(), Nmos6502); 1406 | cpu.registers.status.insert(Status::PS_DECIMAL_MODE); 1407 | 1408 | cpu.add_with_carry(0x09); 1409 | assert_eq!(cpu.registers.accumulator, 0x09); 1410 | assert!(!cpu.registers.status.contains(Status::PS_CARRY)); 1411 | assert!(!cpu.registers.status.contains(Status::PS_ZERO)); 1412 | assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE)); 1413 | // Overflow flag is calculated from binary result 1414 | assert!(!cpu.registers.status.contains(Status::PS_OVERFLOW)); 1415 | 1416 | cpu.add_with_carry(0x43); 1417 | assert_eq!(cpu.registers.accumulator, 0x52); 1418 | assert!(!cpu.registers.status.contains(Status::PS_CARRY)); 1419 | assert!(!cpu.registers.status.contains(Status::PS_ZERO)); 1420 | assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE)); 1421 | // No overflow: 0x09 + 0x43 = 0x4C (binary), both positive, result positive 1422 | assert!(!cpu.registers.status.contains(Status::PS_OVERFLOW)); 1423 | 1424 | cpu.add_with_carry(0x48); 1425 | assert_eq!(cpu.registers.accumulator, 0x00); 1426 | assert!(cpu.registers.status.contains(Status::PS_CARRY)); 1427 | assert!(cpu.registers.status.contains(Status::PS_ZERO)); 1428 | assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE)); 1429 | // Overflow: 0x52 + 0x48 = 0x9A (binary), both positive, result negative 1430 | assert!(cpu.registers.status.contains(Status::PS_OVERFLOW)); 1431 | } 1432 | 1433 | #[test] 1434 | fn decimal_subtract_test() { 1435 | let mut cpu = CPU::new(Ram::new(), Nmos6502); 1436 | cpu.registers 1437 | .status 1438 | .insert(Status::PS_DECIMAL_MODE | Status::PS_CARRY); 1439 | cpu.registers.accumulator = 0; 1440 | 1441 | cpu.subtract_with_carry(0x01); 1442 | assert_eq!(cpu.registers.accumulator, 0x99); 1443 | assert!(!cpu.registers.status.contains(Status::PS_CARRY)); 1444 | assert!(!cpu.registers.status.contains(Status::PS_ZERO)); 1445 | assert!(cpu.registers.status.contains(Status::PS_NEGATIVE)); 1446 | assert!(!cpu.registers.status.contains(Status::PS_OVERFLOW)); 1447 | 1448 | cpu.registers.status.insert(Status::PS_CARRY); 1449 | cpu.registers.accumulator = 0x50; 1450 | cpu.subtract_with_carry(0x25); 1451 | assert_eq!(cpu.registers.accumulator, 0x25); 1452 | assert!(cpu.registers.status.contains(Status::PS_CARRY)); 1453 | assert!(!cpu.registers.status.contains(Status::PS_ZERO)); 1454 | assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE)); 1455 | assert!(!cpu.registers.status.contains(Status::PS_OVERFLOW)); 1456 | } 1457 | 1458 | #[test] 1459 | fn add_with_carry_test() { 1460 | let mut cpu = CPU::new(Ram::new(), Nmos6502); 1461 | 1462 | // Non-decimal mode tests 1463 | cpu.registers.status.remove(Status::PS_DECIMAL_MODE); 1464 | 1465 | // Test case 1: 0 + 0 with carry clear 1466 | cpu.registers.accumulator = 0; 1467 | cpu.registers.status.remove(Status::PS_CARRY); 1468 | cpu.add_with_carry(0); 1469 | assert_eq!(cpu.registers.accumulator, 0); 1470 | assert!(!cpu.registers.status.contains(Status::PS_CARRY)); 1471 | assert!(cpu.registers.status.contains(Status::PS_ZERO)); 1472 | assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE)); 1473 | assert!(!cpu.registers.status.contains(Status::PS_OVERFLOW)); 1474 | 1475 | // Test case 2: 0 + 1 with carry set 1476 | cpu.registers.accumulator = 0; 1477 | cpu.registers.status.insert(Status::PS_CARRY); 1478 | cpu.add_with_carry(1); 1479 | assert_eq!(cpu.registers.accumulator, 2); 1480 | assert!(!cpu.registers.status.contains(Status::PS_CARRY)); 1481 | assert!(!cpu.registers.status.contains(Status::PS_ZERO)); 1482 | assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE)); 1483 | assert!(!cpu.registers.status.contains(Status::PS_OVERFLOW)); 1484 | 1485 | // Test case 3: 0x7F + 0x01 (overflow case) 1486 | cpu.registers.accumulator = 0x7F; 1487 | cpu.registers.status.remove(Status::PS_CARRY); 1488 | cpu.add_with_carry(0x01); 1489 | assert_eq!(cpu.registers.accumulator, 0x80); 1490 | assert!(!cpu.registers.status.contains(Status::PS_CARRY)); 1491 | assert!(!cpu.registers.status.contains(Status::PS_ZERO)); 1492 | assert!(cpu.registers.status.contains(Status::PS_NEGATIVE)); 1493 | assert!(cpu.registers.status.contains(Status::PS_OVERFLOW)); 1494 | 1495 | // Decimal mode tests 1496 | cpu.registers.status.insert(Status::PS_DECIMAL_MODE); 1497 | 1498 | // Test case 4: 09 + 01 with carry clear (decimal) 1499 | cpu.registers.accumulator = 0x09; 1500 | cpu.registers.status.remove(Status::PS_CARRY); 1501 | cpu.add_with_carry(0x01); 1502 | assert_eq!(cpu.registers.accumulator, 0x10); 1503 | assert!(!cpu.registers.status.contains(Status::PS_CARRY)); 1504 | assert!(!cpu.registers.status.contains(Status::PS_ZERO)); 1505 | assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE)); 1506 | 1507 | // Test case 5: 50 + 50 with carry clear (decimal) 1508 | cpu.registers.accumulator = 0x50; 1509 | cpu.registers.status.remove(Status::PS_CARRY); 1510 | cpu.add_with_carry(0x50); 1511 | assert_eq!(cpu.registers.accumulator, 0x00); 1512 | assert!(cpu.registers.status.contains(Status::PS_CARRY)); 1513 | assert!(cpu.registers.status.contains(Status::PS_ZERO)); 1514 | assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE)); 1515 | 1516 | // Test case 6: 99 + 01 with carry set (decimal) 1517 | cpu.registers.accumulator = 0x99; 1518 | cpu.registers.status.insert(Status::PS_CARRY); 1519 | cpu.add_with_carry(0x01); 1520 | assert_eq!(cpu.registers.accumulator, 0x01); 1521 | assert!(cpu.registers.status.contains(Status::PS_CARRY)); 1522 | assert!(!cpu.registers.status.contains(Status::PS_ZERO)); 1523 | assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE)); 1524 | 1525 | // Additional test cases for comprehensive verification 1526 | 1527 | // Non-decimal mode: Test carry flag with 0xFF + 0x01 1528 | cpu.registers.status.remove(Status::PS_DECIMAL_MODE); 1529 | cpu.registers.accumulator = 0xFF; 1530 | cpu.registers.status.remove(Status::PS_CARRY); 1531 | cpu.add_with_carry(0x01); 1532 | assert_eq!(cpu.registers.accumulator, 0x00); 1533 | assert!(cpu.registers.status.contains(Status::PS_CARRY)); 1534 | assert!(cpu.registers.status.contains(Status::PS_ZERO)); 1535 | assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE)); 1536 | assert!(!cpu.registers.status.contains(Status::PS_OVERFLOW)); 1537 | 1538 | // Non-decimal mode: Test carry flag with 0xFF + 0x01 + carry 1539 | cpu.registers.accumulator = 0xFF; 1540 | cpu.registers.status.insert(Status::PS_CARRY); 1541 | cpu.add_with_carry(0x01); 1542 | assert_eq!(cpu.registers.accumulator, 0x01); 1543 | assert!(cpu.registers.status.contains(Status::PS_CARRY)); 1544 | assert!(!cpu.registers.status.contains(Status::PS_ZERO)); 1545 | assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE)); 1546 | assert!(!cpu.registers.status.contains(Status::PS_OVERFLOW)); 1547 | 1548 | // Non-decimal mode: Test negative overflow (0x80 + 0x80) 1549 | cpu.registers.accumulator = 0x80; 1550 | cpu.registers.status.remove(Status::PS_CARRY); 1551 | cpu.add_with_carry(0x80); 1552 | assert_eq!(cpu.registers.accumulator, 0x00); 1553 | assert!(cpu.registers.status.contains(Status::PS_CARRY)); 1554 | assert!(cpu.registers.status.contains(Status::PS_ZERO)); 1555 | assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE)); 1556 | assert!(cpu.registers.status.contains(Status::PS_OVERFLOW)); 1557 | } 1558 | 1559 | #[test] 1560 | fn solid65_adc_immediate() { 1561 | let mut cpu = CPU::new(Ram::new(), Nmos6502); 1562 | 1563 | // Adding $FF plus carry should be the same as adding $00 and no carry, so these three 1564 | // instructions should leave the carry flags unaffected, i.e. set. 1565 | cpu.execute_instruction((Instruction::LDA, OpInput::UseImmediate(0x9c))); 1566 | cpu.execute_instruction((Instruction::SEC, OpInput::UseImplied)); 1567 | cpu.execute_instruction((Instruction::ADC, OpInput::UseImmediate(0xff))); 1568 | 1569 | assert_eq!(cpu.registers.accumulator, 0x9c); 1570 | assert!(cpu.registers.status.contains(Status::PS_CARRY)); 1571 | } 1572 | 1573 | #[test] 1574 | fn php_sets_bits_4_and_5() { 1575 | let mut cpu = CPU::new(Ram::new(), Nmos6502); 1576 | cpu.execute_instruction((Instruction::PHP, OpInput::UseImplied)); 1577 | cpu.execute_instruction((Instruction::PLA, OpInput::UseImplied)); 1578 | cpu.execute_instruction((Instruction::AND, OpInput::UseImmediate(0x30))); 1579 | 1580 | assert_eq!(cpu.registers.accumulator, 0x30); 1581 | } 1582 | 1583 | #[test] 1584 | fn and_test() { 1585 | let mut cpu = CPU::new(Ram::new(), Nmos6502); 1586 | 1587 | cpu.registers.accumulator = 0; 1588 | cpu.and(0xff); 1589 | assert_eq!(cpu.registers.accumulator, 0); 1590 | assert!(cpu.registers.status.contains(Status::PS_ZERO)); 1591 | assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE)); 1592 | 1593 | cpu.registers.accumulator = 0xff; 1594 | cpu.and(0); 1595 | assert_eq!(cpu.registers.accumulator, 0); 1596 | assert!(cpu.registers.status.contains(Status::PS_ZERO)); 1597 | assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE)); 1598 | 1599 | cpu.registers.accumulator = 0xff; 1600 | cpu.and(0x0f); 1601 | assert_eq!(cpu.registers.accumulator, 0x0f); 1602 | assert!(!cpu.registers.status.contains(Status::PS_ZERO)); 1603 | assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE)); 1604 | 1605 | cpu.registers.accumulator = 0xff; 1606 | cpu.and(0x80); 1607 | assert_eq!(cpu.registers.accumulator, 0x80); 1608 | assert!(!cpu.registers.status.contains(Status::PS_ZERO)); 1609 | assert!(cpu.registers.status.contains(Status::PS_NEGATIVE)); 1610 | } 1611 | 1612 | #[test] 1613 | fn subtract_with_carry_comprehensive_test() { 1614 | let mut cpu = CPU::new(Ram::new(), Nmos6502); 1615 | 1616 | // Non-decimal mode tests 1617 | cpu.registers.status.remove(Status::PS_DECIMAL_MODE); 1618 | 1619 | // Test case 1: 0 - 0 with carry set 1620 | cpu.registers.accumulator = 0; 1621 | cpu.registers.status.insert(Status::PS_CARRY); 1622 | cpu.subtract_with_carry(0); 1623 | assert_eq!(cpu.registers.accumulator, 0); 1624 | assert!(cpu.registers.status.contains(Status::PS_CARRY)); 1625 | assert!(cpu.registers.status.contains(Status::PS_ZERO)); 1626 | assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE)); 1627 | assert!(!cpu.registers.status.contains(Status::PS_OVERFLOW)); 1628 | 1629 | // Test case 2: 0 - 1 with carry set 1630 | cpu.registers.accumulator = 0; 1631 | cpu.registers.status.insert(Status::PS_CARRY); 1632 | cpu.subtract_with_carry(1); 1633 | assert_eq!(cpu.registers.accumulator, 0xFF); 1634 | assert!(!cpu.registers.status.contains(Status::PS_CARRY)); 1635 | assert!(!cpu.registers.status.contains(Status::PS_ZERO)); 1636 | assert!(cpu.registers.status.contains(Status::PS_NEGATIVE)); 1637 | assert!(!cpu.registers.status.contains(Status::PS_OVERFLOW)); 1638 | 1639 | // Test case 3: 0x80 - 0x01 with carry set (overflow case) 1640 | cpu.registers.accumulator = 0x80; 1641 | cpu.registers.status.insert(Status::PS_CARRY); 1642 | cpu.subtract_with_carry(0x01); 1643 | assert_eq!(cpu.registers.accumulator, 0x7F); 1644 | assert!(cpu.registers.status.contains(Status::PS_CARRY)); 1645 | assert!(!cpu.registers.status.contains(Status::PS_ZERO)); 1646 | assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE)); 1647 | assert!(cpu.registers.status.contains(Status::PS_OVERFLOW)); 1648 | 1649 | // Decimal mode tests 1650 | cpu.registers.status.insert(Status::PS_DECIMAL_MODE); 1651 | 1652 | // Test case 4: 10 - 05 with carry set (decimal) 1653 | cpu.registers.accumulator = 0x10; 1654 | cpu.registers.status.insert(Status::PS_CARRY); 1655 | cpu.subtract_with_carry(0x05); 1656 | assert_eq!(cpu.registers.accumulator, 0x05); 1657 | assert!(cpu.registers.status.contains(Status::PS_CARRY)); 1658 | assert!(!cpu.registers.status.contains(Status::PS_ZERO)); 1659 | assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE)); 1660 | 1661 | // Test case 5: 20 - 10 with carry clear (decimal) 1662 | cpu.registers.accumulator = 0x20; 1663 | cpu.registers.status.remove(Status::PS_CARRY); 1664 | cpu.subtract_with_carry(0x10); 1665 | assert_eq!(cpu.registers.accumulator, 0x09); 1666 | assert!(cpu.registers.status.contains(Status::PS_CARRY)); 1667 | assert!(!cpu.registers.status.contains(Status::PS_ZERO)); 1668 | assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE)); 1669 | 1670 | // Test case 6: 00 - 01 with carry set (decimal, borrow) 1671 | cpu.registers.accumulator = 0x00; 1672 | cpu.registers.status.insert(Status::PS_CARRY); 1673 | cpu.subtract_with_carry(0x01); 1674 | assert_eq!(cpu.registers.accumulator, 0x99); 1675 | assert!(!cpu.registers.status.contains(Status::PS_CARRY)); 1676 | assert!(!cpu.registers.status.contains(Status::PS_ZERO)); 1677 | assert!(cpu.registers.status.contains(Status::PS_NEGATIVE)); 1678 | 1679 | // Test case 7: 99 - 01 with carry set (decimal, no borrow) 1680 | cpu.registers.accumulator = 0x99; 1681 | cpu.registers.status.insert(Status::PS_CARRY); 1682 | cpu.subtract_with_carry(0x01); 1683 | assert_eq!(cpu.registers.accumulator, 0x98); 1684 | assert!(cpu.registers.status.contains(Status::PS_CARRY)); 1685 | assert!(!cpu.registers.status.contains(Status::PS_ZERO)); 1686 | assert!(cpu.registers.status.contains(Status::PS_NEGATIVE)); 1687 | } 1688 | 1689 | #[test] 1690 | fn decrement_memory_test() { 1691 | let mut cpu = CPU::new(Ram::new(), Nmos6502); 1692 | let addr: u16 = 0xA1B2; 1693 | 1694 | cpu.memory.set_byte(addr, 5); 1695 | 1696 | cpu.execute_instruction((Instruction::DEC, OpInput::UseAddress(addr))); 1697 | assert_eq!(cpu.memory.get_byte(addr), 4); 1698 | assert!(!cpu.registers.status.contains(Status::PS_ZERO)); 1699 | assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE)); 1700 | 1701 | cpu.execute_instruction((Instruction::DEC, OpInput::UseAddress(addr))); 1702 | assert_eq!(cpu.memory.get_byte(addr), 3); 1703 | assert!(!cpu.registers.status.contains(Status::PS_ZERO)); 1704 | assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE)); 1705 | 1706 | cpu.execute_instruction((Instruction::DEC, OpInput::UseAddress(addr))); 1707 | cpu.execute_instruction((Instruction::DEC, OpInput::UseAddress(addr))); 1708 | cpu.execute_instruction((Instruction::DEC, OpInput::UseAddress(addr))); 1709 | assert_eq!(cpu.memory.get_byte(addr), 0); 1710 | assert!(cpu.registers.status.contains(Status::PS_ZERO)); 1711 | assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE)); 1712 | 1713 | cpu.execute_instruction((Instruction::DEC, OpInput::UseAddress(addr))); 1714 | assert_eq!(cpu.memory.get_byte(addr) as i8, -1); 1715 | assert!(!cpu.registers.status.contains(Status::PS_ZERO)); 1716 | assert!(cpu.registers.status.contains(Status::PS_NEGATIVE)); 1717 | 1718 | cpu.memory.set_byte(addr, 0); 1719 | 1720 | cpu.execute_instruction((Instruction::DEC, OpInput::UseAddress(addr))); 1721 | assert_eq!(cpu.memory.get_byte(addr), 0xff); 1722 | assert!(!cpu.registers.status.contains(Status::PS_ZERO)); 1723 | assert!(cpu.registers.status.contains(Status::PS_NEGATIVE)); 1724 | } 1725 | 1726 | #[test] 1727 | fn decrement_x_test() { 1728 | let mut cpu = CPU::new(Ram::new(), Nmos6502); 1729 | cpu.registers.index_x = 0x80; 1730 | cpu.execute_instruction((Instruction::DEX, OpInput::UseImplied)); 1731 | assert_eq!(cpu.registers.index_x, 127); 1732 | assert!(!cpu.registers.status.contains(Status::PS_ZERO)); 1733 | assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE)); 1734 | } 1735 | 1736 | #[test] 1737 | fn decrement_y_test() { 1738 | let mut cpu = CPU::new(Ram::new(), Nmos6502); 1739 | cpu.registers.index_y = 0x80; 1740 | cpu.execute_instruction((Instruction::DEY, OpInput::UseImplied)); 1741 | assert_eq!(cpu.registers.index_y, 127); 1742 | assert!(!cpu.registers.status.contains(Status::PS_ZERO)); 1743 | assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE)); 1744 | } 1745 | 1746 | #[test] 1747 | fn logical_shift_right_test() { 1748 | // Testing UseImplied version (which targets the accumulator) only, for now 1749 | 1750 | let mut cpu = CPU::new(Ram::new(), Nmos6502); 1751 | cpu.execute_instruction((Instruction::LDA, OpInput::UseImmediate(0))); 1752 | cpu.execute_instruction((Instruction::LSR, OpInput::UseImplied)); 1753 | assert_eq!(cpu.registers.accumulator, 0); 1754 | assert!(!cpu.registers.status.contains(Status::PS_CARRY)); 1755 | assert!(cpu.registers.status.contains(Status::PS_ZERO)); 1756 | assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE)); 1757 | assert!(!cpu.registers.status.contains(Status::PS_OVERFLOW)); 1758 | 1759 | cpu.execute_instruction((Instruction::LDA, OpInput::UseImmediate(1))); 1760 | cpu.execute_instruction((Instruction::LSR, OpInput::UseImplied)); 1761 | assert_eq!(cpu.registers.accumulator, 0); 1762 | assert!(cpu.registers.status.contains(Status::PS_CARRY)); 1763 | assert!(cpu.registers.status.contains(Status::PS_ZERO)); 1764 | assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE)); 1765 | assert!(!cpu.registers.status.contains(Status::PS_OVERFLOW)); 1766 | 1767 | cpu.execute_instruction((Instruction::LDA, OpInput::UseImmediate(255))); 1768 | cpu.execute_instruction((Instruction::LSR, OpInput::UseImplied)); 1769 | assert_eq!(cpu.registers.accumulator, 0x7F); 1770 | assert!(cpu.registers.status.contains(Status::PS_CARRY)); 1771 | assert!(!cpu.registers.status.contains(Status::PS_ZERO)); 1772 | assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE)); 1773 | assert!(!cpu.registers.status.contains(Status::PS_OVERFLOW)); 1774 | 1775 | cpu.execute_instruction((Instruction::LDA, OpInput::UseImmediate(254))); 1776 | cpu.execute_instruction((Instruction::LSR, OpInput::UseImplied)); 1777 | assert_eq!(cpu.registers.accumulator, 0x7F); 1778 | assert!(!cpu.registers.status.contains(Status::PS_CARRY)); 1779 | assert!(!cpu.registers.status.contains(Status::PS_ZERO)); 1780 | assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE)); 1781 | assert!(!cpu.registers.status.contains(Status::PS_OVERFLOW)); 1782 | } 1783 | 1784 | #[test] 1785 | fn dec_x_test() { 1786 | let mut cpu = CPU::new(Ram::new(), Nmos6502); 1787 | 1788 | cpu.execute_instruction((Instruction::DEX, OpInput::UseImplied)); 1789 | assert_eq!(cpu.registers.index_x, 0xff); 1790 | assert!(!cpu.registers.status.contains(Status::PS_CARRY)); 1791 | assert!(!cpu.registers.status.contains(Status::PS_ZERO)); 1792 | assert!(cpu.registers.status.contains(Status::PS_NEGATIVE)); 1793 | assert!(!cpu.registers.status.contains(Status::PS_OVERFLOW)); 1794 | 1795 | cpu.execute_instruction((Instruction::DEX, OpInput::UseImplied)); 1796 | assert_eq!(cpu.registers.index_x, 0xfe); 1797 | assert!(!cpu.registers.status.contains(Status::PS_CARRY)); 1798 | assert!(!cpu.registers.status.contains(Status::PS_ZERO)); 1799 | assert!(cpu.registers.status.contains(Status::PS_NEGATIVE)); 1800 | assert!(!cpu.registers.status.contains(Status::PS_OVERFLOW)); 1801 | 1802 | cpu.load_x_register(5); 1803 | cpu.execute_instruction((Instruction::DEX, OpInput::UseImplied)); 1804 | assert_eq!(cpu.registers.index_x, 4); 1805 | assert!(!cpu.registers.status.contains(Status::PS_CARRY)); 1806 | assert!(!cpu.registers.status.contains(Status::PS_ZERO)); 1807 | assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE)); 1808 | assert!(!cpu.registers.status.contains(Status::PS_OVERFLOW)); 1809 | 1810 | cpu.execute_instruction((Instruction::DEX, OpInput::UseImplied)); 1811 | cpu.execute_instruction((Instruction::DEX, OpInput::UseImplied)); 1812 | cpu.execute_instruction((Instruction::DEX, OpInput::UseImplied)); 1813 | cpu.execute_instruction((Instruction::DEX, OpInput::UseImplied)); 1814 | 1815 | assert_eq!(cpu.registers.index_x, 0); 1816 | assert!(!cpu.registers.status.contains(Status::PS_CARRY)); 1817 | assert!(cpu.registers.status.contains(Status::PS_ZERO)); 1818 | assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE)); 1819 | assert!(!cpu.registers.status.contains(Status::PS_OVERFLOW)); 1820 | 1821 | cpu.execute_instruction((Instruction::DEX, OpInput::UseImplied)); 1822 | assert_eq!(cpu.registers.index_x, 0xff); 1823 | assert!(!cpu.registers.status.contains(Status::PS_CARRY)); 1824 | assert!(!cpu.registers.status.contains(Status::PS_ZERO)); 1825 | assert!(cpu.registers.status.contains(Status::PS_NEGATIVE)); 1826 | assert!(!cpu.registers.status.contains(Status::PS_OVERFLOW)); 1827 | } 1828 | 1829 | #[test] 1830 | fn jump_test() { 1831 | let mut cpu = CPU::new(Ram::new(), Nmos6502); 1832 | let addr: u16 = 0xA1B1; 1833 | 1834 | cpu.jump(addr); 1835 | assert_eq!(cpu.registers.program_counter, addr); 1836 | } 1837 | 1838 | #[test] 1839 | fn branch_if_carry_clear_test() { 1840 | let mut cpu = CPU::new(Ram::new(), Nmos6502); 1841 | 1842 | cpu.execute_instruction((Instruction::SEC, OpInput::UseImplied)); 1843 | cpu.branch_if_carry_clear(0xABCD); 1844 | assert_eq!(cpu.registers.program_counter, (0)); 1845 | 1846 | cpu.execute_instruction((Instruction::CLC, OpInput::UseImplied)); 1847 | cpu.branch_if_carry_clear(0xABCD); 1848 | assert_eq!(cpu.registers.program_counter, (0xABCD)); 1849 | } 1850 | 1851 | #[test] 1852 | fn branch_if_carry_set_test() { 1853 | let mut cpu = CPU::new(Ram::new(), Nmos6502); 1854 | 1855 | cpu.execute_instruction((Instruction::CLC, OpInput::UseImplied)); 1856 | cpu.branch_if_carry_set(0xABCD); 1857 | assert_eq!(cpu.registers.program_counter, (0)); 1858 | 1859 | cpu.execute_instruction((Instruction::SEC, OpInput::UseImplied)); 1860 | cpu.branch_if_carry_set(0xABCD); 1861 | assert_eq!(cpu.registers.program_counter, (0xABCD)); 1862 | } 1863 | 1864 | #[test] 1865 | fn branch_if_equal_test() { 1866 | let mut cpu = CPU::new(Ram::new(), Nmos6502); 1867 | 1868 | cpu.branch_if_equal(0xABCD); 1869 | assert_eq!(cpu.registers.program_counter, (0)); 1870 | 1871 | cpu.set_flag(Status::PS_ZERO); 1872 | cpu.branch_if_equal(0xABCD); 1873 | assert_eq!(cpu.registers.program_counter, (0xABCD)); 1874 | } 1875 | 1876 | #[test] 1877 | fn branch_if_minus_test() { 1878 | { 1879 | let mut cpu = CPU::new(Ram::new(), Nmos6502); 1880 | let registers_before = cpu.registers; 1881 | 1882 | cpu.branch_if_minus(0xABCD); 1883 | assert_eq!(cpu.registers, registers_before); 1884 | assert_eq!(cpu.registers.program_counter, (0)); 1885 | } 1886 | 1887 | { 1888 | let mut cpu = CPU::new(Ram::new(), Nmos6502); 1889 | 1890 | cpu.set_flag(Status::PS_NEGATIVE); 1891 | let registers_before = cpu.registers; 1892 | 1893 | cpu.branch_if_minus(0xABCD); 1894 | assert_eq!(cpu.registers.status, registers_before.status); 1895 | assert_eq!(cpu.registers.program_counter, (0xABCD)); 1896 | } 1897 | } 1898 | 1899 | #[test] 1900 | fn branch_if_positive_test() { 1901 | let mut cpu = CPU::new(Ram::new(), Nmos6502); 1902 | 1903 | cpu.registers.status.insert(Status::PS_NEGATIVE); 1904 | cpu.branch_if_positive(0xABCD); 1905 | assert_eq!(cpu.registers.program_counter, (0)); 1906 | 1907 | cpu.registers.status.remove(Status::PS_NEGATIVE); 1908 | cpu.branch_if_positive(0xABCD); 1909 | assert_eq!(cpu.registers.program_counter, (0xABCD)); 1910 | } 1911 | 1912 | #[test] 1913 | fn branch_if_overflow_clear_test() { 1914 | let mut cpu = CPU::new(Ram::new(), Nmos6502); 1915 | 1916 | cpu.registers.status.insert(Status::PS_OVERFLOW); 1917 | cpu.branch_if_overflow_clear(0xABCD); 1918 | assert_eq!(cpu.registers.program_counter, (0)); 1919 | 1920 | cpu.registers.status.remove(Status::PS_OVERFLOW); 1921 | cpu.branch_if_overflow_clear(0xABCD); 1922 | assert_eq!(cpu.registers.program_counter, (0xABCD)); 1923 | } 1924 | 1925 | #[test] 1926 | fn branch_across_end_of_address_space() { 1927 | let mut cpu = CPU::new(Ram::new(), Nmos6502); 1928 | cpu.registers.program_counter = 0xffff; 1929 | 1930 | cpu.registers.status.insert(Status::PS_OVERFLOW); 1931 | cpu.branch_if_overflow_set(0xABCD); 1932 | assert_eq!(cpu.registers.program_counter, (0xABCD)); 1933 | } 1934 | 1935 | #[test] 1936 | fn branch_if_overflow_set_test() { 1937 | let mut cpu = CPU::new(Ram::new(), Nmos6502); 1938 | 1939 | cpu.branch_if_overflow_set(0xABCD); 1940 | assert_eq!(cpu.registers.program_counter, (0)); 1941 | 1942 | cpu.registers.status.insert(Status::PS_OVERFLOW); 1943 | cpu.branch_if_overflow_set(0xABCD); 1944 | assert_eq!(cpu.registers.program_counter, (0xABCD)); 1945 | } 1946 | 1947 | #[cfg(test)] 1948 | fn compare_test_helper(compare: &mut F, load_instruction: Instruction) 1949 | where 1950 | F: FnMut(&mut CPU, u8), 1951 | { 1952 | let mut cpu = CPU::new(Ram::new(), Nmos6502); 1953 | 1954 | cpu.execute_instruction((load_instruction, OpInput::UseImmediate(127))); 1955 | 1956 | compare(&mut cpu, 127); 1957 | assert!(cpu.registers.status.contains(Status::PS_ZERO)); 1958 | assert!(cpu.registers.status.contains(Status::PS_CARRY)); 1959 | assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE)); 1960 | 1961 | cpu.execute_instruction((load_instruction, OpInput::UseImmediate(127))); 1962 | 1963 | compare(&mut cpu, 1); 1964 | assert!(!cpu.registers.status.contains(Status::PS_ZERO)); 1965 | assert!(cpu.registers.status.contains(Status::PS_CARRY)); 1966 | assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE)); 1967 | 1968 | cpu.execute_instruction((load_instruction, OpInput::UseImmediate(1))); 1969 | 1970 | compare(&mut cpu, 2); 1971 | assert!(!cpu.registers.status.contains(Status::PS_ZERO)); 1972 | assert!(!cpu.registers.status.contains(Status::PS_CARRY)); 1973 | assert!(cpu.registers.status.contains(Status::PS_NEGATIVE)); 1974 | 1975 | cpu.execute_instruction((load_instruction, OpInput::UseImmediate(20))); 1976 | 1977 | compare(&mut cpu, -50i8 as u8); 1978 | assert!(!cpu.registers.status.contains(Status::PS_ZERO)); 1979 | assert!(!cpu.registers.status.contains(Status::PS_CARRY)); 1980 | assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE)); 1981 | 1982 | cpu.execute_instruction((load_instruction, OpInput::UseImmediate(1))); 1983 | 1984 | compare(&mut cpu, -1i8 as u8); 1985 | assert!(!cpu.registers.status.contains(Status::PS_ZERO)); 1986 | assert!(!cpu.registers.status.contains(Status::PS_CARRY)); 1987 | assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE)); 1988 | 1989 | cpu.execute_instruction((load_instruction, OpInput::UseImmediate(127))); 1990 | 1991 | compare(&mut cpu, -128i8 as u8); 1992 | assert!(!cpu.registers.status.contains(Status::PS_ZERO)); 1993 | assert!(!cpu.registers.status.contains(Status::PS_CARRY)); 1994 | assert!(cpu.registers.status.contains(Status::PS_NEGATIVE)); 1995 | } 1996 | 1997 | #[test] 1998 | fn compare_with_a_register_test() { 1999 | compare_test_helper( 2000 | &mut |cpu: &mut CPU, val: u8| { 2001 | cpu.compare_with_a_register(val); 2002 | }, 2003 | Instruction::LDA, 2004 | ); 2005 | } 2006 | 2007 | #[test] 2008 | fn compare_with_x_register_test() { 2009 | compare_test_helper( 2010 | &mut |cpu: &mut CPU, val: u8| { 2011 | cpu.compare_with_x_register(val); 2012 | }, 2013 | Instruction::LDX, 2014 | ); 2015 | } 2016 | 2017 | #[test] 2018 | fn compare_with_y_register_test() { 2019 | compare_test_helper( 2020 | &mut |cpu: &mut CPU, val: u8| { 2021 | cpu.compare_with_y_register(val); 2022 | }, 2023 | Instruction::LDY, 2024 | ); 2025 | } 2026 | 2027 | #[test] 2028 | fn exclusive_or_test() { 2029 | let mut cpu = CPU::new(Ram::new(), Nmos6502); 2030 | 2031 | for a_before in 0u8..=255u8 { 2032 | for val in 0u8..=255u8 { 2033 | cpu.execute_instruction((Instruction::LDA, OpInput::UseImmediate(a_before))); 2034 | 2035 | cpu.exclusive_or(val); 2036 | 2037 | let a_after = a_before ^ val; 2038 | assert_eq!(cpu.registers.accumulator, a_after); 2039 | 2040 | if a_after == 0 { 2041 | assert!(cpu.registers.status.contains(Status::PS_ZERO)); 2042 | } else { 2043 | assert!(!cpu.registers.status.contains(Status::PS_ZERO)); 2044 | } 2045 | 2046 | if (a_after as i8) < 0 { 2047 | assert!(cpu.registers.status.contains(Status::PS_NEGATIVE)); 2048 | } else { 2049 | assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE)); 2050 | } 2051 | } 2052 | } 2053 | } 2054 | 2055 | #[test] 2056 | fn inclusive_or_test() { 2057 | let mut cpu = CPU::new(Ram::new(), Nmos6502); 2058 | 2059 | for a_before in 0u8..=255u8 { 2060 | for val in 0u8..=255u8 { 2061 | cpu.execute_instruction((Instruction::LDA, OpInput::UseImmediate(a_before))); 2062 | 2063 | cpu.inclusive_or(val); 2064 | 2065 | let a_after = a_before | val; 2066 | assert_eq!(cpu.registers.accumulator, a_after); 2067 | 2068 | if a_after == 0 { 2069 | assert!(cpu.registers.status.contains(Status::PS_ZERO)); 2070 | } else { 2071 | assert!(!cpu.registers.status.contains(Status::PS_ZERO)); 2072 | } 2073 | 2074 | if (a_after as i8) < 0 { 2075 | assert!(cpu.registers.status.contains(Status::PS_NEGATIVE)); 2076 | } else { 2077 | assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE)); 2078 | } 2079 | } 2080 | } 2081 | } 2082 | 2083 | #[test] 2084 | fn stack_underflow() { 2085 | let mut cpu = CPU::new(Ram::new(), Nmos6502); 2086 | let _val: u8 = cpu.pull_from_stack(); 2087 | } 2088 | 2089 | #[test] 2090 | fn nmos6502_adc_decimal_mode() { 2091 | let mut cpu = CPU::new(Ram::new(), Nmos6502); 2092 | cpu.registers.accumulator = 0x09; 2093 | cpu.registers.status.insert(Status::PS_DECIMAL_MODE); 2094 | cpu.registers.status.remove(Status::PS_CARRY); 2095 | 2096 | cpu.add_with_carry(0x01); 2097 | 2098 | // Should produce BCD result: 09 + 01 = 10 (decimal) 2099 | assert_eq!(cpu.registers.accumulator, 0x10); 2100 | assert!(!cpu.get_flag(Status::PS_CARRY)); 2101 | } 2102 | 2103 | #[test] 2104 | fn ricoh2a03_ignores_decimal_mode() { 2105 | use crate::instruction::Ricoh2a03; 2106 | 2107 | let mut cpu = CPU::new(Ram::new(), Ricoh2a03); 2108 | cpu.registers.accumulator = 0x09; 2109 | cpu.registers.status.insert(Status::PS_DECIMAL_MODE); 2110 | cpu.registers.status.remove(Status::PS_CARRY); 2111 | 2112 | cpu.add_with_carry(0x01); 2113 | 2114 | // Should be binary arithmetic: 0x09 + 0x01 = 0x0A (not 0x10) 2115 | assert_eq!(cpu.registers.accumulator, 0x0A); 2116 | assert!(!cpu.get_flag(Status::PS_CARRY)); 2117 | } 2118 | 2119 | #[test] 2120 | fn cmos6502_adc_decimal_mode() { 2121 | use crate::instruction::Cmos6502; 2122 | 2123 | let mut cpu = CPU::new(Ram::new(), Cmos6502); 2124 | cpu.registers.accumulator = 0x09; 2125 | cpu.registers.status.insert(Status::PS_DECIMAL_MODE); 2126 | cpu.registers.status.remove(Status::PS_CARRY); 2127 | 2128 | cpu.add_with_carry(0x01); 2129 | 2130 | // Should produce BCD result like NMOS: 09 + 01 = 10 (decimal) 2131 | assert_eq!(cpu.registers.accumulator, 0x10); 2132 | assert!(!cpu.get_flag(Status::PS_CARRY)); 2133 | } 2134 | 2135 | #[test] 2136 | fn revision_a_adc_same_as_nmos() { 2137 | use crate::instruction::RevisionA; 2138 | 2139 | let mut cpu = CPU::new(Ram::new(), RevisionA); 2140 | cpu.registers.accumulator = 0x09; 2141 | cpu.registers.status.insert(Status::PS_DECIMAL_MODE); 2142 | cpu.registers.status.remove(Status::PS_CARRY); 2143 | 2144 | cpu.add_with_carry(0x01); 2145 | 2146 | // Should behave identically to NMOS 6502 2147 | assert_eq!(cpu.registers.accumulator, 0x10); 2148 | assert!(!cpu.get_flag(Status::PS_CARRY)); 2149 | } 2150 | 2151 | #[test] 2152 | fn get_flag() { 2153 | let mut cpu = CPU::new(Ram::new(), Nmos6502); 2154 | 2155 | // Demonstrate checking multiple flags 2156 | assert!(!cpu.get_flag(Status::PS_CARRY)); 2157 | assert!(!cpu.get_flag(Status::PS_ZERO)); 2158 | assert!(!cpu.get_flag(Status::PS_NEGATIVE)); 2159 | assert!(!cpu.get_flag(Status::PS_OVERFLOW)); 2160 | assert!(!cpu.get_flag(Status::PS_DECIMAL_MODE)); 2161 | 2162 | // Set some flags and check them 2163 | cpu.registers 2164 | .status 2165 | .insert(Status::PS_CARRY | Status::PS_ZERO); 2166 | assert!(cpu.get_flag(Status::PS_CARRY)); 2167 | assert!(cpu.get_flag(Status::PS_ZERO)); 2168 | assert!(!cpu.get_flag(Status::PS_NEGATIVE)); 2169 | } 2170 | 2171 | #[test] 2172 | fn set_flag() { 2173 | let mut cpu = CPU::new(Ram::new(), Nmos6502); 2174 | 2175 | // Initially, no flags are set 2176 | assert!(!cpu.get_flag(Status::PS_CARRY)); 2177 | assert!(!cpu.get_flag(Status::PS_ZERO)); 2178 | assert!(!cpu.get_flag(Status::PS_NEGATIVE)); 2179 | 2180 | // Set the carry flag 2181 | cpu.set_flag(Status::PS_CARRY); 2182 | assert!(cpu.get_flag(Status::PS_CARRY)); 2183 | assert!(!cpu.get_flag(Status::PS_ZERO)); 2184 | assert!(!cpu.get_flag(Status::PS_NEGATIVE)); 2185 | 2186 | // Set the zero flag 2187 | cpu.set_flag(Status::PS_ZERO); 2188 | assert!(cpu.get_flag(Status::PS_CARRY)); 2189 | assert!(cpu.get_flag(Status::PS_ZERO)); 2190 | assert!(!cpu.get_flag(Status::PS_NEGATIVE)); 2191 | 2192 | // Set the negative flag 2193 | cpu.set_flag(Status::PS_NEGATIVE); 2194 | assert!(cpu.get_flag(Status::PS_CARRY)); 2195 | assert!(cpu.get_flag(Status::PS_ZERO)); 2196 | assert!(cpu.get_flag(Status::PS_NEGATIVE)); 2197 | } 2198 | 2199 | #[test] 2200 | fn unset_flag() { 2201 | let mut cpu = CPU::new(Ram::new(), Nmos6502); 2202 | 2203 | // Set all flags first 2204 | cpu.set_flag(Status::PS_CARRY); 2205 | cpu.set_flag(Status::PS_ZERO); 2206 | cpu.set_flag(Status::PS_NEGATIVE); 2207 | assert!(cpu.get_flag(Status::PS_CARRY)); 2208 | assert!(cpu.get_flag(Status::PS_ZERO)); 2209 | assert!(cpu.get_flag(Status::PS_NEGATIVE)); 2210 | 2211 | // Clear the carry flag 2212 | cpu.unset_flag(Status::PS_CARRY); 2213 | assert!(!cpu.get_flag(Status::PS_CARRY)); 2214 | assert!(cpu.get_flag(Status::PS_ZERO)); 2215 | assert!(cpu.get_flag(Status::PS_NEGATIVE)); 2216 | 2217 | // Clear the zero flag 2218 | cpu.unset_flag(Status::PS_ZERO); 2219 | assert!(!cpu.get_flag(Status::PS_CARRY)); 2220 | assert!(!cpu.get_flag(Status::PS_ZERO)); 2221 | assert!(cpu.get_flag(Status::PS_NEGATIVE)); 2222 | 2223 | // Clear the negative flag 2224 | cpu.unset_flag(Status::PS_NEGATIVE); 2225 | assert!(!cpu.get_flag(Status::PS_CARRY)); 2226 | assert!(!cpu.get_flag(Status::PS_ZERO)); 2227 | assert!(!cpu.get_flag(Status::PS_NEGATIVE)); 2228 | } 2229 | 2230 | // ADC function-level tests - test the pure ADC logic without CPU state 2231 | #[test] 2232 | fn adc_function_nmos6502_binary_basic() { 2233 | use crate::instruction::Nmos6502; 2234 | use crate::{AdcOutput, Variant}; 2235 | 2236 | // Test basic binary addition: 5 + 3 = 8 2237 | let result = Nmos6502::adc_binary(5, 3, 0); 2238 | assert_eq!( 2239 | result, 2240 | AdcOutput { 2241 | result: 8, 2242 | did_carry: false, 2243 | overflow: false, 2244 | negative: false, 2245 | zero: false, 2246 | } 2247 | ); 2248 | 2249 | // Test with carry: 5 + 3 + 1 = 9 2250 | let result = Nmos6502::adc_binary(5, 3, 1); 2251 | assert_eq!( 2252 | result, 2253 | AdcOutput { 2254 | result: 9, 2255 | did_carry: false, 2256 | overflow: false, 2257 | negative: false, 2258 | zero: false, 2259 | } 2260 | ); 2261 | } 2262 | 2263 | #[test] 2264 | fn adc_function_nmos6502_binary_overflow() { 2265 | use crate::instruction::Nmos6502; 2266 | use crate::{AdcOutput, Variant}; 2267 | 2268 | // Test signed overflow: 127 + 1 = -128 (0x80) 2269 | let result = Nmos6502::adc_binary(0x7F, 1, 0); 2270 | assert_eq!( 2271 | result, 2272 | AdcOutput { 2273 | result: 0x80, 2274 | did_carry: false, 2275 | overflow: true, // V flag set for signed overflow 2276 | negative: true, // N flag set because result has bit 7 set 2277 | zero: false, 2278 | } 2279 | ); 2280 | } 2281 | 2282 | #[test] 2283 | fn adc_function_nmos6502_binary_carry() { 2284 | use crate::instruction::Nmos6502; 2285 | use crate::{AdcOutput, Variant}; 2286 | 2287 | // Test carry: 255 + 1 = 0 with carry 2288 | let result = Nmos6502::adc_binary(255, 1, 0); 2289 | assert_eq!( 2290 | result, 2291 | AdcOutput { 2292 | result: 0, 2293 | did_carry: true, // C flag set for unsigned overflow 2294 | overflow: false, 2295 | negative: false, 2296 | zero: true, // Z flag set because result is 0 2297 | } 2298 | ); 2299 | } 2300 | 2301 | #[test] 2302 | fn adc_function_nmos6502_decimal_basic() { 2303 | use crate::instruction::Nmos6502; 2304 | use crate::{AdcOutput, Variant}; 2305 | 2306 | // Test BCD addition: 09 + 01 = 10 (0x10 in BCD) 2307 | let result = Nmos6502::adc_decimal(0x09, 0x01, 0); 2308 | assert_eq!( 2309 | result, 2310 | AdcOutput { 2311 | result: 0x10, // BCD result 2312 | did_carry: false, 2313 | overflow: false, // V calculated from binary operation 2314 | negative: false, 2315 | zero: false, 2316 | } 2317 | ); 2318 | } 2319 | 2320 | #[test] 2321 | fn adc_function_ricoh2a03_decimal_calls_binary() { 2322 | use crate::Variant; 2323 | use crate::instruction::Ricoh2a03; 2324 | 2325 | // Ricoh2A03 has no decimal mode, so decimal should match binary 2326 | let binary_result = Ricoh2a03::adc_binary(0x09, 0x01, 0); 2327 | let decimal_result = Ricoh2a03::adc_decimal(0x09, 0x01, 0); 2328 | assert_eq!(binary_result, decimal_result); 2329 | } 2330 | 2331 | #[test] 2332 | fn reset_sequence_behavior() { 2333 | let mut cpu = CPU::new(Ram::new(), Nmos6502); 2334 | 2335 | // Set up reset vector in memory: $1234 2336 | cpu.memory.set_byte(0xFFFC, 0x34); // Low byte 2337 | cpu.memory.set_byte(0xFFFD, 0x12); // High byte 2338 | 2339 | // Initialize SP to some value to see it change 2340 | cpu.registers.stack_pointer = StackPointer(0xFF); 2341 | 2342 | cpu.reset(); 2343 | 2344 | // Check that PC was set from reset vector 2345 | assert_eq!(cpu.registers.program_counter, 0x1234); 2346 | 2347 | // Check that SP was decremented 3 times (0xFF - 3 = 0xFC) 2348 | assert_eq!(cpu.registers.stack_pointer.0, 0xFC); 2349 | 2350 | // Check that interrupt disable flag is set 2351 | assert!(cpu.registers.status.contains(Status::PS_DISABLE_INTERRUPTS)); 2352 | } 2353 | } 2354 | --------------------------------------------------------------------------------