From b26004facf4a48ad1c8aeec6db67ad85bfea55a1 Mon Sep 17 00:00:00 2001 From: Alexander Akhmetov Date: Tue, 23 Apr 2019 20:15:18 +0200 Subject: [PATCH] Running the Woz Monitor --- Cargo.lock | 296 +++- Cargo.toml | 25 +- Makefile | 11 + README.md | 94 ++ asm/apple1hello.asm | 38 + asm/cycle.asm | 7 + asm/hello.asm | 4 + asm/stack.asm | 14 + asm/wozmon.asm | 260 ++++ hex/ASMmchess.txt | 281 ++++ hex/convert.py | 28 + roms/6502_functional_test.bin | Bin 0 -> 65536 bytes roms/ASMmchess.bin | Bin 0 -> 2248 bytes roms/apple1basic.bin | Bin 0 -> 4090 bytes roms/apple1basic.md | 8 + roms/apple30.bin | Bin 0 -> 3456 bytes roms/nestest.bin | Bin 0 -> 24592 bytes roms/replica1.bin | Bin 0 -> 8192 bytes src/apple1.rs | 216 +++ src/asm.rs | 147 ++ src/cli.rs | 76 + src/cpu.rs | 2599 +++++++++++++++++++++++++++++++-- src/lib.rs | 8 + src/main.rs | 9 - src/mem.rs | 65 + src/operation.rs | 1156 ++++++++++++++- src/utils.rs | 4 +- sys/wozmon.bin | Bin 0 -> 256 bytes tests/cpu_test.rs | 137 ++ 29 files changed, 5308 insertions(+), 175 deletions(-) create mode 100644 Makefile create mode 100644 README.md create mode 100644 asm/apple1hello.asm create mode 100644 asm/cycle.asm create mode 100644 asm/hello.asm create mode 100644 asm/stack.asm create mode 100644 asm/wozmon.asm create mode 100644 hex/ASMmchess.txt create mode 100644 hex/convert.py create mode 100644 roms/6502_functional_test.bin create mode 100644 roms/ASMmchess.bin create mode 100644 roms/apple1basic.bin create mode 100644 roms/apple1basic.md create mode 100644 roms/apple30.bin create mode 100644 roms/nestest.bin create mode 100644 roms/replica1.bin create mode 100644 src/apple1.rs create mode 100644 src/cli.rs create mode 100644 src/lib.rs delete mode 100644 src/main.rs create mode 100644 src/mem.rs create mode 100644 sys/wozmon.bin create mode 100644 tests/cpu_test.rs diff --git a/Cargo.lock b/Cargo.lock index ba0efaa..0a1f1f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,300 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] -name = "mos6502" +name = "aho-corasick" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "atty" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)", + "termion 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bitflags" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cc" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cfg-if" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "clap" +version = "2.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "env_logger" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "humantime" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "lazy_static" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "log" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "memchr" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "mos6502-cpu-emulator" +version = "0.1.0" +dependencies = [ + "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", + "env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "ncurses 5.99.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ncurses" +version = "5.99.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "numtoa" version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "pkg-config" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "quick-error" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "redox_syscall" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "redox_termios" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "aho-corasick 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex-syntax" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "termcolor" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "termion" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)", + "numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "thread_local" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ucd-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unicode-width" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "utf8-ranges" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "vec_map" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "wincolor" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] +[metadata] +"checksum aho-corasick 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e6f484ae0c99fec2e858eb6134949117399f222608d84cadb3f58c1f97c2364c" +"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" +"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" +"checksum cc 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)" = "a0c56216487bb80eec9c4516337b2588a4f2a2290d72a1416d930e4dcdb0c90d" +"checksum cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "11d43355396e872eefb45ce6342e4374ed7bc2b3a502d1b28e36d6e23c05d1f4" +"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" +"checksum env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b61fa891024a945da30a9581546e8cfaf5602c7b3f4c137a2805cf388f92075a" +"checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114" +"checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" +"checksum libc 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)" = "c6785aa7dd976f5fbf3b71cfd9cd49d7f783c1ff565a858d71031c6c313aa5c6" +"checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" +"checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39" +"checksum ncurses 5.99.0 (registry+https://github.com/rust-lang/crates.io-index)" = "15699bee2f37e9f8828c7b35b2bc70d13846db453f2d507713b758fabe536b82" +"checksum numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" +"checksum pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "676e8eb2b1b4c9043511a9b7bea0915320d7e502b0a079fb03f9635a5252b18c" +"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" +"checksum redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)" = "12229c14a0f65c4f1cb046a3b52047cdd9da1f4b30f8a39c5063c8bae515e252" +"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" +"checksum regex 1.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "8f0a0bcab2fd7d1d7c54fa9eae6f43eddeb9ce2e7352f8518a814a4f65d60c58" +"checksum regex-syntax 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "dcfd8681eebe297b81d98498869d4aae052137651ad7b96822f09ceb690d0a96" +"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +"checksum termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4096add70612622289f2fdcdbd5086dc81c1e2675e6ae58d6c4f62a16c6d7f2f" +"checksum termion 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dde0593aeb8d47accea5392b39350015b5eccb12c0d98044d856983d89548dea" +"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" +"checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86" +"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" +"checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737" +"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" +"checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770" +"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +"checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9" +"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +"checksum wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "561ed901ae465d6185fa7864d63fbd5720d0ef718366c9a4dc83cf6170d7e9ba" diff --git a/Cargo.toml b/Cargo.toml index c7f9de9..f739a05 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,30 @@ [package] -name = "mos6502" +name = "mos6502-cpu-emulator" version = "0.1.0" authors = ["Alexander Akhmetov "] edition = "2018" +[lib] +name = "mos6502" +path = "src/lib.rs" +authors = ["Alexander Akhmetov "] +edition = "2018" + +[[bin]] +name = "apple1" +path = "src/apple1.rs" +required-features = ["build-binary"] + +[[bin]] +name = "mos6502-cli" +path = "src/cli.rs" +required-features = ["build-binary"] + [dependencies] +log = "0.4" +env_logger = {version = "0.6.1", optional = true } +clap = {version = "2.33.0", optional = true } +ncurses = {version = "5.99.0", optional = true} + +[features] +build-binary = ["clap", "env_logger", "ncurses"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5919f2f --- /dev/null +++ b/Makefile @@ -0,0 +1,11 @@ +replica1: + RUST_LOG=warn cargo run --features build-binary --bin apple1 -- -p roms/replica1.bin -l e000 + +apple30: + RUST_LOG=warn cargo run --features build-binary --bin apple1 -- -p roms/apple30.bin -l 280 + +minichess: + RUST_LOG=error cargo run --features build-binary --bin apple1 -- -p roms/ASMmchess.bin -l 300 + +functional-tests: + RUST_LOG=info cargo run --features build-binary --bin mos6502-cli roms/6502_functional_test.bin 400 diff --git a/README.md b/README.md new file mode 100644 index 0000000..9bdf731 --- /dev/null +++ b/README.md @@ -0,0 +1,94 @@ +# Apple1 emulator + +## How to use + +Run binary: + +``` +cargo run --features build-binary --bin apple1 +``` + +The command above starts Apple1 with WozMonitor at the address `0xFF00`. You should see the screen and the command line prompt: + +``` +\ + +``` + + +With optional flag `-p` you can load an additional program to the memory: + +``` +cargo run --features build-binary --bin apple1 -- -p asm/apple1hello.asm +``` +It will be loaded to the memory with starting address `0xE000`. To run it using Woz Monitor type `E000R` and press enter. +To see the hex content of the program: `E000..`, for example: `E000.E0FF`. + +## Debug + +You can disable the screen and enable debug logging: + +``` +RUST_LOG=debug cargo run --features build-binary --bin apple1 -- -p asm/apple1hello.asm +``` + +## Tests + +Run tests: + +``` +cargo test +``` + +Run tests with logging: + +``` +RUST_LOG=mos6502=debug cargo test -- --nocapture +``` + +## Apple 1 Basic + +* [BASIC source code listing](https://github.com/jefftranter/6502/blob/master/asm/a1basic/a1basic.s) +* [Disassembled BASIC](http://www.brouhaha.com/~eric/retrocomputing/apple/apple1/basic/) + + +There are two different ROMs, one of them is from Replica1 and it works. The original one, `roms/apple1basic.bin` does not work yet. Seems like it tries to read DSP at `0xD0F2` instead of `0xD012`. You can inspect this if you load it and then print hex data at `E3D5.E3DF` with WozMonitor. + + note: http://www.brielcomputers.com/phpBB3/viewtopic.php?f=10&t=404 + discussion about the same problem + +``` +E3D5.E3DF + +E3D5: 2C F2 D0 +E3D8: 30 FB 8D F2 D0 60 A0 06 +``` + +Replica1 basic content: + +``` +E3D5.E3DF + +E3D5: 2C 12 D0 +E3D8: 30 FB 8D 12 D0 60 A0 06 +``` + +### Start basic + +``` +RUST_LOG=error cargo run --features build-binary --bin apple1 -- -p roms/replica1.bin -b +``` + +and then type `E000R`. + + +## Resources + +* [6502 instruction set](https://www.masswerk.at/6502/6502_instruction_set.html#BIT) +* [Apple1 BASIC manual](https://archive.org/stream/apple1_basic_manual/apple1_basic_manual_djvu.txt) +* [www.applefritter.com](https://www.applefritter.com) +* [6502 memory test](http://www.willegal.net/appleii/6502mem.htm) +* [apple1 programs](http://hoop-la.ca/apple2/2008/retrochallenge.net.html) +* [apple1 programs 2](http://www.willegal.net/appleii/apple1-software.htm) +* [Woz Monitor description](https://www.sbprojects.net/projects/apple1/wozmon.php) +* [6502 instructions description with undocumented commands](http://www.zimmers.net/anonftp/pub/cbm/documents/chipdata/64doc) diff --git a/asm/apple1hello.asm b/asm/apple1hello.asm new file mode 100644 index 0000000..671970d --- /dev/null +++ b/asm/apple1hello.asm @@ -0,0 +1,38 @@ +LDA #$8D +JSR $FFEF + +LDA #$C8 +JSR $FFEF + +LDA #$C5 +JSR $FFEF + +LDA #$CC +JSR $FFEF +LDA #$CC +JSR $FFEF + +LDA #$CF +JSR $FFEF + +LDA #$A0 +JSR $FFEF + +LDA #$D7 +JSR $FFEF +LDA #$CF +JSR $FFEF +LDA #$D2 +JSR $FFEF +LDA #$CC +JSR $FFEF +LDA #$C4 +JSR $FFEF + +LDA #$A1 +JSR $FFEF + +LDA #$8D +JSR $FFEF + +JMP $FF1F diff --git a/asm/cycle.asm b/asm/cycle.asm new file mode 100644 index 0000000..08800cd --- /dev/null +++ b/asm/cycle.asm @@ -0,0 +1,7 @@ + LDX #$5 + LDY #$0 + DEX + INY + CPX #$0 + BNE #$FC + BRK diff --git a/asm/hello.asm b/asm/hello.asm new file mode 100644 index 0000000..0e40f19 --- /dev/null +++ b/asm/hello.asm @@ -0,0 +1,4 @@ + LDA #$0 + LDX #$1 + LDY #$2 + BRK diff --git a/asm/stack.asm b/asm/stack.asm new file mode 100644 index 0000000..656353a --- /dev/null +++ b/asm/stack.asm @@ -0,0 +1,14 @@ +LDX #$00 +LDY #$00 +TXA +STA $0200 +PHA +INX +INY +CPY #$10 +BNE #$F7 +PLA +STA $0200 +INY +CPY #$20 +BNE #$F9 diff --git a/asm/wozmon.asm b/asm/wozmon.asm new file mode 100644 index 0000000..2ddbb24 --- /dev/null +++ b/asm/wozmon.asm @@ -0,0 +1,260 @@ +;------------------------------------------------------------------------- +; +; The WOZ Monitor for the Apple 1 +; Written by Steve Wozniak 1976 +; +;------------------------------------------------------------------------- + + .CR 6502 + .OR $FF00 + .TF WOZMON.HEX,HEX,8 + +;------------------------------------------------------------------------- +; Memory declaration +;------------------------------------------------------------------------- + +XAML .EQ $24 Last "opened" location Low +XAMH .EQ $25 Last "opened" location High +STL .EQ $26 Store address Low +STH .EQ $27 Store address High +L .EQ $28 Hex value parsing Low +H .EQ $29 Hex value parsing High +YSAV .EQ $2A Used to see if hex value is given +MODE .EQ $2B $00=XAM, $7F=STOR, $AE=BLOCK XAM + +IN .EQ $0200,$027F Input buffer + +KBD .EQ $D010 PIA.A keyboard input +KBDCR .EQ $D011 PIA.A keyboard control register +DSP .EQ $D012 PIA.B display output register +DSPCR .EQ $D013 PIA.B display control register + +; KBD b7..b0 are inputs, b6..b0 is ASCII input, b7 is constant high +; Programmed to respond to low to high KBD strobe +; DSP b6..b0 are outputs, b7 is input +; CB2 goes low when data is written, returns high when CB1 goes high +; Interrupts are enabled, though not used. KBD can be jumpered to IRQ, +; whereas DSP can be jumpered to NMI. + +;------------------------------------------------------------------------- +; Constants +;------------------------------------------------------------------------- + +BS .EQ $DF Backspace key, arrow left key +CR .EQ $8D Carriage Return +ESC .EQ $9B ESC key +PROMPT .EQ "\" Prompt character + +;------------------------------------------------------------------------- +; Let's get started +; +; Remark the RESET routine is only to be entered by asserting the RESET +; line of the system. This ensures that the data direction registers +; are selected. +;------------------------------------------------------------------------- + +RESET CLD Clear decimal arithmetic mode + CLI + LDY #%0111.1111 Mask for DSP data direction reg + STY DSP (DDR mode is assumed after reset) + LDA #%1010.0111 KBD and DSP control register mask + STA KBDCR Enable interrupts, set CA1, CB1 for + STA DSPCR positive edge sense/output mode. + +; Program falls through to the GETLINE routine to save some program bytes +; Please note that Y still holds $7F, which will cause an automatic Escape + +;------------------------------------------------------------------------- +; The GETLINE process +;------------------------------------------------------------------------- + +NOTCR CMP #BS Backspace key? + BEQ BACKSPACE Yes + CMP #ESC ESC? + BEQ ESCAPE Yes + INY Advance text index + BPL NEXTCHAR Auto ESC if line longer than 127 + +ESCAPE LDA #PROMPT Print prompt character + JSR ECHO Output it. + +GETLINE LDA #CR Send CR + JSR ECHO + + LDY #0+1 Start a new input line +BACKSPACE DEY Backup text index + BMI GETLINE Oops, line's empty, reinitialize + +NEXTCHAR LDA KBDCR Wait for key press + BPL NEXTCHAR No key yet! + LDA KBD Load character. B7 should be '1' + STA IN,Y Add to text buffer + JSR ECHO Display character + CMP #CR + BNE NOTCR It's not CR! + +; Line received, now let's parse it + + LDY #-1 Reset text index + LDA #0 Default mode is XAM + TAX X=0 + +SETSTOR ASL Leaves $7B if setting STOR mode + +SETMODE STA MODE Set mode flags + +BLSKIP INY Advance text index + +NEXTITEM LDA IN,Y Get character + CMP #CR + BEQ GETLINE We're done if it's CR! + CMP #"." + BCC BLSKIP Ignore everything below "."! + BEQ SETMODE Set BLOCK XAM mode ("." = $AE) + CMP #":" + BEQ SETSTOR Set STOR mode! $BA will become $7B + CMP #"R" + BEQ RUN Run the program! Forget the rest + STX L Clear input value (X=0) + STX H + STY YSAV Save Y for comparison + +; Here we're trying to parse a new hex value + +NEXTHEX LDA IN,Y Get character for hex test + EOR #$B0 Map digits to 0-9 + CMP #9+1 Is it a decimal digit? + BCC DIG Yes! + ADC #$88 Map letter "A"-"F" to $FA-FF + CMP #$FA Hex letter? + BCC NOTHEX No! Character not hex + +DIG ASL + ASL Hex digit to MSD of A + ASL + ASL + + LDX #4 Shift count +HEXSHIFT ASL Hex digit left, MSB to carry + ROL L Rotate into LSD + ROL H Rotate into MSD's + DEX Done 4 shifts? + BNE HEXSHIFT No, loop + INY Advance text index + BNE NEXTHEX Always taken + +NOTHEX CPY YSAV Was at least 1 hex digit given? + BEQ ESCAPE No! Ignore all, start from scratch + + BIT MODE Test MODE byte + BVC NOTSTOR B6=0 is STOR, 1 is XAM or BLOCK XAM + +; STOR mode, save LSD of new hex byte + + LDA L LSD's of hex data + STA (STL,X) Store current 'store index'(X=0) + INC STL Increment store index. + BNE NEXTITEM No carry! + INC STH Add carry to 'store index' high +TONEXTITEM JMP NEXTITEM Get next command item. + +;------------------------------------------------------------------------- +; RUN user's program from last opened location +;------------------------------------------------------------------------- + +RUN JMP (XAML) Run user's program + +;------------------------------------------------------------------------- +; We're not in Store mode +;------------------------------------------------------------------------- + +NOTSTOR BMI XAMNEXT B7 = 0 for XAM, 1 for BLOCK XAM + +; We're in XAM mode now + + LDX #2 Copy 2 bytes +SETADR LDA L-1,X Copy hex data to + STA STL-1,X 'store index' + STA XAML-1,X and to 'XAM index' + DEX Next of 2 bytes + BNE SETADR Loop unless X = 0 + +; Print address and data from this address, fall through next BNE. + +NXTPRNT BNE PRDATA NE means no address to print + LDA #CR Print CR first + JSR ECHO + LDA XAMH Output high-order byte of address + JSR PRBYTE + LDA XAML Output low-order byte of address + JSR PRBYTE + LDA #":" Print colon + JSR ECHO + +PRDATA LDA #" " Print space + JSR ECHO + LDA (XAML,X) Get data from address (X=0) + JSR PRBYTE Output it in hex format +XAMNEXT STX MODE 0 -> MODE (XAM mode). + LDA XAML See if there's more to print + CMP L + LDA XAMH + SBC H + BCS TONEXTITEM Not less! No more data to output + + INC XAML Increment 'examine index' + BNE MOD8CHK No carry! + INC XAMH + +MOD8CHK LDA XAML If address MOD 8 = 0 start new line + AND #%0000.0111 + BPL NXTPRNT Always taken. + +;------------------------------------------------------------------------- +; Subroutine to print a byte in A in hex form (destructive) +;------------------------------------------------------------------------- + +PRBYTE PHA Save A for LSD + LSR + LSR + LSR MSD to LSD position + LSR + JSR PRHEX Output hex digit + PLA Restore A + +; Fall through to print hex routine + +;------------------------------------------------------------------------- +; Subroutine to print a hexadecimal digit +;------------------------------------------------------------------------- + +PRHEX AND #%0000.1111 Mask LSD for hex print + ORA #"0" Add "0" + CMP #"9"+1 Is it a decimal digit? + BCC ECHO Yes! output it + ADC #6 Add offset for letter A-F + +; Fall through to print routine + +;------------------------------------------------------------------------- +; Subroutine to print a character to the terminal +;------------------------------------------------------------------------- + +ECHO BIT DSP DA bit (B7) cleared yet? + BMI ECHO No! Wait for display ready + STA DSP Output character. Sets DA + RTS + +;------------------------------------------------------------------------- +; Vector area +;------------------------------------------------------------------------- + + .DA $0000 Unused, what a pity +NMI_VEC .DA $0F00 NMI vector +RESET_VEC .DA RESET RESET vector +IRQ_VEC .DA $0000 IRQ vector + +;------------------------------------------------------------------------- + + .LI OFF + diff --git a/hex/ASMmchess.txt b/hex/ASMmchess.txt new file mode 100644 index 0000000..469b644 --- /dev/null +++ b/hex/ASMmchess.txt @@ -0,0 +1,281 @@ +A2 58 BD 80 04 20 EF FF +CA 10 F7 20 E0 05 29 03 +F0 10 C9 01 F0 06 A0 FF +A9 00 F0 0A A0 FB A9 00 +F0 04 A0 FB A9 08 84 BE +85 BF EA EA A2 00 BD E6 +09 C9 AB F0 06 20 EF FF +E8 D0 F3 20 E0 05 29 0F +C9 0A 30 0B A5 FF 38 E9 +0A B0 FC 69 0A 29 0E 0A +AA BD EB 03 85 FD BD EC +03 85 FE A0 1B B1 FD 99 +C0 00 88 10 F8 A2 10 BD +EF 05 95 2F CA D0 F8 A2 +40 BD AF 06 95 6F CA D0 +F8 4C 00 04 FF FF FF FF +FF FF FF FF FF FF FF FF +FF FF FF FF FF FF FF FF +FF FF FF FF FF FF FF FF +FF FF FF FF FF A2 1F B5 +50 C5 FA F0 03 CA 10 F7 +86 FB 86 B0 D8 A2 FF 9A +A2 C8 86 B2 4C 0B 04 C9 +FF D0 04 A2 D9 D0 0E C9 +EE D0 04 A2 E4 D0 06 C9 +CC D0 0D A2 F3 BD 00 04 +F0 13 20 EF FF E8 D0 F5 +B5 F8 20 DC FF A9 A0 20 +EF FF CA D0 F3 A9 8D 20 +EF FF 60 B0 0A CC 0A E8 +0A 04 0B 20 0B 3C 0B 58 +0B 74 0B 90 0B AC 0B 00 +D8 A2 FF 9A A2 C8 86 B2 +20 00 09 20 3D 09 EA EA +EA EA 85 F3 C9 0C D0 0F +A2 1F B5 70 95 50 CA 10 +F9 86 DC A9 CC D0 12 C9 +0E D0 07 20 B2 07 A9 EE +D0 07 C9 14 D0 0B 20 A2 +08 85 FB 85 FA 85 F9 D0 +BF C9 0F D0 06 20 50 04 +4C 9D 03 4C 96 06 00 00 +85 B9 86 BA 84 BB 20 00 +09 A5 B9 A6 BA A4 BB 4C +4B 08 FF FF FF FF FF FF +FF FF FF FF FF FF FF FF +FF FF FF FF FF FF FF FF +FF FF FF FF FF FF FF FF +8D A0 A0 BF 8D 8D DA D4 +C9 CC C2 D2 C5 D0 D5 D3 +BD B2 8D DA D4 C9 CC C2 +BD B1 8D D9 C1 CC D0 A0 +CC C1 CD D2 CF CE BD B0 +8D 8D CD CF C3 AE CF CC +CE C5 C2 AE D7 D7 D7 8D +D3 C7 CE C9 CE CE C5 CA +A0 D2 C5 D4 C5 D0 A0 D9 +C2 8D 8D A9 C3 A8 D3 D3 +C5 C8 C3 CF D2 C3 C9 CD +8D C3 C8 C5 C3 CB CD C1 +D4 C5 8D 00 D2 C5 D6 C5 +D2 D3 C5 A0 D3 C9 C4 C5 +D3 8D 00 CE C5 D7 A0 C7 +C1 CD C5 8D 00 FF FF FF +8A 48 A2 08 A9 A1 20 EF +FF A9 AD 20 EF FF 20 EF +FF CA D0 F0 A9 A1 20 EF +FF A9 8D 20 EF FF 68 AA +60 84 F4 85 F5 86 F6 A9 +8D 20 EF FF 20 00 05 A2 +00 A0 00 A9 A1 20 EF FF +86 F7 84 F8 8A 0A 0A 0A +0A 65 F8 A2 1F D5 50 F0 +0E CA 10 F9 20 C9 05 4C +77 05 EA EA EA EA EA 8A +4A 4A 4A 4A EA 18 65 B8 +29 01 F0 04 A9 C2 D0 02 +A9 D7 20 EF FF 8A EA 29 +0F AA B5 30 20 EF FF A6 +F7 A4 F8 C8 C0 08 D0 B3 +A9 A1 20 BC 05 A9 8D 20 +EF FF 20 00 05 E8 E0 08 +D0 9F A4 F4 A9 8D 20 EF +FF A9 A0 20 EF FF A9 B0 +AA 20 EF FF A9 A0 20 EF +FF 20 EF FF E8 8A C9 B8 +D0 EF A9 8D 20 EF FF A5 +F5 A6 F6 60 20 EF FF A9 +A0 20 EF FF 8A 20 E5 FF +60 18 A5 F7 65 F8 29 01 +F0 04 A9 A0 D0 02 A9 AA +20 EF FF 20 EF FF 60 EA +E6 FF AD 11 D0 10 F9 AD +10 D0 20 EF FF 60 FF FF +CB D1 D2 D2 C2 C2 CE CE +D0 D0 D0 D0 D0 D0 D0 D0 +A6 B5 30 5C A5 B0 F0 08 +E0 08 D0 04 C5 E6 F0 2E +F6 E3 C9 01 D0 02 F6 E3 +50 1E A0 0F A5 B1 D9 60 +00 F0 03 88 10 F8 B9 A0 +00 D5 E4 90 04 94 E6 95 +E4 18 08 75 E5 95 E5 28 +E0 04 F0 03 30 31 60 A5 +E8 85 DD A9 00 85 B5 20 +4B 08 20 B2 07 20 00 07 +20 B2 07 A9 08 85 B5 20 +09 07 20 31 08 4C 80 09 +E0 F9 D0 0B A5 60 C5 B1 +D0 04 A9 00 85 B4 60 50 +FD A0 07 A5 B1 D9 60 00 +F0 05 88 F0 F1 10 F6 B9 +A0 00 D5 E2 90 02 95 E2 +C6 B5 A5 BE C5 B5 F0 03 +20 25 08 E6 B5 60 C9 08 +B0 12 20 EA 08 A2 1F B5 +50 C5 FA F0 03 CA 10 F7 +86 FB 86 B0 4C 0B 04 A0 +03 04 00 07 02 05 01 06 +10 17 11 16 12 15 14 13 +73 74 70 77 72 75 71 76 +60 67 61 66 62 65 64 63 +F0 FF 01 10 11 0F EF F1 +DF E1 EE F2 12 0E 1F 21 +0B 0A 06 06 04 04 04 04 +02 02 02 02 02 02 02 02 +A2 1F B5 70 95 50 CA 10 +F9 20 21 05 60 FF FF FF +A2 10 A9 00 95 DE CA 10 +FB A9 10 85 B0 C6 B0 10 +01 60 20 1E 08 A4 B0 A2 +08 86 B6 C0 08 10 41 C0 +06 10 2E C0 04 10 1F C0 +01 F0 09 10 0E 20 8E 07 +D0 FB F0 D9 20 9C 07 D0 +FB F0 D2 A2 04 86 B6 20 +9C 07 D0 FB F0 C7 20 9C +07 A5 B6 C9 04 D0 F7 F0 +BC A2 10 86 B6 20 8E 07 +A5 B6 C9 08 D0 F7 F0 AD +A2 06 86 B6 20 CA 07 50 +05 30 03 20 00 06 20 1E +08 C6 B6 A5 B6 C9 05 F0 +EB 20 CA 07 70 8F 30 8D +20 00 06 A5 B1 29 F0 C9 +20 F0 EE 4C 0D 07 20 CA +07 30 03 20 00 06 20 1E +08 C6 B6 60 20 CA 07 90 +02 50 F9 30 07 08 20 00 +06 28 50 F0 20 1E 08 C6 +B6 60 A2 0F 38 B4 60 A9 +77 F5 50 95 60 94 50 38 +A9 77 F5 50 95 50 CA 10 +EB 60 A5 B1 A6 B6 18 75 +8F 85 B1 29 88 D0 42 A5 +B1 A2 20 CA 30 0E D5 50 +D0 F9 E0 10 30 33 A9 7F +69 01 70 01 B8 A5 B5 30 +24 C5 BF 10 20 48 08 A9 +F9 85 B5 85 B4 20 4B 08 +20 B2 07 20 09 07 20 2E +08 28 68 85 B5 A5 B4 30 +04 38 A9 FF 60 18 A9 00 +60 A9 FF 18 B8 60 A6 B0 +B5 50 85 B1 60 20 4B 08 +20 B2 07 20 09 07 20 B2 +07 BA 86 B3 A6 B2 9A 68 +85 B6 68 85 B0 AA 68 95 +50 68 AA 68 85 B1 95 50 +4C 70 08 BA 86 B3 A6 B2 +9A A5 B1 48 A8 A2 1F D5 +50 F0 03 CA 10 F9 A9 CC +95 50 8A 48 A6 B0 B5 50 +94 50 48 8A 48 A5 B6 48 +BA 86 B2 A6 B3 9A 60 A6 +E4 E4 A0 D0 04 A9 00 F0 +0A A6 E3 D0 06 A6 EE D0 +02 A9 FF A2 04 86 B5 C5 +FA 90 0C F0 0A 85 FA A5 +B0 85 FB A5 B1 85 F9 60 +EA EA A6 DC 10 17 A5 F9 +D5 DC D0 0F CA B5 DC 85 +FB CA B5 DC 85 F9 CA 86 +DC D0 1A 85 DC A2 0C 86 +B5 86 FA A2 14 20 02 07 +A2 04 86 B5 20 00 07 A6 +FA E0 0F 90 12 A6 FB B5 +50 85 FA 86 B0 A5 F9 85 +B1 20 4B 08 4C FA 08 A9 +FF 60 A2 04 06 F9 26 FA +CA D0 F9 05 F9 85 F9 85 +B1 60 20 21 05 4C 00 04 +A9 8D 20 EF FF A2 03 A5 +FB 30 27 4A 4A 4A 4A 18 +65 B8 29 01 D0 04 A9 D7 +D0 02 A9 C2 20 EF FF B5 +F8 29 0F AA B5 30 20 EF +FF A2 02 A9 A0 20 EF FF +B5 F8 20 B7 03 60 FF FF +FF FF FF FF FF 20 E0 05 +C9 8D D0 03 A9 14 60 C9 +C7 D0 0A A9 00 85 B8 20 +F0 06 A9 0C 60 C9 D2 D0 +05 E6 B8 A9 0E 60 C9 CD +D0 03 A9 0F 60 C9 CC D0 +03 4C 00 03 C9 D1 D0 03 +4C 00 FF C9 D0 D0 06 20 +21 05 4C 3D 09 29 07 60 +18 A9 80 65 EB 65 EC 65 +ED 65 E1 65 DF 38 E5 F0 +E5 F1 E5 E2 E5 E0 E5 DE +E5 EF E5 E3 B0 02 A9 00 +4A 18 69 40 65 EC 65 ED +38 E5 E4 4A 18 69 90 65 +DD 65 DD 65 DD 65 DD 65 +E1 38 E5 E4 E5 E4 E5 E5 +E5 E5 E5 E0 A6 B1 E0 33 +F0 16 E0 34 F0 12 E0 22 +F0 0E E0 25 F0 0A A6 B0 +F0 09 B4 50 C0 10 10 03 +18 69 02 4C 77 08 8D 8D +CF D0 C5 CE C9 CE C7 D3 +8D 8D C3 CF CD D0 D5 D4 +C5 D2 AE AE AE AE AE AE +AE AE D7 C8 C9 D4 C5 A0 +C2 CC C1 C3 CB 8D C6 D2 +C5 CE C3 C8 A0 C4 C5 C6 +C5 CE C3 C5 AE AE AE B0 +A0 A0 A0 A0 A0 B1 8D C7 +C9 D5 CF C3 CF A0 D0 C9 +C1 CE CF AE AE AE AE AE +B2 A0 A0 A0 A0 A0 B3 8D +D2 D5 D9 A0 CC CF D0 C5 +DA AE AE AE AE AE AE AE +AE B4 A0 A0 A0 A0 A0 B5 +8D D1 D5 C5 C5 CE A7 D3 +A0 C9 CE C4 C9 C1 CE AE +AE AE B6 A0 A0 A0 A0 A0 +B7 8D C6 CF D5 D2 A0 CB +CE C9 C7 C8 D4 D3 AE AE +AE AE AE B8 A0 A0 A0 A0 +A0 B9 8D 8D C6 CF D2 A0 +D2 C1 CE C4 CF CD A0 CF +D0 C5 CE C9 CE C7 AC A0 +D0 D2 C5 D3 D3 A0 C5 CE +D4 C5 D2 8D BF AB 00 00 +99 22 06 45 32 0C 72 14 +01 63 63 05 64 43 0F 63 +41 05 52 25 07 44 34 0E +53 33 0F CC 99 22 07 55 +32 0D 45 06 00 63 14 01 +14 13 06 34 14 04 36 25 +06 52 33 0E 43 24 0F 44 +99 25 0B 25 01 00 33 25 +07 36 34 0D 34 34 0E 52 +25 0D 45 35 04 55 22 06 +43 33 0F CC 99 52 04 52 +52 06 75 44 06 52 41 04 +43 43 0F 43 25 06 52 32 +04 42 22 07 55 34 0F 44 +99 25 07 66 43 0E 55 55 +04 54 13 01 63 34 0E 33 +01 00 52 46 04 55 22 06 +43 33 0F CC 99 06 00 52 +11 06 34 22 0B 22 23 06 +64 14 04 43 44 06 75 25 +06 31 22 07 55 34 0F 44 +99 25 01 25 15 01 33 25 +07 72 01 00 63 11 04 66 +21 0A 56 22 06 53 35 0D +52 34 0E CC 99 35 0C 52 +52 06 62 44 06 52 06 00 +75 14 04 66 11 05 56 21 +0B 55 24 0F 42 25 06 43 +99 03 02 63 25 0B 25 41 +05 54 24 0E 72 01 00 36 +46 04 52 25 07 55 22 06 +43 33 0F CC 99 03 07 74 +14 01 52 52 04 36 23 0E +53 06 00 75 41 04 31 25 +06 52 22 07 55 34 0F 44 diff --git a/hex/convert.py b/hex/convert.py new file mode 100644 index 0000000..b64d491 --- /dev/null +++ b/hex/convert.py @@ -0,0 +1,28 @@ +#!/usr/bin/python3 +import sys + + +""" +Probably, some files can be converted with xxd tool +""" + + +def convert(in_filename, out_filename): + with open(in_filename, 'r') as in_f: + content = in_f.readlines() + + content = [l.lstrip(': ').strip().split() for l in content] + + byte_array = [] + for line in content: + for byte in line: + byte_array.append(bytearray.fromhex(byte)) + + byte_array = b''.join(byte_array) + + with open(out_filename, 'wb') as out_f: + out_f.write(byte_array) + + +if __name__ == '__main__': + convert(sys.argv[1], sys.argv[2]) diff --git a/roms/6502_functional_test.bin b/roms/6502_functional_test.bin new file mode 100644 index 0000000000000000000000000000000000000000..c9a35e1d6bd2e7d85844da2abf7034d5ed820e6e GIT binary patch literal 65536 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 E3ya{&@Bjb+ literal 0 HcmV?d00001 diff --git a/roms/ASMmchess.bin b/roms/ASMmchess.bin new file mode 100644 index 0000000000000000000000000000000000000000..3354a44ab289a161fedd7d3c77475440a813fecc GIT binary patch literal 2248 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 O2L?Sb=z;&AJn-*m;2p96 literal 0 HcmV?d00001 diff --git a/roms/replica1.bin b/roms/replica1.bin new file mode 100644 index 0000000000000000000000000000000000000000..78df339d509cd72bd8c5d78bab2413746871543e GIT binary patch literal 8192 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 Apple1 { + let mut apple1 = Apple1 { + cpu: CPU::new(), + disable_screen, + }; + + let wozmon_bytes = fs::read(wozmon).expect("Can't read wozmon file"); + apple1.load(&wozmon_bytes, WOZMON_ADDR); + + fn read_callback(addr: u16) -> Option<(u16, u8)> { + match addr { + KBD => Some((KBDCR, 0)), + _ => None, + } + }; + + apple1.cpu.memory.register_read_callback(read_callback); + + fn write_callback(addr: u16, value: u8) -> Option<(u16, u8)> { + match (addr, value) { + (DSP, 0) => None, + (KBD, _) => None, + (KBDCR, _) => None, + (DSP, v) => Some((DSP, v | 0b1000_0000)), + _ => None, + } + }; + apple1.cpu.memory.register_write_callback(write_callback); + + apple1 + } + + pub fn run(&mut self) { + let (tx, rx): (Sender, Receiver) = mpsc::channel(); + if !self.disable_screen { + ncurses::initscr(); + ncurses::resize_term(60, 40); + ncurses::scrollok(ncurses::stdscr(), true); + ncurses::noecho(); + ncurses::raw(); + thread::spawn(move || Apple1::read_input(tx)); + } + + while !self.cpu.step() { + self.print_output_to_display(); + + if let Ok(c) = rx.try_recv() { + match c { + 0x03 => break, // ^c + 0x05 => { + self.print_status(); + continue; + } // ^e + _ => {} + }; + + self.write_kbd_input(c); + } + thread::sleep(time::Duration::from_micros(100)); + } + ncurses::endwin(); + } + + fn char_to_apple1(&self, c: u8) -> u8 { + let mut c = c.to_ascii_uppercase() as u8; + if c == 0xA { + c = CR; // CR instead of NL + } + c |= 0x80; // apple1 ascii + set bit 7 + c + } + + fn write_kbd_input(&mut self, c: u8) { + self.cpu.memory.set(KBD, self.char_to_apple1(c)); + self.cpu.memory.set(KBDCR, 0b1000_0000); + } + + fn read_input(tx: Sender) { + // reads user input from keyboard + loop { + tx.send(ncurses::getch() as u8).unwrap(); + } + } + + fn print_status(&mut self) { + let status = &format!("{}", &self.status_string()); + debug!("{}", status); + if !self.disable_screen { + ncurses::addstr(status); + ncurses::addstr("\n"); + }; + } + + fn status_string(&mut self) -> String { + format!( + "[apple1] pc=0x{:X} a=0x{:X} x=0x{:X} y=0x{:X} p=0b{:08b} video=0b{:08b} kbd=0b{:08b}", + self.cpu.pc, + self.cpu.a, + self.cpu.x, + self.cpu.y, + self.cpu.p, + self.cpu.memory.get(DSP), + self.cpu.memory.get(KBD), + ) + } + + pub fn load(&mut self, program: &[u8], addr: u16) { + self.cpu.load(program, addr); + } + + fn print_output_to_display(&mut self) { + let value = self.cpu.memory.get(DSP); + + if value & 0b1000_0000 != 0 { + self.cpu.memory.set(DSP, 0); + let value = value & 0b0111_1111; // remove 7 bit + if value == CR { + ncurses::addch('\n' as u64); + } else { + ncurses::addch(u64::from(value)); + } + info!("[apple1 -> screen] 0x{:X}", value); + ncurses::refresh(); + } else { + debug!("no video output"); + } + } +} + +fn main() { + env_logger::init(); + + let matches = App::new("mos6502-cli") + .version("0.0.1") + .author("Alexander Akhmetov") + .arg(Arg::with_name("binary").short("b").help("Load binary file")) + .arg( + Arg::with_name("disable-screen") + .short("s") + .help("Disable ncurses screen"), + ) + .arg( + Arg::with_name("start-addr") + .short("a") + .help("Start at addr") + .takes_value(true), + ) + .arg( + Arg::with_name("load-addr") + .short("l") + .help("Load program at addr") + .takes_value(true), + ) + .arg( + Arg::with_name("program") + .short("p") + .help("Load additional program to EA60") + .takes_value(true), + ) + .get_matches(); + + let start_at_addr = matches.value_of("start-addr").unwrap_or("FF00"); + let start_at_addr = + u16::from_str_radix(start_at_addr, 16).expect("Can't parse HEX start address"); + let load_program_at = matches.value_of("load-addr").unwrap_or("E000"); + let load_program_at = + u16::from_str_radix(load_program_at, 16).expect("Can't parse HEX start address"); + + let mut apple1 = Apple1::new(matches.is_present("disable-screen"), "sys/wozmon.bin"); + + if matches.is_present("program") { + let original_pc = apple1.cpu.pc; + let filename = matches.value_of("program").unwrap(); + if filename.ends_with("asm") { + apple1.load(&assemble_file(filename), load_program_at); + } else { + apple1.load(&fs::read(filename).unwrap(), load_program_at); + } + apple1.cpu.pc = original_pc; + } + + apple1.cpu.pc = start_at_addr; + + apple1.run(); +} diff --git a/src/asm.rs b/src/asm.rs index e69de29..4b8abd1 100644 --- a/src/asm.rs +++ b/src/asm.rs @@ -0,0 +1,147 @@ +use std::fs; + +use crate::operation; + +pub fn assemble_file(filename: &str) -> Vec { + let asm_content = fs::read_to_string(filename).expect("Can't read the asm file"); + assemble(&asm_content) +} + +pub fn assemble(program: &str) -> Vec { + let lines = program.split('\n'); + let mut bytes = vec![]; + + for line in lines { + let t_line = line.trim(); + if !t_line.is_empty() { + let (operation, mut args) = parse_line(t_line); + bytes.push(operation); + bytes.append(&mut args); + } + } + + bytes +} + +fn parse_line(line: &str) -> (u8, Vec) { + let split: Vec<&str> = line.splitn(2, ' ').collect(); + + let mut args = vec![]; + let mut addressing = operation::AddressingMode::Implied; + let operation_name = split[0]; + + if split.len() != 1 { + match split[1] { + arg if arg.starts_with("#$") => { + addressing = operation::AddressingMode::Immediate; + let arg = arg.trim_start_matches("#$"); + args.push(u8::from_str_radix(arg, 16).unwrap()); + } + arg if arg.starts_with('$') => { + let arg = arg.trim_start_matches('$'); + + // if arg.len() == 2 - we use zeropage addressing + match arg.len() { + 2 => { + addressing = operation::AddressingMode::ZeroPage; + args.push(u8::from_str_radix(arg, 16).unwrap()); + } + 4 => { + addressing = operation::AddressingMode::Absolute; + let arg = u16::from_str_radix(arg, 16).unwrap(); + args.push(arg as u8); + args.push((arg >> 8) as u8); + } + _ => panic!("Can't parse argument"), + } + } + _ => panic!("unknown operand"), + }; + } + + // first it looks only by name, if there are many opcodes with such name, + // it looks by name and addressing + // todo: change + let opcodes = operation::by_name(operation_name); + let opcode = match opcodes.len() { + 0 => panic!("unknown operation name: {}", operation_name), + 1 => opcodes[0], + _ => operation::by_name_and_addressing(operation_name, addressing), + }; + + (opcode.code, args) +} + +pub fn disassemble(buf: &[u8]) -> String { + let op = operation::by_code(buf[0]); + + let mut instruction = String::from(op.name); + + if op.length > 1 { + match op.addressing { + operation::AddressingMode::Immediate => instruction = format!("{} #$", instruction), + _ => instruction = format!("{} #", instruction), + } + instruction = format!("{}{:X}", instruction, buf[1]); + } + if op.length > 2 { + instruction = format!("{}{:X}", instruction, buf[2]); + } + + instruction +} + +#[cfg(test)] +mod tests { + use crate::asm::assemble; + + #[test] + fn test_assemble_one_line() { + let program = "BRK"; + let bytes = assemble(&program); + let expected: [u8; 1] = [0]; + assert_eq!(&bytes, &expected); + } + + #[test] + fn test_assemble_one_line_immediate_addressing() { + let program = "LDA #$AF"; + let bytes = assemble(&program); + let expected: [u8; 2] = [0xA9, 0xAF]; + assert_eq!(&bytes, &expected); + } + + #[test] + fn test_assemble() { + let program = "LDA #$c0 + TAX + INX + ADC #$c4 + BRK"; + let bytes = assemble(&program); + let expected: [u8; 7] = [0xa9, 0xc0, 0xaa, 0xe8, 0x69, 0xc4, 0x00]; + assert_eq!(&bytes, &expected); + } + + #[test] + fn test_assemble_2() { + let program = "LDA #$80 + STA $01 + ADC $01"; + let bytes = assemble(&program); + let expected: [u8; 6] = [0xa9, 0x80, 0x85, 0x01, 0x65, 0x01]; + assert_eq!(&bytes, &expected); + } + + #[test] + fn code_with_bne_instruction() { + let program = "LDX #$5 + DEX + CPX #$0 + BNE #$FD + BRK"; + let bytes = assemble(&program); + let expected: [u8; 8] = [0xa2, 0x05, 0xca, 0xe0, 0x00, 0xd0, 0xfd, 0x00]; + assert_eq!(&bytes, &expected); + } +} diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..37c2e5b --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,76 @@ +use std::fs; +use std::sync::mpsc; +use std::sync::mpsc::{Receiver, Sender}; +use std::{thread, time}; + +extern crate mos6502; +use mos6502::asm::assemble_file; +use mos6502::cpu::CPU; + +extern crate ncurses; + +extern crate clap; +use clap::{App, Arg}; + +#[macro_use] +extern crate log; +extern crate env_logger; + +fn main() { + env_logger::init(); + + let matches = App::new("mos6502-cli") + .version("0.1.0") + .author("Alexander Akhmetov") + .arg( + Arg::with_name("binary") + .short("b") + .help("Load binary file") + .index(1), + ) + .arg( + Arg::with_name("start-addr") + .short("a") + .help("Start at addr") + .index(2), + ) + .get_matches(); + + let start_at_addr = matches.value_of("start-addr").unwrap(); + let start_at_addr = + u16::from_str_radix(start_at_addr, 16).expect("Can't parse HEX start address"); + + let mut cpu = CPU::new(); + + fn write_callback(addr: u16, value: u8) -> Option<(u16, u8)> { + match (addr, value) { + (0x000F, v) => { + // info!("0x000f: 0x{:X}", v); + None + } + _ => None, + } + }; + cpu.memory.register_write_callback(write_callback); + + let filename = matches.value_of("binary").unwrap(); + cpu.load(&fs::read(filename).unwrap(), 0); + cpu.pc = start_at_addr; + + let mut trapped = 0; + let mut brk = false; + while !brk { + brk = cpu.step(); + if cpu.memory.get(cpu.pc) == 0xD0 && cpu.memory.get(cpu.pc + 1) == 0xFE { + trapped += 1; + } else { + trapped = 0; + } + if trapped == 3 { + brk = true; + info!("possible cycle detected"); + } + } + + info!("mem 000F: 0x{:X}", cpu.memory.get(0x000F)); +} diff --git a/src/cpu.rs b/src/cpu.rs index 1963c34..b109631 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -1,14 +1,41 @@ +use crate::mem::Memory; use crate::operation; use crate::utils; +const DEFAULT_FLAGS: u8 = 0b0011_0000; +const FLAG_NEGATIVE: u8 = 0b1000_0000; +const FLAG_OVERFLOW: u8 = 0b0100_0000; +const FLAG_B: u8 = 0b0001_0000; +const FLAG_DECIMAL: u8 = 0b0000_1000; +const FLAG_INTERRUPT: u8 = 0b0000_0100; +const FLAG_ZERO: u8 = 0b0000_0010; +const FLAG_CARRY: u8 = 0b0000_0001; + +const STACK_BOTTOM: u16 = 0x0100; + pub struct CPU { - a: u8, - x: u8, - y: u8, - s: u8, - p: u8, - pc: u16, - memory: [u8; 64 * 1024], + pub a: u8, + pub x: u8, + pub y: u8, + + // status register + // Bit No. 7 6 5 4 3 2 1 0 + // N O B D I Z C + // bit 5 is not used, supposed to be 1 all the time + // C - carry + // Z - zero + // I - interrupt disable + // D - decimal + // B - + // O - overflow + // N - negative + pub p: u8, + pub sp: u8, + + // + pub pc: u16, + pub memory: Memory, + pub cycle: usize, } impl CPU { @@ -17,232 +44,2522 @@ impl CPU { a: 0, x: 0, y: 0, - s: 0, - p: 0, + p: DEFAULT_FLAGS, + sp: 0xff, pc: 0, - memory: [0; 64 * 1024], + memory: Memory::new(), + cycle: 0, + } + } + + pub fn load(&mut self, buf: &[u8], addr: u16) { + self.memory.load(addr, buf); + self.pc = addr; + } + + pub fn step(&mut self) -> bool { + let operation = operation::by_code(self.memory.get(self.pc)); + info!( + "[cpu] [PC:${:04X} SP:${:02X} STP:${:02X} A:${:02X} X:${:02X} Y:${:02X} P:{:08b}] (op:{} code:${:02X} length:{} data:{:X?}) cycle:{}", + self.pc, self.sp, self.memory.get(STACK_BOTTOM + 0xff), self.a, self.x, self.y, self.p, operation.name, operation.code, + operation.length, + &self.memory.bytes()[(self.pc) as usize..(self.pc + operation.length as u16) as usize], + self.cycle + ); + + self.pc += u16::from(operation.length); + self.cycle += 1; + + match operation.name { + "BRK" => self.operation_brk(), + "RTI" => self.operation_rti(), + "LDA" => self.operation_lda(operation), + "SBC" => self.operation_sbc(operation), + "LDX" => self.operation_ldx(operation), + "EOR" => self.operation_eor(operation), + "AND" => self.operation_and(operation), + "ORA" => self.operation_ora(operation), + "LDY" => self.operation_ldy(operation), + "ADC" => self.operation_adc(operation), + "STA" => self.operation_sta(operation), + "STX" => self.operation_stx(operation), + "STY" => self.operation_sty(operation), + "INC" => self.operation_inc(operation), + "DEC" => self.operation_dec(operation), + "INX" => self.operation_inx(), + "INY" => self.operation_iny(), + "DEX" => self.operation_dex(), + "DEY" => self.operation_dey(), + "TAX" => self.operation_tax(), + "BNE" => self.operation_bne(operation), + "BEQ" => self.operation_beq(operation), + "BCC" => self.operation_bcc(operation), + "BCS" => self.operation_bcs(operation), + "BVC" => self.operation_bvc(operation), + "BVS" => self.operation_bvs(operation), + "BPL" => self.operation_bpl(operation), + "BMI" => self.operation_bmi(operation), + "CMP" => self.operation_cmp(operation), + "CPX" => self.operation_cpx(operation), + "CPY" => self.operation_cpy(operation), + "CLC" => self.operation_clc(), + "CLD" => self.operation_cld(), + "CLI" => self.operation_cli(), + "CLV" => self.operation_clv(), + "SEC" => self.operation_sec(), + "SED" => self.operation_sed(), + "SEI" => self.operation_sei(), + "JMP" => self.operation_jmp(operation), + "JSR" => self.operation_jsr(operation), + "RTS" => self.operation_rts(), + "LSR" => self.operation_lsr(operation), + "PHA" => self.operation_pha(), + "PLA" => self.operation_pla(), + "TXA" => self.operation_txa(), + "TXS" => self.operation_txs(), + "TSX" => self.operation_tsx(), + "PLP" => self.operation_plp(), + "PHP" => self.operation_php(), + "TYA" => self.operation_tya(), + "TAY" => self.operation_tay(), + "ROR" => self.operation_ror(operation), + "ROL" => self.operation_rol(operation), + "BIT" => self.operation_bit(operation), + "ASL" => self.operation_asl(operation), + "NOP" => {} + + _ => { + error!("unknown operation: code=0x{:X}", operation.code); + panic!("unknown operation"); + } + }; + + false + } + pub fn run(&mut self) { + while !self.step() {} + } + + fn operation_clc(&mut self) { + self.clear_carry(); + } + + fn operation_cli(&mut self) { + self.clear_interrupt(); + } + + fn operation_clv(&mut self) { + self.clear_overflow(); + } + + fn operation_cld(&mut self) { + self.clear_decimal(); + } + + fn operation_sec(&mut self) { + self.set_carry(); + } + + fn operation_sed(&mut self) { + self.set_decimal(); + } + + fn operation_sei(&mut self) { + self.set_interrupt(); + } + + fn operation_tax(&mut self) { + self.x = self.a; + + self.set_zero_if_needed(self.x); + self.set_negative_if_needed(self.x); + } + + fn operation_dex(&mut self) { + let (result, _) = self.x.overflowing_sub(1); + self.x = result; + + self.set_zero_if_needed(self.x); + self.set_negative_if_needed(self.x); + } + + fn operation_dey(&mut self) { + let (result, _) = self.y.overflowing_sub(1); + self.y = result; + + self.set_zero_if_needed(self.y); + self.set_negative_if_needed(self.y); + } + + fn operation_brk(&mut self) { + // set B flag (0b0001_0000) + self.set_b(); + // push return address (it's already incremented by 1, so we increment it only by 1) + self.stack_push_word(self.pc + 1); + self.stack_push(self.p); + + // now set interrupt flag and go to ISR + self.set_interrupt(); + let addr = utils::little_endian_to_u16(self.memory.get(0xFFFF), self.memory.get(0xFFFE)); + self.pc = addr; + } + + fn operation_rti(&mut self) { + self.p = self.stack_pop(); + self.pc = self.stack_pop_word(); + } + + fn set_b(&mut self) { + self.p |= FLAG_B; + } + + fn operation_inc(&mut self, operation: &operation::Operation) { + let addr = self.get_operand_address(operation); + let value = self.memory.get(addr); + let (result, _) = value.overflowing_add(1); + self.memory.set(addr, result); + + self.set_zero_if_needed(result); + self.set_negative_if_needed(result); + } + + fn operation_dec(&mut self, op: &operation::Operation) { + let addr = self.get_operand_address(op); + let value = self.memory.get(addr); + let (result, _) = value.overflowing_sub(1); + self.memory.set(addr, result); + + self.set_zero_if_needed(result); + self.set_negative_if_needed(result); + } + + fn operation_inx(&mut self) { + let (result, _) = self.x.overflowing_add(1); + self.x = result; + + self.set_negative_if_needed(self.x); + self.set_zero_if_needed(self.x); + } + + fn operation_iny(&mut self) { + let (result, _) = self.y.overflowing_add(1); + self.y = result; + + self.set_negative_if_needed(self.y); + self.set_zero_if_needed(self.y); + } + + fn operation_ora(&mut self, op: &operation::Operation) { + self.a |= self.get_operand(op); + + self.set_negative_if_needed(self.a); + self.set_zero_if_needed(self.a); + } + + fn operation_and(&mut self, op: &operation::Operation) { + self.a &= self.get_operand(op); + + self.set_negative_if_needed(self.a); + self.set_zero_if_needed(self.a); + } + + fn operation_eor(&mut self, op: &operation::Operation) { + self.a ^= self.get_operand(op); + + self.set_negative_if_needed(self.a); + self.set_zero_if_needed(self.a); + } + + fn operation_ldx(&mut self, operation: &operation::Operation) { + self.x = self.get_operand(operation); + + self.set_negative_if_needed(self.x); + self.set_zero_if_needed(self.x); + } + + fn operation_ldy(&mut self, operation: &operation::Operation) { + self.y = self.get_operand(operation); + + self.set_negative_if_needed(self.y); + self.set_zero_if_needed(self.y); + } + + fn operation_lda(&mut self, op: &operation::Operation) { + self.a = self.get_operand(op); + + self.set_negative_if_needed(self.a); + self.set_zero_if_needed(self.a); + } + + fn operation_sbc(&mut self, op: &operation::Operation) { + // SBC uses the complement of the carry + let operand = self.get_operand(op); + let carry = i16::from(1 - self.get_carry()); + let original_a = self.a; + let result = i16::from(original_a) - carry - i16::from(operand); + + self.a = result as u8; + + self.clear_overflow(); + if original_a as u8 >> 7 != self.a >> 7 { + self.set_overflow(); + } + + // clear carry if overflow in the bit 7 + if (result as u16) < 256 { + self.set_carry(); + } else { + self.clear_carry(); + } + + // http://www.righto.com/2012/12/the-6502-overflow-flag-explained.html + self.clear_overflow(); + if ((255 - operand) ^ self.a) & (original_a ^ self.a) & 0x80 != 0 { + self.set_overflow(); + }; + + self.set_negative_if_needed(self.a); + self.set_zero_if_needed(self.a); + } + + fn operation_adc(&mut self, op: &operation::Operation) { + let operand = u16::from(self.get_operand(op)); + let original_a = u16::from(self.a); + let result = original_a + operand + u16::from(self.get_carry()); + self.a = result as u8; + + // unsigned carry + self.clear_carry(); + if result > 255 { + self.set_carry(); + }; + + // http://www.righto.com/2012/12/the-6502-overflow-flag-explained.html + self.clear_overflow(); + if (operand ^ result) & (original_a ^ result) & 0x80 != 0 { + self.set_overflow(); + }; + + self.set_negative_if_needed(self.a); + self.set_zero_if_needed(self.a); + } + + fn operation_beq(&mut self, operation: &operation::Operation) { + if self.get_zero() == FLAG_ZERO { + self.branch(operation); + } + } + + fn operation_bne(&mut self, operation: &operation::Operation) { + if self.get_zero() != FLAG_ZERO { + self.branch(operation); + } + } + + fn branch(&mut self, operation: &operation::Operation) { + let offset = self.get_operand(operation); + if offset > 127 { + debug!("branch jump, offset=-{}", (256 - offset as isize)); + self.pc -= 256 - u16::from(offset); + } else { + debug!("branch jump, offset={}", offset); + self.pc += u16::from(offset); + } + } + + fn operation_bcc(&mut self, operation: &operation::Operation) { + if self.get_carry() == 0 { + self.branch(operation); + } + } + + fn operation_bvs(&mut self, operation: &operation::Operation) { + if self.get_overflow() != 0 { + self.branch(operation); + } + } + + fn operation_bvc(&mut self, operation: &operation::Operation) { + if self.get_overflow() == 0 { + self.branch(operation); + } + } + + fn operation_bcs(&mut self, operation: &operation::Operation) { + if self.get_carry() != 0 { + self.branch(operation); + } + } + + fn operation_bmi(&mut self, operation: &operation::Operation) { + if self.get_negative() == FLAG_NEGATIVE { + self.branch(operation); + } + } + + fn operation_bpl(&mut self, operation: &operation::Operation) { + if self.get_negative() == 0 { + self.branch(operation); + } + } + + fn operation_cpy(&mut self, operation: &operation::Operation) { + let operand = self.get_operand(operation); + self.compare(self.y, operand); + } + + fn operation_jmp(&mut self, op: &operation::Operation) { + match op.addressing { + operation::AddressingMode::IndirectAbsolute => { + let addr = self.get_operand_address(op); + self.pc = + utils::little_endian_to_u16(self.memory.get(addr + 1), self.memory.get(addr)); + } + _ => self.pc = self.get_operand_address(op), + } + } + + fn operation_rts(&mut self) { + self.pc = self.stack_pop_word() + 1; + } + + fn operation_jsr(&mut self, operation: &operation::Operation) { + self.stack_push_word(self.pc - 1); + self.pc = self.get_operand_address(operation); + } + + fn operation_asl(&mut self, _op: &operation::Operation) { + self.clear_carry(); + if self.a >> 7 == 1 { + self.set_carry(); // bit 7 to carry + } + + self.a <<= 1; + + if self.get_zero() != 0 { + // zero flag to 0 bit + self.a |= 0b0000_0001; + } + self.set_zero_if_needed(self.a); + self.set_negative_if_needed(self.a); + } + + fn operation_bit(&mut self, op: &operation::Operation) { + let operand = self.get_operand(op); + + self.set_zero_if_needed(self.a & operand); + + self.clear_overflow(); + if operand & 0b0100_0000 != 0 { + self.set_overflow(); + } + self.set_negative_if_needed(operand); + } + + fn operation_rol(&mut self, op: &operation::Operation) { + // rotate left, carry to 0 bit and 7 bit to carry + let mut value = match op.addressing { + operation::AddressingMode::AccumulatorAddressing => self.a, + _ => self.get_operand(op), + }; + + let original_carry = match self.get_carry() { + FLAG_CARRY => true, + _ => false, + }; + + if (value & 0x80) != 0 { + self.set_carry(); + } else { + self.clear_carry(); + } + + value = (value << 1) as u8; + + if original_carry { + value |= 0b0000_0001; + }; + + self.set_zero_if_needed(value); + self.set_negative_if_needed(value); + + match op.addressing { + operation::AddressingMode::AccumulatorAddressing => self.a = value, + _ => { + let addr = self.get_operand_address(op); + self.memory.set(addr, value); + } + } + } + + fn operation_ror(&mut self, op: &operation::Operation) { + // rotate right, carry to 7 bit and 0 bit to carry + let mut value = self.get_operand(op); + + let original_carry = match self.get_carry() { + FLAG_CARRY => true, + _ => false, + }; + + if (value & 0x01) != 0 { + self.set_carry(); + } else { + self.clear_carry(); + } + + value = (value >> 1) as u8; + + if original_carry { + value |= 0b1000_0000; + }; + + self.set_zero_if_needed(value); + self.set_negative_if_needed(value); + let addr = self.get_operand_address(op); + + match op.addressing { + operation::AddressingMode::AccumulatorAddressing => self.a = value, + _ => self.memory.set(addr, value), + } + } + + fn operation_lsr(&mut self, op: &operation::Operation) { + let operand = match op.addressing { + operation::AddressingMode::AccumulatorAddressing => self.a, + _ => self.get_operand(op), + }; + let zero_bit = operand & 0xF; + let value = operand >> 1; + + if zero_bit != 0 { + self.set_carry(); + } else { + self.clear_carry(); + } + + self.set_zero_if_needed(value); + + match op.addressing { + operation::AddressingMode::AccumulatorAddressing => self.a = value, + _ => { + let addr = self.get_operand_address(op); + self.memory.set(addr, value); + } + } + } + + fn operation_txa(&mut self) { + self.a = self.x; + self.set_negative_if_needed(self.a); + self.set_zero_if_needed(self.a); + } + + fn operation_tay(&mut self) { + self.y = self.a; + self.set_negative_if_needed(self.y); + self.set_zero_if_needed(self.y); + } + + fn operation_txs(&mut self) { + self.sp = self.x; + } + + fn operation_tsx(&mut self) { + self.x = self.sp; + self.set_negative_if_needed(self.x); + self.set_zero_if_needed(self.x); + } + + fn operation_php(&mut self) { + self.stack_push(self.p); + } + + fn operation_plp(&mut self) { + self.p = self.stack_pop() | 0b0011_0000; + } + + fn operation_tya(&mut self) { + self.a = self.y; + self.set_negative_if_needed(self.y); + self.set_zero_if_needed(self.y); + } + + fn operation_pha(&mut self) { + self.stack_push(self.a); + } + + fn stack_push_word(&mut self, value: u16) { + let higher = ((value >> 8) & 0xFF) as u8; + self.stack_push(higher); + + let lower = (value & 0xFF) as u8; + self.stack_push(lower); + } + + fn stack_pop_word(&mut self) -> u16 { + let lower = self.stack_pop(); + let higher = self.stack_pop(); + + (u16::from(higher) << 8) | u16::from(lower) + } + + fn stack_push(&mut self, value: u8) { + self.memory.set(STACK_BOTTOM + u16::from(self.sp), value); + self.sp = (((self.sp as i16) - 1) & 0xFF) as u8; + } + + fn operation_pla(&mut self) { + self.a = self.stack_pop(); + self.set_negative_if_needed(self.a); + self.set_zero_if_needed(self.a); + } + + fn stack_pop(&mut self) -> u8 { + self.sp = (((self.sp as i16) + 1) & 0xFF) as u8; + self.memory.get(STACK_BOTTOM + u16::from(self.sp)) + } + + fn operation_cmp(&mut self, operation: &operation::Operation) { + let operand = self.get_operand(operation); + self.compare(self.a, operand); + } + + fn operation_cpx(&mut self, operation: &operation::Operation) { + let operand = self.get_operand(operation); + self.compare(self.x, operand); + } + + fn compare(&mut self, left_operand: u8, right_operand: u8) { + self.clear_zero(); + self.clear_carry(); + self.clear_negative(); + + if left_operand == right_operand { + self.set_zero(); + self.set_carry(); + } + + if left_operand > right_operand { + self.set_carry(); + } + + let result = (i16::from(left_operand) - i16::from(right_operand)) as u8; + self.set_negative_if_needed(result); + } + + fn get_overflow(&self) -> u8 { + self.p & FLAG_OVERFLOW + } + + fn set_overflow(&mut self) { + self.p |= FLAG_OVERFLOW; + } + + fn clear_overflow(&mut self) { + self.p &= !FLAG_OVERFLOW; + } + + fn set_decimal(&mut self) { + self.p |= FLAG_DECIMAL; + } + + fn set_interrupt(&mut self) { + self.p |= FLAG_INTERRUPT; + } + + fn set_carry(&mut self) { + self.p |= FLAG_CARRY; + } + + fn clear_carry(&mut self) { + self.p &= !FLAG_CARRY; + } + + fn get_negative(&self) -> u8 { + self.p & FLAG_NEGATIVE + } + + fn get_carry(&self) -> u8 { + self.p & FLAG_CARRY + } + + fn clear_decimal(&mut self) { + self.p &= !FLAG_DECIMAL; + } + + fn clear_interrupt(&mut self) { + self.p &= !FLAG_INTERRUPT; + } + + fn get_zero(&self) -> u8 { + self.p & FLAG_ZERO + } + + fn set_zero_if_needed(&mut self, result: u8) { + self.clear_zero(); + if result == 0 { + self.set_zero(); + } + } + + fn set_zero(&mut self) { + self.p |= FLAG_ZERO; + } + + fn clear_zero(&mut self) { + self.p &= !FLAG_ZERO; + } + + fn set_negative_if_needed(&mut self, result: u8) { + self.clear_negative(); + if result > 127 { + self.set_negative(); + } + } + + fn set_negative(&mut self) { + self.p |= FLAG_NEGATIVE; + } + + fn clear_negative(&mut self) { + self.p &= !FLAG_NEGATIVE; + } + + fn operation_sty(&mut self, operation: &operation::Operation) { + let addr = self.get_operand_address(operation); + self.memory.set(addr, self.y); + } + + fn operation_stx(&mut self, operation: &operation::Operation) { + let addr = self.get_operand_address(operation); + self.memory.set(addr, self.x); + } + + fn operation_sta(&mut self, op: &operation::Operation) { + let addr = self.get_operand_address(op); + self.memory.set(addr, self.a); + } + + fn get_operand(&mut self, op: &operation::Operation) -> u8 { + let addr = self.get_operand_address(op); + self.memory.get(addr) + } + + fn get_operand_address(&mut self, op: &operation::Operation) -> u16 { + match op.addressing { + operation::AddressingMode::Immediate => self.pc - 1, + operation::AddressingMode::ZeroPage => u16::from(self.next_byte_number()) & 0xFF, + operation::AddressingMode::ZeroPageIndexedX => { + (u16::from(self.next_byte_number()) + u16::from(self.x)) & 0xFF + } + operation::AddressingMode::ZeroPageIndexedY => { + (u16::from(self.next_byte_number()) + u16::from(self.y)) & 0xFF + } + operation::AddressingMode::Absolute => self.next_word_in_memory(), + operation::AddressingMode::IndirectAbsolute => self.next_word_in_memory(), + operation::AddressingMode::IndirectIndexedX => { + let addr = (u16::from(self.next_byte_number()) + u16::from(self.x)) & 0xFF; + utils::little_endian_to_u16(self.memory.get(addr + 1), self.memory.get(addr)) + } + operation::AddressingMode::IndirectIndexed => { + let addr = u16::from(self.next_byte_number()); + let value = + utils::little_endian_to_u16(self.memory.get(addr + 1), self.memory.get(addr)); + value + self.y as u16 + } + operation::AddressingMode::RelativeAddressing => self.pc - 1, + operation::AddressingMode::AbsoluteIndexedY => { + self.next_word_in_memory() + u16::from(self.y) + } + operation::AddressingMode::AbsoluteIndexedX => { + self.next_word_in_memory() + u16::from(self.x) + } + _ => { + error!("unknown addressing mode for operation code=0x{:X}", op.code); + panic!("unknown addressing mode"); + } + } + } + + fn next_byte_number(&mut self) -> u8 { + self.memory.get(self.pc - 1) + } + + fn next_word_in_memory(&mut self) -> u16 { + // returns next two bytes (self.pc - 1, self.pc - 2) as one u16 + // (converts from little endian) + utils::little_endian_to_u16(self.memory.get(self.pc - 1), self.memory.get(self.pc - 2)) + } +} + +#[cfg(test)] +mod tests { + use crate::cpu::*; + use crate::operation; + + const STACK_TOP: u16 = 0x01ff; + + #[test] + fn new() { + let c = CPU::new(); + + assert_eq!(c.a, 0); + assert_eq!(c.x, 0); + assert_eq!(c.y, 0); + assert_eq!(c.p, DEFAULT_FLAGS); + assert_eq!(c.pc, 0); + assert_eq!(c.sp, 0xff); + assert_eq!(&c.memory.bytes()[..], &[0; 64 * 1024][..]); + } + + #[test] + fn load() { + // test when it loads a three bytes dump at a specific location + let mut cpu = CPU::new(); + + let addr = 32768; + let bytes = [1; 3]; + + assert_eq!(&cpu.memory.bytes()[..], &[0; 64 * 1024][..]); + cpu.load(&bytes, addr); + + let exp_memory = [0, 1, 1, 1, 0]; + assert_eq!(&cpu.memory.bytes()[32767..32772], exp_memory); + + assert_eq!(cpu.pc, addr); + } + + #[test] + #[should_panic] + fn load_data_too_big() { + let mut cpu = CPU::new(); + cpu.load(&[1, 2, 3], 65535); + } + + #[test] + fn clear_carry() { + let mut cpu = CPU::new(); + + cpu.set_carry(); + assert_eq!(cpu.p & 0b0000_0001, 1); + + cpu.clear_carry(); + assert_eq!(cpu.p & 0b0000_0001, 0); + } + + #[test] + fn operation_sed() { + let mut cpu = CPU::new(); + + let program: [u8; 2] = [ + operation::by_name_and_addressing("SED", operation::AddressingMode::Implied).code, + operation::by_name_and_addressing("BRK", operation::AddressingMode::Implied).code, + ]; + cpu.load(&program, 0); + + assert_eq!(cpu.p & FLAG_DECIMAL, 0); + + cpu.step(); + + assert_eq!(cpu.p & FLAG_DECIMAL, FLAG_DECIMAL); + } + + #[test] + fn operation_sei() { + let mut cpu = CPU::new(); + + let program: [u8; 2] = [ + operation::by_name_and_addressing("SEI", operation::AddressingMode::Implied).code, + operation::by_name_and_addressing("BRK", operation::AddressingMode::Implied).code, + ]; + cpu.load(&program, 0); + + assert_eq!(cpu.p & FLAG_INTERRUPT, 0); + + cpu.step(); + + assert_eq!(cpu.p & FLAG_INTERRUPT, FLAG_INTERRUPT); + } + + #[test] + fn operation_sec() { + let mut cpu = CPU::new(); + + let program: [u8; 2] = [ + operation::by_name_and_addressing("SEC", operation::AddressingMode::Implied).code, + operation::by_name_and_addressing("BRK", operation::AddressingMode::Implied).code, + ]; + cpu.load(&program, 0); + + assert_eq!(cpu.p & FLAG_CARRY, 0); + + cpu.step(); + + assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY); + } + + #[test] + fn clear_overflow() { + let mut cpu = CPU::new(); + + cpu.set_overflow(); + assert_eq!(cpu.p & FLAG_OVERFLOW, FLAG_OVERFLOW); + + cpu.clear_overflow(); + assert_eq!(cpu.p & FLAG_OVERFLOW, 0); + } + + #[test] + fn clear_interrupt() { + let mut cpu = CPU::new(); + + cpu.p = FLAG_INTERRUPT; + cpu.clear_interrupt(); + assert_eq!(cpu.p & FLAG_INTERRUPT, 0); + } + + #[test] + fn clear_decimal() { + let mut cpu = CPU::new(); + + cpu.p = FLAG_DECIMAL; + cpu.clear_decimal(); + assert_eq!(cpu.p & FLAG_DECIMAL, 0); + } + + // ############# LDA + + #[test] + fn operation_lda() { + let mut cpu = CPU::new(); + + let program: [u8; 3] = [ + operation::by_name_and_addressing("LDA", operation::AddressingMode::Immediate).code, + 10, + operation::by_name_and_addressing("BRK", operation::AddressingMode::Implied).code, + ]; + cpu.load(&program, 0); + + assert_eq!(cpu.a, 0); + + cpu.step(); + + assert_eq!(cpu.a, 10); + } + + #[test] + fn operation_lda_zero_page() { + let mut cpu = CPU::new(); + + let program: [u8; 3] = [ + operation::by_name_and_addressing("LDA", operation::AddressingMode::ZeroPage).code, + 10, + operation::by_name_and_addressing("BRK", operation::AddressingMode::Implied).code, + ]; + cpu.load(&program, 0); + cpu.memory.set(10, 55); + + assert_eq!(cpu.a, 0); + + cpu.step(); + + assert_eq!(cpu.a, 55); + } + + #[test] + fn operation_lda_absolute() { + let mut cpu = CPU::new(); + + let program: [u8; 4] = [ + operation::by_name_and_addressing("LDA", operation::AddressingMode::Absolute).code, + 0x00, + 0xff, + operation::by_name_and_addressing("BRK", operation::AddressingMode::Implied).code, + ]; + cpu.load(&program, 0); + cpu.memory.set(0xff00, 99); + + assert_eq!(cpu.a, 0); + + cpu.step(); + + assert_eq!(cpu.a, 99); + } + + #[test] + fn operation_lda_absolute_indexed_y() { + let mut cpu = CPU::new(); + cpu.y = 1; + + let program: [u8; 4] = [ + operation::by_name_and_addressing("LDA", operation::AddressingMode::AbsoluteIndexedY) + .code, + 0x01, + 0xff, + operation::by_name_and_addressing("BRK", operation::AddressingMode::Implied).code, + ]; + cpu.load(&program, 0); + cpu.memory.set(0xff02, 21); + + assert_eq!(cpu.a, 0); + + cpu.step(); + + assert_eq!(cpu.a, 21); + } + + #[test] + fn operation_lda_absolute_indexed_x() { + let mut cpu = CPU::new(); + cpu.x = 3; + + let program: [u8; 3] = [ + operation::by_name_and_addressing("LDA", operation::AddressingMode::AbsoluteIndexedX) + .code, + 0x02, + 0xff, + ]; + cpu.load(&program, 0); + cpu.memory.set(0xff05, 11); + + assert_eq!(cpu.a, 0); + + cpu.step(); + + assert_eq!(cpu.a, 11); + } + // ############# LDX + + #[test] + fn operation_ldx() { + let mut cpu = CPU::new(); + + let program: [u8; 2] = [ + operation::by_name_and_addressing("LDX", operation::AddressingMode::Immediate).code, + 10, + ]; + cpu.load(&program, 0); + + assert_eq!(cpu.x, 0); + + cpu.step(); + + assert_eq!(cpu.x, 10); + } + + #[test] + fn operation_ldx_zero_page() { + let mut cpu = CPU::new(); + + let program: [u8; 2] = [ + operation::by_name_and_addressing("LDX", operation::AddressingMode::ZeroPage).code, + 10, + ]; + cpu.load(&program, 0); + cpu.memory.set(10, 55); + + assert_eq!(cpu.x, 0); + + cpu.step(); + + assert_eq!(cpu.x, 55); + } + + #[test] + fn operation_ldx_absolute() { + let mut cpu = CPU::new(); + + let program: [u8; 3] = [ + operation::by_name_and_addressing("LDX", operation::AddressingMode::Absolute).code, + 0x00, + 0xff, + ]; + cpu.load(&program, 0); + cpu.memory.set(0xff00, 99); + + assert_eq!(cpu.x, 0); + + cpu.step(); + + assert_eq!(cpu.x, 99); + } + + // ############# ADC + + #[test] + fn operation_adc_immediate_with_overflow() { + let mut cpu = CPU::new(); + cpu.a = 255; + + let program: [u8; 2] = [ + operation::by_name_and_addressing("ADC", operation::AddressingMode::Immediate).code, + 1, + ]; + cpu.load(&program, 0); + + assert_eq!(cpu.a, 255); + + cpu.step(); + + assert_eq!(cpu.a, 0); + assert_eq!(cpu.p, 0b00110011); // overflow flag + } + + #[test] + fn test_adc_for_overflow_and_carry() { + // http://www.righto.com/2012/12/the-6502-overflow-flag-explained.html + let experiments = [ + (80, 16, false, false), + (80, 80, false, true), + (80, 144, false, false), + (80, 208, true, false), + (208, 16, false, false), + (208, 80, true, false), + (208, 144, true, true), + (208, 208, true, false), + ]; + + for (a, operand, carry, overflow) in experiments.iter() { + let mut cpu = CPU::new(); + cpu.a = *a; + + let program: [u8; 2] = [ + operation::by_name_and_addressing("ADC", operation::AddressingMode::Immediate).code, + *operand, + ]; + cpu.load(&program, 0); + cpu.step(); + + if *carry { + assert_eq!(cpu.p & FLAG_CARRY, 0b0000_0001); + } else { + assert_eq!(cpu.p & FLAG_CARRY, 0b0000_0000); + } + if *overflow { + assert_eq!(cpu.p & FLAG_OVERFLOW, 0b0100_0000); + } else { + assert_eq!(cpu.p & FLAG_OVERFLOW, 0b0000_0000); + } + } + } + + #[test] + fn operation_adc_add_two_positive_numbers_and_get_negative_result_must_set_overflow() { + let mut cpu = CPU::new(); + cpu.a = 80; + + let program: [u8; 2] = [ + operation::by_name_and_addressing("ADC", operation::AddressingMode::Immediate).code, + 80, + ]; + cpu.load(&program, 0); + + cpu.step(); + + assert_eq!(cpu.a, 160); + assert_eq!(cpu.p, 0b1111_0000); + } + + #[test] + fn operation_adc_immediate() { + let mut cpu = CPU::new(); + cpu.a = 10; + + let program: [u8; 2] = [ + operation::by_name_and_addressing("ADC", operation::AddressingMode::Immediate).code, + 15, + ]; + cpu.load(&program, 0); + + assert_eq!(cpu.a, 10); + + cpu.step(); + + assert_eq!(cpu.a, 25); + assert_eq!(cpu.p, 0b00110000); // no overflow + } + + #[test] + fn operation_adc_absolute() { + let mut cpu = CPU::new(); + cpu.a = 10; + + let program: [u8; 3] = [ + operation::by_name_and_addressing("ADC", operation::AddressingMode::Absolute).code, + 0xff, + 0xaa, + ]; + cpu.load(&program, 0); + cpu.memory.set(43775, 15); // 0xaaff + + assert_eq!(cpu.a, 10); + + cpu.step(); + + assert_eq!(cpu.a, 25); + } + + #[test] + fn operation_stx_zeropage() { + let mut cpu = CPU::new(); + cpu.x = 5; + + let program: [u8; 3] = [ + operation::by_name_and_addressing("STX", operation::AddressingMode::ZeroPage).code, + 22, + operation::by_name_and_addressing("BRK", operation::AddressingMode::Implied).code, + ]; + cpu.load(&program, 0); + + assert_eq!(cpu.memory.get(22), 0); + + cpu.step(); + + assert_eq!(cpu.memory.get(22), 5); + } + + #[test] + fn operation_stx() { + let mut cpu = CPU::new(); + cpu.x = 5; + + let program: [u8; 4] = [ + operation::by_name_and_addressing("STX", operation::AddressingMode::Absolute).code, + 12, + 0, + operation::by_name_and_addressing("BRK", operation::AddressingMode::Implied).code, + ]; + cpu.load(&program, 0); + + assert_eq!(cpu.memory.get(12), 0); + + cpu.step(); + + assert_eq!(cpu.memory.get(12), 5); + } + + #[test] + fn operation_sta() { + let mut cpu = CPU::new(); + cpu.a = 10; + + let program: [u8; 4] = [ + operation::by_name_and_addressing("STA", operation::AddressingMode::Absolute).code, + 0x30, + 0x01, + operation::by_name_and_addressing("BRK", operation::AddressingMode::Implied).code, + ]; + cpu.load(&program, 0); + + assert_eq!(cpu.memory.get(0x0130), 0); + + cpu.step(); + + assert_eq!(cpu.memory.get(0x0130), cpu.a); + } + + #[test] + fn operation_sta_indirect_indexed_x() { + let mut cpu = CPU::new(); + cpu.a = 10; + cpu.x = 5; + + // STA ($0010,X) + let program: [u8; 2] = [ + operation::by_name_and_addressing("STA", operation::AddressingMode::IndirectIndexedX) + .code, + 0x2, + ]; + cpu.memory.set(0x7, 0xCC); + cpu.memory.set(0x8, 0xDD); + cpu.load(&program, 0); + + assert_eq!(cpu.memory.get(0xDDCC), 0); + + cpu.step(); + + assert_eq!(cpu.memory.get(0xDDCC), 10); + } + + #[test] + fn operation_sta_zero_page_x_with_overflow() { + let mut cpu = CPU::new(); + cpu.a = 0xEE; + cpu.x = 0xCC; + + let program: [u8; 2] = [ + operation::by_name_and_addressing("STA", operation::AddressingMode::ZeroPageIndexedX) + .code, + 0xFF, + ]; + cpu.load(&program, 0); + + assert_eq!(cpu.memory.get(0xCB), 0); + + cpu.step(); + + assert_eq!(cpu.memory.get(0xCB), 0xEE); + } + + #[test] + fn operation_sta_zero_page_x() { + let mut cpu = CPU::new(); + cpu.a = 0xEE; + cpu.x = 2; + + let program: [u8; 2] = [ + operation::by_name_and_addressing("STA", operation::AddressingMode::ZeroPageIndexedX) + .code, + 0x5, + ]; + cpu.load(&program, 0); + + assert_eq!(cpu.memory.get(0x7), 0); + + cpu.step(); + + assert_eq!(cpu.memory.get(0x7), 0xEE); + } + + #[test] + fn operation_sta_indexed_indirect_y() { + let mut cpu = CPU::new(); + cpu.a = 0xFF; + cpu.y = 0x03; + + // STA ($10E0,)Y + // memory at location $00E0 holds a number 0xAA01 + // a must be stored at the address (0xAA01 + cpu.y) + let program: [u8; 2] = [ + operation::by_name_and_addressing("STA", operation::AddressingMode::IndirectIndexed) + .code, + 0x10, + ]; + cpu.memory.set(0x10, 0x01); + cpu.memory.set(0x11, 0xAA); + cpu.load(&program, 0); + + assert_eq!(cpu.memory.get(0xAA04), 0); + + cpu.step(); + + assert_eq!(cpu.memory.get(0xAA04), 0xFF); + assert_eq!(cpu.get_negative(), 0); + } + + #[test] + fn operation_sta_absolute_y() { + let mut cpu = CPU::new(); + cpu.a = 0xEE; + cpu.y = 2; + + let program: [u8; 3] = [ + operation::by_name_and_addressing("STA", operation::AddressingMode::AbsoluteIndexedY) + .code, + 0x2, + 0xE, + ]; + cpu.load(&program, 0); + + assert_eq!(cpu.memory.get(0x0E04), 0); + + cpu.step(); + + assert_eq!(cpu.memory.get(0x0E04), 0xEE); + } + + #[test] + fn operation_sta_absolute() { + let mut cpu = CPU::new(); + cpu.a = 0xEE; + + let program: [u8; 3] = [ + operation::by_name_and_addressing("STA", operation::AddressingMode::Absolute).code, + 0x2, + 0xE, + ]; + cpu.load(&program, 0); + + assert_eq!(cpu.memory.get(0x0E02), 0); + + cpu.step(); + + assert_eq!(cpu.memory.get(0x0E02), 0xEE); + } + + #[test] + fn test_next_two_bytes() { + let mut cpu = CPU::new(); + let bytes = [0, 0x0A, 0x0B]; + cpu.load(&bytes, 0); + cpu.pc = 3; + assert_eq!(cpu.next_word_in_memory(), 0x0B0A); + } + + #[test] + fn test_next_two_bytes_with_only_lower_byte() { + let mut cpu = CPU::new(); + let bytes = [0, 10, 0]; + cpu.load(&bytes, 0); + cpu.pc = 3; + assert_eq!(cpu.next_word_in_memory(), 10); + } + + #[test] + fn test_simple_program() { + // loads 100 from the memory + // adds 7 to the a register + // stores a register at address 0x000f + let mut cpu = CPU::new(); + cpu.a = 10; + + let program: [u8; 7] = [ + operation::by_name_and_addressing("LDA", operation::AddressingMode::Immediate).code, + 100, + operation::by_name_and_addressing("ADC", operation::AddressingMode::Immediate).code, + 7, + operation::by_name_and_addressing("STA", operation::AddressingMode::Absolute).code, + 15, + 0, + ]; + cpu.load(&program, 0); + + for _ in 0..3 { + cpu.step(); + } + + assert_eq!(cpu.a, 107); + assert_eq!(cpu.memory.get(15), 107); + } + + #[test] + fn test_dex_below_zero_sets_negative_flag() { + let mut cpu = CPU::new(); + + let program: [u8; 2] = [ + operation::by_name_and_addressing("DEX", operation::AddressingMode::Implied).code, + operation::by_name_and_addressing("BRK", operation::AddressingMode::Implied).code, + ]; + cpu.load(&program, 0); + cpu.x = 0; + + cpu.step(); + assert_eq!(cpu.x, 0xFF); + assert_eq!(cpu.p, 0b1011_0000); + } + + #[test] + fn test_dex_to_zero_sets_zero_flag() { + let mut cpu = CPU::new(); + + let program: [u8; 2] = [ + operation::by_name_and_addressing("DEX", operation::AddressingMode::Implied).code, + operation::by_name_and_addressing("BRK", operation::AddressingMode::Implied).code, + ]; + cpu.load(&program, 0); + cpu.x = 1; + + cpu.step(); + assert_eq!(cpu.x, 0); + assert_eq!(cpu.p, 0b0011_0010); + } + + #[test] + fn test_dex() { + let mut cpu = CPU::new(); + + let program: [u8; 2] = [ + operation::by_name_and_addressing("DEX", operation::AddressingMode::Implied).code, + operation::by_name_and_addressing("BRK", operation::AddressingMode::Implied).code, + ]; + cpu.load(&program, 0); + cpu.x = 10; + + cpu.step(); + assert_eq!(cpu.x, 9); + + cpu.pc = 0; + cpu.step(); + assert_eq!(cpu.x, 8); + } + + #[test] + fn operation_dey() { + let mut cpu = CPU::new(); + + let program: [u8; 2] = [ + operation::by_name_and_addressing("DEY", operation::AddressingMode::Implied).code, + operation::by_name_and_addressing("BRK", operation::AddressingMode::Implied).code, + ]; + cpu.load(&program, 0); + cpu.y = 5; + + cpu.step(); + assert_eq!(cpu.y, 4); + + cpu.pc = 0; + cpu.step(); + assert_eq!(cpu.y, 3); + } + + #[test] + fn operation_dey_below_zero_sets_negative_flag() { + let mut cpu = CPU::new(); + + let program: [u8; 2] = [ + operation::by_name_and_addressing("DEY", operation::AddressingMode::Implied).code, + operation::by_name_and_addressing("BRK", operation::AddressingMode::Implied).code, + ]; + cpu.load(&program, 0); + cpu.y = 0; + + cpu.step(); + assert_eq!(cpu.y, 0xFF); + assert_eq!(cpu.p, 0b1011_0000); + } + + #[test] + fn dey_to_zero_sets_zero_flag() { + let mut cpu = CPU::new(); + + let program: [u8; 2] = [ + operation::by_name_and_addressing("DEY", operation::AddressingMode::Implied).code, + operation::by_name_and_addressing("BRK", operation::AddressingMode::Implied).code, + ]; + cpu.load(&program, 0); + cpu.y = 1; + + cpu.step(); + assert_eq!(cpu.y, 0); + assert_eq!(cpu.p, 0b0011_0010); + } + + #[test] + fn operation_inc() { + let mut cpu = CPU::new(); + + let program: [u8; 3] = [ + operation::by_name_and_addressing("INC", operation::AddressingMode::ZeroPage).code, + 100, + operation::by_name_and_addressing("BRK", operation::AddressingMode::Implied).code, + ]; + cpu.load(&program, 0); + cpu.memory.set(100, 10); + + cpu.step(); + assert_eq!(cpu.memory.get(100), 11); + + cpu.pc = 0; + cpu.step(); + assert_eq!(cpu.memory.get(100), 12); + } + + #[test] + fn operation_inx() { + let mut cpu = CPU::new(); + + let program: [u8; 2] = [ + operation::by_name_and_addressing("INX", operation::AddressingMode::Implied).code, + operation::by_name_and_addressing("BRK", operation::AddressingMode::Implied).code, + ]; + cpu.load(&program, 0); + + assert_eq!(cpu.x, 0); + cpu.step(); + assert_eq!(cpu.x, 1); + + cpu.pc = 0; + cpu.step(); + assert_eq!(cpu.x, 2); + } + + #[test] + fn test_iny() { + let mut cpu = CPU::new(); + + let program: [u8; 2] = [ + operation::by_name_and_addressing("INY", operation::AddressingMode::Implied).code, + operation::by_name_and_addressing("BRK", operation::AddressingMode::Implied).code, + ]; + cpu.load(&program, 0); + + assert_eq!(cpu.y, 0); + cpu.step(); + assert_eq!(cpu.y, 1); + + cpu.pc = 0; + cpu.step(); + assert_eq!(cpu.y, 2); + } + + #[test] + fn test_tax_sets_zero() { + let mut cpu = CPU::new(); + + let program: [u8; 1] = + [operation::by_name_and_addressing("TAX", operation::AddressingMode::Implied).code]; + cpu.load(&program, 0); + cpu.a = 0; + cpu.x = 0xAA; + + cpu.step(); + + assert_eq!(cpu.a, 0); + assert_eq!(cpu.x, 0); + assert_eq!(cpu.p, DEFAULT_FLAGS | FLAG_ZERO); + } + + #[test] + fn test_tax() { + let mut cpu = CPU::new(); + + let program: [u8; 1] = + [operation::by_name_and_addressing("TAX", operation::AddressingMode::Implied).code]; + cpu.load(&program, 0); + cpu.a = 0xA; + cpu.x = 0; + + cpu.step(); + + assert_eq!(cpu.a, 0xA); + assert_eq!(cpu.x, 0xA); + assert_eq!(cpu.p, DEFAULT_FLAGS); // no negative flag + } + + #[test] + fn cmp_equals_immediate() { + // L == R (L > 0; R > 0) + let mut cpu = CPU::new(); + + let program: [u8; 4] = [ + operation::by_name_and_addressing("LDA", operation::AddressingMode::Immediate).code, + 100, + operation::by_name_and_addressing("CMP", operation::AddressingMode::Immediate).code, + 100, + ]; + cpu.load(&program, 0); + + for _ in 0..2 { + cpu.step(); + } + + assert_eq!(cpu.get_zero(), FLAG_ZERO); + } + + #[test] + fn cmp_equals_immediate_both_negative() { + // L == R (L < 0; R < 0) + let mut cpu = CPU::new(); + + let program: [u8; 4] = [ + operation::by_name_and_addressing("LDA", operation::AddressingMode::Immediate).code, + 0xFE, // -1 + operation::by_name_and_addressing("CMP", operation::AddressingMode::Immediate).code, + 0xFE, + ]; + cpu.load(&program, 0); + + cpu.step(); + cpu.step(); + + assert_eq!(cpu.get_zero(), FLAG_ZERO); + assert_eq!(cpu.get_negative(), 0); + assert_eq!(cpu.get_carry(), FLAG_CARRY); + } + + #[test] + fn cmp_left_less_right_both_negative_immediate() { + // L < R and (L < 0 && R < 0) + let mut cpu = CPU::new(); + + let program: [u8; 4] = [ + operation::by_name_and_addressing("LDA", operation::AddressingMode::Immediate).code, + 0xFE, // -2 + operation::by_name_and_addressing("CMP", operation::AddressingMode::Immediate).code, + 0xFF, // -1 + ]; + cpu.load(&program, 0); + + cpu.step(); + cpu.step(); + + assert_eq!(cpu.get_zero(), 0); + assert_eq!(cpu.get_negative(), FLAG_NEGATIVE); + assert_eq!(cpu.get_carry(), 0); + } + + #[test] + fn cmp_left_less_right_left_negative_immediate() { + // L < R and (L < 0) + let mut cpu = CPU::new(); + + let program: [u8; 4] = [ + operation::by_name_and_addressing("LDA", operation::AddressingMode::Immediate).code, + 0xFE, // -2 + operation::by_name_and_addressing("CMP", operation::AddressingMode::Immediate).code, + 10, + ]; + cpu.load(&program, 0); + + cpu.step(); + cpu.step(); + + assert_eq!(cpu.get_zero(), 0); + assert_eq!(cpu.get_negative(), FLAG_NEGATIVE); + assert_eq!(cpu.get_carry(), FLAG_CARRY); // L > R (unsigned) + } + + #[test] + fn cmp_left_greater_right_right_negative_immediate() { + // L < R and (R < 0) + let mut cpu = CPU::new(); + + let program: [u8; 4] = [ + operation::by_name_and_addressing("LDA", operation::AddressingMode::Immediate).code, + 0xF, + operation::by_name_and_addressing("CMP", operation::AddressingMode::Immediate).code, + 0xFE, // -2 + ]; + cpu.load(&program, 0); + + cpu.step(); + cpu.step(); + + assert_eq!(cpu.get_zero(), 0); + assert_eq!(cpu.get_negative(), 0); + assert_eq!(cpu.get_carry(), 0); + } + + #[test] + fn cmp_not_equals_immediate() { + let mut cpu = CPU::new(); + + let program: [u8; 4] = [ + operation::by_name_and_addressing("LDA", operation::AddressingMode::Immediate).code, + 55, + operation::by_name_and_addressing("CMP", operation::AddressingMode::Immediate).code, + 99, + ]; + cpu.load(&program, 0); + + cpu.step(); + cpu.step(); + + assert_eq!(cpu.get_zero(), 0); + } + + #[test] + fn test_cpy_equals_immediate() { + let mut cpu = CPU::new(); + + let program: [u8; 4] = [ + operation::by_name_and_addressing("LDY", operation::AddressingMode::Immediate).code, + 1, + operation::by_name_and_addressing("CPY", operation::AddressingMode::Immediate).code, + 1, + ]; + cpu.load(&program, 0); + + cpu.step(); + cpu.step(); + + assert_eq!(cpu.get_zero(), FLAG_ZERO); + } + + #[test] + fn test_cpy_not_equals_immediate() { + let mut cpu = CPU::new(); + + let program: [u8; 4] = [ + operation::by_name_and_addressing("LDY", operation::AddressingMode::Immediate).code, + 1, + operation::by_name_and_addressing("CPY", operation::AddressingMode::Immediate).code, + 5, + ]; + cpu.load(&program, 0); + + cpu.step(); + cpu.step(); + + assert_eq!(cpu.get_zero(), 0); + } + + #[test] + fn test_cpx_equals_immediate() { + let mut cpu = CPU::new(); + + let program: [u8; 4] = [ + operation::by_name_and_addressing("LDX", operation::AddressingMode::Immediate).code, + 1, + operation::by_name_and_addressing("CPX", operation::AddressingMode::Immediate).code, + 1, + ]; + cpu.load(&program, 0); + + cpu.step(); + cpu.step(); + + assert_eq!(cpu.get_zero(), FLAG_ZERO); + } + + #[test] + fn test_cpx_not_equals_immediate() { + let mut cpu = CPU::new(); + + let program: [u8; 4] = [ + operation::by_name_and_addressing("LDX", operation::AddressingMode::Immediate).code, + 1, + operation::by_name_and_addressing("CPX", operation::AddressingMode::Immediate).code, + 5, + ]; + cpu.load(&program, 0); + + cpu.step(); + cpu.step(); + + assert_eq!(cpu.get_zero(), 0); + } + + #[test] + fn test_bne_positive_offset() { + let mut cpu = CPU::new(); + + let program: [u8; 2] = [ + operation::by_name_and_addressing("BNE", operation::AddressingMode::RelativeAddressing) + .code, + 0xA, + ]; + cpu.load(&program, 0); + cpu.step(); + + assert_eq!(cpu.pc, 0xA + 2); + } + + #[test] + fn test_bne_negative_offset() { + let mut cpu = CPU::new(); + + let program: [u8; 2] = [ + operation::by_name_and_addressing("BNE", operation::AddressingMode::RelativeAddressing) + .code, + 250, + ]; + cpu.load(&program, 10); + cpu.step(); + + assert_eq!(cpu.pc, 4 + 2); // 10 - (256 - 250) + } + + #[test] + fn test_bne_branching() { + let mut cpu = CPU::new(); + + let program: [u8; 7] = [ + operation::by_name_and_addressing("LDX", operation::AddressingMode::Immediate).code, + 8, + operation::by_name_and_addressing("DEX", operation::AddressingMode::Implied).code, + operation::by_name_and_addressing("CPX", operation::AddressingMode::Immediate).code, + 3, + operation::by_name_and_addressing("BNE", operation::AddressingMode::RelativeAddressing) + .code, + 251, + ]; + cpu.load(&program, 0); + + // 16: 1 - ldx + 15 - cycle + for _ in 0..16 { + cpu.step(); } + + assert_eq!(cpu.x, 3); + assert_eq!(cpu.pc, 7); + assert_eq!(cpu.p, 0b0011_0011); } - pub fn load(&mut self, buf: &[u8], addr: u16) { - let program_end_addr = buf.len() + addr as usize; - if program_end_addr > 65536 { - panic!("Wrong address to load the program"); + #[test] + fn pha() { + let mut cpu = CPU::new(); + let program: [u8; 4] = [ + operation::by_name_and_addressing("LDA", operation::AddressingMode::Immediate).code, + 5, + operation::by_name_and_addressing("PHA", operation::AddressingMode::Implied).code, + operation::by_name_and_addressing("PHA", operation::AddressingMode::Implied).code, + ]; + + cpu.load(&program, 0x800); + + for _ in 0..3 { + cpu.step(); } - self.memory[(addr as usize)..program_end_addr].clone_from_slice(buf); - self.pc = addr; + assert_eq!(cpu.a, 5); + assert_eq!(cpu.sp, 0xfd); + assert_eq!(cpu.memory.get(STACK_TOP), 5); + assert_eq!(cpu.memory.get(STACK_TOP - 1), 5); + + let zero: [u8; 253] = [0; 253]; + assert_eq!( + &cpu.memory.bytes()[STACK_BOTTOM as usize..STACK_TOP as usize - 2], + &zero[..] + ); } - pub fn run(&mut self) { - loop { - let operation = operation::by_code(self.memory[self.pc as usize]); + #[test] + fn stack_pop_word() { + let mut cpu = CPU::new(); - match operation.name { - "BRK" => return, - "LDA" => self.operation_lda(operation), - "ADC" => self.operation_adc(operation), - "STA" => self.operation_sta(operation), - _ => panic!("unknown operation"), - }; + cpu.memory.set(STACK_TOP, 0x80); + cpu.memory.set(STACK_TOP - 1, 0x01); + cpu.sp = 0xfd; - self.pc += operation.length as u16; - } + assert_eq!(cpu.stack_pop_word(), 0x8001); } - fn operation_lda(&mut self, operation: &operation::Operation) { - self.a = self.get_operand(operation) as u8; + #[test] + fn stack_push_word() { + let mut cpu = CPU::new(); + let word: u16 = 0x8001; + + cpu.stack_push_word(word); + + assert_eq!(cpu.sp, 0xfd); + assert_eq!(cpu.memory.get(STACK_TOP), 0x80); + assert_eq!(cpu.memory.get(STACK_TOP - 1), 0x01); } - fn operation_adc(&mut self, operation: &operation::Operation) { - self.a += self.get_operand(operation) as u8; + #[test] + fn pla() { + let mut cpu = CPU::new(); + cpu.memory.set(0x0000, 0x68); + cpu.memory.set(STACK_TOP, 0xAB); + cpu.sp = 0xFE; + + cpu.step(); + + assert_eq!(cpu.pc, 0x0001); + assert_eq!(cpu.a, 0xAB); + assert_eq!(cpu.sp, 0xFF); } - fn operation_sta(&mut self, operation: &operation::Operation) { - let addr = self.next_two_byte_number(); - self.memory[addr as usize] = self.a; + #[test] + fn jsr_must_push_return_address() { + let mut cpu = CPU::new(); + let program: [u8; 3] = [ + operation::by_name_and_addressing("JSR", operation::AddressingMode::Absolute).code, + 0xD2, + 0xFF, + ]; + + cpu.load(&program, 0x800); + cpu.step(); + + assert_eq!(cpu.pc, 0xFFD2); + assert_eq!(cpu.sp, 0xFD); + assert_eq!(cpu.stack_pop_word(), 0x802); } - fn get_operand(&self, operation: &operation::Operation) -> usize { - match operation.addressing { - operation::AddressingMode::Immediate => self.memory[self.pc as usize + 1] as usize, - operation::AddressingMode::Absolute => { - self.memory[self.next_two_byte_number() as usize] as usize - } - _ => panic!("unknown addressing mode"), - } + #[test] + fn lsr_zero_page() { + let mut cpu = CPU::new(); + let program: [u8; 2] = [ + operation::by_name_and_addressing("LSR", operation::AddressingMode::ZeroPage).code, + 10, + ]; + cpu.memory.set(10, 0xFF); + + cpu.load(&program, 0x800); + cpu.step(); + + assert_eq!(cpu.memory.get(10), 0x7F); // 0xFF >> 1 + assert_eq!(cpu.get_carry(), 1); } - fn next_two_byte_number(&self) -> u16 { - // returns next two bytes (self.pc + 1, self.pc + 2) as one u16 - // (converts from little endian) - utils::little_endian_to_u16( - self.memory[self.pc as usize + 1], - self.memory[self.pc as usize + 2], - ) + #[test] + fn sbc_simple_test() { + let mut cpu = CPU::new(); + cpu.a = 10; + cpu.set_carry(); + let program: [u8; 2] = [ + operation::by_name_and_addressing("SBC", operation::AddressingMode::Immediate).code, + 10, + ]; + + cpu.load(&program, 0x800); + cpu.step(); + + assert_eq!(cpu.a, 0); + assert_eq!(cpu.get_zero(), FLAG_ZERO); } -} -#[cfg(test)] -mod tests { - use crate::cpu::CPU; - use crate::operation; + #[test] + fn sbc_immediate_zeros() { + let mut cpu = CPU::new(); + cpu.a = 0; + cpu.set_carry(); + let program: [u8; 2] = [ + operation::by_name_and_addressing("SBC", operation::AddressingMode::Immediate).code, + 0, + ]; + + cpu.load(&program, 0x800); + cpu.step(); + + assert_eq!(cpu.a, 0); + assert_eq!(cpu.get_zero(), FLAG_ZERO); + assert_eq!(cpu.get_carry(), FLAG_CARRY); + assert_eq!(cpu.get_negative(), 0); + } #[test] - fn test_new() { - let c = CPU::new(); + fn sbc_immediate_one_minus_one() { + let mut cpu = CPU::new(); + cpu.a = 1; + cpu.set_carry(); + let program: [u8; 2] = [ + operation::by_name_and_addressing("SBC", operation::AddressingMode::Immediate).code, + 1, + ]; - assert_eq!(c.a, 0); - assert_eq!(c.x, 0); - assert_eq!(c.y, 0); - assert_eq!(c.s, 0); - assert_eq!(c.p, 0); - assert_eq!(c.pc, 0); - assert_eq!(&c.memory[..], &[0; 64 * 1024][..]); + cpu.load(&program, 0x800); + cpu.step(); + + assert_eq!(cpu.a, 0); + assert_eq!(cpu.get_zero(), FLAG_ZERO); + assert_eq!(cpu.get_carry(), FLAG_CARRY); + assert_eq!(cpu.get_negative(), 0); } #[test] - fn test_load() { - // test when it loads a three bytes dump at a specific location + fn sbc_immediate_10_minus_5_minus_carry() { let mut cpu = CPU::new(); + cpu.a = 10; + cpu.clear_carry(); + let program: [u8; 2] = [ + operation::by_name_and_addressing("SBC", operation::AddressingMode::Immediate).code, + 5, + ]; - let addr = 32768; - let bytes = [1; 3]; + cpu.load(&program, 0x800); + cpu.step(); - assert_eq!(&cpu.memory[..], &[0; 64 * 1024][..]); - cpu.load(&bytes, addr); + assert_eq!(cpu.a, 4); + assert_eq!(cpu.get_zero(), 0); + assert_eq!(cpu.get_carry(), FLAG_CARRY); + assert_eq!(cpu.get_negative(), 0); + } - let exp_memory = [0, 1, 1, 1, 0]; - assert_eq!(&cpu.memory[32767..32772], exp_memory); + #[test] + fn sbc_immediate_with_zero() { + let mut cpu = CPU::new(); + cpu.a = 1; + cpu.set_carry(); + let program: [u8; 2] = [ + operation::by_name_and_addressing("SBC", operation::AddressingMode::Immediate).code, + 5, + ]; - assert_eq!(cpu.pc, addr); + cpu.load(&program, 0x800); + cpu.step(); + + assert_eq!(cpu.a, 252); + assert_eq!(cpu.get_zero(), 0); + assert_eq!(cpu.get_carry(), 0); + assert_eq!(cpu.get_negative(), FLAG_NEGATIVE); } #[test] - #[should_panic] - fn test_load_data_too_big() { + fn sbc_immediate_carry_but_no_overflow() { let mut cpu = CPU::new(); - cpu.load(&[1, 2, 3], 65535); + cpu.a = 0x50; + cpu.set_carry(); + let program: [u8; 2] = [ + operation::by_name_and_addressing("SBC", operation::AddressingMode::Immediate).code, + 0xf0, + ]; + + cpu.load(&program, 0x800); + cpu.step(); + + assert_eq!(cpu.a, 0x60); + assert_eq!(cpu.get_zero(), 0); + assert_eq!(cpu.get_carry(), 0); + assert_eq!(cpu.get_negative(), 0); + assert_eq!(cpu.get_overflow(), 0); + } + + #[test] + fn sbc_immediate_carry_and_overflow() { + let mut cpu = CPU::new(); + cpu.a = 0x50; + cpu.set_carry(); + let program: [u8; 2] = [ + operation::by_name_and_addressing("SBC", operation::AddressingMode::Immediate).code, + 0xb0, + ]; + + cpu.load(&program, 0x800); + cpu.step(); + + assert_eq!(cpu.a, 0xa0); + assert_eq!(cpu.get_zero(), 0); + assert_eq!(cpu.get_carry(), 0); + assert_eq!(cpu.get_negative(), FLAG_NEGATIVE); + assert_eq!(cpu.get_overflow(), FLAG_OVERFLOW); + } + + #[test] + fn sbc_immediate_no_carry_and_overflow() { + let mut cpu = CPU::new(); + cpu.a = 0xd0; + cpu.set_carry(); + let program: [u8; 2] = [ + operation::by_name_and_addressing("SBC", operation::AddressingMode::Immediate).code, + 0x70, + ]; + + cpu.load(&program, 0x800); + cpu.step(); + + assert_eq!(cpu.a, 0x60); + assert_eq!(cpu.get_zero(), 0); + assert_eq!(cpu.get_carry(), FLAG_CARRY); + assert_eq!(cpu.get_negative(), 0); + assert_eq!(cpu.get_overflow(), FLAG_OVERFLOW); + } + + #[test] + fn sbc_immediate_no_carry_and_no_overflow() { + let mut cpu = CPU::new(); + cpu.a = 0xd0; + cpu.set_carry(); + let program: [u8; 2] = [ + operation::by_name_and_addressing("SBC", operation::AddressingMode::Immediate).code, + 0x30, + ]; + + cpu.load(&program, 0x800); + cpu.step(); + + assert_eq!(cpu.a, 0xa0); + assert_eq!(cpu.get_zero(), 0); + assert_eq!(cpu.get_carry(), FLAG_CARRY); + assert_eq!(cpu.get_negative(), FLAG_NEGATIVE); + assert_eq!(cpu.get_overflow(), 0); } #[test] - fn test_operation_lda() { + fn sbc_immediate_with_overflow() { let mut cpu = CPU::new(); + cpu.a = 1; + cpu.set_carry(); + let program: [u8; 2] = [ + operation::by_name_and_addressing("SBC", operation::AddressingMode::Immediate).code, + 5, + ]; + + cpu.load(&program, 0x800); + cpu.step(); + assert_eq!(cpu.a, 252); + assert_eq!(cpu.get_zero(), 0); + assert_eq!(cpu.get_carry(), 0); + assert_eq!(cpu.get_negative(), FLAG_NEGATIVE); + } + + #[test] + fn ror_absolute_zero_and_carry_zero() { + let mut cpu = CPU::new(); let program: [u8; 3] = [ - operation::by_name("LDA", operation::AddressingMode::Immediate).code, - 10, - operation::by_name("BRK", operation::AddressingMode::Immediate).code, + operation::by_name_and_addressing("ROR", operation::AddressingMode::Absolute).code, + 0x00, + 0x80, ]; - cpu.load(&program, 0); + + cpu.load(&program, 0x800); + cpu.step(); assert_eq!(cpu.a, 0); + assert_eq!(cpu.get_zero(), FLAG_ZERO); + assert_eq!(cpu.get_carry(), 0); + assert_eq!(cpu.get_negative(), 0); + assert_eq!(cpu.memory.get(0x8000), 0); + } + + #[test] + fn ror_absolute_zero_and_carry_one() { + let mut cpu = CPU::new(); + cpu.set_carry(); + let program: [u8; 3] = [ + operation::by_name_and_addressing("ROR", operation::AddressingMode::Absolute).code, + 0x00, + 0x80, + ]; - cpu.run(); + cpu.load(&program, 0x800); + cpu.step(); - assert_eq!(cpu.a, 10); + assert_eq!(cpu.a, 0); + assert_eq!(cpu.get_zero(), 0); + assert_eq!(cpu.get_carry(), 0); + assert_eq!(cpu.get_negative(), FLAG_NEGATIVE); + assert_eq!(cpu.memory.get(0x8000), 0x80); } #[test] - fn test_operation_adc_immediate() { + fn bmi() { + let mut cpu = CPU::new(); + cpu.set_negative(); + let program: [u8; 2] = [ + operation::by_name_and_addressing("BMI", operation::AddressingMode::RelativeAddressing) + .code, + 0x06, + ]; + + cpu.load(&program, 0x0); + cpu.step(); + + assert_eq!(cpu.pc, 0x0002 + 0x06); + } + + #[test] + fn bit_absolite_zero_flag() { let mut cpu = CPU::new(); - cpu.a = 10; let program: [u8; 3] = [ - operation::by_name("ADC", operation::AddressingMode::Immediate).code, - 15, - operation::by_name("BRK", operation::AddressingMode::Immediate).code, + operation::by_name_and_addressing("BIT", operation::AddressingMode::Absolute).code, + 0x10, + 0x11, ]; - cpu.load(&program, 0); + cpu.memory.set(0x1110, 0b0001_0000); + cpu.a = 0b1100_0000; - assert_eq!(cpu.a, 10); + cpu.load(&program, 0x0); + cpu.step(); - cpu.run(); + assert_eq!(cpu.get_zero(), FLAG_ZERO); + assert_eq!(cpu.get_negative(), 0); + assert_eq!(cpu.get_overflow(), 0); + } - assert_eq!(cpu.a, 25); + #[test] + fn bit_absolute_saves_6_and_7_bits() { + let mut cpu = CPU::new(); + + let program: [u8; 3] = [ + operation::by_name_and_addressing("BIT", operation::AddressingMode::Absolute).code, + 0x10, + 0x11, + ]; + cpu.memory.set(0x1110, 0b1100_0000); + cpu.a = 0b1100_0000; + + cpu.load(&program, 0x0); + cpu.step(); + + assert_eq!(cpu.get_zero(), 0); + assert_eq!(cpu.get_negative(), FLAG_NEGATIVE); + assert_eq!(cpu.get_overflow(), FLAG_OVERFLOW); } #[test] - fn test_operation_adc_absolute() { + fn bit_absolute_saves_6_and_7_bits_when_they_are_0() { let mut cpu = CPU::new(); - cpu.a = 10; + cpu.set_negative(); + cpu.set_overflow(); + cpu.set_zero(); - let program: [u8; 4] = [ - operation::by_name("ADC", operation::AddressingMode::Absolute).code, - 0xff, - 0xaa, - operation::by_name("BRK", operation::AddressingMode::Immediate).code, + let program: [u8; 3] = [ + operation::by_name_and_addressing("BIT", operation::AddressingMode::Absolute).code, + 0x10, + 0x11, ]; - cpu.load(&program, 0); - cpu.memory[43775] = 15; // 0xaaff + cpu.memory.set(0x1110, 1); + cpu.a = 1; - assert_eq!(cpu.a, 10); + cpu.load(&program, 0x0); + cpu.step(); - cpu.run(); + assert_eq!(cpu.get_zero(), 0); + assert_eq!(cpu.get_negative(), 0); + assert_eq!(cpu.get_overflow(), 0); + } - assert_eq!(cpu.a, 25); + #[test] + fn bit_zero_page_zero_flag() { + let mut cpu = CPU::new(); + + let program: [u8; 2] = [ + operation::by_name_and_addressing("BIT", operation::AddressingMode::ZeroPage).code, + 0x10, + ]; + cpu.memory.set(0x10, 0b0001_0000); + cpu.a = 0b1100_0000; + + cpu.load(&program, 0x0); + cpu.step(); + + assert_eq!(cpu.get_zero(), FLAG_ZERO); + assert_eq!(cpu.get_negative(), 0); + assert_eq!(cpu.get_overflow(), 0); } #[test] - fn test_operation_sta() { + fn bit_zero_page_saves_6_and_7_bits() { let mut cpu = CPU::new(); - cpu.a = 10; - let program: [u8; 4] = [ - operation::by_name("STA", operation::AddressingMode::Absolute).code, - 50, - 0, - operation::by_name("BRK", operation::AddressingMode::Immediate).code, + let program: [u8; 2] = [ + operation::by_name_and_addressing("BIT", operation::AddressingMode::ZeroPage).code, + 0x10, ]; - cpu.load(&program, 0); + cpu.memory.set(0x10, 0b1100_0000); + cpu.a = 0b1100_0000; + + cpu.load(&program, 0x0); + cpu.step(); + + assert_eq!(cpu.get_zero(), 0); + assert_eq!(cpu.get_negative(), FLAG_NEGATIVE); + assert_eq!(cpu.get_overflow(), FLAG_OVERFLOW); + } + + #[test] + fn bit_zero_page_saves_6_and_7_bits_when_they_are_0() { + let mut cpu = CPU::new(); + cpu.set_negative(); + cpu.set_overflow(); + cpu.set_zero(); - assert_eq!(cpu.memory[50], 0); + let program: [u8; 2] = [ + operation::by_name_and_addressing("BIT", operation::AddressingMode::ZeroPage).code, + 0x10, + ]; + cpu.memory.set(0x10, 1); + cpu.a = 1; - cpu.run(); + cpu.load(&program, 0x0); + cpu.step(); - assert_eq!(cpu.memory[50], cpu.a); + assert_eq!(cpu.get_zero(), 0); + assert_eq!(cpu.get_negative(), 0); + assert_eq!(cpu.get_overflow(), 0); } #[test] - fn test_next_two_bytes() { + fn rts() { let mut cpu = CPU::new(); - let bytes = [0, 0x0a, 0x0b]; - cpu.load(&bytes, 0); - assert_eq!(cpu.next_two_byte_number(), 2826); + + let program: [u8; 5] = [ + operation::by_name_and_addressing("LDA", operation::AddressingMode::Immediate).code, + 0x50, + operation::by_name_and_addressing("PHA", operation::AddressingMode::Implied).code, + operation::by_name_and_addressing("PHA", operation::AddressingMode::Implied).code, + operation::by_name_and_addressing("RTS", operation::AddressingMode::Implied).code, + ]; + + cpu.load(&program, 0x0); + + for _ in 0..4 { + cpu.step(); + } + + assert_eq!(cpu.a, 0x50); + assert_eq!(cpu.pc, 0x5051); + assert_eq!(cpu.p, 0b0011_0000); } #[test] - fn test_next_two_bytes_with_only_lower_byte() { + fn jmp_indirect() { let mut cpu = CPU::new(); - let bytes = [0, 10, 0]; - cpu.load(&bytes, 0); - assert_eq!(cpu.next_two_byte_number(), 10); + + let program: [u8; 3] = [ + operation::by_name_and_addressing("JMP", operation::AddressingMode::IndirectAbsolute) + .code, + 0x00, + 0x02, + ]; + + cpu.memory.set(0x0200, 0xCD); + cpu.memory.set(0x0201, 0xAB); + + cpu.load(&program, 0x0); + cpu.step(); + + assert_eq!(cpu.pc, 0xABCD); } #[test] - fn test_simple_program() { - // loads 100 from the memory - // adds 7 to the a register - // stores a register at address 0x000f + fn asl_accumulator_80_to_zero() { let mut cpu = CPU::new(); - cpu.a = 10; - let program: [u8; 8] = [ - operation::by_name("LDA", operation::AddressingMode::Immediate).code, - 100, - operation::by_name("ADC", operation::AddressingMode::Immediate).code, - 7, - operation::by_name("STA", operation::AddressingMode::Absolute).code, - 15, - 0, - operation::by_name("BRK", operation::AddressingMode::Immediate).code, + let program: [u8; 1] = [operation::by_name_and_addressing( + "ASL", + operation::AddressingMode::AccumulatorAddressing, + ) + .code]; + cpu.a = 0x80; + cpu.load(&program, 0x0); + cpu.step(); + + assert_eq!(cpu.a, 0); + assert_eq!(cpu.get_zero(), FLAG_ZERO); + assert_eq!(cpu.get_carry(), FLAG_CARRY); + } + + #[test] + fn asl_accumulator_shift() { + let mut cpu = CPU::new(); + + let program: [u8; 1] = [operation::by_name_and_addressing( + "ASL", + operation::AddressingMode::AccumulatorAddressing, + ) + .code]; + cpu.a = 1; + cpu.load(&program, 0x0); + cpu.step(); + + assert_eq!(cpu.a, 0b10); + assert_eq!(cpu.get_zero(), 0); + assert_eq!(cpu.get_negative(), 0); + assert_eq!(cpu.get_overflow(), 0); + } + + #[test] + fn rol_accumulator_zero() { + let mut cpu = CPU::new(); + cpu.clear_zero(); + cpu.clear_carry(); + cpu.clear_negative(); + + let program: [u8; 1] = [operation::by_name_and_addressing( + "ROL", + operation::AddressingMode::AccumulatorAddressing, + ) + .code]; + + cpu.a = 0; + cpu.load(&program, 0x0); + cpu.step(); + + assert_eq!(cpu.a, 0); + assert_eq!(cpu.get_zero(), FLAG_ZERO); + assert_eq!(cpu.get_negative(), 0); + assert_eq!(cpu.get_carry(), 0); + } + + #[test] + fn rol_accumulator_to_zero() { + let mut cpu = CPU::new(); + cpu.clear_zero(); + cpu.clear_carry(); + cpu.clear_negative(); + + let program: [u8; 1] = [operation::by_name_and_addressing( + "ROL", + operation::AddressingMode::AccumulatorAddressing, + ) + .code]; + + cpu.a = 0x80; + cpu.load(&program, 0x0); + cpu.step(); + + assert_eq!(cpu.a, 0); + assert_eq!(cpu.get_zero(), FLAG_ZERO); + assert_eq!(cpu.get_negative(), 0); + assert_eq!(cpu.get_carry(), FLAG_CARRY); + } + + #[test] + fn rol_accumulator_with_initial_carry() { + let mut cpu = CPU::new(); + cpu.clear_zero(); + cpu.set_carry(); + cpu.clear_negative(); + + let program: [u8; 1] = [operation::by_name_and_addressing( + "ROL", + operation::AddressingMode::AccumulatorAddressing, + ) + .code]; + + cpu.a = 0x40; + cpu.load(&program, 0x0); + cpu.step(); + + assert_eq!(cpu.a, 0x81); + assert_eq!(cpu.get_zero(), 0); + assert_eq!(cpu.get_negative(), FLAG_NEGATIVE); + assert_eq!(cpu.get_carry(), 0); + } + + #[test] + fn rol_accumulator() { + let mut cpu = CPU::new(); + cpu.clear_zero(); + cpu.clear_carry(); + cpu.clear_negative(); + + let program: [u8; 1] = [operation::by_name_and_addressing( + "ROL", + operation::AddressingMode::AccumulatorAddressing, + ) + .code]; + + cpu.a = 0x1; + cpu.load(&program, 0x0); + cpu.step(); + + assert_eq!(cpu.a, 0x2); + assert_eq!(cpu.get_zero(), 0); + assert_eq!(cpu.get_negative(), 0); + assert_eq!(cpu.get_carry(), 0); + } + + #[test] + fn php_and_then_plp() { + let mut cpu = CPU::new(); + + cpu.p = 0b1100_0101; + + let program: [u8; 2] = [ + operation::by_name_and_addressing("PHP", operation::AddressingMode::Implied).code, + operation::by_name_and_addressing("PLP", operation::AddressingMode::Implied).code, ]; - cpu.load(&program, 0); + cpu.load(&program, 0x800); - cpu.run(); + cpu.step(); - assert_eq!(cpu.a, 107); - assert_eq!(cpu.memory[15], 107); + assert_eq!(cpu.sp, 0xfe); + assert_eq!( + cpu.memory.get(STACK_BOTTOM + u16::from(cpu.sp) + 1), + 0b1100_0101 + ); + + cpu.p = 0; + + cpu.step(); + + assert_eq!(cpu.sp, 0xff); + assert_eq!(cpu.p, 0b1111_0101); } + #[test] + fn brk() { + let mut cpu = CPU::new(); + + let program = + [operation::by_name_and_addressing("BRK", operation::AddressingMode::Implied).code]; + cpu.load(&program, 0x800); + cpu.memory.set(0xfffe, 0x11); + cpu.memory.set(0xffff, 0x22); + + cpu.step(); + + assert_eq!(cpu.pc, 0x2211); + assert_eq!(cpu.sp, 0xfc); + assert_eq!(cpu.memory.get(STACK_BOTTOM + 0xff), 0x08); + assert_eq!(cpu.memory.get(STACK_BOTTOM + 0xfe), 0x02); + assert_eq!(cpu.memory.get(STACK_BOTTOM + 0xfd), 0b0011_0000); + } } diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..4f44945 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,8 @@ +pub mod asm; +pub mod cpu; +pub mod mem; +mod operation; +mod utils; + +#[macro_use] +extern crate log; diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index b307f83..0000000 --- a/src/main.rs +++ /dev/null @@ -1,9 +0,0 @@ -pub mod cpu; -mod operation; -mod utils; -use cpu::CPU; - -fn main() { - println!("Hello, world!"); - let cpu = CPU::new(); -} diff --git a/src/mem.rs b/src/mem.rs new file mode 100644 index 0000000..c22faca --- /dev/null +++ b/src/mem.rs @@ -0,0 +1,65 @@ +pub type ReadCallback = fn(addr: u16) -> Option<(u16, u8)>; +pub type WriteCallback = fn(addr: u16, value: u8) -> Option<(u16, u8)>; + +pub struct Memory { + bytes: [u8; 64 * 1024], + read_callback: Option, + write_callback: Option, +} + +impl Default for Memory { + fn default() -> Self { + Self::new() + } +} + +impl Memory { + pub fn new() -> Memory { + Memory { + bytes: [0; 64 * 1024], + read_callback: None, + write_callback: None, + } + } + + pub fn load(&mut self, addr: u16, data: &[u8]) { + let program_end_addr = data.len() + addr as usize; + self.bytes[(addr as usize)..program_end_addr].clone_from_slice(data); + } + + pub fn register_read_callback(&mut self, cb: ReadCallback) { + self.read_callback = Some(cb); + } + + pub fn register_write_callback(&mut self, cb: WriteCallback) { + self.write_callback = Some(cb); + } + + pub fn set(&mut self, addr: u16, value: u8) { + self.bytes[addr as usize] = value; + + if let Some(cb) = self.write_callback { + self.process_callback(cb(addr, value)); + } + } + + fn process_callback(&mut self, result: Option<(u16, u8)>) { + if let Some((addr, value)) = result { + self.bytes[addr as usize] = value; + } + } + + pub fn get(&mut self, addr: u16) -> u8 { + let value = self.bytes[addr as usize]; + + if let Some(cb) = self.read_callback { + self.process_callback(cb(addr)); + } + + value + } + + pub fn bytes(&self) -> &[u8] { + &self.bytes + } +} diff --git a/src/operation.rs b/src/operation.rs index fb6118d..27eb38f 100644 --- a/src/operation.rs +++ b/src/operation.rs @@ -1,14 +1,16 @@ #[derive(PartialEq, Eq)] pub enum AddressingMode { - Immediate = 1, + Implied = 0, + Immediate, Absolute, ZeroPage, - Implied, IndirectAbsolute, - AbsoluteIndexed, - ZeroPageIndexed, - IndexedIndirect, + AbsoluteIndexedX, + AbsoluteIndexedY, + ZeroPageIndexedX, + ZeroPageIndexedY, IndirectIndexed, + IndirectIndexedX, RelativeAddressing, AccumulatorAddressing, } @@ -25,7 +27,93 @@ pub static OPERATIONS: &'static [Operation] = &[ name: "BRK", code: 0x00, length: 1, - addressing: AddressingMode::Immediate, + addressing: AddressingMode::Implied, + }, + Operation { + name: "RTI", + code: 0x40, + length: 1, + addressing: AddressingMode::Implied, + }, + // DEC + Operation { + name: "DEC", + code: 0xC6, + length: 2, + addressing: AddressingMode::ZeroPage, + }, + Operation { + name: "DEC", + code: 0xD6, + length: 2, + addressing: AddressingMode::ZeroPageIndexedX, + }, + Operation { + name: "DEC", + code: 0xCE, + length: 3, + addressing: AddressingMode::Absolute, + }, + Operation { + name: "DEC", + code: 0xDE, + length: 3, + addressing: AddressingMode::AbsoluteIndexedX, + }, + // INC + Operation { + name: "INC", + code: 0xEE, + length: 3, + addressing: AddressingMode::Absolute, + }, + Operation { + name: "INC", + code: 0xE6, + length: 2, + addressing: AddressingMode::ZeroPage, + }, + Operation { + name: "INC", + code: 0xF6, + length: 2, + addressing: AddressingMode::ZeroPageIndexedX, + }, + Operation { + name: "INX", + code: 0xE8, + length: 1, + addressing: AddressingMode::Implied, + }, + Operation { + name: "INY", + code: 0xC8, + length: 1, + addressing: AddressingMode::Implied, + }, + Operation { + name: "TAX", + code: 0xAA, + length: 1, + addressing: AddressingMode::Implied, + }, + Operation { + name: "TAY", + code: 0xA8, + length: 1, + addressing: AddressingMode::Implied, + }, + Operation { + name: "DEX", + code: 0xCA, + length: 1, + addressing: AddressingMode::Implied, + }, + Operation { + name: "DEY", + code: 0x88, + length: 1, + addressing: AddressingMode::Implied, }, Operation { name: "LDA", @@ -33,6 +121,110 @@ pub static OPERATIONS: &'static [Operation] = &[ length: 2, addressing: AddressingMode::Immediate, }, + Operation { + name: "LDA", + code: 0xa5, + length: 2, + addressing: AddressingMode::ZeroPage, + }, + Operation { + name: "LDA", + code: 0xb5, + length: 2, + addressing: AddressingMode::ZeroPageIndexedX, + }, + Operation { + name: "LDA", + code: 0xad, + length: 3, + addressing: AddressingMode::Absolute, + }, + Operation { + name: "LDA", + code: 0xB9, + length: 3, + addressing: AddressingMode::AbsoluteIndexedY, + }, + Operation { + name: "LDA", + code: 0xBD, + length: 3, + addressing: AddressingMode::AbsoluteIndexedX, + }, + Operation { + name: "LDA", + code: 0xA1, + length: 2, + addressing: AddressingMode::IndirectIndexedX, + }, + Operation { + name: "LDA", + code: 0xB1, + length: 2, + addressing: AddressingMode::IndirectIndexed, + }, + // LDX + Operation { + name: "LDX", + code: 0xa2, + length: 2, + addressing: AddressingMode::Immediate, + }, + Operation { + name: "LDX", + code: 0xa6, + length: 2, + addressing: AddressingMode::ZeroPage, + }, + Operation { + name: "LDX", + code: 0xB6, + length: 2, + addressing: AddressingMode::ZeroPageIndexedY, + }, + Operation { + name: "LDX", + code: 0xae, + length: 3, + addressing: AddressingMode::Absolute, + }, + Operation { + name: "LDX", + code: 0xBE, + length: 3, + addressing: AddressingMode::AbsoluteIndexedY, + }, + // LDY + Operation { + name: "LDY", + code: 0xA0, + length: 2, + addressing: AddressingMode::Immediate, + }, + Operation { + name: "LDY", + code: 0xA4, + length: 2, + addressing: AddressingMode::ZeroPage, + }, + Operation { + name: "LDY", + code: 0xB4, + length: 2, + addressing: AddressingMode::ZeroPageIndexedX, + }, + Operation { + name: "LDY", + code: 0xAC, + length: 3, + addressing: AddressingMode::Absolute, + }, + Operation { + name: "LDY", + code: 0xBC, + length: 3, + addressing: AddressingMode::AbsoluteIndexedX, + }, Operation { name: "ADC", code: 0x69, @@ -41,7 +233,104 @@ pub static OPERATIONS: &'static [Operation] = &[ }, Operation { name: "ADC", - code: 0x60, + code: 0x6D, + length: 3, + addressing: AddressingMode::Absolute, + }, + Operation { + name: "ADC", + code: 0x65, + length: 2, + addressing: AddressingMode::ZeroPage, + }, + Operation { + name: "ADC", + code: 0x75, + length: 2, + addressing: AddressingMode::ZeroPageIndexedX, + }, + Operation { + name: "ADC", + code: 0x7D, + length: 3, + addressing: AddressingMode::AbsoluteIndexedX, + }, + Operation { + name: "ADC", + code: 0x79, + length: 3, + addressing: AddressingMode::AbsoluteIndexedY, + }, + // SBC + Operation { + name: "SBC", + code: 0xE9, + length: 2, + addressing: AddressingMode::Immediate, + }, + Operation { + name: "SBC", + code: 0xED, + length: 3, + addressing: AddressingMode::Absolute, + }, + Operation { + name: "SBC", + code: 0xE5, + length: 2, + addressing: AddressingMode::ZeroPage, + }, + Operation { + name: "SBC", + code: 0xF5, + length: 2, + addressing: AddressingMode::ZeroPageIndexedX, + }, + Operation { + name: "SBC", + code: 0xF1, + length: 2, + addressing: AddressingMode::IndirectIndexed, + }, + Operation { + name: "STX", + code: 0x8E, + length: 3, + addressing: AddressingMode::Absolute, + }, + Operation { + name: "STX", + code: 0x86, + length: 2, + addressing: AddressingMode::ZeroPage, + }, + Operation { + name: "STX", + code: 0x96, + length: 2, + addressing: AddressingMode::ZeroPageIndexedY, + }, + Operation { + name: "STY", + code: 0x8C, + length: 3, + addressing: AddressingMode::Absolute, + }, + Operation { + name: "STY", + code: 0x84, + length: 2, + addressing: AddressingMode::ZeroPage, + }, + Operation { + name: "STY", + code: 0x94, + length: 2, + addressing: AddressingMode::ZeroPageIndexedX, + }, + Operation { + name: "STA", + code: 0x8D, length: 3, addressing: AddressingMode::Absolute, }, @@ -49,48 +338,873 @@ pub static OPERATIONS: &'static [Operation] = &[ name: "STA", code: 0x85, length: 2, + addressing: AddressingMode::ZeroPage, + }, + Operation { + name: "STA", + code: 0x95, + length: 2, + addressing: AddressingMode::ZeroPageIndexedX, + }, + Operation { + name: "STA", + code: 0x9D, + length: 3, + addressing: AddressingMode::AbsoluteIndexedX, + }, + Operation { + name: "STA", + code: 0x99, + length: 3, + addressing: AddressingMode::AbsoluteIndexedY, + }, + Operation { + name: "STA", + code: 0x81, + length: 2, + addressing: AddressingMode::IndirectIndexedX, + }, + Operation { + name: "STA", + code: 0x91, + length: 2, + addressing: AddressingMode::IndirectIndexed, + }, + Operation { + name: "BEQ", + code: 0xF0, + length: 2, + addressing: AddressingMode::RelativeAddressing, + }, + Operation { + name: "BNE", + code: 0xD0, + length: 2, + addressing: AddressingMode::RelativeAddressing, + }, + Operation { + name: "BVC", + code: 0x50, + length: 2, + addressing: AddressingMode::RelativeAddressing, + }, + Operation { + name: "BVS", + code: 0x70, + length: 2, + addressing: AddressingMode::RelativeAddressing, + }, + Operation { + name: "BIT", + code: 0x24, + length: 2, + addressing: AddressingMode::ZeroPage, + }, + Operation { + name: "BIT", + code: 0x2C, + length: 3, + addressing: AddressingMode::Absolute, + }, + Operation { + name: "BCC", + code: 0x90, + length: 2, + addressing: AddressingMode::RelativeAddressing, + }, + Operation { + name: "BMI", + code: 0x30, + length: 2, + addressing: AddressingMode::RelativeAddressing, + }, + Operation { + name: "BPL", + code: 0x10, + length: 2, + addressing: AddressingMode::RelativeAddressing, + }, + Operation { + name: "BCS", + code: 0xB0, + length: 2, + addressing: AddressingMode::RelativeAddressing, + }, + // CPX + Operation { + name: "CPX", + code: 0xE0, + length: 2, + addressing: AddressingMode::Immediate, + }, + Operation { + name: "CPX", + code: 0xE4, + length: 2, + addressing: AddressingMode::ZeroPage, + }, + Operation { + name: "CPX", + code: 0xEC, + length: 3, addressing: AddressingMode::Absolute, }, + // EOR + Operation { + name: "EOR", + code: 0x49, + length: 2, + addressing: AddressingMode::Immediate, + }, + // CMP + Operation { + name: "CMP", + code: 0xC9, + length: 2, + addressing: AddressingMode::Immediate, + }, + Operation { + name: "CMP", + code: 0xC5, + length: 2, + addressing: AddressingMode::ZeroPage, + }, + Operation { + name: "CMP", + code: 0xD5, + length: 2, + addressing: AddressingMode::ZeroPageIndexedX, + }, + Operation { + name: "CMP", + code: 0xCD, + length: 3, + addressing: AddressingMode::Absolute, + }, + Operation { + name: "CMP", + code: 0xDD, + length: 3, + addressing: AddressingMode::AbsoluteIndexedX, + }, + Operation { + name: "CMP", + code: 0xD9, + length: 3, + addressing: AddressingMode::AbsoluteIndexedY, + }, + Operation { + name: "CMP", + code: 0xC1, + length: 2, + addressing: AddressingMode::IndirectIndexedX, + }, + Operation { + name: "CMP", + code: 0xD1, + length: 2, + addressing: AddressingMode::IndirectIndexed, + }, + // CPY + Operation { + name: "CPY", + code: 0xC0, + length: 2, + addressing: AddressingMode::Immediate, + }, + Operation { + name: "CPY", + code: 0xC4, + length: 2, + addressing: AddressingMode::ZeroPage, + }, + Operation { + name: "CPY", + code: 0xCC, + length: 3, + addressing: AddressingMode::Absolute, + }, + Operation { + name: "CLC", + code: 0x18, + length: 1, + addressing: AddressingMode::Implied, + }, + Operation { + name: "CLD", + code: 0xD8, + length: 1, + addressing: AddressingMode::Implied, + }, + Operation { + name: "CLI", + code: 0x58, + length: 1, + addressing: AddressingMode::Implied, + }, + Operation { + name: "CLV", + code: 0xB8, + length: 1, + addressing: AddressingMode::Implied, + }, + Operation { + name: "SEC", + code: 0x38, + length: 1, + addressing: AddressingMode::Implied, + }, + Operation { + name: "SED", + code: 0xF8, + length: 1, + addressing: AddressingMode::Implied, + }, + Operation { + name: "SEI", + code: 0x78, + length: 1, + addressing: AddressingMode::Implied, + }, + Operation { + name: "NOP", + code: 0xEA, + length: 1, + addressing: AddressingMode::Implied, + }, + Operation { + name: "JMP", + code: 0x4C, + length: 3, + addressing: AddressingMode::Absolute, + }, + Operation { + name: "JMP", + code: 0x6C, + length: 3, + addressing: AddressingMode::IndirectAbsolute, + }, + Operation { + name: "PHA", + code: 0x48, + length: 1, + addressing: AddressingMode::Implied, + }, + Operation { + name: "TXA", + code: 0x8A, + length: 1, + addressing: AddressingMode::Implied, + }, + Operation { + name: "TYA", + code: 0x98, + length: 1, + addressing: AddressingMode::Implied, + }, + Operation { + name: "TSX", + code: 0xBA, + length: 1, + addressing: AddressingMode::Implied, + }, + Operation { + name: "PLA", + code: 0x68, + length: 1, + addressing: AddressingMode::Implied, + }, + Operation { + name: "PLP", + code: 0x28, + length: 1, + addressing: AddressingMode::Implied, + }, + Operation { + name: "PHP", + code: 0x08, + length: 1, + addressing: AddressingMode::Implied, + }, + Operation { + name: "JSR", + code: 0x20, + length: 3, + addressing: AddressingMode::Absolute, + }, + Operation { + name: "RTS", + code: 0x60, + length: 1, + addressing: AddressingMode::Implied, + }, + // LSR + Operation { + name: "LSR", + code: 0x46, + length: 2, + addressing: AddressingMode::ZeroPage, + }, + Operation { + name: "LSR", + code: 0x56, + length: 2, + addressing: AddressingMode::ZeroPageIndexedX, + }, + Operation { + name: "LSR", + code: 0x4E, + length: 3, + addressing: AddressingMode::Absolute, + }, + Operation { + name: "LSR", + code: 0x5E, + length: 3, + addressing: AddressingMode::AbsoluteIndexedX, + }, + Operation { + name: "LSR", + code: 0x4A, + length: 1, + addressing: AddressingMode::AccumulatorAddressing, + }, + // ROL + Operation { + name: "ROL", + code: 0x2A, + length: 1, + addressing: AddressingMode::AccumulatorAddressing, + }, + Operation { + name: "ROL", + code: 0x26, + length: 2, + addressing: AddressingMode::ZeroPage, + }, + Operation { + name: "ROL", + code: 0x2E, + length: 3, + addressing: AddressingMode::Absolute, + }, + Operation { + name: "ROL", + code: 0x36, + length: 3, + addressing: AddressingMode::ZeroPageIndexedX, + }, + Operation { + name: "ROL", + code: 0x3E, + length: 3, + addressing: AddressingMode::AbsoluteIndexedX, + }, + Operation { + name: "TXS", + code: 0x9A, + length: 1, + addressing: AddressingMode::Implied, + }, + // ROR + Operation { + name: "ROR", + code: 0x6A, + length: 1, + addressing: AddressingMode::AccumulatorAddressing, + }, + Operation { + name: "ROR", + code: 0x66, + length: 2, + addressing: AddressingMode::ZeroPage, + }, + Operation { + name: "ROR", + code: 0x6E, + length: 3, + addressing: AddressingMode::Absolute, + }, + Operation { + name: "TXS", + code: 0x9A, + length: 1, + addressing: AddressingMode::Implied, + }, + // ASL + Operation { + name: "ASL", + code: 0x0A, + length: 1, + addressing: AddressingMode::AccumulatorAddressing, + }, + Operation { + name: "ASL", + code: 0x06, + length: 2, + addressing: AddressingMode::ZeroPage, + }, + Operation { + name: "ASL", + code: 0x16, + length: 2, + addressing: AddressingMode::ZeroPageIndexedX, + }, + Operation { + name: "ASL", + code: 0x0E, + length: 3, + addressing: AddressingMode::Absolute, + }, + Operation { + name: "ASL", + code: 0x1E, + length: 3, + addressing: AddressingMode::AbsoluteIndexedX, + }, + // AND + Operation { + name: "AND", + code: 0x29, + length: 2, + addressing: AddressingMode::Immediate, + }, + // + // ORA + // + Operation { + name: "ORA", + code: 0x09, + length: 2, + addressing: AddressingMode::Immediate, + }, + Operation { + name: "ORA", + code: 0x05, + length: 2, + addressing: AddressingMode::ZeroPage, + }, + Operation { + name: "ORA", + code: 0x15, + length: 2, + addressing: AddressingMode::ZeroPageIndexedX, + }, + Operation { + name: "ORA", + code: 0x0D, + length: 3, + addressing: AddressingMode::Absolute, + }, + Operation { + name: "ORA", + code: 0x1D, + length: 3, + addressing: AddressingMode::AbsoluteIndexedX, + }, + Operation { + name: "ORA", + code: 0x11, + length: 2, + addressing: AddressingMode::IndirectIndexed, + }, + Operation { + name: "ORA", + code: 0x01, + length: 2, + addressing: AddressingMode::IndirectIndexedX, + }, + Operation { + name: "ORA", + code: 0x19, + length: 3, + addressing: AddressingMode::AbsoluteIndexedY, + }, ]; pub fn by_code(code: u8) -> &'static Operation { + debug!("searching for operation by code={:X}", code); let opcode = OPERATIONS.iter().find(|o| o.code == code); if let Some(opcode) = opcode { return opcode; } else { + error!("Unknown operation code: 0x{:X}", code); panic!("unknown operation code"); } } -pub fn by_name(name: &str, addressing: AddressingMode) -> &Operation { - let opcode = OPERATIONS - .iter() - .find(|o| o.name == name && o.addressing == addressing); +pub fn by_name(name: &str) -> Vec<&'static Operation> { + debug!("searching for operation by name={}", name); + OPERATIONS.iter().filter(|o| o.name == name).collect() +} - if let Some(opcode) = opcode { - return opcode; - } else { - panic!("unknown operation name"); - } +pub fn by_name_and_addressing(name: &str, addressing: AddressingMode) -> &'static Operation { + debug!("searching for operation by name={}", name); + OPERATIONS + .iter() + .find(|o| o.name == name && o.addressing == addressing) + .expect("unknown operation name") } #[cfg(test)] mod tests { - use crate::operation::{by_name, AddressingMode}; + use crate::operation::{by_name_and_addressing, AddressingMode}; macro_rules! test_opcode { ($test_name:ident, $name:expr, $mode:expr, $expected:expr) => { #[test] fn $test_name() { - assert_eq!(by_name($name, $mode).code, $expected); + assert_eq!(by_name_and_addressing($name, $mode).code, $expected); } }; } - test_opcode!(brk_immediate, "BRK", AddressingMode::Immediate, 0x00); + test_opcode!(brk, "BRK", AddressingMode::Implied, 0x00); + test_opcode!(rti, "RTI", AddressingMode::Implied, 0x40); + test_opcode!(tax, "TAX", AddressingMode::Implied, 0xAA); + test_opcode!(tay, "TAY", AddressingMode::Implied, 0xA8); + test_opcode!(txa, "TXA", AddressingMode::Implied, 0x8A); + test_opcode!(tya, "TYA", AddressingMode::Implied, 0x98); + test_opcode!(tsx, "TSX", AddressingMode::Implied, 0xBA); + + test_opcode!(php, "PHP", AddressingMode::Implied, 0x08); + test_opcode!(plp, "PLP", AddressingMode::Implied, 0x28); + + test_opcode!(clc, "CLC", AddressingMode::Implied, 0x18); + test_opcode!(cld, "CLD", AddressingMode::Implied, 0xD8); + test_opcode!(cli, "CLI", AddressingMode::Implied, 0x58); + test_opcode!(clv, "CLV", AddressingMode::Implied, 0xB8); + + test_opcode!(sec, "SEC", AddressingMode::Implied, 0x38); + test_opcode!(sed, "SED", AddressingMode::Implied, 0xF8); + test_opcode!(sei, "SEI", AddressingMode::Implied, 0x78); + + test_opcode!(nop, "NOP", AddressingMode::Implied, 0xEA); + + test_opcode!(inc_zero_page, "INC", AddressingMode::ZeroPage, 0xE6); + test_opcode!( + inc_zero_page_x, + "INC", + AddressingMode::ZeroPageIndexedX, + 0xF6 + ); + test_opcode!(inc_absolute, "INC", AddressingMode::Absolute, 0xEE); + + test_opcode!(dec_zero_page, "DEC", AddressingMode::ZeroPage, 0xC6); + test_opcode!( + dec_zero_page_x, + "DEC", + AddressingMode::ZeroPageIndexedX, + 0xD6 + ); + test_opcode!(dec_absolute, "DEC", AddressingMode::Absolute, 0xCE); + test_opcode!( + dec_absolute_x, + "DEC", + AddressingMode::AbsoluteIndexedX, + 0xDE + ); + + test_opcode!(inx, "INX", AddressingMode::Implied, 0xE8); + test_opcode!(iny, "INY", AddressingMode::Implied, 0xC8); + + test_opcode!(dex, "DEX", AddressingMode::Implied, 0xCA); + test_opcode!(dey, "DEY", AddressingMode::Implied, 0x88); + + test_opcode!(jmp_absolute, "JMP", AddressingMode::Absolute, 0x4C); + test_opcode!(pha, "PHA", AddressingMode::Implied, 0x48); + test_opcode!(pla, "PLA", AddressingMode::Implied, 0x68); + + test_opcode!(lsr_zero_page, "LSR", AddressingMode::ZeroPage, 0x46); + test_opcode!( + lsr_zero_page_x, + "LSR", + AddressingMode::ZeroPageIndexedX, + 0x56 + ); + test_opcode!(lsr_absolute, "LSR", AddressingMode::Absolute, 0x4E); + test_opcode!( + lsr_absolute_x, + "LSR", + AddressingMode::AbsoluteIndexedX, + 0x5E + ); + test_opcode!( + lsr_accumulator, + "LSR", + AddressingMode::AccumulatorAddressing, + 0x4A + ); + + test_opcode!(bit_zero_page, "BIT", AddressingMode::ZeroPage, 0x24); + test_opcode!(bit_absolute, "BIT", AddressingMode::Absolute, 0x2C); + + test_opcode!(and_immediate, "AND", AddressingMode::Immediate, 0x29); + + test_opcode!(ora_immediate, "ORA", AddressingMode::Immediate, 0x09); + test_opcode!(ora_zero_page, "ORA", AddressingMode::ZeroPage, 0x05); + test_opcode!( + ora_zero_page_x, + "ORA", + AddressingMode::ZeroPageIndexedX, + 0x15 + ); + test_opcode!(ora_absolute, "ORA", AddressingMode::Absolute, 0x0D); + test_opcode!( + ora_absolute_x, + "ORA", + AddressingMode::AbsoluteIndexedX, + 0x1D + ); + test_opcode!( + ora_absolute_y, + "ORA", + AddressingMode::AbsoluteIndexedY, + 0x19 + ); + + test_opcode!(cmp_immediate, "CMP", AddressingMode::Immediate, 0xC9); + test_opcode!(cmp_zero_page, "CMP", AddressingMode::ZeroPage, 0xC5); + test_opcode!(cmp_absolute, "CMP", AddressingMode::Absolute, 0xCD); + test_opcode!( + cmp_indirect_x, + "CMP", + AddressingMode::IndirectIndexedX, + 0xC1 + ); + test_opcode!(cmp_indirect_y, "CMP", AddressingMode::IndirectIndexed, 0xD1); + test_opcode!( + cmp_zero_page_x, + "CMP", + AddressingMode::ZeroPageIndexedX, + 0xD5 + ); + test_opcode!( + cmp_absolute_x, + "CMP", + AddressingMode::AbsoluteIndexedX, + 0xDD + ); + test_opcode!( + cmp_absolute_y, + "CMP", + AddressingMode::AbsoluteIndexedY, + 0xD9 + ); + + test_opcode!(cpx_immediate, "CPX", AddressingMode::Immediate, 0xE0); + test_opcode!(cpx_zero_page, "CPX", AddressingMode::ZeroPage, 0xE4); + test_opcode!(cpx_absolute, "CPX", AddressingMode::Absolute, 0xEC); + + test_opcode!(cpy_immediate, "CPY", AddressingMode::Immediate, 0xC0); + test_opcode!(cpy_zero_page, "CPY", AddressingMode::ZeroPage, 0xC4); + test_opcode!(cpy_absolute, "CPY", AddressingMode::Absolute, 0xCC); + test_opcode!(lda_immediate, "LDA", AddressingMode::Immediate, 0xa9); + test_opcode!( + lda_absolute_indexed_y, + "LDA", + AddressingMode::AbsoluteIndexedY, + 0xB9 + ); + test_opcode!( + lda_absolute_indexed_x, + "LDA", + AddressingMode::AbsoluteIndexedX, + 0xBD + ); + test_opcode!(lda_zero_page, "LDA", AddressingMode::ZeroPage, 0xa5); + test_opcode!( + lda_zero_page_x, + "LDA", + AddressingMode::ZeroPageIndexedX, + 0xB5 + ); + test_opcode!(lda_absolute, "LDA", AddressingMode::Absolute, 0xad); + test_opcode!( + lda_indirect_x, + "LDA", + AddressingMode::IndirectIndexedX, + 0xA1 + ); + test_opcode!(lda_indirect_y, "LDA", AddressingMode::IndirectIndexed, 0xB1); + + test_opcode!(ldx_immediate, "LDX", AddressingMode::Immediate, 0xa2); + test_opcode!(ldx_zero_page, "LDX", AddressingMode::ZeroPage, 0xa6); + test_opcode!( + ldx_zero_page_y, + "LDX", + AddressingMode::ZeroPageIndexedY, + 0xB6 + ); + test_opcode!(ldx_absolute, "LDX", AddressingMode::Absolute, 0xae); + test_opcode!( + ldx_absolute_y, + "LDX", + AddressingMode::AbsoluteIndexedY, + 0xBE + ); + + test_opcode!(ldy_immediate, "LDY", AddressingMode::Immediate, 0xA0); + test_opcode!(ldy_zero_page, "LDY", AddressingMode::ZeroPage, 0xA4); + test_opcode!( + ldy_zero_page_x, + "LDY", + AddressingMode::ZeroPageIndexedX, + 0xB4 + ); + test_opcode!(ldy_absolute, "LDY", AddressingMode::Absolute, 0xAC); + test_opcode!( + ldy_absolute_x, + "LDY", + AddressingMode::AbsoluteIndexedX, + 0xBC + ); + test_opcode!(adc_immediate, "ADC", AddressingMode::Immediate, 0x69); - test_opcode!(adc_absolute, "ADC", AddressingMode::Absolute, 0x60); - test_opcode!(sta_absolute, "STA", AddressingMode::Absolute, 0x85); + test_opcode!(adc_absolute, "ADC", AddressingMode::Absolute, 0x6D); + test_opcode!(adc_zero_page, "ADC", AddressingMode::ZeroPage, 0x65); + test_opcode!( + adc_zero_page_x, + "ADC", + AddressingMode::ZeroPageIndexedX, + 0x75 + ); + test_opcode!( + adc_absolute_x, + "ADC", + AddressingMode::AbsoluteIndexedX, + 0x7D + ); + test_opcode!( + adc_absolute_y, + "ADC", + AddressingMode::AbsoluteIndexedY, + 0x79 + ); + + test_opcode!(sbc_immediate, "SBC", AddressingMode::Immediate, 0xE9); + test_opcode!(sbc_absolute, "SBC", AddressingMode::Absolute, 0xED); + test_opcode!(sbc_zero_page, "SBC", AddressingMode::ZeroPage, 0xE5); + test_opcode!( + sbc_zero_page_x, + "SBC", + AddressingMode::ZeroPageIndexedX, + 0xF5 + ); + test_opcode!(sbc_indirect_y, "SBC", AddressingMode::IndirectIndexed, 0xF1); + + test_opcode!(stx_absolute, "STX", AddressingMode::Absolute, 0x8E); + test_opcode!(stx_zero_page, "STX", AddressingMode::ZeroPage, 0x86); + + test_opcode!(txs, "TXS", AddressingMode::Implied, 0x9A); + + test_opcode!(eor_immediate, "EOR", AddressingMode::Immediate, 0x49); + + test_opcode!( + asl_accumulator, + "ASL", + AddressingMode::AccumulatorAddressing, + 0x0A + ); + test_opcode!(asl_zero_page, "ASL", AddressingMode::ZeroPage, 0x06); + test_opcode!( + asl_zero_page_x, + "ASL", + AddressingMode::ZeroPageIndexedX, + 0x16 + ); + test_opcode!(asl_absolute, "ASL", AddressingMode::Absolute, 0x0e); + test_opcode!( + asl_absolute_x, + "ASL", + AddressingMode::AbsoluteIndexedX, + 0x1e + ); + + test_opcode!(sty_absolute, "STY", AddressingMode::Absolute, 0x8C); + test_opcode!( + sty_zero_page_x, + "STY", + AddressingMode::ZeroPageIndexedX, + 0x94 + ); + test_opcode!(sty_zero_page, "STY", AddressingMode::ZeroPage, 0x84); + + test_opcode!(sta_absolute, "STA", AddressingMode::Absolute, 0x8D); + test_opcode!(sta_zero_page, "STA", AddressingMode::ZeroPage, 0x85); + test_opcode!( + sta_zero_page_x, + "STA", + AddressingMode::ZeroPageIndexedX, + 0x95 + ); + test_opcode!( + stx_zero_page_y, + "STX", + AddressingMode::ZeroPageIndexedY, + 0x96 + ); + test_opcode!( + sta_absolute_x, + "STA", + AddressingMode::AbsoluteIndexedX, + 0x9D + ); + test_opcode!( + sta_indirect_x, + "STA", + AddressingMode::IndirectIndexedX, + 0x81 + ); + test_opcode!(sta_indirect_y, "STA", AddressingMode::IndirectIndexed, 0x91); + test_opcode!( + sta_absolute_y, + "STA", + AddressingMode::AbsoluteIndexedY, + 0x99 + ); + + test_opcode!(bne, "BNE", AddressingMode::RelativeAddressing, 0xD0); + test_opcode!(bvc, "BVC", AddressingMode::RelativeAddressing, 0x50); + test_opcode!(bvs, "BVS", AddressingMode::RelativeAddressing, 0x70); + test_opcode!(beq, "BEQ", AddressingMode::RelativeAddressing, 0xF0); + test_opcode!(bmi, "BMI", AddressingMode::RelativeAddressing, 0x30); + test_opcode!(bpl, "BPL", AddressingMode::RelativeAddressing, 0x10); + test_opcode!(bcc, "BCC", AddressingMode::RelativeAddressing, 0x90); + test_opcode!(bcs, "BCS", AddressingMode::RelativeAddressing, 0xB0); + + test_opcode!(jsr, "JSR", AddressingMode::Absolute, 0x20); + test_opcode!(rts, "RTS", AddressingMode::Implied, 0x60); + + test_opcode!(ror_zero_page, "ROR", AddressingMode::ZeroPage, 0x66); + test_opcode!(ror_absolute, "ROR", AddressingMode::Absolute, 0x6E); + test_opcode!( + ror_accumulator, + "ROR", + AddressingMode::AccumulatorAddressing, + 0x6A + ); + + test_opcode!(rol_zero_page, "ROL", AddressingMode::ZeroPage, 0x26); + test_opcode!( + rol_zero_page_x, + "ROL", + AddressingMode::ZeroPageIndexedX, + 0x36 + ); + test_opcode!( + rol_absolute_x, + "ROL", + AddressingMode::AbsoluteIndexedX, + 0x3E + ); + test_opcode!(rol_absolute, "ROL", AddressingMode::Absolute, 0x2E); + test_opcode!( + rol_accumulator, + "ROL", + AddressingMode::AccumulatorAddressing, + 0x2A + ); } diff --git a/src/utils.rs b/src/utils.rs index 76b17a6..77daa68 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,3 +1,3 @@ -pub fn little_endian_to_u16(big: u8, little: u8) -> u16 { - (little as u16) << 8 | (big as u16) +pub fn little_endian_to_u16(bigger: u8, little: u8) -> u16 { + u16::from(bigger) << 8 | u16::from(little) } diff --git a/sys/wozmon.bin b/sys/wozmon.bin new file mode 100644 index 0000000000000000000000000000000000000000..7280907226f38c9243aa42bcba245bf5824b764e GIT binary patch literal 256 zcmcZ+v7o+3=)%h7y@D5dg)f}E|3UcV><`Q*1o&6pQF#AMm0|CJ1@xLUPO>||g%+56$j$#oOHeE4v3*N4|9FMY6X(`eId(E>_& zZaB#`fi1J+WTtKjhg-cCCP4m=+A15w6IHL7ox{7vyz*3DywP$J