diff --git a/Cargo.lock b/Cargo.lock index 51faf5b..e44aa68 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,5 +3,682 @@ version = 3 [[package]] -name = "rustboy" +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bumpalo" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cmake" +version = "0.1.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db34956e100b30725f2eb215f90d4871051239535632f84fea3bc92722c66b7c" +dependencies = [ + "cc", +] + +[[package]] +name = "cty" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" + +[[package]] +name = "dlib" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794" +dependencies = [ + "libloading", +] + +[[package]] +name = "downcast-rs" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" + +[[package]] +name = "fastrand" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +dependencies = [ + "instant", +] + +[[package]] +name = "futures" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" + +[[package]] +name = "futures-executor" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" + +[[package]] +name = "futures-macro" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" + +[[package]] +name = "futures-task" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" + +[[package]] +name = "futures-util" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "js-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "minifb" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9e21c5f89bb820c7878c300c5b944e65de0f1b2a75e0be92ce670b95943740e" +dependencies = [ + "cc", + "dlib", + "futures", + "instant", + "js-sys", + "lazy_static", + "libc", + "orbclient", + "raw-window-handle 0.4.3", + "serde", + "serde_derive", + "tempfile", + "wasm-bindgen-futures", + "wayland-client", + "wayland-cursor", + "wayland-protocols", + "winapi", + "x11-dl", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nix" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" +dependencies = [ + "bitflags", + "cfg-if", + "libc", + "memoffset", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "once_cell" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" + +[[package]] +name = "orbclient" +version = "0.3.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba683f1641c11041c59d5d93689187abcab3c1349dc6d9d70c550c9f9360802f" +dependencies = [ + "cfg-if", + "libc", + "raw-window-handle 0.3.4", + "redox_syscall", + "sdl2", + "sdl2-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" + +[[package]] +name = "proc-macro2" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "qoboy" +version = "1.0.0" +dependencies = [ + "minifb", +] + +[[package]] +name = "quote" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "raw-window-handle" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28f55143d0548dad60bb4fbdc835a3d7ac6acc3324506450c5fdd6e42903a76" +dependencies = [ + "libc", + "raw-window-handle 0.4.3", +] + +[[package]] +name = "raw-window-handle" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b800beb9b6e7d2df1fe337c9e3d04e3af22a124460fb4c30fcc22c9117cefb41" +dependencies = [ + "cty", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "sdl2" +version = "0.35.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7959277b623f1fb9e04aea73686c3ca52f01b2145f8ea16f4ff30d8b7623b1a" +dependencies = [ + "bitflags", + "lazy_static", + "libc", + "raw-window-handle 0.4.3", + "sdl2-sys", +] + +[[package]] +name = "sdl2-sys" +version = "0.35.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3586be2cf6c0a8099a79a12b4084357aa9b3e0b0d7980e3b67aaf7a9d55f9f0" +dependencies = [ + "cfg-if", + "cmake", + "libc", + "version-compare", +] + +[[package]] +name = "serde" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "slab" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "syn" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "unicode-ident" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" + +[[package]] +name = "version-compare" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" + +[[package]] +name = "wasm-bindgen" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" + +[[package]] +name = "wayland-client" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f3b068c05a039c9f755f881dc50f01732214f5685e379829759088967c46715" +dependencies = [ + "bitflags", + "downcast-rs", + "libc", + "nix", + "scoped-tls", + "wayland-commons", + "wayland-scanner", + "wayland-sys", +] + +[[package]] +name = "wayland-commons" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8691f134d584a33a6606d9d717b95c4fa20065605f798a3f350d78dced02a902" +dependencies = [ + "nix", + "once_cell", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-cursor" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661" +dependencies = [ + "nix", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6" +dependencies = [ + "bitflags", + "wayland-client", + "wayland-commons", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f4303d8fa22ab852f789e75a967f0a2cdc430a607751c0499bada3e451cbd53" +dependencies = [ + "proc-macro2", + "quote", + "xml-rs", +] + +[[package]] +name = "wayland-sys" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be12ce1a3c39ec7dba25594b97b42cb3195d54953ddb9d3d95a7c3902bc6e9d4" +dependencies = [ + "dlib", + "lazy_static", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "xcursor" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "463705a63313cd4301184381c5e8042f0a7e9b4bb63653f216311d4ae74690b7" +dependencies = [ + "nom", +] + +[[package]] +name = "xml-rs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" diff --git a/Cargo.toml b/Cargo.toml index ec6597f..a241bc8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,9 @@ [package] -name = "rustboy" -version = "0.1.0" +name = "qoboy" +version = "1.0.0" edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +minifb = "0.23.0" \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..d9c415c --- /dev/null +++ b/README.md @@ -0,0 +1,120 @@ +# Qoboy + +A gameboy emulator with an embedded debugger and a video ram viewer. + +## Installation and running + +Clone and build the projet with the following commands + +```shell +git clone https://github.com/qoda-dev/qoboy.git + +cd qoboy/ + +cargo build --release +``` + +To use the emulator, you have to bring your own **boot rom** and **game rom** files. Then you can run the game with the following command: + +```shell +cargo run +``` + +The keyboard mapping is defined as follows: + +| Gameboy control | Keyboard | +| ----------------- | ------- | +| A | a | +| B | z | +| start | backspace | +| select | enter | +| left | left arrow | +| right | right arrow | +| up | up arrow | +| down | down arrow | + +## Embedded debugger + +This emulator comes with an embedded **video ram viewer** and a light **debugger** which can ease the development of your game or your own emulator by using this one as a reference. + +To launch the debugger, add **--debug** when running your game rom: + +```shell +cargo run --debug +``` + +Till now, the debugger can handle the following commands: + +| command | argument | description | +| ----------------- | ------- | ------ | +| run | none | run the cpu until it encounters a breakpoint or a halt command is received | +| halt | none | when the cpu is running, halt its execution to the current program counter | +| step | none | when the cpu is halted, execute the instruction pointed by the program counter and update the PC to the next instruction | +| break_set | address | set a breakpoint to the address | +| break_reset | none | reset the breakpoint | + +The emulator can manage only **one breakpoint** and the address passed to the **break_set** command shall meet the following format: + +```shell +break_set C012 +``` + +Where **C012** is the program address on which we want to break in **hexadecimal** format. + +> When launched with the **--debug** option, the emulator stops at address 0x0000 by default and waits for a command just like after a **halt** command has been typed. +> Type **run** or **step** to run your program. + +## Tests + +In addition to unit tests for each module, more general functionnal tests are done with blargg's and Acid2 test roms. + +### Blargg's tests + +Source files can be found [here](https://github.com/retrio/gb-test-roms). These roms are used to test general behaviour of CPU, timer and memory subsystems. + +| Blargg's test rom | Comment | Result | +| ----------------- | ------- | ------ | +| cpu_instrs | none | :heavy_check_mark: | +| instr_timing | none | :heavy_check_mark: | +| interrupt_time | need sound to pass | :x: | +| dmg_sound | need sound to pass | :x: | +| oam_bug | not implemented | :x: | +| halt_bug | not implemented | :x: | +| mem_timing | need a clock cycle accurate emulator | :x: | +| mem_timing-2 | need a clock cycle accurate emulator | :x: | + +### Acid2 tests + +Source files can be found [here](https://github.com/mattcurrie/dmg-acid2). This rom is used to test the PPU unit. + +| test rom | Comment | Result | +| -------- | ------- | ------ | +| dmg_acid2 | sprite priority follows GB color behaviour | :x: | + +## Features + +- [X] implement a gameboy emulator which passes all cpu_instr and instr_timing tests +- [X] add support to no_mbc / mbc1 / mbc3 cartridge types +- [X] implement a lightweight debugger +- [X] implement a vram viewer +- [ ] fix sprite priority to pass ACID2 test +- [ ] add possibility to save a game +- [ ] use winit and softbuffer instead of minifb (which is not as stable as expected) + +## Ressources + +### General + +- https://gbdev.io/pandocs/Specifications.html +- https://gbdev.gg8.se/wiki/articles/Main_Page + +### Opcodes + +- https://meganesu.github.io/generate-gb-opcodes/ +- https://www.pastraiser.com/cpu/gameboy/gameboy_opcodes.html + +### Reference emulators + +- https://github.com/Gekkio/mooneye-gb +- https://github.com/mohanson/gameboy +- https://github.com/rylev/DMG-01 \ No newline at end of file diff --git a/assets/ASSET.md b/assets/ASSET.md new file mode 100644 index 0000000..8cd9513 --- /dev/null +++ b/assets/ASSET.md @@ -0,0 +1 @@ +Here can be found custom logo for the project. Use the STARGAZE font for the project title. \ No newline at end of file diff --git a/src/cartridge/mbc1.rs b/src/cartridge/mbc1.rs new file mode 100644 index 0000000..891c362 --- /dev/null +++ b/src/cartridge/mbc1.rs @@ -0,0 +1,158 @@ +use crate::cartridge::{MbcType, RomSize, RamSize, Mbc}; + +const RAM_ENABLE_SPACE_START: u16 = 0x0000; +const RAM_ENABLE_SPACE_END: u16 = 0x1FFF; + +const ROM_BANK_NB_SPACE_START: u16 = 0x2000; +const ROM_BANK_NB_SPACE_END: u16 = 0x3FFF; + +const RAM_BANK_NB_SPACE_START: u16 = 0x4000; +const RAM_BANK_NB_SPACE_END: u16 = 0x5FFF; + +const BANKING_MODE_SPACE_START: u16 = 0x6000; +const BANKING_MODE_SPACE_END: u16 = 0x7FFF; + +const ENABLE_RAM_FLAG: u8 = 0x0A; + +const GB_ADDR_BIT_MASK: usize = 0x3FFF; +const ROM_BANK_BIT_OFFSET: usize = 14; +const RAM_BANK_BIT_OFFSET: usize = 19; + +#[allow(non_camel_case_types)] +enum RomBankMask { + MASK_1_BIT = 0x01, + MASK_2_BIT = 0x03, + MASK_3_BIT = 0x07, + MASK_4_BIT = 0x0F, + MASK_5_BIT = 0x1F, +} + +pub struct Mbc1 { + // config + rom_size: RomSize, + // internal registers + ram_enable: bool, + rom_bank_number: u8, + ram_bank_number: u8, + banking_mode: bool, + // memory + rom_bank: Vec, + ram_bank: Vec, +} + +impl Mbc1 { + pub fn new(_: MbcType, rom_size: RomSize, ram_size: RamSize, rom: &[u8]) -> Mbc1 { + let mut rom_bank: Vec = vec![0xFF; rom_size.clone() as usize]; + let ram_bank: Vec = vec![0xFF; ram_size.clone() as usize]; + + // copy all rom data + for rom_index in 0..(rom_size as usize){ + rom_bank[rom_index as usize] = rom[rom_index as usize]; + } + + Mbc1 { + // config + rom_size: rom_size, + // internal registers + ram_enable: false, + rom_bank_number: 1, + ram_bank_number: 0, + banking_mode: false, + // memory + rom_bank: rom_bank, + ram_bank: ram_bank, + } + } +} + +impl Mbc for Mbc1 { + fn read_bank_0 (&self, address: usize) -> u8 { + if self.banking_mode { + let gb_addr = ((self.ram_bank_number as usize) << RAM_BANK_BIT_OFFSET) | (address & GB_ADDR_BIT_MASK); + self.rom_bank[gb_addr] + } else { + let gb_addr = address & GB_ADDR_BIT_MASK; + self.rom_bank[gb_addr] + } + } + + fn read_bank_n (&self, address: usize) -> u8 { + let gb_addr = ((self.ram_bank_number as usize) << RAM_BANK_BIT_OFFSET) + | ((self.rom_bank_number as usize) << ROM_BANK_BIT_OFFSET) + | (address & GB_ADDR_BIT_MASK); + self.rom_bank[gb_addr] + } + + fn read_ram (&self, address: usize) -> u8 { + if self.ram_enable { + if self.banking_mode { + let gb_addr = address & 0x1FFF; + self.ram_bank[gb_addr] + } else { + let gb_addr = ((self.ram_bank_number as usize) << 13) + | (address & 0x1FFF); + self.ram_bank[gb_addr] + } + } else { + // RAM is disabled, returns 0xFF + 0xFF + } + } + + fn write_bank_0 (&mut self, address: usize, data: u8) { + match address as u16 { + RAM_ENABLE_SPACE_START..=RAM_ENABLE_SPACE_END => { + if data == ENABLE_RAM_FLAG { + self.ram_enable = true; + } + }, + ROM_BANK_NB_SPACE_START..=ROM_BANK_NB_SPACE_END => { + let rom_bank_mask = match self.rom_size { + RomSize::SIZE_32_KB => RomBankMask::MASK_1_BIT, + RomSize::SIZE_64_KB => RomBankMask::MASK_2_BIT, + RomSize::SIZE_128_KB => RomBankMask::MASK_3_BIT, + RomSize::SIZE_256_KB => RomBankMask::MASK_4_BIT, + _ => RomBankMask::MASK_5_BIT, + }; + + self.rom_bank_number = if data != 0 { + data & (rom_bank_mask as u8) + } else { + // if register is set to 0, set it to 1 + 1 + }; + }, + _ => panic!("mbc 1 bank 0 address {:x} doesn't exists.", address), + } + } + + fn write_bank_n (&mut self, address: usize, data: u8) { + match address as u16 { + RAM_BANK_NB_SPACE_START..=RAM_BANK_NB_SPACE_END => { + self.ram_bank_number = data & 0x03; + }, + BANKING_MODE_SPACE_START..=BANKING_MODE_SPACE_END => { + self.banking_mode = (data & 0x01) != 0; + }, + _ => panic!("mbc 1 bank n address {:x} doesn't exists.", address), + } + } + + fn write_ram (&mut self, address: usize, data: u8) { + if self.ram_enable { + if self.banking_mode { + let gb_addr = address & 0x1FFF; + self.ram_bank[gb_addr] = data; + } else { + let gb_addr = ((self.ram_bank_number as usize) << 13) + | (address & 0x1FFF); + self.ram_bank[gb_addr] = data; + } + } else { + // do nothing when ram is disabled + } + } + + // not used for this mbc, doesn't do anything + fn run (&mut self, _: u8) {} +} \ No newline at end of file diff --git a/src/cartridge/mbc3.rs b/src/cartridge/mbc3.rs new file mode 100644 index 0000000..569f6c3 --- /dev/null +++ b/src/cartridge/mbc3.rs @@ -0,0 +1,258 @@ +use crate::cartridge::{MbcType, RomSize, RamSize, Mbc}; +use crate::emulator::ONE_SECOND_IN_CYCLES; + +const RAM_ENABLE_SPACE_START: u16 = 0x0000; +const RAM_ENABLE_SPACE_END: u16 = 0x1FFF; + +const ROM_BANK_NB_SPACE_START: u16 = 0x2000; +const ROM_BANK_NB_SPACE_END: u16 = 0x3FFF; + +const RAM_BANK_NB_SPACE_START: u16 = 0x4000; +const RAM_BANK_NB_SPACE_END: u16 = 0x5FFF; + +const LATCH_CLOCK_SPACE_START: u16 = 0x6000; +const LATCH_CLOCK_SPACE_END: u16 = 0x7FFF; + +const ENABLE_RAM_FLAG: u8 = 0x0A; + +const GB_ADDR_BIT_MASK: usize = 0x3FFF; +const ROM_BANK_BIT_OFFSET: usize = 14; +const RAM_BANK_BIT_OFFSET: usize = 13; + +#[allow(non_camel_case_types)] +enum RomBankMask { + MASK_1_BIT = 0x01, + MASK_2_BIT = 0x03, + MASK_3_BIT = 0x07, + MASK_4_BIT = 0x0F, + MASK_5_BIT = 0x1F, + MASK_6_BIT = 0x3F, + MASK_7_BIT = 0x7F, +} + +pub struct Mbc3 { + // config + rom_size: RomSize, + // internal registers + ram_enable: bool, + rom_bank_number: u8, + ram_bank_number: u8, + // memory + rom_bank: Vec, + ram_bank: Vec, + // rtc + latch_rtc_flag: bool, + latch_rtc_enable: bool, + rtc_cycles: usize, + rtc_sec: u8, + rtc_min: u8, + rtc_hours: u8, + rtc_day_lo: u8, + rtc_day_hi: bool, + rtc_halt: bool, + rtc_overflow: bool, + rtc_sec_latch: u8, + rtc_min_latch: u8, + rtc_hours_latch: u8, + rtc_day_latch: u8, +} + +impl Mbc3 { + pub fn new(_: MbcType, rom_size: RomSize, ram_size: RamSize, rom: &[u8]) -> Mbc3 { + let mut rom_bank: Vec = vec![0xFF; rom_size.clone() as usize]; + let ram_bank: Vec = vec![0xFF; ram_size.clone() as usize]; + + // copy all rom data + for rom_index in 0..(rom_size as usize){ + rom_bank[rom_index as usize] = rom[rom_index as usize]; + } + + Mbc3 { + // config + rom_size: rom_size, + // internal registers + ram_enable: false, + rom_bank_number: 1, + ram_bank_number: 0, + // memory + rom_bank: rom_bank, + ram_bank: ram_bank, + // rtc + latch_rtc_flag: false, + latch_rtc_enable: false, + rtc_cycles: 0, + rtc_sec: 0, + rtc_min: 0, + rtc_hours: 0, + rtc_day_lo: 0, + rtc_day_hi: false, + rtc_halt: false, + rtc_overflow: false, + rtc_sec_latch: 0, + rtc_min_latch: 0, + rtc_hours_latch: 0, + rtc_day_latch: 0, + } + } +} + +impl Mbc for Mbc3 { + fn read_bank_0 (&self, address: usize) -> u8 { + let gb_addr = address & GB_ADDR_BIT_MASK; + self.rom_bank[gb_addr] + } + + fn read_bank_n (&self, address: usize) -> u8 { + let gb_addr = ((self.rom_bank_number as usize) << ROM_BANK_BIT_OFFSET) + | (address & GB_ADDR_BIT_MASK); + self.rom_bank[gb_addr] + } + + fn read_ram (&self, address: usize) -> u8 { + if self.ram_enable { + match self.ram_bank_number { + // here we access the ram banks + 0x00..=0x03 => { + let gb_addr = ((self.ram_bank_number as usize) << RAM_BANK_BIT_OFFSET) + | (address & 0x1FFF); + self.ram_bank[gb_addr] + } + // here we access rtc registers + 0x08 => self.rtc_sec_latch, + 0x09 => self.rtc_min_latch, + 0x0A => self.rtc_hours_latch, + 0x0B => self.rtc_day_latch, + 0x0C => (self.rtc_day_hi as u8) + | (self.rtc_halt as u8) << 6 + | (self.rtc_overflow as u8) << 7, + _ => 0xFF, + } + } else { + // RAM is disabled, returns 0xFF + 0xFF + } + } + + fn write_bank_0 (&mut self, address: usize, data: u8) { + match address as u16 { + RAM_ENABLE_SPACE_START..=RAM_ENABLE_SPACE_END => { + if data == ENABLE_RAM_FLAG { + self.ram_enable = true; + } + }, + ROM_BANK_NB_SPACE_START..=ROM_BANK_NB_SPACE_END => { + let rom_bank_mask = match self.rom_size { + RomSize::SIZE_32_KB => RomBankMask::MASK_1_BIT, + RomSize::SIZE_64_KB => RomBankMask::MASK_2_BIT, + RomSize::SIZE_128_KB => RomBankMask::MASK_3_BIT, + RomSize::SIZE_256_KB => RomBankMask::MASK_4_BIT, + RomSize::SIZE_512_KB => RomBankMask::MASK_5_BIT, + RomSize::SIZE_1_MB => RomBankMask::MASK_6_BIT, + _ => RomBankMask::MASK_7_BIT, + }; + + self.rom_bank_number = if data != 0 { + data & (rom_bank_mask as u8) + } else { + // if register is set to 0, set it to 1 + 1 + }; + }, + _ => panic!("mbc 1 bank 0 address {:x} doesn't exists.", address), + } + } + + fn write_bank_n (&mut self, address: usize, data: u8) { + match address as u16 { + RAM_BANK_NB_SPACE_START..=RAM_BANK_NB_SPACE_END => { + match data { + 0x00..=0x03 => self.ram_bank_number = data & 0x03, + 0x08..=0x0C => self.ram_bank_number = data, + _ => {/* do nothing here */}, + } + }, + LATCH_CLOCK_SPACE_START..=LATCH_CLOCK_SPACE_END => { + if data == 0x00 { + self.latch_rtc_flag = true; + } + + if data == 0x01 && self.latch_rtc_flag { + self.latch_rtc_flag = false; + self.latch_rtc_enable = true; + } + }, + _ => panic!("mbc 1 bank n address {:x} doesn't exists.", address), + } + } + + fn write_ram (&mut self, address: usize, data: u8) { + if self.ram_enable { + match self.ram_bank_number { + // here we access the ram banks + 0x00..=0x03 => { + let gb_addr = ((self.ram_bank_number as usize) << RAM_BANK_BIT_OFFSET) + | (address & 0x1FFF); + self.ram_bank[gb_addr] = data; + } + // here we access rtc registers + 0x08 => { self.rtc_sec = data } + 0x09 => { self.rtc_min = data } + 0x0A => { self.rtc_hours = data } + 0x0B => { self.rtc_day_lo = data } + 0x0C => { + self.rtc_day_hi = (data & 0x01) != 0; + self.rtc_halt = (data & 0x40) != 0; + self.rtc_overflow = (data & 0x80) != 0; + } + _ => {/* do nothing here */} + } + } else { + // do nothing when ram is disabled + } + } + + fn run (&mut self, cycles: u8) { + if !self.rtc_halt { + self.rtc_cycles += cycles as usize; + + if self.rtc_cycles > ONE_SECOND_IN_CYCLES { + let add_sec = (self.rtc_cycles / ONE_SECOND_IN_CYCLES) as u8; + // update rtc cycles + self.rtc_cycles = self.rtc_cycles % ONE_SECOND_IN_CYCLES; + // update rtc seconds + self.rtc_sec += add_sec; + if self.rtc_sec > 60 { + self.rtc_sec = 0; + self.rtc_min += 1; + }; + // update rtc minutes + if self.rtc_min > 60 { + self.rtc_min = 0; + self.rtc_hours += 1; + } + // update rtc hours + if self.rtc_hours >= 24 { + self.rtc_hours = 0; + // check if day has overflowed + if self.rtc_day_hi && self.rtc_day_lo == 0xFF { + self.rtc_overflow = true; + } + // update day value + let (new_value, overflow) = self.rtc_day_lo.overflowing_add(1); + self.rtc_day_lo = new_value; + if overflow {self.rtc_day_hi = overflow}; + } + } + } + + if self.latch_rtc_enable { + // save current counter + self.rtc_sec_latch = self.rtc_sec; + self.rtc_min_latch = self.rtc_min; + self.rtc_hours_latch = self.rtc_hours; + self.rtc_day_latch = self.rtc_day_lo; + // reset latch + self.latch_rtc_enable = false; + } + } +} \ No newline at end of file diff --git a/src/cartridge/mod.rs b/src/cartridge/mod.rs new file mode 100644 index 0000000..b6917d4 --- /dev/null +++ b/src/cartridge/mod.rs @@ -0,0 +1,262 @@ +mod rom; +mod mbc1; +mod mbc3; + +use rom::Rom; +use mbc1::Mbc1; +use mbc3::Mbc3; + +pub const CARTRIDGE_TYPE_OFFSET: u16 = 0x147; +pub const CARTRIDGE_ROM_SIZE_OFFSET: u16 = 0x148; +pub const CARTRIDGE_RAM_SIZE_OFFSET: u16 = 0x149; + +#[allow(non_camel_case_types)] +pub enum MbcType { + ROM_ONLY, + MBC_1, + MBC_1_RAM, + MBC_1_RAM_BAT, + MBC_2, + MBC_2_BAT, + ROM_RAM, + ROM_RAM_BAT, + MMM01, + MMM01_RAM, + MMM01_RAM_BAT, + MBC_3_TIM_BAT, + MBC_3_TIM_RAM_BAT, + MBC_3, + MBC_3_RAM, + MBC_3_RAM_BAT, + MBC_5, + MBC_5_RAM, + MBC_5_RAM_BAT, + MBC_5_RUMBLE, + MBC_5_RUMBLE_RAM, + MBC_5_RUMBLE_RAM_BAT, + MBC_6, + MBC_7, + CAMERA, + TAMA_5, + HUC3, + HUC1, +} + +impl std::fmt::Display for MbcType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mbc_type = match &*self { + MbcType::ROM_ONLY => "ROM_ONLY", + MbcType::MBC_1 => "MBC_1", + MbcType::MBC_1_RAM => "MBC_1_RAM", + MbcType::MBC_1_RAM_BAT => "MBC_1_RAM_BAT", + MbcType::MBC_2 => "MBC_2", + MbcType::MBC_2_BAT => "MBC_2_BAT", + MbcType::ROM_RAM => "ROM_RAM", + MbcType::ROM_RAM_BAT => "ROM_RAM_BAT", + MbcType::MMM01 => "MMM01", + MbcType::MMM01_RAM => "MMM01_RAM", + MbcType::MMM01_RAM_BAT => "MMM01_RAM_BAT", + MbcType::MBC_3_TIM_BAT => "MBC_3_TIM_BAT", + MbcType::MBC_3_TIM_RAM_BAT => "MBC_3_TIM_RAM_BAT", + MbcType::MBC_3 => "MBC_3", + MbcType::MBC_3_RAM => "MBC_3_RAM", + MbcType::MBC_3_RAM_BAT => "MBC_3_RAM_BAT", + MbcType::MBC_5 => "MBC_5", + MbcType::MBC_5_RAM => "MBC_5_RAM", + MbcType::MBC_5_RAM_BAT => "MBC_5_RAM_BAT", + MbcType::MBC_5_RUMBLE => "MBC_5_RUMBLE", + MbcType::MBC_5_RUMBLE_RAM => "MBC_5_RUMBLE_RAM", + MbcType::MBC_5_RUMBLE_RAM_BAT => "MBC_5_RUMBLE_RAM_BAT", + MbcType::MBC_6 => "MBC_6", + MbcType::MBC_7 => "MBC_7", + MbcType::CAMERA => "CAMERA", + MbcType::TAMA_5 => "TAMA_5", + MbcType::HUC3 => "HUC3", + MbcType::HUC1 => "HUC1", + }; + write!(f, "{}", mbc_type) + } +} + +#[allow(non_camel_case_types)] +#[derive(Copy, Clone)] +pub enum RomSize { + SIZE_32_KB = 0x8000, + SIZE_64_KB = 0x10000, + SIZE_128_KB = 0x20000, + SIZE_256_KB = 0x40000, + SIZE_512_KB = 0x80000, + SIZE_1_MB = 0x100000, + SIZE_2_MB = 0x200000, + SIZE_4_MB = 0x400000, + SIZE_8_MB = 0x800000, +} + +impl std::fmt::Display for RomSize { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let rom_size = match &*self { + RomSize::SIZE_32_KB => "SIZE_32_KB", + RomSize::SIZE_64_KB => "SIZE_64_KB", + RomSize::SIZE_128_KB => "SIZE_128_KB", + RomSize::SIZE_256_KB => "SIZE_256_KB", + RomSize::SIZE_512_KB => "SIZE_512_KB", + RomSize::SIZE_1_MB => "SIZE_1_MB", + RomSize::SIZE_2_MB => "SIZE_2_MB", + RomSize::SIZE_4_MB => "SIZE_4_MB", + RomSize::SIZE_8_MB => "SIZE_8_MB", + }; + write!(f, "{}", rom_size) + } +} + +#[allow(non_camel_case_types)] +#[derive(Copy, Clone)] +pub enum RamSize { + NO_RAM = 0x0000, + SIZE_8_KB = 0x2000, + SIZE_32_KB = 0x8000, + SIZE_128_KB = 0x20000, + SIZE_64_KB = 0x10000, +} + +impl std::fmt::Display for RamSize { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let ram_size = match &*self { + RamSize::NO_RAM => "NO_RAM", + RamSize::SIZE_8_KB => "SIZE_8_KB", + RamSize::SIZE_32_KB => "SIZE_32_KB", + RamSize::SIZE_128_KB => "SIZE_128_KB", + RamSize::SIZE_64_KB => "SIZE_64_KB", + }; + write!(f, "{}", ram_size) + } +} + +fn get_mbc_type(raw_data: u8) -> MbcType { + match raw_data { + 0x00 => MbcType::ROM_ONLY, + 0x01 => MbcType::MBC_1, + 0x02 => MbcType::MBC_1_RAM, + 0x03 => MbcType::MBC_1_RAM_BAT, + 0x05 => MbcType::MBC_2, + 0x06 => MbcType::MBC_2_BAT, + 0x08 => MbcType::ROM_RAM, + 0x09 => MbcType::ROM_RAM_BAT, + 0x0B => MbcType::MMM01, + 0x0C => MbcType::MMM01_RAM, + 0x0D => MbcType::MMM01_RAM_BAT, + 0x0F => MbcType::MBC_3_TIM_BAT, + 0x10 => MbcType::MBC_3_TIM_RAM_BAT, + 0x11 => MbcType::MBC_3, + 0x12 => MbcType::MBC_3_RAM, + 0x13 => MbcType::MBC_3_RAM_BAT, + 0x19 => MbcType::MBC_5, + 0x1A => MbcType::MBC_5_RAM, + 0x1B => MbcType::MBC_5_RAM_BAT, + 0x1C => MbcType::MBC_5_RUMBLE, + 0x1D => MbcType::MBC_5_RUMBLE_RAM, + 0x1E => MbcType::MBC_5_RUMBLE_RAM_BAT, + 0x20 => MbcType::MBC_6, + 0x22 => MbcType::MBC_7, + 0xFC => MbcType::CAMERA, + 0xFD => MbcType::TAMA_5, + 0xFE => MbcType::HUC3, + 0xFF => MbcType::HUC1, + _=> panic!("Catridge with mbc type {:x} is unknown", raw_data), + } +} + +fn get_rom_size(raw_data: u8) -> RomSize { + match raw_data { + 0x00 => RomSize::SIZE_32_KB, + 0x01 => RomSize::SIZE_64_KB, + 0x02 => RomSize::SIZE_128_KB, + 0x03 => RomSize::SIZE_256_KB, + 0x04 => RomSize::SIZE_512_KB, + 0x05 => RomSize::SIZE_1_MB, + 0x06 => RomSize::SIZE_2_MB, + 0x07 => RomSize::SIZE_4_MB, + 0x08 => RomSize::SIZE_8_MB, + _=> panic!("Catridge with Rom size code {:x} is unknown", raw_data), + } +} + +fn get_ram_size(raw_data: u8) -> RamSize { + match raw_data { + 0x00 => RamSize::NO_RAM, + 0x02 => RamSize::SIZE_8_KB, + 0x03 => RamSize::SIZE_32_KB, + 0x04 => RamSize::SIZE_128_KB, + 0x05 => RamSize::SIZE_64_KB, + _=> panic!("Catridge with Ram size code {:x} is unknown", raw_data), + } +} + +pub trait Mbc { + fn read_bank_0 (&self, address: usize) -> u8; + + fn read_bank_n (&self, address: usize) -> u8; + + fn read_ram (&self, address: usize) -> u8; + + fn write_bank_0 (&mut self, address: usize, data: u8); + + fn write_bank_n (&mut self, address: usize, data: u8); + + fn write_ram (&mut self, address: usize, data: u8); + + fn run(&mut self, cycles: u8); +} + +pub struct Cartridge { + mbc: Box, +} + +impl Cartridge { + pub fn new(rom: &[u8]) -> Cartridge { + // find the mbctype in the rom data + let mbc_type = get_mbc_type(rom[CARTRIDGE_TYPE_OFFSET as usize]); + let rom_size = get_rom_size(rom[CARTRIDGE_ROM_SIZE_OFFSET as usize]); + let ram_size = get_ram_size(rom[CARTRIDGE_RAM_SIZE_OFFSET as usize]); + + println!("Catridge with mbc type {}, rom size: {}, ram_size: {}", mbc_type, rom_size, ram_size); + + // find the correct mbc structure for the cartridge interface + Cartridge { + mbc: match mbc_type { + MbcType::ROM_ONLY => Box::new(Rom::new(rom)), + MbcType::MBC_1 => Box::new(Mbc1::new(mbc_type, rom_size, ram_size, rom)), + MbcType::MBC_3_RAM_BAT => Box::new(Mbc3::new(mbc_type, rom_size, ram_size, rom)), + _ => panic!("Catridge with mbc type {} is not supported", mbc_type), + }, + } + } + + pub fn read_bank_0(&self, address: usize) -> u8 { + self.mbc.read_bank_0(address) + } + + pub fn read_ram(&self, address: usize) -> u8 { + self.mbc.read_ram(address) + } + + pub fn read_bank_n(&self, address: usize) -> u8 { + self.mbc.read_bank_n(address) + } + + pub fn write_bank_0(&mut self, address: usize, data: u8) { + self.mbc.write_bank_0(address, data); + } + + pub fn write_bank_n(&mut self, address: usize, data: u8) { + self.mbc.write_bank_n(address, data); + } + + pub fn write_ram(&mut self, address: usize, data: u8) { + self.mbc.write_ram(address, data); + } + + pub fn run(&mut self, cycles: u8) { + self.mbc.run(cycles); + } +} \ No newline at end of file diff --git a/src/cartridge/rom.rs b/src/cartridge/rom.rs new file mode 100644 index 0000000..0108963 --- /dev/null +++ b/src/cartridge/rom.rs @@ -0,0 +1,46 @@ +use crate::cartridge::Mbc; +use crate::soc::peripheral::{ROM_BANK_0_SIZE, ROM_BANK_N_SIZE}; + +pub struct Rom { + rom_bank: [u8; (ROM_BANK_0_SIZE + ROM_BANK_N_SIZE) as usize], +} + +impl Rom { + pub fn new(rom: &[u8]) -> Rom { + // copy data + let mut rom_bank = [0x00; (ROM_BANK_0_SIZE + ROM_BANK_N_SIZE) as usize]; + for rom_index in 0..(ROM_BANK_0_SIZE + ROM_BANK_N_SIZE) { + rom_bank[rom_index as usize] = rom[rom_index as usize]; + } + + Rom { + rom_bank : rom_bank, + } + } +} + +impl Mbc for Rom { + fn read_bank_0 (&self, address: usize) -> u8 { + self.rom_bank[address as usize] + } + + fn read_bank_n (&self, address: usize) -> u8 { + self.rom_bank[address as usize] + } + + // not used for this mbc, returns 0xFF + fn read_ram (&self, _: usize) -> u8 { + 0xFF + } + + fn write_bank_0 (&mut self, _: usize, _: u8) { + } + + fn write_bank_n (&mut self, _: usize, _: u8) { + } + + // not used for this mbc, doesn't do anything + fn write_ram (&mut self, _: usize, _: u8) {} + + fn run (&mut self, _: u8) {} +} \ No newline at end of file diff --git a/src/debug.rs b/src/debug.rs new file mode 100644 index 0000000..9c59671 --- /dev/null +++ b/src/debug.rs @@ -0,0 +1,221 @@ +use crate::emulator::{Emulator, EmulatorState, ONE_FRAME_IN_NS, ONE_FRAME_IN_CYCLES}; +use crate::soc::peripheral::IoAccess; +use std::time::Instant; + +use std::io::{stdin, stdout, Write}; +use std::thread; +use std::sync::{Arc, Mutex}; +use minifb::{Window, WindowOptions}; + +// VRAM Window parameters +const NB_TILE_X: usize = 16; +const NB_TILE_Y: usize = 24; +const SCALE_FACTOR: usize = 3; +const TILE_SIZE: usize = 8; +const WINDOW_DIMENSIONS: [usize; 2] = [(NB_TILE_X * TILE_SIZE * SCALE_FACTOR), (NB_TILE_Y * TILE_SIZE * SCALE_FACTOR)]; + +#[derive(Clone, Copy)] +pub enum DebuggerCommand { + HALT, + RUN, + STEP, +} + +pub enum DebuggerState { + HALT, + RUN, + STEP, +} + +pub struct DebugCtx { + cmd: Vec, + breakpoint: u16, + break_enabled: bool, + debugger_state: DebuggerState, + display_cpu_reg: bool, + vram_viewer_buffer: [u32; 32 * TILE_SIZE * 12 * TILE_SIZE], +} + +impl DebugCtx { + pub fn new() -> DebugCtx { + DebugCtx { + cmd: Vec::new(), + breakpoint: 0, + break_enabled: false, + debugger_state: DebuggerState::HALT, + display_cpu_reg: true, + vram_viewer_buffer: [0; 32 * TILE_SIZE * 12 * TILE_SIZE], + } + } +} + +pub fn run_debug_mode(emulator: &mut Emulator, dbg_ctx: &mut DebugCtx) { + match emulator.state { + EmulatorState::GetTime => { + emulator.frame_tick = Instant::now(); + + emulator.state = EmulatorState::RunMachine; + } + EmulatorState::RunMachine => { + match dbg_ctx.debugger_state { + DebuggerState::HALT => { + // display cpu internal registers + if dbg_ctx.display_cpu_reg { + dbg_ctx.display_cpu_reg = false; + println!("instruction byte : {:#04x} / pc : {:#06x} / sp : {:#04x}", emulator.soc.peripheral.read(emulator.soc.cpu.pc), emulator.soc.cpu.pc, emulator.soc.cpu.sp); + println!("BC : {:#06x} / AF : {:#06x} / DE : {:#06x} / HL : {:#06x}", emulator.soc.cpu.registers.read_bc(), emulator.soc.cpu.registers.read_af(), emulator.soc.cpu.registers.read_de(), emulator.soc.cpu.registers.read_hl()); + } + + // wait until a new debug command is entered + let cmd = dbg_ctx.cmd.pop(); + if let Some(DebuggerCommand::RUN) = cmd { + dbg_ctx.display_cpu_reg = true; + dbg_ctx.debugger_state = DebuggerState::RUN; + } + + if let Some(DebuggerCommand::STEP) = cmd { + dbg_ctx.display_cpu_reg = true; + dbg_ctx.debugger_state = DebuggerState::STEP; + } + } + DebuggerState::RUN => { + // run the emulator as in normal mode + emulator.cycles_elapsed_in_frame += emulator.soc.run() as usize; + + if emulator.cycles_elapsed_in_frame >= ONE_FRAME_IN_CYCLES { + emulator.cycles_elapsed_in_frame = 0; + emulator.state = EmulatorState::WaitNextFrame; + } + + // check if we have to break + if dbg_ctx.break_enabled && (dbg_ctx.breakpoint == emulator.soc.cpu.pc) { + // check pc + dbg_ctx.display_cpu_reg = true; + dbg_ctx.debugger_state = DebuggerState::HALT; + } + + // wait until a new debug command is entered + if let Some(DebuggerCommand::HALT) = dbg_ctx.cmd.pop() { + dbg_ctx.display_cpu_reg = true; + dbg_ctx.debugger_state = DebuggerState::HALT; + } + } + DebuggerState::STEP => { + // run the emulator once then go to halt state + emulator.cycles_elapsed_in_frame += emulator.soc.run() as usize; + + if emulator.cycles_elapsed_in_frame >= ONE_FRAME_IN_CYCLES { + emulator.cycles_elapsed_in_frame = 0; + emulator.state = EmulatorState::WaitNextFrame; + } + + dbg_ctx.debugger_state = DebuggerState::HALT; + } + } + } + EmulatorState::WaitNextFrame => { + // check if 16,742706 ms have passed during this frame + if emulator.frame_tick.elapsed().as_nanos() >= ONE_FRAME_IN_NS as u128{ + emulator.state = EmulatorState::DisplayFrame; + } + } + EmulatorState::DisplayFrame => { + emulator.state = EmulatorState::GetTime; + + // update vram debug buffer + for pixel_index in 0..NB_TILE_X * TILE_SIZE * NB_TILE_Y * TILE_SIZE { + // compute pixel_x and pixel_y indexes + let pixel_y_index = pixel_index / (NB_TILE_X * 8); + let pixel_x_index = pixel_index % (NB_TILE_X * 8); + + // compute the tile index + let tile_y_index = pixel_y_index / 8; + let tile_x_index = pixel_x_index / 8; + let tile_index = tile_y_index * NB_TILE_X + tile_x_index; + + // compute VRAM address from pixel_index + let tile_row_offset = pixel_y_index % 8 * 2; + + // get row for the needed pixel + let data_0 = emulator.soc.peripheral.gpu.vram[tile_index * 16 + tile_row_offset]; + let data_1 = emulator.soc.peripheral.gpu.vram[tile_index * 16 + tile_row_offset + 1]; + + // get pixel bits + let bit_0 = data_0 >> (7 - (pixel_index % 8)) & 0x01; + let bit_1 = data_1 >> (7 - (pixel_index % 8)) & 0x01; + + let pixel_color = emulator.soc.peripheral.gpu.get_bg_pixel_color_from_palette((bit_1 << 1) | bit_0); + + dbg_ctx.vram_viewer_buffer[pixel_index] = 0xFF << 24 + | (pixel_color as u32) << 16 + | (pixel_color as u32) << 8 + | (pixel_color as u32) << 0; + } + } + } +} + +pub fn debug_cli(debug_ctx: &Arc>) { + let debug_ctx_ref = Arc::clone(&debug_ctx); + thread::spawn(move || { + println!("Qoboy debugger CLI"); + // check new commands in console + loop { + // get next instruction from console + let mut command = String::new(); + command.clear(); + stdout().flush().unwrap(); + stdin().read_line(&mut command).expect("Incorrect string is read."); + + // process command + if command.trim().contains("break_set") { + let split: Vec<&str> = command.trim().split(" ").collect(); + let brk_addr = u16::from_str_radix(split[1], 16).unwrap(); + (*debug_ctx_ref.lock().unwrap()).breakpoint = brk_addr; + (*debug_ctx_ref.lock().unwrap()).break_enabled = true; + } + + if command.trim().contains("break_reset") { + (*debug_ctx_ref.lock().unwrap()).break_enabled = false; + } + + if command.trim().contains("run") { + (*debug_ctx_ref.lock().unwrap()).cmd.push(DebuggerCommand::RUN); + } + + if command.trim().contains("halt") { + (*debug_ctx_ref.lock().unwrap()).cmd.push(DebuggerCommand::HALT); + } + + if command.trim().contains("step") { + (*debug_ctx_ref.lock().unwrap()).cmd.push(DebuggerCommand::STEP); + } + + if command.trim().contains("help") { + println!("supported commands: break , run, halt, step"); + } + } + }); +} + +pub fn debug_vram(debug_ctx: &Arc>) { + let debug_ctx_ref = Arc::clone(&debug_ctx); + thread::spawn(move || { + // init vram window + let mut buffer = [0; 384 * TILE_SIZE * TILE_SIZE]; + let mut window = Window::new( + "VRAM viewer", + WINDOW_DIMENSIONS[0], + WINDOW_DIMENSIONS[1], + WindowOptions::default(), + ) + .unwrap(); + + // check new commands in console + loop { + // update vram viewer buffer + buffer = (*debug_ctx_ref.lock().unwrap()).vram_viewer_buffer; + window.update_with_buffer(&buffer, NB_TILE_X * TILE_SIZE, NB_TILE_Y * TILE_SIZE).unwrap(); + } + }); +} \ No newline at end of file diff --git a/src/emulator.rs b/src/emulator.rs new file mode 100644 index 0000000..3e55c9c --- /dev/null +++ b/src/emulator.rs @@ -0,0 +1,105 @@ +use crate::soc::Soc; +pub use crate::soc::GameBoyKey; +use crate::cartridge::Cartridge; +use std::time::Instant; +use crate::debug::{DebugCtx, run_debug_mode}; + +pub const SCREEN_HEIGHT: usize = 144; +pub const SCREEN_WIDTH: usize = 160; + +// emulator clock parameters +const ONE_SECOND_IN_MICROS: usize = 1000000000; +pub const ONE_SECOND_IN_CYCLES: usize = 4194304; // Main sys clock 4.194304 MHz +pub const ONE_FRAME_IN_CYCLES: usize = 70224; +pub const ONE_FRAME_IN_NS: usize = ONE_FRAME_IN_CYCLES * ONE_SECOND_IN_MICROS / ONE_SECOND_IN_CYCLES; + +#[derive(PartialEq)] +pub enum EmulatorState { + GetTime, + RunMachine, + WaitNextFrame, + DisplayFrame, +} + +pub struct Emulator { + // gameboy emulated hardware + pub soc: Soc, + // emulator internal parameters + pub state: EmulatorState, + pub cycles_elapsed_in_frame: usize, + pub frame_tick: Instant, + run_routine: fn(&mut Emulator, &mut DebugCtx), +} + +impl Emulator { + pub fn new(boot_rom: &[u8], rom: &[u8], debug_on: bool) -> Emulator { + let cartridge = Cartridge::new(rom); + + let soc = Soc::new(boot_rom, cartridge); + + let run_routine = if debug_on { + run_debug_mode + } else { + run_normal_mode + }; + + Emulator { + // gameboy emulated hardware + soc: soc, + // emulator internal parameters + state: EmulatorState::GetTime, + cycles_elapsed_in_frame: 0 as usize, + frame_tick: Instant::now(), + // debugger parameters + run_routine: run_routine, + } + } + + pub fn run(&mut self, dbg_cmd: &mut DebugCtx) { + (self.run_routine)(self, dbg_cmd); + } + + pub fn frame_ready(&self) -> bool { + if self.state == EmulatorState::DisplayFrame { + true + } else { + false + } + } + + pub fn get_frame_buffer(&self, pixel_index: usize) -> u8 { + self.soc.get_frame_buffer(pixel_index) + } + + pub fn set_key(&mut self, key: GameBoyKey, value: bool) { + self.soc.set_key(key, value); + } +} + +fn run_normal_mode(emulator: &mut Emulator, _dbg_ctx: &mut DebugCtx) { + match emulator.state { + EmulatorState::GetTime => { + emulator.frame_tick = Instant::now(); + + emulator.state = EmulatorState::RunMachine; + } + EmulatorState::RunMachine => { + emulator.cycles_elapsed_in_frame += emulator.soc.run() as usize; + + if emulator.cycles_elapsed_in_frame >= ONE_FRAME_IN_CYCLES { + emulator.cycles_elapsed_in_frame = 0; + emulator.state = EmulatorState::WaitNextFrame; + } + } + EmulatorState::WaitNextFrame => { + // check if 16,742706 ms have passed during this frame + if emulator.frame_tick.elapsed().as_nanos() >= ONE_FRAME_IN_NS as u128{ + emulator.state = EmulatorState::DisplayFrame; + } + } + EmulatorState::DisplayFrame => { + emulator.state = EmulatorState::GetTime; + } + } +} + diff --git a/src/main.rs b/src/main.rs index 1768c45..9d8d83d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,17 +1,145 @@ +mod emulator; +mod soc; +mod debug; +mod cartridge; -struct registers -{ - a: u8, - b: u8, - c: u8, - d: u8, - e: u8, - f: u8, - h: u8, - l: u8 -} +use minifb::{Key, Window, WindowOptions}; +use std::{fs::File, io::Read, env}; +use std::sync::{Arc, Mutex}; + +use crate::emulator::{Emulator, SCREEN_HEIGHT, SCREEN_WIDTH}; +use crate::debug::{DebugCtx, debug_cli, debug_vram}; +// Window parameters +const SCALE_FACTOR: usize = 3; +const WINDOW_DIMENSIONS: [usize; 2] = [(SCREEN_WIDTH * SCALE_FACTOR), (SCREEN_HEIGHT * SCALE_FACTOR)]; fn main() { - println!("Hello, world!"); + // get arguments from the command line + let (boot_rom_path, game_rom_path, debug_mode) = parse_args(); + + let mut file = File::open(boot_rom_path).unwrap(); + let mut bin_data = [0xFF as u8; 256]; + if let Err(message) = file.read_exact(&mut bin_data) { + panic!("Cannot read file with error message: {}", message); + } + + let mut rom_file = File::open(game_rom_path).unwrap(); + let rom_len = rom_file.metadata().unwrap().len(); + let mut rom_data = vec![0xFF as u8; rom_len as usize]; + if let Err(message) = rom_file.read_exact(&mut rom_data) { + panic!("Cannot read file with error message: {}", message); + } + + // launch the debugger cli + let dbg_ctx = Arc::new(Mutex::new(DebugCtx::new())); + if debug_mode { + debug_cli(&dbg_ctx); + debug_vram(&dbg_ctx); + } + + // create the emulated system + let mut emulator = Emulator::new(&bin_data, &rom_data, debug_mode); + + // run the emulator + let mut buffer = [0; SCREEN_HEIGHT * SCREEN_WIDTH]; + + let mut window = Window::new( + "Qoboy", + WINDOW_DIMENSIONS[0], + WINDOW_DIMENSIONS[1], + WindowOptions::default(), + ) + .unwrap(); + + while window.is_open() && !window.is_key_down(Key::Escape) { + // get key from the keyboard + if window.is_key_down(Key::Up) { + emulator.set_key(soc::GameBoyKey::UP, true); + } else { + emulator.set_key(soc::GameBoyKey::UP, false); + } + + if window.is_key_down(Key::Down) { + emulator.set_key(soc::GameBoyKey::DOWN, true); + } else { + emulator.set_key(soc::GameBoyKey::DOWN, false); + } + + if window.is_key_down(Key::Left) { + emulator.set_key(soc::GameBoyKey::LEFT, true); + } else { + emulator.set_key(soc::GameBoyKey::LEFT, false); + } + + if window.is_key_down(Key::Right) { + emulator.set_key(soc::GameBoyKey::RIGHT, true); + } else { + emulator.set_key(soc::GameBoyKey::RIGHT, false); + } + + if window.is_key_down(Key::A) { + emulator.set_key(soc::GameBoyKey::A, true); + } else { + emulator.set_key(soc::GameBoyKey::A, false); + } + + if window.is_key_down(Key::Z) { + emulator.set_key(soc::GameBoyKey::B, true); + } else { + emulator.set_key(soc::GameBoyKey::B, false); + } + + if window.is_key_down(Key::Space) { + emulator.set_key(soc::GameBoyKey::START, true); + } else { + emulator.set_key(soc::GameBoyKey::START, false); + } + + if window.is_key_down(Key::Enter) { + emulator.set_key(soc::GameBoyKey::SELECT, true); + } else { + emulator.set_key(soc::GameBoyKey::SELECT, false); + } + + // run emulator until a new frame is ready + emulator.run(&mut *dbg_ctx.lock().unwrap()); + + if emulator.frame_ready() { + // copy the current frame from gpu frame buffer + for i in 0..SCREEN_HEIGHT * SCREEN_WIDTH { + buffer[i] = 255 << 24 + | (emulator.get_frame_buffer(i) as u32) << 16 + | (emulator.get_frame_buffer(i) as u32) << 8 + | (emulator.get_frame_buffer(i) as u32) << 0; + } + // display the frame rendered by the gpu + window.update_with_buffer(&buffer, SCREEN_WIDTH, SCREEN_HEIGHT).unwrap(); + } + } } + +fn parse_args() -> (String, String, bool) { + let mut boot_rom_path = String::new(); + let mut game_rom_path = String::new(); + let mut debug_opt = false; + + for (index, argument) in env::args().enumerate() { + match index { + 1 => { + boot_rom_path = argument.clone(); + println!("boot_rom: {}", boot_rom_path); + } + 2 => { + game_rom_path = argument.clone(); + println!("game_rom: {}", game_rom_path); + } + 3 => if argument.eq("--debug") { + debug_opt = true; + } + _ => {} // nothing to do + } + } + + (boot_rom_path, game_rom_path, debug_opt) +} \ No newline at end of file diff --git a/src/soc/cpu/instruction.rs b/src/soc/cpu/instruction.rs new file mode 100644 index 0000000..578ae15 --- /dev/null +++ b/src/soc/cpu/instruction.rs @@ -0,0 +1,789 @@ +const LONG_PREFIX: u8 = 0xCB; + +#[derive(Debug, PartialEq)] +pub enum ArithmeticTarget { + A, + B, + C, + D, + E, + H, + L, + HL, + D8, +} + +#[derive(Debug, PartialEq)] +pub enum IncDecTarget { + A, + B, + C, + D, + E, + H, + L, + HL, +} + +#[derive(Debug, PartialEq)] +pub enum U16Target { + BC, + DE, + HL, + SP, +} + +#[allow(non_camel_case_types)] +#[derive(Debug, PartialEq)] +pub enum Load16Target { + BC, + DE, + HL_plus, + HL_minus, +} + +#[derive(Debug, PartialEq)] +pub enum JumpTarget { + IMMEDIATE, + NZ, + NC, + Z, + C, +} + +#[allow(non_camel_case_types)] +#[derive(Debug, PartialEq)] +pub enum SPTarget { + FROM_SP, + TO_HL, + TO_SP, +} + +#[derive(Debug, PartialEq)] +pub enum RamTarget { + OneByteAddress, + AddressFromRegister, + TwoBytesAddress, +} + +#[derive(Debug, PartialEq)] +pub enum PopPushTarget { + BC, + DE, + HL, + AF, +} + +#[allow(non_camel_case_types)] +#[derive(Debug, PartialEq)] +pub enum ResetTarget { + FLASH_0, + FLASH_1, + FLASH_2, + FLASH_3, + FLASH_4, + FLASH_5, + FLASH_6, + FLASH_7, +} + +#[derive(Debug, PartialEq)] +pub enum Direction { + LEFT, + RIGHT, +} + +#[allow(non_camel_case_types)] +#[derive(Debug, PartialEq)] +pub enum BitTarget { + BIT_0, + BIT_1, + BIT_2, + BIT_3, + BIT_4, + BIT_5, + BIT_6, + BIT_7, +} + +#[allow(non_camel_case_types)] +#[derive(Debug, PartialEq)] +pub enum Instruction { + ADD(ArithmeticTarget), + ADDC(ArithmeticTarget), + SUB(ArithmeticTarget), + SBC(ArithmeticTarget), + AND(ArithmeticTarget), + OR(ArithmeticTarget), + XOR(ArithmeticTarget), + CP(ArithmeticTarget), + INC(IncDecTarget), + DEC(IncDecTarget), + INC16(U16Target), + DEC16(U16Target), + ADD16(U16Target), + LOAD(IncDecTarget, ArithmeticTarget), + LOAD_INDIRECT(Load16Target), + LOAD_IMMEDIATE(U16Target), + STORE_INDIRECT(Load16Target), + LOAD_SP(SPTarget), + LOAD_RAM(RamTarget), + STORE_RAM(RamTarget), + JUMP_RELATIVE(JumpTarget), + JUMP_IMMEDIATE(JumpTarget), + JUMP_INDIRECT, + RETURN(JumpTarget), + RETI, + RESET(ResetTarget), + CALL(JumpTarget), + POP(PopPushTarget), + PUSH(PopPushTarget), + AddSp, + EI, + DI, + NOP, + STOP, + HALT, + SCF, + CPL, + CCF, + DAA, + RCA(Direction), + RA(Direction), + RC(Direction, IncDecTarget), + R(Direction, IncDecTarget), + SLA(IncDecTarget), + SRA(IncDecTarget), + SRL(IncDecTarget), + SWAP(IncDecTarget), + BIT(BitTarget, IncDecTarget), + SET_BIT(BitTarget, IncDecTarget), + RESET_BIT(BitTarget, IncDecTarget), +} + +impl Instruction { + pub fn from_byte(byte: u8) -> Option { + match byte { + // ADD + 0x80 => Some(Instruction::ADD(ArithmeticTarget::B)), + 0x81 => Some(Instruction::ADD(ArithmeticTarget::C)), + 0x82 => Some(Instruction::ADD(ArithmeticTarget::D)), + 0x83 => Some(Instruction::ADD(ArithmeticTarget::E)), + 0x84 => Some(Instruction::ADD(ArithmeticTarget::H)), + 0x85 => Some(Instruction::ADD(ArithmeticTarget::L)), + 0x86 => Some(Instruction::ADD(ArithmeticTarget::HL)), + 0x87 => Some(Instruction::ADD(ArithmeticTarget::A)), + 0xC6 => Some(Instruction::ADD(ArithmeticTarget::D8)), + + // ADD 16 bits + 0x09 => Some(Instruction::ADD16(U16Target::BC)), + 0x19 => Some(Instruction::ADD16(U16Target::DE)), + 0x29 => Some(Instruction::ADD16(U16Target::HL)), + 0x39 => Some(Instruction::ADD16(U16Target::SP)), + + // ADDC + 0x88 => Some(Instruction::ADDC(ArithmeticTarget::B)), + 0x89 => Some(Instruction::ADDC(ArithmeticTarget::C)), + 0x8A => Some(Instruction::ADDC(ArithmeticTarget::D)), + 0x8B => Some(Instruction::ADDC(ArithmeticTarget::E)), + 0x8C => Some(Instruction::ADDC(ArithmeticTarget::H)), + 0x8D => Some(Instruction::ADDC(ArithmeticTarget::L)), + 0x8E => Some(Instruction::ADDC(ArithmeticTarget::HL)), + 0x8F => Some(Instruction::ADDC(ArithmeticTarget::A)), + 0xCE => Some(Instruction::ADDC(ArithmeticTarget::D8)), + + // ADD Stack pointer + 0xE8 => Some(Instruction::AddSp), + + // SUB + 0x90 => Some(Instruction::SUB(ArithmeticTarget::B)), + 0x91 => Some(Instruction::SUB(ArithmeticTarget::C)), + 0x92 => Some(Instruction::SUB(ArithmeticTarget::D)), + 0x93 => Some(Instruction::SUB(ArithmeticTarget::E)), + 0x94 => Some(Instruction::SUB(ArithmeticTarget::H)), + 0x95 => Some(Instruction::SUB(ArithmeticTarget::L)), + 0x96 => Some(Instruction::SUB(ArithmeticTarget::HL)), + 0x97 => Some(Instruction::SUB(ArithmeticTarget::A)), + 0xD6 => Some(Instruction::SUB(ArithmeticTarget::D8)), + + // SBC + 0x98 => Some(Instruction::SBC(ArithmeticTarget::B)), + 0x99 => Some(Instruction::SBC(ArithmeticTarget::C)), + 0x9A => Some(Instruction::SBC(ArithmeticTarget::D)), + 0x9B => Some(Instruction::SBC(ArithmeticTarget::E)), + 0x9C => Some(Instruction::SBC(ArithmeticTarget::H)), + 0x9D => Some(Instruction::SBC(ArithmeticTarget::L)), + 0x9E => Some(Instruction::SBC(ArithmeticTarget::HL)), + 0x9F => Some(Instruction::SBC(ArithmeticTarget::A)), + 0xDE => Some(Instruction::SBC(ArithmeticTarget::D8)), + + // AND + 0xA0 => Some(Instruction::AND(ArithmeticTarget::B)), + 0xA1 => Some(Instruction::AND(ArithmeticTarget::C)), + 0xA2 => Some(Instruction::AND(ArithmeticTarget::D)), + 0xA3 => Some(Instruction::AND(ArithmeticTarget::E)), + 0xA4 => Some(Instruction::AND(ArithmeticTarget::H)), + 0xA5 => Some(Instruction::AND(ArithmeticTarget::L)), + 0xA6 => Some(Instruction::AND(ArithmeticTarget::HL)), + 0xA7 => Some(Instruction::AND(ArithmeticTarget::A)), + 0xE6 => Some(Instruction::AND(ArithmeticTarget::D8)), + + // XOR + 0xA8 => Some(Instruction::XOR(ArithmeticTarget::B)), + 0xA9 => Some(Instruction::XOR(ArithmeticTarget::C)), + 0xAA => Some(Instruction::XOR(ArithmeticTarget::D)), + 0xAB => Some(Instruction::XOR(ArithmeticTarget::E)), + 0xAC => Some(Instruction::XOR(ArithmeticTarget::H)), + 0xAD => Some(Instruction::XOR(ArithmeticTarget::L)), + 0xAE => Some(Instruction::XOR(ArithmeticTarget::HL)), + 0xAF => Some(Instruction::XOR(ArithmeticTarget::A)), + 0xEE => Some(Instruction::XOR(ArithmeticTarget::D8)), + + // OR + 0xB0 => Some(Instruction::OR(ArithmeticTarget::B)), + 0xB1 => Some(Instruction::OR(ArithmeticTarget::C)), + 0xB2 => Some(Instruction::OR(ArithmeticTarget::D)), + 0xB3 => Some(Instruction::OR(ArithmeticTarget::E)), + 0xB4 => Some(Instruction::OR(ArithmeticTarget::H)), + 0xB5 => Some(Instruction::OR(ArithmeticTarget::L)), + 0xB6 => Some(Instruction::OR(ArithmeticTarget::HL)), + 0xB7 => Some(Instruction::OR(ArithmeticTarget::A)), + 0xF6 => Some(Instruction::OR(ArithmeticTarget::D8)), + + // CP + 0xB8 => Some(Instruction::CP(ArithmeticTarget::B)), + 0xB9 => Some(Instruction::CP(ArithmeticTarget::C)), + 0xBA => Some(Instruction::CP(ArithmeticTarget::D)), + 0xBB => Some(Instruction::CP(ArithmeticTarget::E)), + 0xBC => Some(Instruction::CP(ArithmeticTarget::H)), + 0xBD => Some(Instruction::CP(ArithmeticTarget::L)), + 0xBE => Some(Instruction::CP(ArithmeticTarget::HL)), + 0xBF => Some(Instruction::CP(ArithmeticTarget::A)), + 0xFE => Some(Instruction::CP(ArithmeticTarget::D8)), + + // INC + 0x04 => Some(Instruction::INC(IncDecTarget::B)), + 0x0C => Some(Instruction::INC(IncDecTarget::C)), + 0x14 => Some(Instruction::INC(IncDecTarget::D)), + 0x1C => Some(Instruction::INC(IncDecTarget::E)), + 0x24 => Some(Instruction::INC(IncDecTarget::H)), + 0x2C => Some(Instruction::INC(IncDecTarget::L)), + 0x34 => Some(Instruction::INC(IncDecTarget::HL)), + 0x3C => Some(Instruction::INC(IncDecTarget::A)), + + // INC 16 bits + 0x03 => Some(Instruction::INC16(U16Target::BC)), + 0x13 => Some(Instruction::INC16(U16Target::DE)), + 0x23 => Some(Instruction::INC16(U16Target::HL)), + 0x33 => Some(Instruction::INC16(U16Target::SP)), + + // DEC + 0x05 => Some(Instruction::DEC(IncDecTarget::B)), + 0x0D => Some(Instruction::DEC(IncDecTarget::C)), + 0x15 => Some(Instruction::DEC(IncDecTarget::D)), + 0x1D => Some(Instruction::DEC(IncDecTarget::E)), + 0x25 => Some(Instruction::DEC(IncDecTarget::H)), + 0x2D => Some(Instruction::DEC(IncDecTarget::L)), + 0x35 => Some(Instruction::DEC(IncDecTarget::HL)), + 0x3D => Some(Instruction::DEC(IncDecTarget::A)), + + // DEC 16 bits + 0x0B => Some(Instruction::DEC16(U16Target::BC)), + 0x1B => Some(Instruction::DEC16(U16Target::DE)), + 0x2B => Some(Instruction::DEC16(U16Target::HL)), + 0x3B => Some(Instruction::DEC16(U16Target::SP)), + + // LOAD instruction + 0x40 => Some(Instruction::LOAD(IncDecTarget::B, ArithmeticTarget::B)), + 0x41 => Some(Instruction::LOAD(IncDecTarget::B, ArithmeticTarget::C)), + 0x42 => Some(Instruction::LOAD(IncDecTarget::B, ArithmeticTarget::D)), + 0x43 => Some(Instruction::LOAD(IncDecTarget::B, ArithmeticTarget::E)), + 0x44 => Some(Instruction::LOAD(IncDecTarget::B, ArithmeticTarget::H)), + 0x45 => Some(Instruction::LOAD(IncDecTarget::B, ArithmeticTarget::L)), + 0x46 => Some(Instruction::LOAD(IncDecTarget::B, ArithmeticTarget::HL)), + 0x47 => Some(Instruction::LOAD(IncDecTarget::B, ArithmeticTarget::A)), + 0x06 => Some(Instruction::LOAD(IncDecTarget::B, ArithmeticTarget::D8)), + + 0x48 => Some(Instruction::LOAD(IncDecTarget::C, ArithmeticTarget::B)), + 0x49 => Some(Instruction::LOAD(IncDecTarget::C, ArithmeticTarget::C)), + 0x4A => Some(Instruction::LOAD(IncDecTarget::C, ArithmeticTarget::D)), + 0x4B => Some(Instruction::LOAD(IncDecTarget::C, ArithmeticTarget::E)), + 0x4C => Some(Instruction::LOAD(IncDecTarget::C, ArithmeticTarget::H)), + 0x4D => Some(Instruction::LOAD(IncDecTarget::C, ArithmeticTarget::L)), + 0x4E => Some(Instruction::LOAD(IncDecTarget::C, ArithmeticTarget::HL)), + 0x4F => Some(Instruction::LOAD(IncDecTarget::C, ArithmeticTarget::A)), + 0x0E => Some(Instruction::LOAD(IncDecTarget::C, ArithmeticTarget::D8)), + + 0x50 => Some(Instruction::LOAD(IncDecTarget::D, ArithmeticTarget::B)), + 0x51 => Some(Instruction::LOAD(IncDecTarget::D, ArithmeticTarget::C)), + 0x52 => Some(Instruction::LOAD(IncDecTarget::D, ArithmeticTarget::D)), + 0x53 => Some(Instruction::LOAD(IncDecTarget::D, ArithmeticTarget::E)), + 0x54 => Some(Instruction::LOAD(IncDecTarget::D, ArithmeticTarget::H)), + 0x55 => Some(Instruction::LOAD(IncDecTarget::D, ArithmeticTarget::L)), + 0x56 => Some(Instruction::LOAD(IncDecTarget::D, ArithmeticTarget::HL)), + 0x57 => Some(Instruction::LOAD(IncDecTarget::D, ArithmeticTarget::A)), + 0x16 => Some(Instruction::LOAD(IncDecTarget::D, ArithmeticTarget::D8)), + + 0x58 => Some(Instruction::LOAD(IncDecTarget::E, ArithmeticTarget::B)), + 0x59 => Some(Instruction::LOAD(IncDecTarget::E, ArithmeticTarget::C)), + 0x5A => Some(Instruction::LOAD(IncDecTarget::E, ArithmeticTarget::D)), + 0x5B => Some(Instruction::LOAD(IncDecTarget::E, ArithmeticTarget::E)), + 0x5C => Some(Instruction::LOAD(IncDecTarget::E, ArithmeticTarget::H)), + 0x5D => Some(Instruction::LOAD(IncDecTarget::E, ArithmeticTarget::L)), + 0x5E => Some(Instruction::LOAD(IncDecTarget::E, ArithmeticTarget::HL)), + 0x5F => Some(Instruction::LOAD(IncDecTarget::E, ArithmeticTarget::A)), + 0x1E => Some(Instruction::LOAD(IncDecTarget::E, ArithmeticTarget::D8)), + + 0x60 => Some(Instruction::LOAD(IncDecTarget::H, ArithmeticTarget::B)), + 0x61 => Some(Instruction::LOAD(IncDecTarget::H, ArithmeticTarget::C)), + 0x62 => Some(Instruction::LOAD(IncDecTarget::H, ArithmeticTarget::D)), + 0x63 => Some(Instruction::LOAD(IncDecTarget::H, ArithmeticTarget::E)), + 0x64 => Some(Instruction::LOAD(IncDecTarget::H, ArithmeticTarget::H)), + 0x65 => Some(Instruction::LOAD(IncDecTarget::H, ArithmeticTarget::L)), + 0x66 => Some(Instruction::LOAD(IncDecTarget::H, ArithmeticTarget::HL)), + 0x67 => Some(Instruction::LOAD(IncDecTarget::H, ArithmeticTarget::A)), + 0x26 => Some(Instruction::LOAD(IncDecTarget::H, ArithmeticTarget::D8)), + + 0x68 => Some(Instruction::LOAD(IncDecTarget::L, ArithmeticTarget::B)), + 0x69 => Some(Instruction::LOAD(IncDecTarget::L, ArithmeticTarget::C)), + 0x6A => Some(Instruction::LOAD(IncDecTarget::L, ArithmeticTarget::D)), + 0x6B => Some(Instruction::LOAD(IncDecTarget::L, ArithmeticTarget::E)), + 0x6C => Some(Instruction::LOAD(IncDecTarget::L, ArithmeticTarget::H)), + 0x6D => Some(Instruction::LOAD(IncDecTarget::L, ArithmeticTarget::L)), + 0x6E => Some(Instruction::LOAD(IncDecTarget::L, ArithmeticTarget::HL)), + 0x6F => Some(Instruction::LOAD(IncDecTarget::L, ArithmeticTarget::A)), + 0x2E => Some(Instruction::LOAD(IncDecTarget::L, ArithmeticTarget::D8)), + + 0x70 => Some(Instruction::LOAD(IncDecTarget::HL, ArithmeticTarget::B)), + 0x71 => Some(Instruction::LOAD(IncDecTarget::HL, ArithmeticTarget::C)), + 0x72 => Some(Instruction::LOAD(IncDecTarget::HL, ArithmeticTarget::D)), + 0x73 => Some(Instruction::LOAD(IncDecTarget::HL, ArithmeticTarget::E)), + 0x74 => Some(Instruction::LOAD(IncDecTarget::HL, ArithmeticTarget::H)), + 0x75 => Some(Instruction::LOAD(IncDecTarget::HL, ArithmeticTarget::L)), + 0x77 => Some(Instruction::LOAD(IncDecTarget::HL, ArithmeticTarget::A)), + 0x36 => Some(Instruction::LOAD(IncDecTarget::HL, ArithmeticTarget::D8)), + + 0x78 => Some(Instruction::LOAD(IncDecTarget::A, ArithmeticTarget::B)), + 0x79 => Some(Instruction::LOAD(IncDecTarget::A, ArithmeticTarget::C)), + 0x7A => Some(Instruction::LOAD(IncDecTarget::A, ArithmeticTarget::D)), + 0x7B => Some(Instruction::LOAD(IncDecTarget::A, ArithmeticTarget::E)), + 0x7C => Some(Instruction::LOAD(IncDecTarget::A, ArithmeticTarget::H)), + 0x7D => Some(Instruction::LOAD(IncDecTarget::A, ArithmeticTarget::L)), + 0x7E => Some(Instruction::LOAD(IncDecTarget::A, ArithmeticTarget::HL)), + 0x7F => Some(Instruction::LOAD(IncDecTarget::A, ArithmeticTarget::A)), + 0x3E => Some(Instruction::LOAD(IncDecTarget::A, ArithmeticTarget::D8)), + + 0x0A => Some(Instruction::LOAD_INDIRECT(Load16Target::BC)), + 0x1A => Some(Instruction::LOAD_INDIRECT(Load16Target::DE)), + 0x2A => Some(Instruction::LOAD_INDIRECT(Load16Target::HL_plus)), + 0x3A => Some(Instruction::LOAD_INDIRECT(Load16Target::HL_minus)), + + 0x01 => Some(Instruction::LOAD_IMMEDIATE(U16Target::BC)), + 0x11 => Some(Instruction::LOAD_IMMEDIATE(U16Target::DE)), + 0x21 => Some(Instruction::LOAD_IMMEDIATE(U16Target::HL)), + 0x31 => Some(Instruction::LOAD_IMMEDIATE(U16Target::SP)), + + 0x02 => Some(Instruction::STORE_INDIRECT(Load16Target::BC)), + 0x12 => Some(Instruction::STORE_INDIRECT(Load16Target::DE)), + 0x22 => Some(Instruction::STORE_INDIRECT(Load16Target::HL_plus)), + 0x32 => Some(Instruction::STORE_INDIRECT(Load16Target::HL_minus)), + + 0x08 => Some(Instruction::LOAD_SP(SPTarget::FROM_SP)), + 0xF8 => Some(Instruction::LOAD_SP(SPTarget::TO_HL)), + 0xF9 => Some(Instruction::LOAD_SP(SPTarget::TO_SP)), + + 0xF0 => Some(Instruction::LOAD_RAM(RamTarget::OneByteAddress)), + 0xF2 => Some(Instruction::LOAD_RAM(RamTarget::AddressFromRegister)), + 0xFA => Some(Instruction::LOAD_RAM(RamTarget::TwoBytesAddress)), + + 0xE0 => Some(Instruction::STORE_RAM(RamTarget::OneByteAddress)), + 0xE2 => Some(Instruction::STORE_RAM(RamTarget::AddressFromRegister)), + 0xEA => Some(Instruction::STORE_RAM(RamTarget::TwoBytesAddress)), + + // JUMP instructions + 0x20 => Some(Instruction::JUMP_RELATIVE(JumpTarget::NZ)), + 0x30 => Some(Instruction::JUMP_RELATIVE(JumpTarget::NC)), + 0x18 => Some(Instruction::JUMP_RELATIVE(JumpTarget::IMMEDIATE)), + 0x28 => Some(Instruction::JUMP_RELATIVE(JumpTarget::Z)), + 0x38 => Some(Instruction::JUMP_RELATIVE(JumpTarget::C)), + + 0xC2 => Some(Instruction::JUMP_IMMEDIATE(JumpTarget::NZ)), + 0xD2 => Some(Instruction::JUMP_IMMEDIATE(JumpTarget::NC)), + 0xC3 => Some(Instruction::JUMP_IMMEDIATE(JumpTarget::IMMEDIATE)), + 0xCA => Some(Instruction::JUMP_IMMEDIATE(JumpTarget::Z)), + 0xDA => Some(Instruction::JUMP_IMMEDIATE(JumpTarget::C)), + + 0xE9 => Some(Instruction::JUMP_INDIRECT), + + // RETURN instructions + 0xC0 => Some(Instruction::RETURN(JumpTarget::NZ)), + 0xD0 => Some(Instruction::RETURN(JumpTarget::NC)), + 0xC8 => Some(Instruction::RETURN(JumpTarget::Z)), + 0xD8 => Some(Instruction::RETURN(JumpTarget::C)), + 0xC9 => Some(Instruction::RETURN(JumpTarget::IMMEDIATE)), + + 0xD9 => Some(Instruction::RETI), + + // RESET instructions + 0xC7 => Some(Instruction::RESET(ResetTarget::FLASH_0)), + 0xCF => Some(Instruction::RESET(ResetTarget::FLASH_1)), + 0xD7 => Some(Instruction::RESET(ResetTarget::FLASH_2)), + 0xDF => Some(Instruction::RESET(ResetTarget::FLASH_3)), + 0xE7 => Some(Instruction::RESET(ResetTarget::FLASH_4)), + 0xEF => Some(Instruction::RESET(ResetTarget::FLASH_5)), + 0xF7 => Some(Instruction::RESET(ResetTarget::FLASH_6)), + 0xFF => Some(Instruction::RESET(ResetTarget::FLASH_7)), + + // CALL instructions + 0xC4 => Some(Instruction::CALL(JumpTarget::NZ)), + 0xD4 => Some(Instruction::CALL(JumpTarget::NC)), + 0xCC => Some(Instruction::CALL(JumpTarget::Z)), + 0xDC => Some(Instruction::CALL(JumpTarget::C)), + 0xCD => Some(Instruction::CALL(JumpTarget::IMMEDIATE)), + + // POP & PUSH instructions + 0xC1 => Some(Instruction::POP(PopPushTarget::BC)), + 0xD1 => Some(Instruction::POP(PopPushTarget::DE)), + 0xE1 => Some(Instruction::POP(PopPushTarget::HL)), + 0xF1 => Some(Instruction::POP(PopPushTarget::AF)), + + 0xC5 => Some(Instruction::PUSH(PopPushTarget::BC)), + 0xD5 => Some(Instruction::PUSH(PopPushTarget::DE)), + 0xE5 => Some(Instruction::PUSH(PopPushTarget::HL)), + 0xF5 => Some(Instruction::PUSH(PopPushTarget::AF)), + + // Interrupt instructions + 0xF3 => Some(Instruction::DI), + 0xFB => Some(Instruction::EI), + + // Control instructions + 0x00 => Some(Instruction::NOP), + 0x10 => Some(Instruction::STOP), + 0x76 => Some(Instruction::HALT), + 0x27 => Some(Instruction::DAA), + 0x37 => Some(Instruction::SCF), + 0x2F => Some(Instruction::CPL), + 0x3F => Some(Instruction::CCF), + + // Rotate and Shift instructions + 0x07 => Some(Instruction::RCA(Direction::LEFT)), + 0x17 => Some(Instruction::RA(Direction::LEFT)), + 0x0F => Some(Instruction::RCA(Direction::RIGHT)), + 0x1F => Some(Instruction::RA(Direction::RIGHT)), + + _ => None, + } + } + + pub fn is_long_instruction(prefix: u8) -> bool { + prefix == LONG_PREFIX + } + + pub fn from_long_byte(long_byte: u8) -> Option { + match long_byte { + // rotate left instructions + 0x00 => Some(Instruction::RC(Direction::LEFT, IncDecTarget::B)), + 0x01 => Some(Instruction::RC(Direction::LEFT, IncDecTarget::C)), + 0x02 => Some(Instruction::RC(Direction::LEFT, IncDecTarget::D)), + 0x03 => Some(Instruction::RC(Direction::LEFT, IncDecTarget::E)), + 0x04 => Some(Instruction::RC(Direction::LEFT, IncDecTarget::H)), + 0x05 => Some(Instruction::RC(Direction::LEFT, IncDecTarget::L)), + 0x06 => Some(Instruction::RC(Direction::LEFT, IncDecTarget::HL)), + 0x07 => Some(Instruction::RC(Direction::LEFT, IncDecTarget::A)), + + // rotate right instructions + 0x08 => Some(Instruction::RC(Direction::RIGHT, IncDecTarget::B)), + 0x09 => Some(Instruction::RC(Direction::RIGHT, IncDecTarget::C)), + 0x0A => Some(Instruction::RC(Direction::RIGHT, IncDecTarget::D)), + 0x0B => Some(Instruction::RC(Direction::RIGHT, IncDecTarget::E)), + 0x0C => Some(Instruction::RC(Direction::RIGHT, IncDecTarget::H)), + 0x0D => Some(Instruction::RC(Direction::RIGHT, IncDecTarget::L)), + 0x0E => Some(Instruction::RC(Direction::RIGHT, IncDecTarget::HL)), + 0x0F => Some(Instruction::RC(Direction::RIGHT, IncDecTarget::A)), + + // rotate left through carry instructions + 0x10 => Some(Instruction::R(Direction::LEFT, IncDecTarget::B)), + 0x11 => Some(Instruction::R(Direction::LEFT, IncDecTarget::C)), + 0x12 => Some(Instruction::R(Direction::LEFT, IncDecTarget::D)), + 0x13 => Some(Instruction::R(Direction::LEFT, IncDecTarget::E)), + 0x14 => Some(Instruction::R(Direction::LEFT, IncDecTarget::H)), + 0x15 => Some(Instruction::R(Direction::LEFT, IncDecTarget::L)), + 0x16 => Some(Instruction::R(Direction::LEFT, IncDecTarget::HL)), + 0x17 => Some(Instruction::R(Direction::LEFT, IncDecTarget::A)), + + // rotate left through carry instructions + 0x18 => Some(Instruction::R(Direction::RIGHT, IncDecTarget::B)), + 0x19 => Some(Instruction::R(Direction::RIGHT, IncDecTarget::C)), + 0x1A => Some(Instruction::R(Direction::RIGHT, IncDecTarget::D)), + 0x1B => Some(Instruction::R(Direction::RIGHT, IncDecTarget::E)), + 0x1C => Some(Instruction::R(Direction::RIGHT, IncDecTarget::H)), + 0x1D => Some(Instruction::R(Direction::RIGHT, IncDecTarget::L)), + 0x1E => Some(Instruction::R(Direction::RIGHT, IncDecTarget::HL)), + 0x1F => Some(Instruction::R(Direction::RIGHT, IncDecTarget::A)), + + // shift left instructions + 0x20 => Some(Instruction::SLA(IncDecTarget::B)), + 0x21 => Some(Instruction::SLA(IncDecTarget::C)), + 0x22 => Some(Instruction::SLA(IncDecTarget::D)), + 0x23 => Some(Instruction::SLA(IncDecTarget::E)), + 0x24 => Some(Instruction::SLA(IncDecTarget::H)), + 0x25 => Some(Instruction::SLA(IncDecTarget::L)), + 0x26 => Some(Instruction::SLA(IncDecTarget::HL)), + 0x27 => Some(Instruction::SLA(IncDecTarget::A)), + + // shift right instructions + 0x28 => Some(Instruction::SRA(IncDecTarget::B)), + 0x29 => Some(Instruction::SRA(IncDecTarget::C)), + 0x2A => Some(Instruction::SRA(IncDecTarget::D)), + 0x2B => Some(Instruction::SRA(IncDecTarget::E)), + 0x2C => Some(Instruction::SRA(IncDecTarget::H)), + 0x2D => Some(Instruction::SRA(IncDecTarget::L)), + 0x2E => Some(Instruction::SRA(IncDecTarget::HL)), + 0x2F => Some(Instruction::SRA(IncDecTarget::A)), + + // swap instructions + 0x30 => Some(Instruction::SWAP(IncDecTarget::B)), + 0x31 => Some(Instruction::SWAP(IncDecTarget::C)), + 0x32 => Some(Instruction::SWAP(IncDecTarget::D)), + 0x33 => Some(Instruction::SWAP(IncDecTarget::E)), + 0x34 => Some(Instruction::SWAP(IncDecTarget::H)), + 0x35 => Some(Instruction::SWAP(IncDecTarget::L)), + 0x36 => Some(Instruction::SWAP(IncDecTarget::HL)), + 0x37 => Some(Instruction::SWAP(IncDecTarget::A)), + + // swap instructions + 0x38 => Some(Instruction::SRL(IncDecTarget::B)), + 0x39 => Some(Instruction::SRL(IncDecTarget::C)), + 0x3A => Some(Instruction::SRL(IncDecTarget::D)), + 0x3B => Some(Instruction::SRL(IncDecTarget::E)), + 0x3C => Some(Instruction::SRL(IncDecTarget::H)), + 0x3D => Some(Instruction::SRL(IncDecTarget::L)), + 0x3E => Some(Instruction::SRL(IncDecTarget::HL)), + 0x3F => Some(Instruction::SRL(IncDecTarget::A)), + + // Bit 0 + 0x40 => Some(Instruction::BIT(BitTarget::BIT_0, IncDecTarget::B)), + 0x41 => Some(Instruction::BIT(BitTarget::BIT_0, IncDecTarget::C)), + 0x42 => Some(Instruction::BIT(BitTarget::BIT_0, IncDecTarget::D)), + 0x43 => Some(Instruction::BIT(BitTarget::BIT_0, IncDecTarget::E)), + 0x44 => Some(Instruction::BIT(BitTarget::BIT_0, IncDecTarget::H)), + 0x45 => Some(Instruction::BIT(BitTarget::BIT_0, IncDecTarget::L)), + 0x46 => Some(Instruction::BIT(BitTarget::BIT_0, IncDecTarget::HL)), + 0x47 => Some(Instruction::BIT(BitTarget::BIT_0, IncDecTarget::A)), + + 0x80 => Some(Instruction::RESET_BIT(BitTarget::BIT_0, IncDecTarget::B)), + 0x81 => Some(Instruction::RESET_BIT(BitTarget::BIT_0, IncDecTarget::C)), + 0x82 => Some(Instruction::RESET_BIT(BitTarget::BIT_0, IncDecTarget::D)), + 0x83 => Some(Instruction::RESET_BIT(BitTarget::BIT_0, IncDecTarget::E)), + 0x84 => Some(Instruction::RESET_BIT(BitTarget::BIT_0, IncDecTarget::H)), + 0x85 => Some(Instruction::RESET_BIT(BitTarget::BIT_0, IncDecTarget::L)), + 0x86 => Some(Instruction::RESET_BIT(BitTarget::BIT_0, IncDecTarget::HL)), + 0x87 => Some(Instruction::RESET_BIT(BitTarget::BIT_0, IncDecTarget::A)), + + 0xC0 => Some(Instruction::SET_BIT(BitTarget::BIT_0, IncDecTarget::B)), + 0xC1 => Some(Instruction::SET_BIT(BitTarget::BIT_0, IncDecTarget::C)), + 0xC2 => Some(Instruction::SET_BIT(BitTarget::BIT_0, IncDecTarget::D)), + 0xC3 => Some(Instruction::SET_BIT(BitTarget::BIT_0, IncDecTarget::E)), + 0xC4 => Some(Instruction::SET_BIT(BitTarget::BIT_0, IncDecTarget::H)), + 0xC5 => Some(Instruction::SET_BIT(BitTarget::BIT_0, IncDecTarget::L)), + 0xC6 => Some(Instruction::SET_BIT(BitTarget::BIT_0, IncDecTarget::HL)), + 0xC7 => Some(Instruction::SET_BIT(BitTarget::BIT_0, IncDecTarget::A)), + + // Bit 1 + 0x48 => Some(Instruction::BIT(BitTarget::BIT_1, IncDecTarget::B)), + 0x49 => Some(Instruction::BIT(BitTarget::BIT_1, IncDecTarget::C)), + 0x4A => Some(Instruction::BIT(BitTarget::BIT_1, IncDecTarget::D)), + 0x4B => Some(Instruction::BIT(BitTarget::BIT_1, IncDecTarget::E)), + 0x4C => Some(Instruction::BIT(BitTarget::BIT_1, IncDecTarget::H)), + 0x4D => Some(Instruction::BIT(BitTarget::BIT_1, IncDecTarget::L)), + 0x4E => Some(Instruction::BIT(BitTarget::BIT_1, IncDecTarget::HL)), + 0x4F => Some(Instruction::BIT(BitTarget::BIT_1, IncDecTarget::A)), + + 0x88 => Some(Instruction::RESET_BIT(BitTarget::BIT_1, IncDecTarget::B)), + 0x89 => Some(Instruction::RESET_BIT(BitTarget::BIT_1, IncDecTarget::C)), + 0x8A => Some(Instruction::RESET_BIT(BitTarget::BIT_1, IncDecTarget::D)), + 0x8B => Some(Instruction::RESET_BIT(BitTarget::BIT_1, IncDecTarget::E)), + 0x8C => Some(Instruction::RESET_BIT(BitTarget::BIT_1, IncDecTarget::H)), + 0x8D => Some(Instruction::RESET_BIT(BitTarget::BIT_1, IncDecTarget::L)), + 0x8E => Some(Instruction::RESET_BIT(BitTarget::BIT_1, IncDecTarget::HL)), + 0x8F => Some(Instruction::RESET_BIT(BitTarget::BIT_1, IncDecTarget::A)), + + 0xC8 => Some(Instruction::SET_BIT(BitTarget::BIT_1, IncDecTarget::B)), + 0xC9 => Some(Instruction::SET_BIT(BitTarget::BIT_1, IncDecTarget::C)), + 0xCA => Some(Instruction::SET_BIT(BitTarget::BIT_1, IncDecTarget::D)), + 0xCB => Some(Instruction::SET_BIT(BitTarget::BIT_1, IncDecTarget::E)), + 0xCC => Some(Instruction::SET_BIT(BitTarget::BIT_1, IncDecTarget::H)), + 0xCD => Some(Instruction::SET_BIT(BitTarget::BIT_1, IncDecTarget::L)), + 0xCE => Some(Instruction::SET_BIT(BitTarget::BIT_1, IncDecTarget::HL)), + 0xCF => Some(Instruction::SET_BIT(BitTarget::BIT_1, IncDecTarget::A)), + + // Bit 2 + 0x50 => Some(Instruction::BIT(BitTarget::BIT_2, IncDecTarget::B)), + 0x51 => Some(Instruction::BIT(BitTarget::BIT_2, IncDecTarget::C)), + 0x52 => Some(Instruction::BIT(BitTarget::BIT_2, IncDecTarget::D)), + 0x53 => Some(Instruction::BIT(BitTarget::BIT_2, IncDecTarget::E)), + 0x54 => Some(Instruction::BIT(BitTarget::BIT_2, IncDecTarget::H)), + 0x55 => Some(Instruction::BIT(BitTarget::BIT_2, IncDecTarget::L)), + 0x56 => Some(Instruction::BIT(BitTarget::BIT_2, IncDecTarget::HL)), + 0x57 => Some(Instruction::BIT(BitTarget::BIT_2, IncDecTarget::A)), + + 0x90 => Some(Instruction::RESET_BIT(BitTarget::BIT_2, IncDecTarget::B)), + 0x91 => Some(Instruction::RESET_BIT(BitTarget::BIT_2, IncDecTarget::C)), + 0x92 => Some(Instruction::RESET_BIT(BitTarget::BIT_2, IncDecTarget::D)), + 0x93 => Some(Instruction::RESET_BIT(BitTarget::BIT_2, IncDecTarget::E)), + 0x94 => Some(Instruction::RESET_BIT(BitTarget::BIT_2, IncDecTarget::H)), + 0x95 => Some(Instruction::RESET_BIT(BitTarget::BIT_2, IncDecTarget::L)), + 0x96 => Some(Instruction::RESET_BIT(BitTarget::BIT_2, IncDecTarget::HL)), + 0x97 => Some(Instruction::RESET_BIT(BitTarget::BIT_2, IncDecTarget::A)), + + 0xD0 => Some(Instruction::SET_BIT(BitTarget::BIT_2, IncDecTarget::B)), + 0xD1 => Some(Instruction::SET_BIT(BitTarget::BIT_2, IncDecTarget::C)), + 0xD2 => Some(Instruction::SET_BIT(BitTarget::BIT_2, IncDecTarget::D)), + 0xD3 => Some(Instruction::SET_BIT(BitTarget::BIT_2, IncDecTarget::E)), + 0xD4 => Some(Instruction::SET_BIT(BitTarget::BIT_2, IncDecTarget::H)), + 0xD5 => Some(Instruction::SET_BIT(BitTarget::BIT_2, IncDecTarget::L)), + 0xD6 => Some(Instruction::SET_BIT(BitTarget::BIT_2, IncDecTarget::HL)), + 0xD7 => Some(Instruction::SET_BIT(BitTarget::BIT_2, IncDecTarget::A)), + + // Bit 3 + 0x58 => Some(Instruction::BIT(BitTarget::BIT_3, IncDecTarget::B)), + 0x59 => Some(Instruction::BIT(BitTarget::BIT_3, IncDecTarget::C)), + 0x5A => Some(Instruction::BIT(BitTarget::BIT_3, IncDecTarget::D)), + 0x5B => Some(Instruction::BIT(BitTarget::BIT_3, IncDecTarget::E)), + 0x5C => Some(Instruction::BIT(BitTarget::BIT_3, IncDecTarget::H)), + 0x5D => Some(Instruction::BIT(BitTarget::BIT_3, IncDecTarget::L)), + 0x5E => Some(Instruction::BIT(BitTarget::BIT_3, IncDecTarget::HL)), + 0x5F => Some(Instruction::BIT(BitTarget::BIT_3, IncDecTarget::A)), + + 0x98 => Some(Instruction::RESET_BIT(BitTarget::BIT_3, IncDecTarget::B)), + 0x99 => Some(Instruction::RESET_BIT(BitTarget::BIT_3, IncDecTarget::C)), + 0x9A => Some(Instruction::RESET_BIT(BitTarget::BIT_3, IncDecTarget::D)), + 0x9B => Some(Instruction::RESET_BIT(BitTarget::BIT_3, IncDecTarget::E)), + 0x9C => Some(Instruction::RESET_BIT(BitTarget::BIT_3, IncDecTarget::H)), + 0x9D => Some(Instruction::RESET_BIT(BitTarget::BIT_3, IncDecTarget::L)), + 0x9E => Some(Instruction::RESET_BIT(BitTarget::BIT_3, IncDecTarget::HL)), + 0x9F => Some(Instruction::RESET_BIT(BitTarget::BIT_3, IncDecTarget::A)), + + 0xD8 => Some(Instruction::SET_BIT(BitTarget::BIT_3, IncDecTarget::B)), + 0xD9 => Some(Instruction::SET_BIT(BitTarget::BIT_3, IncDecTarget::C)), + 0xDA => Some(Instruction::SET_BIT(BitTarget::BIT_3, IncDecTarget::D)), + 0xDB => Some(Instruction::SET_BIT(BitTarget::BIT_3, IncDecTarget::E)), + 0xDC => Some(Instruction::SET_BIT(BitTarget::BIT_3, IncDecTarget::H)), + 0xDD => Some(Instruction::SET_BIT(BitTarget::BIT_3, IncDecTarget::L)), + 0xDE => Some(Instruction::SET_BIT(BitTarget::BIT_3, IncDecTarget::HL)), + 0xDF => Some(Instruction::SET_BIT(BitTarget::BIT_3, IncDecTarget::A)), + + // Bit 4 + 0x60 => Some(Instruction::BIT(BitTarget::BIT_4, IncDecTarget::B)), + 0x61 => Some(Instruction::BIT(BitTarget::BIT_4, IncDecTarget::C)), + 0x62 => Some(Instruction::BIT(BitTarget::BIT_4, IncDecTarget::D)), + 0x63 => Some(Instruction::BIT(BitTarget::BIT_4, IncDecTarget::E)), + 0x64 => Some(Instruction::BIT(BitTarget::BIT_4, IncDecTarget::H)), + 0x65 => Some(Instruction::BIT(BitTarget::BIT_4, IncDecTarget::L)), + 0x66 => Some(Instruction::BIT(BitTarget::BIT_4, IncDecTarget::HL)), + 0x67 => Some(Instruction::BIT(BitTarget::BIT_4, IncDecTarget::A)), + + 0xA0 => Some(Instruction::RESET_BIT(BitTarget::BIT_4, IncDecTarget::B)), + 0xA1 => Some(Instruction::RESET_BIT(BitTarget::BIT_4, IncDecTarget::C)), + 0xA2 => Some(Instruction::RESET_BIT(BitTarget::BIT_4, IncDecTarget::D)), + 0xA3 => Some(Instruction::RESET_BIT(BitTarget::BIT_4, IncDecTarget::E)), + 0xA4 => Some(Instruction::RESET_BIT(BitTarget::BIT_4, IncDecTarget::H)), + 0xA5 => Some(Instruction::RESET_BIT(BitTarget::BIT_4, IncDecTarget::L)), + 0xA6 => Some(Instruction::RESET_BIT(BitTarget::BIT_4, IncDecTarget::HL)), + 0xA7 => Some(Instruction::RESET_BIT(BitTarget::BIT_4, IncDecTarget::A)), + + 0xE0 => Some(Instruction::SET_BIT(BitTarget::BIT_4, IncDecTarget::B)), + 0xE1 => Some(Instruction::SET_BIT(BitTarget::BIT_4, IncDecTarget::C)), + 0xE2 => Some(Instruction::SET_BIT(BitTarget::BIT_4, IncDecTarget::D)), + 0xE3 => Some(Instruction::SET_BIT(BitTarget::BIT_4, IncDecTarget::E)), + 0xE4 => Some(Instruction::SET_BIT(BitTarget::BIT_4, IncDecTarget::H)), + 0xE5 => Some(Instruction::SET_BIT(BitTarget::BIT_4, IncDecTarget::L)), + 0xE6 => Some(Instruction::SET_BIT(BitTarget::BIT_4, IncDecTarget::HL)), + 0xE7 => Some(Instruction::SET_BIT(BitTarget::BIT_4, IncDecTarget::A)), + + // Bit 5 + 0x68 => Some(Instruction::BIT(BitTarget::BIT_5, IncDecTarget::B)), + 0x69 => Some(Instruction::BIT(BitTarget::BIT_5, IncDecTarget::C)), + 0x6A => Some(Instruction::BIT(BitTarget::BIT_5, IncDecTarget::D)), + 0x6B => Some(Instruction::BIT(BitTarget::BIT_5, IncDecTarget::E)), + 0x6C => Some(Instruction::BIT(BitTarget::BIT_5, IncDecTarget::H)), + 0x6D => Some(Instruction::BIT(BitTarget::BIT_5, IncDecTarget::L)), + 0x6E => Some(Instruction::BIT(BitTarget::BIT_5, IncDecTarget::HL)), + 0x6F => Some(Instruction::BIT(BitTarget::BIT_5, IncDecTarget::A)), + + 0xA8 => Some(Instruction::RESET_BIT(BitTarget::BIT_5, IncDecTarget::B)), + 0xA9 => Some(Instruction::RESET_BIT(BitTarget::BIT_5, IncDecTarget::C)), + 0xAA => Some(Instruction::RESET_BIT(BitTarget::BIT_5, IncDecTarget::D)), + 0xAB => Some(Instruction::RESET_BIT(BitTarget::BIT_5, IncDecTarget::E)), + 0xAC => Some(Instruction::RESET_BIT(BitTarget::BIT_5, IncDecTarget::H)), + 0xAD => Some(Instruction::RESET_BIT(BitTarget::BIT_5, IncDecTarget::L)), + 0xAE => Some(Instruction::RESET_BIT(BitTarget::BIT_5, IncDecTarget::HL)), + 0xAF => Some(Instruction::RESET_BIT(BitTarget::BIT_5, IncDecTarget::A)), + + 0xE8 => Some(Instruction::SET_BIT(BitTarget::BIT_5, IncDecTarget::B)), + 0xE9 => Some(Instruction::SET_BIT(BitTarget::BIT_5, IncDecTarget::C)), + 0xEA => Some(Instruction::SET_BIT(BitTarget::BIT_5, IncDecTarget::D)), + 0xEB => Some(Instruction::SET_BIT(BitTarget::BIT_5, IncDecTarget::E)), + 0xEC => Some(Instruction::SET_BIT(BitTarget::BIT_5, IncDecTarget::H)), + 0xED => Some(Instruction::SET_BIT(BitTarget::BIT_5, IncDecTarget::L)), + 0xEE => Some(Instruction::SET_BIT(BitTarget::BIT_5, IncDecTarget::HL)), + 0xEF => Some(Instruction::SET_BIT(BitTarget::BIT_5, IncDecTarget::A)), + + // Bit 6 + 0x70 => Some(Instruction::BIT(BitTarget::BIT_6, IncDecTarget::B)), + 0x71 => Some(Instruction::BIT(BitTarget::BIT_6, IncDecTarget::C)), + 0x72 => Some(Instruction::BIT(BitTarget::BIT_6, IncDecTarget::D)), + 0x73 => Some(Instruction::BIT(BitTarget::BIT_6, IncDecTarget::E)), + 0x74 => Some(Instruction::BIT(BitTarget::BIT_6, IncDecTarget::H)), + 0x75 => Some(Instruction::BIT(BitTarget::BIT_6, IncDecTarget::L)), + 0x76 => Some(Instruction::BIT(BitTarget::BIT_6, IncDecTarget::HL)), + 0x77 => Some(Instruction::BIT(BitTarget::BIT_6, IncDecTarget::A)), + + 0xB0 => Some(Instruction::RESET_BIT(BitTarget::BIT_6, IncDecTarget::B)), + 0xB1 => Some(Instruction::RESET_BIT(BitTarget::BIT_6, IncDecTarget::C)), + 0xB2 => Some(Instruction::RESET_BIT(BitTarget::BIT_6, IncDecTarget::D)), + 0xB3 => Some(Instruction::RESET_BIT(BitTarget::BIT_6, IncDecTarget::E)), + 0xB4 => Some(Instruction::RESET_BIT(BitTarget::BIT_6, IncDecTarget::H)), + 0xB5 => Some(Instruction::RESET_BIT(BitTarget::BIT_6, IncDecTarget::L)), + 0xB6 => Some(Instruction::RESET_BIT(BitTarget::BIT_6, IncDecTarget::HL)), + 0xB7 => Some(Instruction::RESET_BIT(BitTarget::BIT_6, IncDecTarget::A)), + + 0xF0 => Some(Instruction::SET_BIT(BitTarget::BIT_6, IncDecTarget::B)), + 0xF1 => Some(Instruction::SET_BIT(BitTarget::BIT_6, IncDecTarget::C)), + 0xF2 => Some(Instruction::SET_BIT(BitTarget::BIT_6, IncDecTarget::D)), + 0xF3 => Some(Instruction::SET_BIT(BitTarget::BIT_6, IncDecTarget::E)), + 0xF4 => Some(Instruction::SET_BIT(BitTarget::BIT_6, IncDecTarget::H)), + 0xF5 => Some(Instruction::SET_BIT(BitTarget::BIT_6, IncDecTarget::L)), + 0xF6 => Some(Instruction::SET_BIT(BitTarget::BIT_6, IncDecTarget::HL)), + 0xF7 => Some(Instruction::SET_BIT(BitTarget::BIT_6, IncDecTarget::A)), + + // Bit 7 + 0x78 => Some(Instruction::BIT(BitTarget::BIT_7, IncDecTarget::B)), + 0x79 => Some(Instruction::BIT(BitTarget::BIT_7, IncDecTarget::C)), + 0x7A => Some(Instruction::BIT(BitTarget::BIT_7, IncDecTarget::D)), + 0x7B => Some(Instruction::BIT(BitTarget::BIT_7, IncDecTarget::E)), + 0x7C => Some(Instruction::BIT(BitTarget::BIT_7, IncDecTarget::H)), + 0x7D => Some(Instruction::BIT(BitTarget::BIT_7, IncDecTarget::L)), + 0x7E => Some(Instruction::BIT(BitTarget::BIT_7, IncDecTarget::HL)), + 0x7F => Some(Instruction::BIT(BitTarget::BIT_7, IncDecTarget::A)), + + 0xB8 => Some(Instruction::RESET_BIT(BitTarget::BIT_7, IncDecTarget::B)), + 0xB9 => Some(Instruction::RESET_BIT(BitTarget::BIT_7, IncDecTarget::C)), + 0xBA => Some(Instruction::RESET_BIT(BitTarget::BIT_7, IncDecTarget::D)), + 0xBB => Some(Instruction::RESET_BIT(BitTarget::BIT_7, IncDecTarget::E)), + 0xBC => Some(Instruction::RESET_BIT(BitTarget::BIT_7, IncDecTarget::H)), + 0xBD => Some(Instruction::RESET_BIT(BitTarget::BIT_7, IncDecTarget::L)), + 0xBE => Some(Instruction::RESET_BIT(BitTarget::BIT_7, IncDecTarget::HL)), + 0xBF => Some(Instruction::RESET_BIT(BitTarget::BIT_7, IncDecTarget::A)), + + 0xF8 => Some(Instruction::SET_BIT(BitTarget::BIT_7, IncDecTarget::B)), + 0xF9 => Some(Instruction::SET_BIT(BitTarget::BIT_7, IncDecTarget::C)), + 0xFA => Some(Instruction::SET_BIT(BitTarget::BIT_7, IncDecTarget::D)), + 0xFB => Some(Instruction::SET_BIT(BitTarget::BIT_7, IncDecTarget::E)), + 0xFC => Some(Instruction::SET_BIT(BitTarget::BIT_7, IncDecTarget::H)), + 0xFD => Some(Instruction::SET_BIT(BitTarget::BIT_7, IncDecTarget::L)), + 0xFE => Some(Instruction::SET_BIT(BitTarget::BIT_7, IncDecTarget::HL)), + 0xFF => Some(Instruction::SET_BIT(BitTarget::BIT_7, IncDecTarget::A)), + } + } +} diff --git a/src/soc/cpu/mod.rs b/src/soc/cpu/mod.rs new file mode 100644 index 0000000..f6d19ae --- /dev/null +++ b/src/soc/cpu/mod.rs @@ -0,0 +1,2821 @@ +mod instruction; +mod register; + +use instruction::{ + ArithmeticTarget, BitTarget, Direction, IncDecTarget, Instruction, JumpTarget, Load16Target, + PopPushTarget, RamTarget, ResetTarget, SPTarget, U16Target, +}; +use register::Registers; + +use crate::soc::peripheral::{IoAccess, Interrupt, VBLANK_VECTOR, LCDSTAT_VECTOR, TIMER_VECTOR}; +use crate::soc::peripheral::nvic::InterruptSources; + +const RUN_0_CYCLE: u8 = 0; +const RUN_1_CYCLE: u8 = 1; +const RUN_2_CYCLES: u8 = 2; +const RUN_3_CYCLES: u8 = 3; +const RUN_4_CYCLES: u8 = 4; +const RUN_5_CYCLES: u8 = 5; +const RUN_6_CYCLES: u8 = 6; + +macro_rules! run_instruction_in_register { + ($register_in: ident => $register_out: ident, $self:ident.$instruction:ident) => {{ + let value = $self.registers.$register_in; + let new_value = $self.$instruction(value); + $self.registers.$register_out = new_value; + // compute next PC value + // modulo operation to avoid overflowing effects + $self.pc.wrapping_add(1) + }}; + + ($read_reg: ident => $target_type: ident => $write_reg: ident, $self:ident.$instruction:ident) => {{ + let value_in_register = $self.registers.$read_reg(); + let new_value = $self.$instruction(value_in_register); + $self.registers.$write_reg(new_value); + // compute next PC value + // modulo operation to avoid overflowing effects + $self.pc.wrapping_add(1) + }}; +} + +macro_rules! arithmetic_instruction { + ($target: ident, $self:ident.$instruction:ident, $peripheral:expr) => {{ + match $target { + ArithmeticTarget::A => (run_instruction_in_register!(a => a, $self.$instruction), RUN_1_CYCLE), + ArithmeticTarget::B => (run_instruction_in_register!(b => a, $self.$instruction), RUN_1_CYCLE), + ArithmeticTarget::C => (run_instruction_in_register!(c => a, $self.$instruction), RUN_1_CYCLE), + ArithmeticTarget::D => (run_instruction_in_register!(d => a, $self.$instruction), RUN_1_CYCLE), + ArithmeticTarget::E => (run_instruction_in_register!(e => a, $self.$instruction), RUN_1_CYCLE), + ArithmeticTarget::H => (run_instruction_in_register!(h => a, $self.$instruction), RUN_1_CYCLE), + ArithmeticTarget::L => (run_instruction_in_register!(l => a, $self.$instruction), RUN_1_CYCLE), + ArithmeticTarget::HL => ({ + let address = $self.registers.read_hl(); + let value = $peripheral.read(address); + let new_value = $self.$instruction(value); + $self.registers.a = new_value; + // compute next PC value + // modulo operation to avoid overflowing effects + $self.pc.wrapping_add(1) + }, RUN_2_CYCLES), + ArithmeticTarget::D8 => ({ + let address = $self.pc.wrapping_add(1); + let value = $peripheral.read(address); + let new_value = $self.$instruction(value); + $self.registers.a = new_value; + // compute next PC value + // modulo operation to avoid overflowing effects + $self.pc.wrapping_add(2) + }, RUN_2_CYCLES), + } + }}; + + ($target: ident => $flag:ident => $self:ident.$instruction:ident) => {{ + match $target { + U16Target::BC => (run_instruction_in_register!(read_bc => u16 => write_hl, $self.$instruction), RUN_2_CYCLES), + U16Target::DE => (run_instruction_in_register!(read_de => u16 => write_hl, $self.$instruction), RUN_2_CYCLES), + U16Target::HL => (run_instruction_in_register!(read_hl => u16 => write_hl, $self.$instruction), RUN_2_CYCLES), + U16Target::SP => ({ + let value_in_register = $self.sp; + let new_value = $self.$instruction(value_in_register); + $self.registers.write_hl(new_value); + // compute next PC value + // modulo operation to avoid overflowing effects + $self.pc.wrapping_add(1) + }, RUN_2_CYCLES), + } + }}; +} + +macro_rules! inc_dec_instruction { + ($target: ident, $self:ident.$instruction:ident, $peripheral:expr) => {{ + match $target { + IncDecTarget::A => (run_instruction_in_register!(a => a, $self.$instruction), RUN_1_CYCLE), + IncDecTarget::B => (run_instruction_in_register!(b => b, $self.$instruction), RUN_1_CYCLE), + IncDecTarget::C => (run_instruction_in_register!(c => c, $self.$instruction), RUN_1_CYCLE), + IncDecTarget::D => (run_instruction_in_register!(d => d, $self.$instruction), RUN_1_CYCLE), + IncDecTarget::E => (run_instruction_in_register!(e => e, $self.$instruction), RUN_1_CYCLE), + IncDecTarget::H => (run_instruction_in_register!(h => h, $self.$instruction), RUN_1_CYCLE), + IncDecTarget::L => (run_instruction_in_register!(l => l, $self.$instruction), RUN_1_CYCLE), + IncDecTarget::HL => ({ + let address = $self.registers.read_hl(); + let value = $peripheral.read(address); + let new_value = $self.$instruction(value); + $peripheral.write(address, new_value); + // compute next PC value + // modulo operation to avoid overflowing effects + $self.pc.wrapping_add(1) + }, RUN_3_CYCLES), + } + }}; + + ($target: ident => $flag:ident => $self:ident.$instruction:ident) => {{ + match $target { + U16Target::BC => (run_instruction_in_register!(read_bc => u16 => write_bc, $self.$instruction), RUN_2_CYCLES), + U16Target::DE => (run_instruction_in_register!(read_de => u16 => write_de, $self.$instruction), RUN_2_CYCLES), + U16Target::HL => (run_instruction_in_register!(read_hl => u16 => write_hl, $self.$instruction), RUN_2_CYCLES), + U16Target::SP => ({ + let value_in_register = $self.sp; + let new_value = $self.$instruction(value_in_register); + $self.sp = new_value; + // compute next PC value + // modulo operation to avoid overflowing effects + $self.pc.wrapping_add(1) + }, RUN_2_CYCLES), + } + }}; +} + +macro_rules! load_in_register { + ($input_register: ident => $main_register: ident, $self:ident) => {{ + let value = $self.registers.$input_register; + $self.registers.$main_register = value; + // compute next PC value + // modulo operation to avoid overflowing effects + $self.pc.wrapping_add(1) + }}; +} + +macro_rules! load_input_register { + ($input_register: ident => $main_register: ident, $self:ident, $peripheral:expr) => {{ + match $input_register { + ArithmeticTarget::A => (load_in_register!(a => $main_register, $self), RUN_1_CYCLE), + ArithmeticTarget::B => (load_in_register!(b => $main_register, $self), RUN_1_CYCLE), + ArithmeticTarget::C => (load_in_register!(c => $main_register, $self), RUN_1_CYCLE), + ArithmeticTarget::D => (load_in_register!(d => $main_register, $self), RUN_1_CYCLE), + ArithmeticTarget::E => (load_in_register!(e => $main_register, $self), RUN_1_CYCLE), + ArithmeticTarget::H => (load_in_register!(h => $main_register, $self), RUN_1_CYCLE), + ArithmeticTarget::L => (load_in_register!(l => $main_register, $self), RUN_1_CYCLE), + ArithmeticTarget::HL => ({ + let address = $self.registers.read_hl(); + let value = $peripheral.read(address); + $self.registers.$main_register = value; + // compute next PC value + // modulo operation to avoid overflowing effects + $self.pc.wrapping_add(1) + }, RUN_2_CYCLES), + ArithmeticTarget::D8 => ({ + let address = $self.pc.wrapping_add(1); + let value = $peripheral.read(address); + $self.registers.$main_register = value; + // compute next PC value + // modulo operation to avoid overflowing effects + $self.pc.wrapping_add(2) + }, RUN_2_CYCLES), + } + }}; +} + +macro_rules! load_in_memory { + ($input_register: ident, $self:ident, $peripheral:expr) => {{ + let address = $self.registers.read_hl(); + let value = $self.registers.$input_register; + $peripheral.write(address, value); + // compute next PC value + // modulo operation to avoid overflowing effects + $self.pc.wrapping_add(1) + }}; +} + +macro_rules! load_reg_in_memory { + ($input_register: ident, $self:ident, $peripheral:expr) => {{ + match $input_register { + ArithmeticTarget::A => (load_in_memory!(a, $self, $peripheral), RUN_2_CYCLES), + ArithmeticTarget::B => (load_in_memory!(b, $self, $peripheral), RUN_2_CYCLES), + ArithmeticTarget::C => (load_in_memory!(c, $self, $peripheral), RUN_2_CYCLES), + ArithmeticTarget::D => (load_in_memory!(d, $self, $peripheral), RUN_2_CYCLES), + ArithmeticTarget::E => (load_in_memory!(e, $self, $peripheral), RUN_2_CYCLES), + ArithmeticTarget::H => (load_in_memory!(h, $self, $peripheral), RUN_2_CYCLES), + ArithmeticTarget::L => (load_in_memory!(l, $self, $peripheral), RUN_2_CYCLES), + ArithmeticTarget::HL => (0, RUN_0_CYCLE), + ArithmeticTarget::D8 => ({ + let value_address = $self.pc.wrapping_add(1); + let value = $peripheral.read(value_address); + let mem_address = $self.registers.read_hl(); + $peripheral.write(mem_address, value); + // compute next PC value + // modulo operation to avoid overflowing effects + $self.pc.wrapping_add(2) + }, RUN_3_CYCLES), + } + }}; +} + +macro_rules! load_indirect { + ($register: ident, $self:ident, $peripheral:expr) => {{ + match $register { + Load16Target::BC => { + let address = $self.registers.read_bc(); + let value = $peripheral.read(address); + $self.registers.a = value; + // compute next PC value + // modulo operation to avoid overflowing effects + $self.pc.wrapping_add(1) + } + Load16Target::DE => { + let address = $self.registers.read_de(); + let value = $peripheral.read(address); + $self.registers.a = value; + // compute next PC value + // modulo operation to avoid overflowing effects + $self.pc.wrapping_add(1) + } + Load16Target::HL_plus => { + let address = $self.registers.read_hl(); + let value = $peripheral.read(address); + $self.registers.a = value; + let new_address = address.wrapping_add(1); + $self.registers.write_hl(new_address); + // compute next PC value + // modulo operation to avoid overflowing effects + $self.pc.wrapping_add(1) + } + Load16Target::HL_minus => { + let address = $self.registers.read_hl(); + let value = $peripheral.read(address); + $self.registers.a = value; + let new_address = address.wrapping_sub(1); + $self.registers.write_hl(new_address); + // compute next PC value + // modulo operation to avoid overflowing effects + $self.pc.wrapping_add(1) + } + } + }}; +} + +macro_rules! store_indirect { + ($register: ident, $self:ident, $peripheral:expr) => {{ + match $register { + Load16Target::BC => { + let value = $self.registers.a; + let address = $self.registers.read_bc(); + $peripheral.write(address, value); + // compute next PC value + // modulo operation to avoid overflowing effects + $self.pc.wrapping_add(1) + } + Load16Target::DE => { + let value = $self.registers.a; + let address = $self.registers.read_de(); + $peripheral.write(address, value); + // compute next PC value + // modulo operation to avoid overflowing effects + $self.pc.wrapping_add(1) + } + Load16Target::HL_plus => { + let value = $self.registers.a; + let address = $self.registers.read_hl(); + $peripheral.write(address, value); + let new_address = address.wrapping_add(1); + $self.registers.write_hl(new_address); + // compute next PC value + // modulo operation to avoid overflowing effects + $self.pc.wrapping_add(1) + } + Load16Target::HL_minus => { + let value = $self.registers.a; + let address = $self.registers.read_hl(); + $peripheral.write(address, value); + let new_address = address.wrapping_sub(1); + $self.registers.write_hl(new_address); + // compute next PC value + // modulo operation to avoid overflowing effects + $self.pc.wrapping_add(1) + } + } + }}; +} + +macro_rules! load_immediate { + ($register: ident, $self:ident, $peripheral:expr) => {{ + match $register { + U16Target::BC => { + let low_address = $self.pc.wrapping_add(1); + let high_address = $self.pc.wrapping_add(2); + let low_byte = $peripheral.read(low_address); + let high_byte = $peripheral.read(high_address); + let value = (low_byte as u16) + ((high_byte as u16) << 8); + $self.registers.write_bc(value); + // compute next PC value + // modulo operation to avoid overflowing effects + $self.pc.wrapping_add(3) + } + U16Target::DE => { + let low_address = $self.pc.wrapping_add(1); + let high_address = $self.pc.wrapping_add(2); + let low_byte = $peripheral.read(low_address); + let high_byte = $peripheral.read(high_address); + let value = (low_byte as u16) + ((high_byte as u16) << 8); + $self.registers.write_de(value); + // compute next PC value + // modulo operation to avoid overflowing effects + $self.pc.wrapping_add(3) + } + U16Target::HL => { + let low_address = $self.pc.wrapping_add(1); + let high_address = $self.pc.wrapping_add(2); + let low_byte = $peripheral.read(low_address); + let high_byte = $peripheral.read(high_address); + let value = (low_byte as u16) + ((high_byte as u16) << 8); + $self.registers.write_hl(value); + // compute next PC value + // modulo operation to avoid overflowing effects + $self.pc.wrapping_add(3) + } + U16Target::SP => { + let low_address = $self.pc.wrapping_add(1); + let high_address = $self.pc.wrapping_add(2); + let low_byte = $peripheral.read(low_address); + let high_byte = $peripheral.read(high_address); + let value = (low_byte as u16) + ((high_byte as u16) << 8); + $self.sp = value; + // compute next PC value + // modulo operation to avoid overflowing effects + $self.pc.wrapping_add(3) + } + } + }}; +} + +macro_rules! control_with_flag { + ($negative: ident, $self:ident.$instruction:ident, $flag:ident, $peripheral:expr) => {{ + let flag_value = $self.registers.f.$flag; + // inverse flag if negative option is selected + let mut new_flag = flag_value; + if $negative { + new_flag = !flag_value; + } + // execute instruction + $self.$instruction(new_flag, $peripheral) + }}; +} + +macro_rules! control { + ($flag: ident, $self:ident.$instruction:ident, $peripheral:expr) => {{ + match $flag { + JumpTarget::NZ => control_with_flag!(true, $self.$instruction, zero, $peripheral), + JumpTarget::NC => control_with_flag!(true, $self.$instruction, carry, $peripheral), + JumpTarget::Z => control_with_flag!(false, $self.$instruction, zero, $peripheral), + JumpTarget::C => control_with_flag!(false, $self.$instruction, carry, $peripheral), + JumpTarget::IMMEDIATE => $self.$instruction(true, $peripheral), + } + }}; +} + +macro_rules! pop { + ($target:ident, $self:ident, $peripheral:expr) => {{ + match $target { + PopPushTarget::BC => { + let pop_data = $self.pop($peripheral); + $self.registers.write_bc(pop_data); + + // return next pc + $self.pc.wrapping_add(1) + } + PopPushTarget::DE => { + let pop_data = $self.pop($peripheral); + $self.registers.write_de(pop_data); + + // return next pc + $self.pc.wrapping_add(1) + } + PopPushTarget::HL => { + let pop_data = $self.pop($peripheral); + $self.registers.write_hl(pop_data); + + // return next pc + $self.pc.wrapping_add(1) + } + PopPushTarget::AF => { + let pop_data = $self.pop($peripheral); + $self.registers.write_af(pop_data); + + // return next pc + $self.pc.wrapping_add(1) + } + } + }}; +} + +macro_rules! push { + ($target: ident, $self:ident, $peripheral:expr) => {{ + match $target { + PopPushTarget::BC => { + let push_data = $self.registers.read_bc(); + $self.push(push_data, $peripheral); + + // return next pc + $self.pc.wrapping_add(1) + } + PopPushTarget::DE => { + let push_data = $self.registers.read_de(); + $self.push(push_data, $peripheral); + + // return next pc + $self.pc.wrapping_add(1) + } + PopPushTarget::HL => { + let push_data = $self.registers.read_hl(); + $self.push(push_data, $peripheral); + + // return next pc + $self.pc.wrapping_add(1) + } + PopPushTarget::AF => { + let push_data = $self.registers.read_af(); + $self.push(push_data, $peripheral); + + // return next pc + $self.pc.wrapping_add(1) + } + } + }}; +} + +macro_rules! ret { + ($target: ident, $self:ident, $peripheral:expr) => {{ + match $target { + JumpTarget::NZ => { + if !$self.registers.f.zero { + ($self.pop($peripheral), RUN_5_CYCLES) + } else { + ($self.pc.wrapping_add(1), RUN_2_CYCLES) + } + } + JumpTarget::NC => { + if !$self.registers.f.carry { + ($self.pop($peripheral), RUN_5_CYCLES) + } else { + ($self.pc.wrapping_add(1), RUN_2_CYCLES) + } + } + JumpTarget::Z => { + if $self.registers.f.zero { + ($self.pop($peripheral), RUN_5_CYCLES) + } else { + ($self.pc.wrapping_add(1), RUN_2_CYCLES) + } + } + JumpTarget::C => { + if $self.registers.f.carry { + ($self.pop($peripheral), RUN_5_CYCLES) + } else { + ($self.pc.wrapping_add(1), RUN_2_CYCLES) + } + } + JumpTarget::IMMEDIATE => ($self.pop($peripheral), RUN_4_CYCLES), + } + }}; +} + +macro_rules! reset { + ($target: ident, $self:ident, $peripheral:expr) => {{ + match $target { + ResetTarget::FLASH_0 => ($self.reset(0x00, $peripheral), RUN_4_CYCLES), + ResetTarget::FLASH_1 => ($self.reset(0x08, $peripheral), RUN_4_CYCLES), + ResetTarget::FLASH_2 => ($self.reset(0x10, $peripheral), RUN_4_CYCLES), + ResetTarget::FLASH_3 => ($self.reset(0x18, $peripheral), RUN_4_CYCLES), + ResetTarget::FLASH_4 => ($self.reset(0x20, $peripheral), RUN_4_CYCLES), + ResetTarget::FLASH_5 => ($self.reset(0x28, $peripheral), RUN_4_CYCLES), + ResetTarget::FLASH_6 => ($self.reset(0x30, $peripheral), RUN_4_CYCLES), + ResetTarget::FLASH_7 => ($self.reset(0x38, $peripheral), RUN_4_CYCLES), + } + }}; +} + +macro_rules! interrupt_enable { + ($enable: ident, $self:ident, $peripheral:expr) => {{ + $peripheral.master_enable($enable); + $self.pc.wrapping_add(1) + }}; +} + +macro_rules! rotate_register { + ($register: ident, $self:ident.$instruction:ident, $direction: ident, $zero:ident) => {{ + // update flag register + $self.registers.f.substraction = false; + $self.registers.f.half_carry = false; + // rotate register + let new_value = $self.$instruction($self.registers.$register, $direction, $zero); + $self.registers.$register = new_value; + // return next pc + $self.pc.wrapping_add(1) + }}; +} + +macro_rules! rotate_from_register { + ($target: ident, $self:ident.$instruction:ident, $direction: ident, $peripheral:expr) => {{ + match $target { + IncDecTarget::A => { + let next_pc = rotate_register!(a, $self.$instruction, $direction, true); + (next_pc.wrapping_add(1), RUN_2_CYCLES) + } + IncDecTarget::B => { + let next_pc = rotate_register!(b, $self.$instruction, $direction, true); + (next_pc.wrapping_add(1), RUN_2_CYCLES) + } + IncDecTarget::C => { + let next_pc = rotate_register!(c, $self.$instruction, $direction, true); + (next_pc.wrapping_add(1), RUN_2_CYCLES) + } + IncDecTarget::D => { + let next_pc = rotate_register!(d, $self.$instruction, $direction, true); + (next_pc.wrapping_add(1), RUN_2_CYCLES) + } + IncDecTarget::E => { + let next_pc = rotate_register!(e, $self.$instruction, $direction, true); + (next_pc.wrapping_add(1), RUN_2_CYCLES) + } + IncDecTarget::H => { + let next_pc = rotate_register!(h, $self.$instruction, $direction, true); + (next_pc.wrapping_add(1), RUN_2_CYCLES) + } + IncDecTarget::L => { + let next_pc = rotate_register!(l, $self.$instruction, $direction, true); + (next_pc.wrapping_add(1), RUN_2_CYCLES) + } + IncDecTarget::HL => { + // update flag register + $self.registers.f.substraction = false; + $self.registers.f.half_carry = false; + // get data from memory + let address = $self.registers.read_hl(); + let value = $peripheral.read(address); + // rotate value + let new_value = $self.$instruction(value, $direction, true); + // save value in memory + $peripheral.write(address, new_value); + // return next pc + ($self.pc.wrapping_add(2), RUN_4_CYCLES) + } + } + }}; +} + +macro_rules! shift { + ($target: ident, $self:ident.$instruction:ident, $peripheral:expr) => {{ + match $target { + IncDecTarget::A => { + let next_pc = run_instruction_in_register!(a => a, $self.$instruction); + (next_pc.wrapping_add(1), RUN_2_CYCLES) + } + IncDecTarget::B => { + let next_pc = run_instruction_in_register!(b => b, $self.$instruction); + (next_pc.wrapping_add(1), RUN_2_CYCLES) + } + IncDecTarget::C => { + let next_pc = run_instruction_in_register!(c => c, $self.$instruction); + (next_pc.wrapping_add(1), RUN_2_CYCLES) + } + IncDecTarget::D => { + let next_pc = run_instruction_in_register!(d => d, $self.$instruction); + (next_pc.wrapping_add(1), RUN_2_CYCLES) + } + IncDecTarget::E => { + let next_pc = run_instruction_in_register!(e => e, $self.$instruction); + (next_pc.wrapping_add(1), RUN_2_CYCLES) + } + IncDecTarget::H => { + let next_pc = run_instruction_in_register!(h => h, $self.$instruction); + (next_pc.wrapping_add(1), RUN_2_CYCLES) + } + IncDecTarget::L => { + let next_pc = run_instruction_in_register!(l => l, $self.$instruction); + (next_pc.wrapping_add(1), RUN_2_CYCLES) + } + IncDecTarget::HL => { + // get data from memory + let address = $self.registers.read_hl(); + let value = $peripheral.read(address); + // rotate value + let new_value = $self.$instruction(value); + // save value in memory + $peripheral.write(address, new_value); + // return next pc + ($self.pc.wrapping_add(2), RUN_4_CYCLES) + } + } + }}; +} + +macro_rules! long_inst_from_reg { + ($bit: expr, $target: ident, $self:ident.$instruction:ident, $peripheral:expr) => {{ + match $target { + IncDecTarget::A => ($self.$instruction($bit, $self.registers.a), RUN_2_CYCLES), + IncDecTarget::B => ($self.$instruction($bit, $self.registers.b), RUN_2_CYCLES), + IncDecTarget::C => ($self.$instruction($bit, $self.registers.c), RUN_2_CYCLES), + IncDecTarget::D => ($self.$instruction($bit, $self.registers.d), RUN_2_CYCLES), + IncDecTarget::E => ($self.$instruction($bit, $self.registers.e), RUN_2_CYCLES), + IncDecTarget::H => ($self.$instruction($bit, $self.registers.h), RUN_2_CYCLES), + IncDecTarget::L => ($self.$instruction($bit, $self.registers.l), RUN_2_CYCLES), + IncDecTarget::HL => ({ + // get data from memory + let address = $self.registers.read_hl(); + let value = $peripheral.read(address); + // complement value + $self.$instruction($bit, value); + // return next pc + $self.pc.wrapping_add(2) + }, RUN_3_CYCLES), + } + }}; + + ($enable: ident, $bit: expr => $target: ident, $self:ident.$instruction:ident, $peripheral:expr) => {{ + match $target { + IncDecTarget::A => { + let new_value = $self.$instruction($enable, $bit, $self.registers.a); + $self.registers.a = new_value; + // return next_pc + ($self.pc.wrapping_add(2), RUN_2_CYCLES) + } + IncDecTarget::B => { + let new_value = $self.$instruction($enable, $bit, $self.registers.b); + $self.registers.b = new_value; + // return next_pc + ($self.pc.wrapping_add(2), RUN_2_CYCLES) + } + IncDecTarget::C => { + let new_value = $self.$instruction($enable, $bit, $self.registers.c); + $self.registers.c = new_value; + // return next_pc + ($self.pc.wrapping_add(2), RUN_2_CYCLES) + } + IncDecTarget::D => { + let new_value = $self.$instruction($enable, $bit, $self.registers.d); + $self.registers.d = new_value; + // return next_pc + ($self.pc.wrapping_add(2), RUN_2_CYCLES) + } + IncDecTarget::E => { + let new_value = $self.$instruction($enable, $bit, $self.registers.e); + $self.registers.e = new_value; + // return next_pc + ($self.pc.wrapping_add(2), RUN_2_CYCLES) + } + IncDecTarget::H => { + let new_value = $self.$instruction($enable, $bit, $self.registers.h); + $self.registers.h = new_value; + // return next_pc + ($self.pc.wrapping_add(2), RUN_2_CYCLES) + } + IncDecTarget::L => { + let new_value = $self.$instruction($enable, $bit, $self.registers.l); + $self.registers.l = new_value; + // return next_pc + ($self.pc.wrapping_add(2), RUN_2_CYCLES) + } + IncDecTarget::HL => { + // get data from memory + let address = $self.registers.read_hl(); + let value = $peripheral.read(address); + // run instruction on value + let new_value = $self.$instruction($enable, $bit, value); + // save new value in memory + $peripheral.write(address, new_value); + // return next pc + ($self.pc.wrapping_add(2), RUN_4_CYCLES) + } + } + }}; +} + +macro_rules! long_inst { + ($bit: ident, $target: ident, $self:ident.$instruction:ident, $peripheral:expr) => {{ + match $bit { + BitTarget::BIT_0 => long_inst_from_reg!(0, $target, $self.$instruction, $peripheral), + BitTarget::BIT_1 => long_inst_from_reg!(1, $target, $self.$instruction, $peripheral), + BitTarget::BIT_2 => long_inst_from_reg!(2, $target, $self.$instruction, $peripheral), + BitTarget::BIT_3 => long_inst_from_reg!(3, $target, $self.$instruction, $peripheral), + BitTarget::BIT_4 => long_inst_from_reg!(4, $target, $self.$instruction, $peripheral), + BitTarget::BIT_5 => long_inst_from_reg!(5, $target, $self.$instruction, $peripheral), + BitTarget::BIT_6 => long_inst_from_reg!(6, $target, $self.$instruction, $peripheral), + BitTarget::BIT_7 => long_inst_from_reg!(7, $target, $self.$instruction, $peripheral), + } + }}; + + ($enable: ident, $bit: ident => $target: ident, $self:ident.$instruction:ident, $peripheral:expr) => {{ + match $bit { + BitTarget::BIT_0 => long_inst_from_reg!($enable, 0 => $target, $self.$instruction, $peripheral), + BitTarget::BIT_1 => long_inst_from_reg!($enable, 1 => $target, $self.$instruction, $peripheral), + BitTarget::BIT_2 => long_inst_from_reg!($enable, 2 => $target, $self.$instruction, $peripheral), + BitTarget::BIT_3 => long_inst_from_reg!($enable, 3 => $target, $self.$instruction, $peripheral), + BitTarget::BIT_4 => long_inst_from_reg!($enable, 4 => $target, $self.$instruction, $peripheral), + BitTarget::BIT_5 => long_inst_from_reg!($enable, 5 => $target, $self.$instruction, $peripheral), + BitTarget::BIT_6 => long_inst_from_reg!($enable, 6 => $target, $self.$instruction, $peripheral), + BitTarget::BIT_7 => long_inst_from_reg!($enable, 7 => $target, $self.$instruction, $peripheral), + } + }}; +} + +#[derive(PartialEq)] +pub enum CpuMode { + RUN, + INTERRUPT, + STOP, + HALT, +} + +pub enum CarryOp { + SET, + FLIP, +} + +pub struct Cpu { + pub registers: Registers, + pub pc: u16, + pub sp: u16, + mode: CpuMode, +} + +impl Cpu { + pub fn new() -> Cpu { + Cpu { + registers: Registers::new(), + pc: 0x0000, + sp: 0x0000, + mode: CpuMode::RUN, + } + } + + fn decode(&mut self, instruction_byte: u8, peripheral: &mut T) -> Option { + if Instruction::is_long_instruction(instruction_byte) { + let long_instruction_byte = peripheral.read(self.pc.wrapping_add(1)); + Instruction::from_long_byte(long_instruction_byte) + } else { + Instruction::from_byte(instruction_byte) + } + } + + pub fn run(&mut self, peripheral: &mut T) -> u8 { + // catch interrupt as soon as possible + if peripheral.is_an_interrupt_to_run() { + self.mode = CpuMode::INTERRUPT; + } + + // run CPU if it's not in HALT or STOP mode + match self.mode { + + CpuMode::RUN => { + // fetch instruction + let instruction_byte = peripheral.read(self.pc); + // decode instruction + let (next_pc, cpu_cycles) = if let Some(instruction) = self.decode(instruction_byte, peripheral) { + // execute instruction + self.execute(instruction, peripheral) + } else { + panic!("Unknown instruction found for 0x{:x}", instruction_byte); + }; + + // update PC value & cycles value + self.pc = next_pc; + + // run the peripheral subsystem + cpu_cycles + } + + CpuMode::INTERRUPT => { + // get the interrupt source + if let Some(interrupt_source) = peripheral.get_interrupt() { + // set the cpu in RUN mode to handle interrupt routine + self.mode = CpuMode::RUN; + // disable interrupts while handling interrupt routine + peripheral.master_enable(false); + // jump to interrupt routine + self.jump_to_interrupt_routine(interrupt_source, peripheral); + // 2 NOP (2 cycles) + PUSH (2 cycles) + set PC (1 cycle) + + // run the peripheral subsystem + RUN_5_CYCLES + } else { + panic!("An interrupt has been triggered but no interrupt source has been found !") + } + } + + CpuMode::HALT => { + // exit HALT mode if an interrupt is pending + if peripheral.is_an_interrupt_pending() { + self.mode = CpuMode::RUN + } + + // oscillator and LCD controller are not stopped in HALT mode + // run the peripheral subsystem + RUN_1_CYCLE + } + + CpuMode::STOP => { + // all system is stopped + RUN_0_CYCLE + } + } + } + + fn jump_to_interrupt_routine(&mut self, interrupt_source: InterruptSources, peripheral: &mut T) { + self.push(self.pc, peripheral); + match interrupt_source { + InterruptSources::VBLANK => self.pc = VBLANK_VECTOR, + InterruptSources::STAT => self.pc = LCDSTAT_VECTOR, + InterruptSources::TIMER => self.pc = TIMER_VECTOR, + _ => {}, + } + } + + fn execute(&mut self, instruction: Instruction, peripheral: &mut T) -> (u16, u8) { + match instruction { + // Arithmetic instructions + Instruction::ADD(target) => arithmetic_instruction!(target, self.add, peripheral), + Instruction::ADD16(target) => arithmetic_instruction!(target => u16 => self.add16), + Instruction::ADDC(target) => arithmetic_instruction!(target, self.addc, peripheral), + Instruction::SUB(target) => arithmetic_instruction!(target, self.sub, peripheral), + Instruction::SBC(target) => arithmetic_instruction!(target, self.subc, peripheral), + Instruction::AND(target) => arithmetic_instruction!(target, self.and, peripheral), + Instruction::XOR(target) => arithmetic_instruction!(target, self.xor, peripheral), + Instruction::OR(target) => arithmetic_instruction!(target, self.or, peripheral), + Instruction::CP(target) => arithmetic_instruction!(target, self.cp, peripheral), + Instruction::AddSp => (self.add_sp(peripheral), RUN_4_CYCLES), + + // Increment & decrement instructions + Instruction::INC(target) => inc_dec_instruction!(target, self.inc, peripheral), + Instruction::INC16(target) => inc_dec_instruction!(target => u16 => self.inc16), + Instruction::DEC(target) => inc_dec_instruction!(target, self.dec, peripheral), + Instruction::DEC16(target) => inc_dec_instruction!(target => u16 => self.dec16), + + // Load & Store instructions + Instruction::LOAD(main_reg, input_reg) => self.load(input_reg, main_reg, peripheral), + Instruction::LOAD_INDIRECT(target) => (load_indirect!(target, self, peripheral), RUN_2_CYCLES), + Instruction::LOAD_IMMEDIATE(target) => (load_immediate!(target, self, peripheral), RUN_3_CYCLES), + Instruction::STORE_INDIRECT(target) => (store_indirect!(target, self, peripheral), RUN_2_CYCLES), + Instruction::LOAD_SP(target) => self.load_sp(target, peripheral), + Instruction::LOAD_RAM(target) => self.load_store_ram(target, true, peripheral), + Instruction::STORE_RAM(target) => self.load_store_ram(target, false, peripheral), + + // JUMP / CALL / RETURN / RESET instructions + Instruction::JUMP_RELATIVE(target) => control!(target, self.jump_relative, peripheral), + Instruction::JUMP_IMMEDIATE(target) => control!(target, self.jump_immediate, peripheral), + Instruction::JUMP_INDIRECT => (self.jump_indirect(), RUN_1_CYCLE), + Instruction::RETURN(target) => ret!(target, self, peripheral), + Instruction::RESET(target) => reset!(target, self, peripheral), + Instruction::CALL(target) => control!(target, self.call, peripheral), + Instruction::RETI => (self.reti(peripheral), RUN_4_CYCLES), + + // Pop & Push instructions + Instruction::POP(target) => (pop!(target, self, peripheral), RUN_3_CYCLES), + Instruction::PUSH(target) => (push!(target, self, peripheral), RUN_4_CYCLES), + + // Interrupt instructions + Instruction::DI => (interrupt_enable!(false, self, peripheral), RUN_1_CYCLE), + Instruction::EI => (interrupt_enable!(true, self, peripheral), RUN_1_CYCLE), + + // Control instructions + Instruction::NOP => (self.pc.wrapping_add(1), RUN_1_CYCLE), + Instruction::STOP => (self.set_cpu_mode(CpuMode::STOP), RUN_1_CYCLE), + Instruction::HALT => (self.set_cpu_mode(CpuMode::HALT), RUN_1_CYCLE), + Instruction::DAA => (self.decimal_adjust(), RUN_1_CYCLE), + Instruction::SCF => (self.set_carry(CarryOp::SET), RUN_1_CYCLE), + Instruction::CPL => (self.flip_register_a(), RUN_1_CYCLE), + Instruction::CCF => (self.set_carry(CarryOp::FLIP), RUN_1_CYCLE), + + // Rotate, Shift & Swap instructions + Instruction::RCA(direction) => (rotate_register!(a, self.rotate, direction, false), RUN_1_CYCLE), + Instruction::RA(direction) => (rotate_register!(a, self.rotate_through_carry, direction, false), RUN_1_CYCLE), + Instruction::RC(direction, target) => rotate_from_register!(target, self.rotate, direction, peripheral), + Instruction::R(direction, target) => rotate_from_register!(target, self.rotate_through_carry, direction, peripheral), + Instruction::SLA(target) => shift!(target, self.shift_left_and_reset, peripheral), + Instruction::SRA(target) => shift!(target, self.shift_right, peripheral), + Instruction::SRL(target) => shift!(target, self.shift_right_and_reset, peripheral), + Instruction::SWAP(target) => shift!(target, self.swap, peripheral), + + // Bit instructions + Instruction::BIT(bit, target) => long_inst!(bit, target, self.complement_bit, peripheral), + Instruction::RESET_BIT(bit, target) => long_inst!(false, bit => target, self.set_bit, peripheral), + Instruction::SET_BIT(bit, target) => long_inst!(true, bit => target, self.set_bit, peripheral), + } + } + + fn add(&mut self, value: u8) -> u8 { + let (new_value, overflow) = self.registers.a.overflowing_add(value); + self.registers.f.zero = new_value == 0; + self.registers.f.substraction = false; + self.registers.f.carry = overflow; + // Half Carry is set if adding the lower bits of the value and register A + // together result in a value bigger than 0xF. If the result is larger than 0xF + // than the addition caused a carry from the lower nibble to the upper nibble. + self.registers.f.half_carry = (self.registers.a & 0xF) + (value & 0xF) > 0xF; + new_value + } + + fn add16(&mut self, value: u16) -> u16 { + let hl_value = self.registers.read_hl(); + let (new_value, overflow) = hl_value.overflowing_add(value); + self.registers.f.substraction = false; + self.registers.f.carry = overflow; + // Half carry tests if we flow over the 11th bit i.e. does adding the two + // numbers together cause the 11th bit to flip + let mask = 0b111_1111_1111; // mask out bits 11-15 + self.registers.f.half_carry = (value & mask) + (hl_value & mask) > mask; + new_value + } + + fn addc(&mut self, value: u8) -> u8 { + let carry = self.registers.f.carry as u8; + let (intermediate_value, first_overflow) = value.overflowing_add(carry as u8); + let (new_value, second_overflow) = self.registers.a.overflowing_add(intermediate_value); + self.registers.f.zero = new_value == 0; + self.registers.f.substraction = false; + self.registers.f.carry = first_overflow || second_overflow; + // Half Carry is set if adding the lower bits of the value and register A + // together result in a value bigger than 0xF. If the result is larger than 0xF + // than the addition caused a carry from the lower nibble to the upper nibble. + self.registers.f.half_carry = (self.registers.a & 0xF) + (value & 0xF) + carry > 0xF; + new_value + } + + fn sub(&mut self, value: u8) -> u8 { + let (new_value, overflow) = self.registers.a.overflowing_sub(value); + self.registers.f.zero = new_value == 0; + self.registers.f.substraction = true; + self.registers.f.carry = overflow; + // Half Carry is set if adding the lower bits of the value and register A + // together result in a value bigger than 0xF. If the result is larger than 0xF + // than the addition caused a carry from the lower nibble to the upper nibble. + self.registers.f.half_carry = (self.registers.a & 0xF) < (value & 0xF); + new_value + } + + fn subc(&mut self, value: u8) -> u8 { + let carry = self.registers.f.carry as u8; + let (intermediate_value, first_overflow) = self.registers.a.overflowing_sub(value); + let (new_value, second_overflow) = intermediate_value.overflowing_sub(carry); + self.registers.f.zero = new_value == 0; + self.registers.f.substraction = true; + self.registers.f.carry = first_overflow || second_overflow; + // Half Carry is set if adding the lower bits of the value and register A + // together result in a value bigger than 0xF. If the result is larger than 0xF + // than the addition caused a carry from the lower nibble to the upper nibble. + self.registers.f.half_carry = (self.registers.a & 0xF) < (value & 0xF) + carry; + new_value + } + + fn and(&mut self, value: u8) -> u8 { + let new_value = self.registers.a & value; + self.registers.f.zero = new_value == 0; + self.registers.f.substraction = false; + self.registers.f.half_carry = true; + self.registers.f.carry = false; + new_value + } + + fn xor(&mut self, value: u8) -> u8 { + let new_value = self.registers.a ^ value; + self.registers.f.zero = new_value == 0; + self.registers.f.substraction = false; + self.registers.f.half_carry = false; + self.registers.f.carry = false; + new_value + } + + fn or(&mut self, value: u8) -> u8 { + let new_value = self.registers.a | value; + self.registers.f.zero = new_value == 0; + self.registers.f.substraction = false; + self.registers.f.half_carry = false; + self.registers.f.carry = false; + new_value + } + + fn cp(&mut self, value: u8) -> u8 { + let new_value = self.registers.a; + self.registers.f.zero = self.registers.a == value; + self.registers.f.substraction = true; + self.registers.f.half_carry = (self.registers.a & 0xF) < (value & 0xF); + self.registers.f.carry = self.registers.a < value; + new_value + } + + fn inc(&mut self, value: u8) -> u8 { + let new_value = value.wrapping_add(1); + self.registers.f.zero = new_value == 0; + self.registers.f.substraction = false; + self.registers.f.half_carry = (value & 0xF) == 0xF; + new_value + } + + fn inc16(&mut self, value: u16) -> u16 { + let new_value = value.wrapping_add(1); + new_value + } + + fn dec(&mut self, value: u8) -> u8 { + let new_value = value.wrapping_sub(1); + self.registers.f.zero = new_value == 0; + self.registers.f.substraction = true; + self.registers.f.half_carry = (value & 0xF) == 0; + new_value + } + + fn dec16(&mut self, value: u16) -> u16 { + let new_value = value.wrapping_sub(1); + new_value + } + + fn load(&mut self, input_register: ArithmeticTarget, main_register: IncDecTarget, peripheral: &mut T) -> (u16, u8) { + match main_register { + IncDecTarget::A => load_input_register!(input_register => a, self, peripheral), + IncDecTarget::B => load_input_register!(input_register => b, self, peripheral), + IncDecTarget::C => load_input_register!(input_register => c, self, peripheral), + IncDecTarget::D => load_input_register!(input_register => d, self, peripheral), + IncDecTarget::E => load_input_register!(input_register => e, self, peripheral), + IncDecTarget::H => load_input_register!(input_register => h, self, peripheral), + IncDecTarget::L => load_input_register!(input_register => l, self, peripheral), + IncDecTarget::HL => load_reg_in_memory!(input_register, self, peripheral), + } + } + + fn load_sp(&mut self, target: SPTarget, peripheral: &mut T) -> (u16, u8) { + match target { + SPTarget::FROM_SP => ({ + let low_byte_address = peripheral.read(self.pc.wrapping_add(1)) as u16; + let high_byte_address = peripheral.read(self.pc.wrapping_add(2)) as u16; + let address = low_byte_address + (high_byte_address << 8); + + // save Stack Pointer lower byte + let mut data = (self.sp & 0x00FF) as u8; + peripheral.write(address, data); + // save Stack Pointer higher byte + data = ((self.sp & 0xFF00) >> 8) as u8; + peripheral.write(address + 1, data); + + // return next program counter value + self.pc.wrapping_add(3) + }, RUN_5_CYCLES), + SPTarget::TO_HL => ({ + let immediate = peripheral.read(self.pc.wrapping_add(1)) as i8 as u16; + let stack_addr = self.sp.wrapping_add(immediate); + self.registers.write_hl(stack_addr); + + // update flags + self.registers.f.zero = false; + self.registers.f.substraction = false; + self.registers.f.half_carry = (self.sp & 0xF) + (immediate & 0xF) > 0xF; + self.registers.f.carry = (self.sp & 0xFF) + (immediate & 0xFF) > 0xFF; + + // return next program counter value + self.pc.wrapping_add(2) + }, RUN_3_CYCLES), + SPTarget::TO_SP => ({ + let value = self.registers.read_hl(); + self.sp = value; + + // return next program counter value + self.pc.wrapping_add(1) + }, RUN_2_CYCLES), + } + } + + fn load_store_ram(&mut self, target: RamTarget, load: bool, peripheral: &mut T) -> (u16, u8) { + match target { + RamTarget::OneByteAddress => ({ + // get address from instruction + let base_ram_address = 0xFF00; + let immediate_address = self.pc.wrapping_add(1); + let ram_offset = peripheral.read(immediate_address) as u16; + + if load { + // read data from ram memory & load it in register a + self.registers.a = peripheral.read(base_ram_address + ram_offset); + } else { + // read data from register A & store it in RAM + peripheral + .write(base_ram_address + ram_offset, self.registers.a); + } + + // return next program counter value + self.pc.wrapping_add(2) + }, RUN_3_CYCLES), + RamTarget::AddressFromRegister => ({ + // get address from instruction + let base_ram_address = 0xFF00; + let ram_offset = self.registers.c as u16; + + if load { + // read data from ram memory & load it in register a + self.registers.a = peripheral.read(base_ram_address + ram_offset); + } else { + // read data from register A & store it in RAM + peripheral + .write(base_ram_address + ram_offset, self.registers.a); + } + + // return next program counter value + self.pc.wrapping_add(1) + }, RUN_2_CYCLES), + RamTarget::TwoBytesAddress => ({ + // get address from instruction + let low_byte_address = peripheral.read(self.pc.wrapping_add(1)) as u16; + let high_byte_address = peripheral.read(self.pc.wrapping_add(2)) as u16; + let address = low_byte_address + (high_byte_address << 8); + + if load { + // read data from ram memory & load it in register a + self.registers.a = peripheral.read(address); + } else { + // read data from register A & store it in RAM + peripheral.write(address, self.registers.a); + } + + // return next program counter value + self.pc.wrapping_add(3) + }, RUN_4_CYCLES), + } + } + + fn jump_relative(&mut self, flag: bool, peripheral: &mut T) -> (u16, u8) { + // get the immediate from memory + let immediate_address = self.pc.wrapping_add(1); + let immediate = peripheral.read(immediate_address) as i8 as u16; + + // do the jump following the flag value + if flag { + // manage signed value to add to PC + (self.pc.wrapping_add(2).wrapping_add(immediate), RUN_3_CYCLES) + } else { + (self.pc.wrapping_add(2), RUN_2_CYCLES) + } + } + + fn jump_immediate(&mut self, flag: bool, peripheral: &mut T) -> (u16, u8) { + // get the immediate from memory + let low_immediate = peripheral.read(self.pc.wrapping_add(1)) as u16; + let high_immediate = peripheral.read(self.pc.wrapping_add(2)) as u16; + let immediate = (high_immediate << 8) | low_immediate; + + // do the jump following the flag value + if flag { + (immediate, RUN_4_CYCLES) + } else { + (self.pc.wrapping_add(3), RUN_3_CYCLES) + } + } + + fn jump_indirect(&mut self) -> u16 { + // get the immediate from memory + self.registers.read_hl() + } + + fn pop(&mut self, peripheral: &mut T) -> u16 { + // get stack pointer values + let low_stack_address = self.sp; + let high_stack_address = self.sp.wrapping_add(1); + // update stack pointer + self.sp = self.sp.wrapping_add(2); + // read data from RAM memory + let low_byte = peripheral.read(low_stack_address) as u16; + let high_byte = peripheral.read(high_stack_address) as u16; + low_byte | (high_byte << 8) + } + + fn push(&mut self, push_data: u16, peripheral: &mut T) { + // get bytes from data + let high_byte = ((push_data & 0xFF00) >> 8) as u8; + let low_byte = (push_data & 0x00FF) as u8; + // get stack pointer values + let high_stack_address = self.sp.wrapping_sub(1); + let low_stack_address = self.sp.wrapping_sub(2); + // save data in memory + peripheral.write(high_stack_address, high_byte); + peripheral.write(low_stack_address, low_byte); + // update stack pointer + self.sp = self.sp.wrapping_sub(2); + } + + fn add_sp(&mut self, peripheral: &mut T) -> u16 { + let immediate = peripheral.read(self.pc.wrapping_add(1)) as i8 as u16; + let result = self.sp.wrapping_add(immediate); + + // update flags + self.registers.f.zero = false; + self.registers.f.substraction = false; + self.registers.f.half_carry = (self.sp & 0x000F) + (immediate & 0x000F) > 0x000F; + self.registers.f.carry = (self.sp & 0x00FF) + (immediate & 0xFF) > 0x00FF; + + self.sp = result; + + // return next program counter value + self.pc.wrapping_add(2) + } + + fn reset(&mut self, addr_to_reset: u8, peripheral: &mut T) -> u16 { + // save PC value on the stack + self.push(self.pc.wrapping_add(1), peripheral); + // return next PC value + addr_to_reset as u16 + } + + fn reti(&mut self, peripheral: &mut T) -> u16 { + peripheral.master_enable(true); + self.pop(peripheral) + } + + fn call(&mut self, flag: bool, peripheral: &mut T) -> (u16, u8) { + // do the call following the flag value + if flag { + // save the return address on the stack + self.push(self.pc.wrapping_add(3), peripheral); + // get the call address + let low_byte_address = peripheral.read(self.pc.wrapping_add(1)) as u16; + let high_byte_address = peripheral.read(self.pc.wrapping_add(2)) as u16; + let call_address = low_byte_address | (high_byte_address << 8); + // return the call address + (call_address, RUN_6_CYCLES) + } else { + // go to the next instruction + (self.pc.wrapping_add(3), RUN_3_CYCLES) + } + } + + fn set_cpu_mode(&mut self, mode: CpuMode) -> u16 { + self.mode = mode; + self.pc.wrapping_add(1) + } + + fn flip_register_a(&mut self) -> u16 { + // flip register a + self.registers.a = !self.registers.a; + + // update flag register + self.registers.f.substraction = true; + self.registers.f.half_carry = true; + + // return next pc value + self.pc.wrapping_add(1) + } + + fn set_carry(&mut self, operation: CarryOp) -> u16 { + // set carry depending on operation value + match operation { + CarryOp::SET => self.registers.f.carry = true, + CarryOp::FLIP => self.registers.f.carry = !self.registers.f.carry, + } + + // update flags + self.registers.f.substraction = false; + self.registers.f.half_carry = false; + + // return next pc value + self.pc.wrapping_add(1) + } + + fn decimal_adjust(&mut self) -> u16 { + // huge help from https://github.com/rylev/DMG-01/blob/master/lib-dmg-01/src/cpu/mod.rs + + let flags = self.registers.f; + let mut carry = false; + + // adjust + let result = if !flags.substraction { + let mut result = self.registers.a; + if flags.carry || self.registers.a > 0x99 { + carry = true; + result = result.wrapping_add(0x60); + } + if flags.half_carry || self.registers.a & 0x0F > 0x09 { + result = result.wrapping_add(0x06); + } + result + } else if flags.carry { + carry = true; + let add = if flags.half_carry { 0x9A } else { 0xA0 }; + self.registers.a.wrapping_add(add) + } else if flags.half_carry { + self.registers.a.wrapping_add(0xFA) + } else { + self.registers.a + }; + // update a register with the new value + self.registers.a = result; + + // update flags + self.registers.f.zero = result == 0; + self.registers.f.carry = carry; + self.registers.f.half_carry = false; + + // return next pc value + self.pc.wrapping_add(1) + } + + fn rotate(&mut self, value: u8, direction: Direction, zero: bool) -> u8 { + match direction { + Direction::LEFT => { + // save bit 7 + let last_bit = (value & 0b1000_0000) >> 7; + // shift register + let output_value = (value << 1) | last_bit; + // update zero flag + if zero { + self.registers.f.zero = output_value == 0; + } else { + self.registers.f.zero = false; + } + // update carry + self.registers.f.carry = last_bit != 0; + // return computed value + output_value + } + Direction::RIGHT => { + // save bit 0 + let first_bit = (value & 0b0000_0001) << 7; + // shift register + let output_value = (value >> 1) | first_bit; + // update zero flag + if zero { + self.registers.f.zero = output_value == 0; + } else { + self.registers.f.zero = false; + } + // update carry + self.registers.f.carry = first_bit != 0; + // return computed value + output_value + } + } + } + + fn rotate_through_carry(&mut self, value: u8, direction: Direction, zero: bool) -> u8 { + match direction { + Direction::LEFT => { + // save bit 7 + let last_bit = (value & 0b1000_0000) >> 7; + // shift register + let output_value = (value << 1) | (self.registers.f.carry as u8); + // update zero flag + if zero { + self.registers.f.zero = output_value == 0; + } else { + self.registers.f.zero = false; + } + // update carry + self.registers.f.carry = last_bit != 0; + // return computed value + output_value + } + Direction::RIGHT => { + // save bit 0 + let first_bit = (value & 0b0000_0001) << 7; + // shift register + let output_value = (value >> 1) | ((self.registers.f.carry as u8) << 7); + // update zero flag + if zero { + self.registers.f.zero = output_value == 0; + } else { + self.registers.f.zero = false; + } + // update carry + self.registers.f.carry = first_bit != 0; + // return computed value + output_value + } + } + } + + fn shift_left_and_reset(&mut self, value: u8) -> u8 { + // save bit 7 + let last_bit = (value & 0b1000_0000) >> 7; + // shift register + let output_value = value << 1; + // update flag register + self.registers.f.substraction = false; + self.registers.f.half_carry = false; + self.registers.f.zero = output_value == 0; + // update carry + self.registers.f.carry = last_bit != 0; + // return computed value + output_value + } + + fn shift_right_and_reset(&mut self, value: u8) -> u8 { + // save bit 0 + let first_bit = (value & 0b0000_0001) << 7; + // shift register + let output_value = value >> 1; + // update flag register + self.registers.f.substraction = false; + self.registers.f.half_carry = false; + self.registers.f.zero = output_value == 0; + // update carry + self.registers.f.carry = first_bit != 0; + // return computed value + output_value + } + + fn shift_right(&mut self, value: u8) -> u8 { + // save bit 0 + let first_bit = (value & 0b0000_0001) << 7; + // shift register + let output_value = (value >> 1) | (value & 0b1000_0000); + // update flag register + self.registers.f.substraction = false; + self.registers.f.half_carry = false; + self.registers.f.zero = output_value == 0; + // update carry + self.registers.f.carry = first_bit != 0; + // return computed value + output_value + } + + fn swap(&mut self, value: u8) -> u8 { + // save bit 0 + let low_bits = value & 0x0F; + let high_bits = value & 0xF0; + // shift register + let output_value = (low_bits << 4) | (high_bits >> 4); + // update flag register + self.registers.f.substraction = false; + self.registers.f.half_carry = false; + self.registers.f.zero = output_value == 0; + self.registers.f.carry = false; + // return computed value + output_value + } + + fn complement_bit(&mut self, bit: u8, value: u8) -> u16 { + // get bit value + let bit_value = (value >> bit) & 0x01; + // update flag register + self.registers.f.substraction = false; + self.registers.f.half_carry = true; + self.registers.f.zero = bit_value == 0; + // return next pc + self.pc.wrapping_add(2) + } + + fn set_bit(&mut self, enable: bool, bit: u8, value: u8) -> u8 { + // set / reset bit + if enable { + value | ((0x01 as u8) << bit) + } else { + value & !((0x01 as u8) << bit) + } + } +} + +#[cfg(test)] +mod cpu_tests { + use super::*; + use crate::soc::cpu::instruction::ArithmeticTarget::{B, C, D, D8, E, H, HL}; + use crate::soc::cpu::instruction::Instruction::{ + ADD, ADD16, ADDC, AND, CP, DEC, DEC16, DI, EI, INC, INC16, LOAD, LOAD_IMMEDIATE, + LOAD_INDIRECT, LOAD_SP, OR, POP, PUSH, RESET, RETI, RETURN, SBC, STORE_INDIRECT, SUB, XOR, + }; + use crate::soc::cpu::instruction::{ + IncDecTarget, JumpTarget, Load16Target, PopPushTarget, ResetTarget, SPTarget, U16Target, + }; + use crate::cartridge::{Cartridge, CARTRIDGE_TYPE_OFFSET, CARTRIDGE_RAM_SIZE_OFFSET, CARTRIDGE_ROM_SIZE_OFFSET}; + use crate::soc::peripheral::Peripheral; + + #[test] + fn test_add_registers() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + cpu.registers.write_bc(0xAABB); + cpu.execute(ADD(B), &mut peripheral); + assert_eq!(cpu.registers.read_af(), 0xAA00); + } + + #[test] + fn test_add_memory() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + let address = 0xC000; + let data = 0xAA; + + peripheral.write(address, data); + cpu.registers.write_hl(address); + cpu.execute(ADD(HL), &mut peripheral); + assert_eq!(cpu.registers.read_af(), 0xAA00); + } + + #[test] + fn test_add_immediate() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + rom[0x0001 as usize] = 0x23; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + cpu.execute(ADD(D8), &mut peripheral); + assert_eq!(cpu.registers.read_af(), 0x2300); + } + + #[test] + fn test_add16_registers() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + cpu.registers.write_bc(0x2200); + cpu.registers.write_hl(0x0125); + cpu.execute(ADD16(U16Target::BC), &mut peripheral); + assert_eq!(cpu.registers.read_hl(), 0x2325); + + cpu.registers.write_de(0x00FF); + cpu.registers.write_hl(0xFF01); + cpu.execute(ADD16(U16Target::DE), &mut peripheral); + assert_eq!(cpu.registers.read_hl(), 0x0000); + + cpu.registers.write_hl(0xF025); + cpu.execute(ADD16(U16Target::HL), &mut peripheral); + assert_eq!(cpu.registers.read_hl(), 0xE04A); + + cpu.sp = 0x0001; + cpu.registers.write_hl(0xF025); + cpu.execute(ADD16(U16Target::SP), &mut peripheral); + assert_eq!(cpu.registers.read_hl(), 0xF026); + } + + #[test] + fn test_addc_registers() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + cpu.registers.write_af(0x0110); + cpu.registers.write_bc(0xAABB); + cpu.execute(ADDC(B), &mut peripheral); + assert_eq!(cpu.registers.read_af(), 0xAC00); + + cpu.registers.write_af(0x0110); + cpu.registers.write_bc(0xFF00); + cpu.execute(ADDC(B), &mut peripheral); + assert_eq!(cpu.registers.read_af(), 0x0130); + } + + #[test] + fn test_addc_memory() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + let address = 0xC000; + let data = 0xAA; + + peripheral.write(address, data); + cpu.registers.write_hl(address); + cpu.execute(ADDC(HL), &mut peripheral); + assert_eq!(cpu.registers.read_af(), 0xAA00); + } + + #[test] + fn test_addc_immediate() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + let address = 0xC001; + let data = 0x23; + + peripheral.write(address, data); + cpu.registers.write_af(0x0110); + cpu.pc = 0xC000; + cpu.execute(ADDC(D8), &mut peripheral); + assert_eq!(cpu.registers.read_af(), 0x2500); + } + + #[test] + fn test_sub_registers() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + cpu.registers.write_bc(0xAABB); + cpu.registers.write_af(0xFF00); + cpu.execute(SUB(C), &mut peripheral); + assert_eq!(cpu.registers.read_af(), 0x4440); + } + + #[test] + fn test_subc_registers() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + cpu.registers.write_bc(0xAABB); + cpu.registers.write_af(0xFF10); + cpu.execute(SBC(C), &mut peripheral); + assert_eq!(cpu.registers.read_af(), 0x4340); + } + + #[test] + fn test_and_registers() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + cpu.registers.write_bc(0xAABB); + cpu.registers.write_af(0xAA00); + cpu.execute(AND(B), &mut peripheral); + assert_eq!(cpu.registers.read_af(), 0xAA20); + } + + #[test] + fn test_xor_registers() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + cpu.registers.write_bc(0x0022); + cpu.registers.write_af(0x2100); + cpu.execute(XOR(C), &mut peripheral); + assert_eq!(cpu.registers.read_af(), 0x0300); + } + + #[test] + fn test_or_registers() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + cpu.registers.write_bc(0x0022); + cpu.registers.write_af(0x2100); + cpu.execute(OR(C), &mut peripheral); + assert_eq!(cpu.registers.read_af(), 0x2300); + } + + #[test] + fn test_cp_registers() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + cpu.registers.write_bc(0x0022); + cpu.registers.write_af(0x2200); + cpu.execute(CP(C), &mut peripheral); + assert_eq!(cpu.registers.read_af(), 0x22C0); + + cpu.registers.write_bc(0x0033); + cpu.registers.write_af(0x2200); + cpu.execute(CP(C), &mut peripheral); + assert_eq!(cpu.registers.read_af(), 0x2270); + } + + #[test] + fn test_inc_registers() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + cpu.registers.write_bc(0x2200); + cpu.execute(INC(IncDecTarget::B), &mut peripheral); + assert_eq!(cpu.registers.read_bc(), 0x2300); + + cpu.registers.write_bc(0x22FF); + cpu.execute(INC(IncDecTarget::C), &mut peripheral); + assert_eq!(cpu.registers.read_bc(), 0x2200); + + let address = 0xc000; + let data = 0xAA; + peripheral.write(address, data); + cpu.registers.write_hl(address); + cpu.execute(INC(IncDecTarget::HL), &mut peripheral); + assert_eq!(peripheral.read(address), 0xAB); + } + + #[test] + fn test_inc16_registers() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + cpu.registers.write_bc(0x2200); + cpu.execute(INC16(U16Target::BC), &mut peripheral); + assert_eq!(cpu.registers.read_bc(), 0x2201); + + cpu.registers.write_de(0x22FF); + cpu.execute(INC16(U16Target::DE), &mut peripheral); + assert_eq!(cpu.registers.read_de(), 0x2300); + + cpu.registers.write_hl(0xFFFF); + cpu.execute(INC16(U16Target::HL), &mut peripheral); + assert_eq!(cpu.registers.read_hl(), 0x0000); + + cpu.sp = 0x3578; + cpu.execute(INC16(U16Target::SP), &mut peripheral); + assert_eq!(cpu.sp, 0x3579); + } + + #[test] + fn test_dec_registers() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + cpu.registers.write_bc(0x2200); + cpu.execute(DEC(IncDecTarget::B), &mut peripheral); + assert_eq!(cpu.registers.read_bc(), 0x2100); + + cpu.registers.write_bc(0x2200); + cpu.execute(DEC(IncDecTarget::C), &mut peripheral); + assert_eq!(cpu.registers.read_bc(), 0x22FF); + + let address = 0xc000; + let data = 0xAA; + peripheral.write(address, data); + cpu.registers.write_hl(address); + cpu.execute(DEC(IncDecTarget::HL), &mut peripheral); + assert_eq!(peripheral.read(address), 0xA9); + } + + #[test] + fn test_dec16_registers() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + cpu.registers.write_bc(0x2200); + cpu.execute(DEC16(U16Target::BC), &mut peripheral); + assert_eq!(cpu.registers.read_bc(), 0x21FF); + + cpu.registers.write_de(0x0000); + cpu.execute(DEC16(U16Target::DE), &mut peripheral); + assert_eq!(cpu.registers.read_de(), 0xFFFF); + + cpu.registers.write_hl(0x1279); + cpu.execute(DEC16(U16Target::HL), &mut peripheral); + assert_eq!(cpu.registers.read_hl(), 0x1278); + + cpu.sp = 0x0001; + cpu.execute(DEC16(U16Target::SP), &mut peripheral); + assert_eq!(cpu.sp, 0x0000); + } + + #[test] + fn test_load_registers() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + cpu.registers.write_de(0x0057); + cpu.execute(LOAD(IncDecTarget::B, E), &mut peripheral); + assert_eq!(cpu.registers.read_bc(), 0x5700); + + cpu.registers.write_hl(0x6400); + cpu.execute(LOAD(IncDecTarget::C, H), &mut peripheral); + assert_eq!(cpu.registers.read_bc(), 0x5764); + + let mut mem_address = 0x0001 + 0xC000; + let mut data = 0x23; + peripheral.write(mem_address, data); + cpu.pc = 0xC000; + cpu.execute(LOAD(IncDecTarget::A, D8), &mut peripheral); + assert_eq!(cpu.registers.read_af(), 0x2300); + + mem_address = 0x0010 + 0xC000; + cpu.registers.write_hl(mem_address); + cpu.execute(LOAD(IncDecTarget::HL, D8), &mut peripheral); + assert_eq!(peripheral.read(mem_address), 0x23); + + mem_address = 0x002A + 0xC000; + cpu.registers.write_hl(mem_address); + cpu.registers.write_de(0xD500); + cpu.execute(LOAD(IncDecTarget::HL, D), &mut peripheral); + assert_eq!(peripheral.read(mem_address), 0xD5); + + mem_address = 0x00C8 + 0xC000; + data = 0x5F; + peripheral.write(mem_address, data); + cpu.registers.write_hl(mem_address); + cpu.execute(LOAD(IncDecTarget::A, HL), &mut peripheral); + assert_eq!(cpu.registers.read_af(), 0x5F00); + + cpu.registers.write_de(0x0000); + cpu.execute(LOAD(IncDecTarget::E, HL), &mut peripheral); + assert_eq!(cpu.registers.read_de(), 0x005F); + } + + #[test] + fn test_load_indirect() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + let mem_address = 0xC000; + let mut data = 0x56; + peripheral.write(mem_address, data); + cpu.registers.write_bc(mem_address); + cpu.execute(LOAD_INDIRECT(Load16Target::BC), &mut peripheral); + assert_eq!(cpu.registers.read_af(), 0x5600); + + data = 0xC6; + peripheral.write(mem_address, data); + cpu.registers.write_hl(mem_address); + cpu.execute(LOAD_INDIRECT(Load16Target::HL_plus), &mut peripheral); + assert_eq!(cpu.registers.read_af(), 0xC600); + assert_eq!(cpu.registers.read_hl(), mem_address + 1); + } + + #[test] + fn test_load_immediate() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + let low_data = 0x4C; + let high_data = 0xB7; + let value = ((high_data as u16) << 8) + low_data as u16; + peripheral.write(0x0001 + 0xC000, low_data); + peripheral.write(0x0002 + 0xC000, high_data); + cpu.pc = 0xC000; + cpu.execute(LOAD_IMMEDIATE(U16Target::DE), &mut peripheral); + assert_eq!(cpu.registers.read_de(), value); + + cpu.execute(LOAD_IMMEDIATE(U16Target::SP), &mut peripheral); + assert_eq!(cpu.sp, value); + } + + #[test] + fn test_store_indirect() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + let mem_address = 0xC000; + let mut data = 0x5600; + cpu.registers.write_af(data); + cpu.registers.write_de(mem_address); + cpu.pc = 0xC000; + cpu.execute(STORE_INDIRECT(Load16Target::DE), &mut peripheral); + assert_eq!(peripheral.read(mem_address), 0x56); + + data = 0xC600; + cpu.registers.write_af(data); + cpu.registers.write_hl(mem_address); + cpu.execute(STORE_INDIRECT(Load16Target::HL_minus), &mut peripheral); + assert_eq!(peripheral.read(mem_address), 0xC6); + assert_eq!(cpu.registers.read_hl(), mem_address - 1); + } + + #[test] + fn test_jump_relative_nzero() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + // first, fill memory with program + let base_address: u16 = 0x0000; + let jump: u8 = 0x05; + let jump_nz: u8 = 0x20; + let program: [u8; 10] = [ + jump_nz, jump, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, + ]; + let mut index = 0; + for data in program { + peripheral.write(base_address + index, data); + index += 1; + } + + // run CPU to do the jump + cpu.run(&mut peripheral); + assert_eq!( + peripheral.read(cpu.pc), + peripheral.read(base_address + (jump as u16) + 2) + ); + + // reset CPU and run it with the flag, we don't do the jump + cpu.registers.f.zero = true; + cpu.pc = base_address; + cpu.run(&mut peripheral); + assert_eq!(peripheral.read(cpu.pc), peripheral.read(base_address + 2)); + } + + #[test] + fn test_jump_relative_carry() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + // first, fill memory with program + let base_address: u16 = 0x0000; + let jump: u8 = 0x05; + let jump_carry: u8 = 0x38; + let program: [u8; 10] = [ + jump_carry, jump, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, + ]; + let mut index = 0; + for data in program { + peripheral.write(base_address + index, data); + index += 1; + } + + // run CPU to do the jump + cpu.registers.f.carry = true; + cpu.run(&mut peripheral); + assert_eq!( + peripheral.read(cpu.pc), + peripheral.read(base_address + (jump as u16) + 2) + ); + + // reset CPU and run it with the flag, we don't do the jump + cpu.registers.f.carry = false; + cpu.pc = base_address; + cpu.run(&mut peripheral); + assert_eq!(peripheral.read(cpu.pc), peripheral.read(base_address + 2)); + } + + #[test] + fn test_jump_relative_immediate() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + // first, fill memory with program + let base_address: u16 = 0x0000; + let jump: u8 = 0x06; + let jump_inst: u8 = 0x18; + let program: [u8; 10] = [ + jump_inst, jump, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, + ]; + let mut index = 0; + for data in program { + peripheral.write(base_address + index, data); + index += 1; + } + + // run CPU to do the jump + cpu.run(&mut peripheral); + assert_eq!( + peripheral.read(cpu.pc), + peripheral.read(base_address + (jump as u16) + 2) + ); + } + + #[test] + fn test_jump_immediate_zero() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + // first, fill memory with program + let base_address: u16 = 0x0000; + let jump: u8 = 0x05; + let jump_carry: u8 = 0xCA; + let program: [u8; 10] = [ + jump_carry, jump, 0x00, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, + ]; + let mut index = 0; + for data in program { + peripheral.write(base_address + index, data); + index += 1; + } + + // run CPU to do the jump + cpu.registers.f.zero = true; + cpu.run(&mut peripheral); + assert_eq!( + peripheral.read(cpu.pc), + peripheral.read(base_address + (jump as u16)) + ); + + // reset CPU and run it with the flag, we don't do the jump + cpu.registers.f.zero = false; + cpu.pc = base_address; + cpu.run(&mut peripheral); + assert_eq!(peripheral.read(cpu.pc), peripheral.read(base_address + 3)); + } + + #[test] + fn test_jump_indirect() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + // first, fill memory with program + let jump_inst: u8 = 0xE9; + let program: [u8; 8] = [jump_inst, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]; + let mut index = 0; + for data in program { + peripheral.write(index, data); + index += 1; + } + + let jump: u16 = 0x05; + cpu.registers.write_hl(jump); + // run CPU to do the jump + cpu.run(&mut peripheral); + assert_eq!(peripheral.read(cpu.pc), peripheral.read(jump)); + } + + #[test] + fn test_load_from_hl_to_sp() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + let data: u16 = 0xA7D8; + cpu.registers.write_hl(data); + cpu.execute(LOAD_SP(SPTarget::TO_SP), &mut peripheral); + assert_eq!(cpu.sp, data); + } + + #[test] + fn test_load_from_sp_to_hl() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + cpu.sp = 0x0010; + let offset: u8 = 0x02; + + // first, fill memory with program + let jump_inst: u8 = 0xF8; + let program: [u8; 2] = [jump_inst, offset]; + let mut index = 0; + for data in program { + peripheral.write(index + 0xC000, data); + index += 1; + } + + cpu.pc = 0xC000; + cpu.run(&mut peripheral); + assert_eq!(cpu.registers.read_hl(), cpu.sp + offset as u16); + } + + #[test] + fn test_load_from_sp_to_mem() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + // first, fill memory with program + let base_address = 0xC000; + let jump_inst: u8 = 0x08; + let low_address = 0x05; + let high_address = 0xC1; + let address = (low_address as u16) + ((high_address as u16) << 8); + let program: [u8; 3] = [jump_inst, low_address, high_address]; + let mut index = 0; + for data in program { + peripheral.write(index + base_address, data); + index += 1; + } + + // set cpu and run it + cpu.pc = base_address; + cpu.sp = 0x57A8; + cpu.run(&mut peripheral); + assert_eq!((cpu.sp & 0x00FF) as u8, peripheral.read(address)); + assert_eq!( + ((cpu.sp & 0xFF00) >> 8) as u8, + peripheral.read(address + 1) + ); + } + + #[test] + fn test_load_ram_from_one_byte() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + // initialize RAM memory + let ram_data_address = 0xFFA5; + let data = 0xF8; + peripheral.write(ram_data_address, data); + + // initialize ROM memory + let base_program_address = 0xC000; + let jump_inst: u8 = 0xF0; + let program: [u8; 2] = [jump_inst, (ram_data_address & 0x00FF) as u8]; + let mut index = 0; + for data in program { + peripheral.write(index + base_program_address, data); + index += 1; + } + + // set cpu and run it + cpu.pc = base_program_address; + cpu.run(&mut peripheral); + assert_eq!(cpu.registers.a, peripheral.read(ram_data_address)); + } + + #[test] + fn test_load_ram_from_two_bytes() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + // initialize RAM memory + let ram_data_address = 0xFFA5; + let data = 0xF8; + peripheral.write(ram_data_address, data); + + // initialize ROM memory + let base_program_address = 0xC000; + let jump_inst: u8 = 0xFA; + let program: [u8; 3] = [ + jump_inst, + (ram_data_address & 0x00FF) as u8, + ((ram_data_address & 0xFF00) >> 8) as u8, + ]; + let mut index = 0; + for data in program { + peripheral.write(index + base_program_address, data); + index += 1; + } + + // set cpu and run it + cpu.pc = base_program_address; + cpu.run(&mut peripheral); + assert_eq!(cpu.registers.a, peripheral.read(ram_data_address)); + } + + #[test] + fn test_load_ram_from_register() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + // initialize RAM memory + let ram_data_address = 0xFFA5; + let data = 0xF8; + peripheral.write(ram_data_address, data); + + // initialize ROM memory + let base_program_address = 0xC000; + let jump_inst: u8 = 0xF2; + let program: [u8; 1] = [jump_inst]; + let mut index = 0; + for data in program { + peripheral.write(index + base_program_address, data); + index += 1; + } + + // set cpu and run it + cpu.pc = base_program_address; + cpu.registers.c = (ram_data_address & 0x00FF) as u8; + cpu.run(&mut peripheral); + assert_eq!(cpu.registers.a, peripheral.read(ram_data_address)); + } + + #[test] + fn test_store_ram_from_one_byte() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + // initialize RAM memory + let ram_data_address = 0xFFA5; + let data = 0xF8; + + // initialize ROM memory + let base_program_address = 0xC000; + let jump_inst: u8 = 0xE0; + let program: [u8; 2] = [jump_inst, (ram_data_address & 0x00FF) as u8]; + let mut index = 0; + for data in program { + peripheral.write(index + base_program_address, data); + index += 1; + } + + // set cpu and run it + cpu.pc = base_program_address; + cpu.registers.a = data; + cpu.run(&mut peripheral); + assert_eq!(cpu.registers.a, peripheral.read(ram_data_address)); + } + + #[test] + fn test_push_and_pop() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + // initialize RAM memory parameters + let ram_address = 0xFFA5; + let push_data = 0xD7F8; + + // test push instruction + cpu.sp = ram_address; + cpu.registers.write_de(push_data); + cpu.execute(PUSH(PopPushTarget::DE), &mut peripheral); + assert_eq!( + peripheral.read(ram_address.wrapping_sub(1)), + ((push_data & 0xFF00) >> 8) as u8 + ); + assert_eq!( + peripheral.read(ram_address.wrapping_sub(2)), + (push_data & 0x00FF) as u8 + ); + + // test pop instruction + cpu.execute(POP(PopPushTarget::HL), &mut peripheral); + assert_eq!(cpu.registers.read_hl(), push_data); + } + + #[test] + fn test_add_sp() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + // init parameters + let data_to_add = 0x88; + let sp_init = 0xF147; + + // initialize ROM memory + let base_program_address = 0xC000; + let inst: u8 = 0xE8; + let program: [u8; 2] = [inst, data_to_add as u8]; + let mut index = 0; + for data in program { + peripheral.write(index + base_program_address, data); + index += 1; + } + + // set cpu and run it + cpu.pc = base_program_address; + cpu.sp = sp_init; + cpu.run(&mut peripheral); + assert_eq!( + sp_init.wrapping_add(data_to_add as i8 as i16 as u16), + cpu.sp + ); + } + + #[test] + fn test_return() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + // initialize RAM memory parameters + let ram_address = 0xFFA5; + let push_data = 0xD7F8; + + // test push instruction + cpu.sp = ram_address; + cpu.registers.write_de(push_data); + cpu.execute(PUSH(PopPushTarget::DE), &mut peripheral); + let (next_pc, _) = cpu.execute(RETURN(JumpTarget::IMMEDIATE), &mut peripheral); + assert_eq!(next_pc, push_data); + } + + #[test] + fn test_reset() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + // test push instruction + cpu.sp = 0xFFAF; + let (next_pc, _) = cpu.execute(RESET(ResetTarget::FLASH_1), &mut peripheral); + assert_eq!(next_pc, 0x08); + } + + #[test] + fn test_interrupt() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + cpu.execute(EI, &mut peripheral); + assert_eq!(peripheral.nvic.interrupt_master_enable, true); + + cpu.execute(DI, &mut peripheral); + assert_eq!(peripheral.nvic.interrupt_master_enable, false); + + // initialize RAM memory parameters + let ram_address = 0xFFA5; + let push_data = 0xD7F8; + + // test push instruction + cpu.sp = ram_address; + cpu.registers.write_de(push_data); + cpu.execute(PUSH(PopPushTarget::DE), &mut peripheral); + let (next_pc, _) = cpu.execute(RETI, &mut peripheral); + assert_eq!(next_pc, push_data); + assert_eq!(peripheral.nvic.interrupt_master_enable, true); + } + + #[test] + fn test_call() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + // first, fill memory with program + let inst: u8 = 0xC4; + let program: [u8; 8] = [inst, 0x00, 0x05, 0x44, 0x55, 0x66, 0x77, 0x88]; + let mut index = 0; + for data in program { + peripheral.write(index + 0xc000, data); + index += 1; + } + + // run CPU to do the jump + let ram_address = 0xFFA5; + cpu.sp = ram_address; + cpu.pc = 0xc000; + cpu.run(&mut peripheral); + assert_eq!(cpu.pc, 0x0500); + } + + #[test] + fn test_nop_stop_halt() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + // first, fill memory with program + let nop_inst: u8 = 0x00; + let stop_inst: u8 = 0x10; + let halt_inst: u8 = 0x76; + let program: [u8; 8] = [ + nop_inst, stop_inst, nop_inst, halt_inst, nop_inst, nop_inst, nop_inst, nop_inst, + ]; + let mut index = 0; + for data in program { + peripheral.write(index + 0xC000, data); + index += 1; + } + + // run CPU to do the NOP + cpu.pc = 0xC000; + cpu.run(&mut peripheral); + assert_eq!(cpu.pc, 0x0001 + 0xC000); + // run CPU to do the STOP + cpu.run(&mut peripheral); + assert_eq!(cpu.pc, 0x0002 + 0xC000); + // then CPU is blocked + cpu.run(&mut peripheral); + assert_eq!(cpu.pc, 0x0002 + 0xC000); + + // Unlock CPU and run NOP inst + cpu.mode = CpuMode::RUN; + cpu.run(&mut peripheral); + assert_eq!(cpu.pc, 0x0003 + 0xC000); + // run HALT inst + cpu.run(&mut peripheral); + assert_eq!(cpu.pc, 0x0004 + 0xC000); + // cpu is blocked + cpu.run(&mut peripheral); + assert_eq!(cpu.pc, 0x0004 + 0xC000); + } + + #[test] + fn test_jump_to_interrupt() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + // init stack pointer + cpu.sp = 0xFFA5; + + // first, fill memory with program + let nop_inst: u8 = 0x00; + let stop_inst: u8 = 0x10; + let halt_inst: u8 = 0x76; + let program: [u8; 8] = [ + nop_inst, stop_inst, nop_inst, halt_inst, nop_inst, nop_inst, nop_inst, nop_inst, + ]; + let mut index = 0; + for data in program { + peripheral.write(index + 0xC000, data); + index += 1; + } + + // run CPU to do the NOP + cpu.pc = 0xC000; + cpu.run(&mut peripheral); + assert_eq!(cpu.pc, 0xC000 + 0x0001); + // run CPU to do the STOP + cpu.run(&mut peripheral); + assert_eq!(cpu.pc, 0xC000 + 0x0002); + // then CPU is blocked + cpu.run(&mut peripheral); + assert_eq!(cpu.pc, 0xC000 + 0x0002); + + // Unlock CPU and run NOP inst + cpu.mode = CpuMode::RUN; + cpu.run(&mut peripheral); + assert_eq!(cpu.pc, 0xC000 + 0x0003); + // run HALT inst + cpu.run(&mut peripheral); + assert_eq!(cpu.pc, 0xC000 + 0x0004); + // cpu is blocked + cpu.run(&mut peripheral); + assert_eq!(cpu.pc, 0xC000 + 0x0004); + + peripheral.nvic.master_enable(true); + peripheral.nvic.enable_interrupt(InterruptSources::STAT, true); + peripheral.nvic.set_interrupt(InterruptSources::STAT); + cpu.run(&mut peripheral); + assert_eq!(cpu.pc, LCDSTAT_VECTOR); + } + + #[test] + fn test_complement() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + cpu.registers.a = 0x55; + cpu.execute(Instruction::CPL, &mut peripheral); + assert_eq!(cpu.registers.a, 0xAA); + } + + #[test] + fn test_set_carry() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + cpu.execute(Instruction::SCF, &mut peripheral); + assert_eq!(cpu.registers.f.carry, true); + cpu.execute(Instruction::CCF, &mut peripheral); + assert_eq!(cpu.registers.f.carry, false); + cpu.execute(Instruction::CCF, &mut peripheral); + assert_eq!(cpu.registers.f.carry, true); + } + + #[test] + fn test_decimal_adjust() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + cpu.registers.a = 0x0B; + cpu.execute(Instruction::DAA, &mut peripheral); + assert_eq!(cpu.registers.a, 0x11); + } + + #[test] + fn test_rotate_left() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + cpu.registers.a = 0xB5; + cpu.execute(Instruction::RCA(Direction::LEFT), &mut peripheral); + assert_eq!(cpu.registers.f.carry, true); + assert_eq!(cpu.registers.a, 0x6B); + } + + #[test] + fn test_rotate_through_carry_right() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + cpu.registers.a = 0xB5; + cpu.registers.f.carry = true; + cpu.execute(Instruction::RA(Direction::RIGHT), &mut peripheral); + assert_eq!(cpu.registers.f.carry, true); + assert_eq!(cpu.registers.a, 0xDA); + } + + #[test] + fn test_decode_long_instruction() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + let program: [u8; 2] = [0xCB, 0x19]; + let mut index = 0; + for data in program { + peripheral.write(index + 0xC000, data); + index += 1; + } + + cpu.pc = 0xC000; + if let Some(instruction) = cpu.decode(0xCB, &mut peripheral) { + assert_eq!( + instruction, + Instruction::R(Direction::RIGHT, IncDecTarget::C) + ); + } else { + panic!("Unkown long instruction"); + } + } + + #[test] + fn test_long_rotate() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + cpu.registers.b = 0xB5; + cpu.execute(Instruction::RC(Direction::LEFT, IncDecTarget::B), &mut peripheral); + assert_eq!(cpu.registers.f.carry, true); + assert_eq!(cpu.registers.b, 0x6B); + } + + #[test] + fn test_long_rotate_hl() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + let address = 0xC000; + let data = 0xB5; + peripheral.write(address, data); + cpu.registers.write_hl(address); + cpu.execute(Instruction::RC(Direction::LEFT, IncDecTarget::HL), &mut peripheral); + assert_eq!(peripheral.read(address), 0x6B); + } + + #[test] + fn test_long_rotate_through_carry_right() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + cpu.registers.e = 0xB5; + cpu.registers.f.carry = true; + cpu.execute(Instruction::R(Direction::RIGHT, IncDecTarget::E), &mut peripheral); + assert_eq!(cpu.registers.f.carry, true); + assert_eq!(cpu.registers.e, 0xDA); + } + + #[test] + fn test_shift_left_and_reset() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + cpu.registers.d = 0xB5; + cpu.execute(Instruction::SLA(IncDecTarget::D), &mut peripheral); + assert_eq!(cpu.registers.f.carry, true); + assert_eq!(cpu.registers.d, 0x6A); + } + + #[test] + fn test_shift_right_and_reset() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + cpu.registers.h = 0xB5; + cpu.execute(Instruction::SRL(IncDecTarget::H), &mut peripheral); + assert_eq!(cpu.registers.f.carry, true); + assert_eq!(cpu.registers.h, 0x5A); + } + + #[test] + fn test_shift_right_and_reset_hl() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + let address = 0xC000; + let data = 0xB5; + peripheral.write(address, data); + cpu.registers.write_hl(address); + cpu.pc = 0xC000; + cpu.execute(Instruction::SRL(IncDecTarget::HL), &mut peripheral); + assert_eq!(peripheral.read(address), 0x5A); + } + + #[test] + fn test_shift_right() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + cpu.registers.c = 0xB5; + cpu.execute(Instruction::SRA(IncDecTarget::C), &mut peripheral); + assert_eq!(cpu.registers.f.carry, true); + assert_eq!(cpu.registers.c, 0xDA); + } + + #[test] + fn test_swap() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + cpu.registers.l = 0xB5; + cpu.execute(Instruction::SWAP(IncDecTarget::L), &mut peripheral); + assert_eq!(cpu.registers.l, 0x5B); + } + + #[test] + fn test_complement_bit() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + cpu.registers.h = 0xB5; + cpu.execute(Instruction::BIT(BitTarget::BIT_1, IncDecTarget::H), &mut peripheral); + assert_eq!(cpu.registers.f.zero, true); + cpu.execute(Instruction::BIT(BitTarget::BIT_4, IncDecTarget::H), &mut peripheral); + assert_eq!(cpu.registers.f.zero, false); + cpu.execute(Instruction::BIT(BitTarget::BIT_6, IncDecTarget::H), &mut peripheral); + assert_eq!(cpu.registers.f.zero, true); + + cpu.registers.d = 0x5B; + cpu.execute(Instruction::BIT(BitTarget::BIT_1, IncDecTarget::D), &mut peripheral); + assert_eq!(cpu.registers.f.zero, false); + cpu.execute(Instruction::BIT(BitTarget::BIT_4, IncDecTarget::D), &mut peripheral); + assert_eq!(cpu.registers.f.zero, false); + cpu.execute(Instruction::BIT(BitTarget::BIT_5, IncDecTarget::D), &mut peripheral); + assert_eq!(cpu.registers.f.zero, true); + } + + #[test] + fn test_set_reset_bit() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + cpu.registers.b = 0xB5; + cpu.execute(Instruction::RESET_BIT(BitTarget::BIT_2, IncDecTarget::B), &mut peripheral); + assert_eq!(cpu.registers.b, 0xB1); + cpu.execute(Instruction::SET_BIT(BitTarget::BIT_3, IncDecTarget::B), &mut peripheral); + assert_eq!(cpu.registers.b, 0xB9); + cpu.execute(Instruction::RESET_BIT(BitTarget::BIT_5, IncDecTarget::B), &mut peripheral); + assert_eq!(cpu.registers.b, 0x99); + } + + #[test] + fn test_set_reset_bit_hl() { + let mut cpu = Cpu::new(); + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + + let address = 0xC000; + let data = 0xB5; + peripheral.write(address, data); + cpu.registers.write_hl(address); + + cpu.pc = 0xC000; + cpu.execute(Instruction::RESET_BIT(BitTarget::BIT_2, IncDecTarget::HL), &mut peripheral); + assert_eq!(peripheral.read(address), 0xB1); + + cpu.execute(Instruction::SET_BIT(BitTarget::BIT_3, IncDecTarget::HL), &mut peripheral); + assert_eq!(peripheral.read(address), 0xB9); + } +} \ No newline at end of file diff --git a/src/soc/cpu/register.rs b/src/soc/cpu/register.rs new file mode 100644 index 0000000..dbaf626 --- /dev/null +++ b/src/soc/cpu/register.rs @@ -0,0 +1,166 @@ +const ZERO_BIT: u8 = 7; +const SUBSTRACTION_BIT: u8 = 6; +const HALF_CARRY_BIT: u8 = 5; +const CARRY_BIT: u8 = 4; + +#[derive(Copy, Clone)] +pub struct FlagRegister { + pub zero: bool, + pub substraction: bool, + pub half_carry: bool, + pub carry: bool, +} + +impl FlagRegister { + fn new() -> FlagRegister { + FlagRegister { + zero: false, + substraction: false, + half_carry: false, + carry: false, + } + } +} + +impl std::convert::From for u8 { + fn from(flag: FlagRegister) -> u8 { + (if flag.zero { 1 } else { 0 }) << ZERO_BIT + | (if flag.substraction { 1 } else { 0 }) << SUBSTRACTION_BIT + | (if flag.half_carry { 1 } else { 0 }) << HALF_CARRY_BIT + | (if flag.carry { 1 } else { 0 }) << CARRY_BIT + } +} + +impl std::convert::From for FlagRegister { + fn from(byte: u8) -> FlagRegister { + let zero = ((byte & 0x80) >> ZERO_BIT) != 0; + let substraction = ((byte & 0x40) >> SUBSTRACTION_BIT) != 0; + let half_carry = ((byte & 0x20) >> HALF_CARRY_BIT) != 0; + let carry = ((byte & 0x10) >> CARRY_BIT) != 0; + + FlagRegister { + zero: zero, + substraction: substraction, + half_carry: half_carry, + carry: carry, + } + } +} + +#[derive(Copy, Clone)] +pub struct Registers { + pub a: u8, + pub b: u8, + pub c: u8, + pub d: u8, + pub e: u8, + pub f: FlagRegister, + pub h: u8, + pub l: u8, +} + +impl Registers { + pub fn new() -> Registers { + Registers { + a: 0, + b: 0, + c: 0, + d: 0, + e: 0, + f: FlagRegister::new(), + h: 0, + l: 0, + } + } + + pub fn read_bc(&self) -> u16 { + ((self.b as u16) << 8) | (self.c as u16) + } + + pub fn write_bc(&mut self, value: u16) { + self.b = (value >> 8) as u8; + self.c = value as u8; + } + + pub fn read_af(&self) -> u16 { + ((self.a as u16) << 8) | (u8::from(self.f) as u16) + } + + pub fn write_af(&mut self, value: u16) { + self.a = (value >> 8) as u8; + self.f = FlagRegister::from(value as u8); + } + + pub fn read_de(&self) -> u16 { + ((self.d as u16) << 8) | (self.e as u16) + } + + pub fn write_de(&mut self, value: u16) { + self.d = (value >> 8) as u8; + self.e = value as u8; + } + + pub fn read_hl(&self) -> u16 { + ((self.h as u16) << 8) | (self.l as u16) + } + + pub fn write_hl(&mut self, value: u16) { + self.h = (value >> 8) as u8; + self.l = value as u8; + } +} + +#[cfg(test)] +mod registers_tests { + use super::*; + + #[test] + fn test_bc() { + let mut regs = Registers::new(); + regs.write_bc(0b0011_1000_1010_0110); + assert_eq!(regs.read_bc(), 0b0011_1000_1010_0110); + } + + #[test] + fn test_af() { + let mut regs = Registers::new(); + regs.write_af(0b0011_1000_1010_0000); + assert_eq!(regs.read_af(), 0b0011_1000_1010_0000); + } + + #[test] + fn test_de() { + let mut regs = Registers::new(); + regs.write_de(0b0011_1000_1010_0110); + assert_eq!(regs.read_de(), 0b0011_1000_1010_0110); + } + + #[test] + fn test_hl() { + let mut regs = Registers::new(); + regs.write_de(0b0011_1011_1010_0110); + assert_eq!(regs.read_de(), 0b0011_1011_1010_0110); + } + + #[test] + fn test_flag() { + let flag_byte = u8::from(FlagRegister { + zero: true, + substraction: false, + half_carry: true, + carry: true, + }); + + assert_eq!(flag_byte, 0b1011_0000); + } + + #[test] + fn test_flag_ex() { + let flag_reg = FlagRegister::from(0b0101_0000); + + assert_eq!(flag_reg.zero, false); + assert_eq!(flag_reg.substraction, true); + assert_eq!(flag_reg.half_carry, false); + assert_eq!(flag_reg.carry, true); + } +} diff --git a/src/soc/mod.rs b/src/soc/mod.rs new file mode 100644 index 0000000..d8726e9 --- /dev/null +++ b/src/soc/mod.rs @@ -0,0 +1,42 @@ +pub mod peripheral; +mod cpu; + +use cpu::Cpu; +use peripheral::Peripheral; +use crate::cartridge::Cartridge; +pub use peripheral::keypad::GameBoyKey; + +const CLOCK_TICK_PER_MACHINE_CYCLE: u8 = 4; + +pub struct Soc { + pub cpu: Cpu, + pub peripheral: Peripheral, +} + +impl Soc { + pub fn new(boot_rom: &[u8], cartridge: Cartridge) -> Soc { + let mut peripheral = Peripheral::new(cartridge); + peripheral.load_bootrom(boot_rom); + + Soc { + cpu: Cpu::new(), + peripheral: peripheral, + } + } + + pub fn run(&mut self) -> u8 { + let cycles = self.cpu.run(&mut self.peripheral) * CLOCK_TICK_PER_MACHINE_CYCLE; + + self.peripheral.run(cycles); + + cycles + } + + pub fn get_frame_buffer(&self, pixel_index: usize) -> u8 { + self.peripheral.gpu.frame_buffer[pixel_index] + } + + pub fn set_key(&mut self, key: GameBoyKey, value: bool) { + self.peripheral.keypad.set(key, value); + } +} \ No newline at end of file diff --git a/src/soc/peripheral/bootrom.rs b/src/soc/peripheral/bootrom.rs new file mode 100644 index 0000000..a7ebd22 --- /dev/null +++ b/src/soc/peripheral/bootrom.rs @@ -0,0 +1,35 @@ +use crate::soc::peripheral::BOOT_ROM_SIZE; + +pub struct BootRom { + rom: [u8; BOOT_ROM_SIZE as usize], + enabled: bool, +} + +impl BootRom { + pub fn new() -> BootRom { + BootRom { + rom: [0xFF; BOOT_ROM_SIZE as usize], + enabled: false, + } + } + + pub fn read(&self, address: u16) -> u8 { + self.rom[address as usize] + } + + pub fn load(&mut self, boot_rom: &[u8]){ + let mut rom_data = [0x00; BOOT_ROM_SIZE as usize]; + rom_data.copy_from_slice(boot_rom); + self.rom = rom_data; + // enable memory once load is complete + self.enabled = true; + } + + pub fn set_state(&mut self, state: bool) { + self.enabled = state; + } + + pub fn get_state(&self) -> bool { + self.enabled + } +} \ No newline at end of file diff --git a/src/soc/peripheral/gpu.rs b/src/soc/peripheral/gpu.rs new file mode 100644 index 0000000..50c1649 --- /dev/null +++ b/src/soc/peripheral/gpu.rs @@ -0,0 +1,1082 @@ +use crate::soc::peripheral::{VRAM_SIZE, OAM_SIZE}; +use crate::soc::peripheral::nvic::{Nvic, InterruptSources}; + +const HORIZONTAL_BLANK_CYCLES: u16 = 204; +const VERTICAL_BLANK_CYCLES: u16 = 4560; +const OAM_SCAN_CYCLES: u16 = 80; +const DRAW_PIXEL_CYCLES: u16 = 172; +const ONE_LINE_CYCLES: u16 = HORIZONTAL_BLANK_CYCLES + OAM_SCAN_CYCLES + DRAW_PIXEL_CYCLES; + +pub const SCREEN_WIDTH: usize = 160; +pub const SCREEN_HEIGHT: usize = 144; + +const TILE_ROW_SIZE_IN_PIXEL: u8 = 8; +const TILE_SIZE_IN_BYTES: u16 = 16; +const TILE_MAP_SIZE: u8 = 32; + +const BYTES_PER_TILE_ROM: u8 = 2; + +const SPRITE_X_OFFSET: i16 = 8; +const SPRITE_Y_OFFSET: i16 = 16; + +const NB_SPRITES_IN_OAM: u16 = 40; +const SPRITE_ATTRIBUTES_SIZE_IN_BYTES: u16 = 4; +const SPRITE_Y_POS_OFFSET: u16 = 0; +const SPRITE_X_POS_OFFSET: u16 = 1; +const SPRITE_TILE_INDEX_OFFSET: u16 = 2; +const SPRITE_ATTRIBUTES_OFFSET: u16 = 3; +const NB_SRITES_TO_DISPLAY_MAX: u16 = 10; +const PIXEL_TRANSPARENT: u8 = 0x00; + +const WINDOW_X_OFFSET: u8 = 7; + +#[allow(non_camel_case_types)] +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum PixelColor { + WHITE = 255, + LIGHT_GRAY = 192, + DARK_GRAY = 96, + BLACK = 0, +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct Palette{ + pub color_0: PixelColor, + pub color_1: PixelColor, + pub color_2: PixelColor, + pub color_3: PixelColor, +} + +impl Palette { + fn new() -> Palette { + Palette { + color_0: PixelColor::WHITE, + color_1: PixelColor::LIGHT_GRAY, + color_2: PixelColor::DARK_GRAY, + color_3: PixelColor::BLACK, + } + } +} + +macro_rules! set_palette { + ($self:ident.$palette:ident.$palette_index:ident, $data: ident, $color_index: expr) => {{ + let value = ($data >> ($color_index * 2)) & 0x03; + + match value { + 0 => $self.$palette.$palette_index = PixelColor::WHITE, + 1 => $self.$palette.$palette_index = PixelColor::LIGHT_GRAY, + 2 => $self.$palette.$palette_index = PixelColor::DARK_GRAY, + _ => $self.$palette.$palette_index = PixelColor::BLACK, + } + }}; +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct ObjectData { + x: i16, + y: i16, + tile: u8, + palette: Palette, + xflip: bool, + yflip: bool, + priority: bool, +} + +#[derive(Eq, PartialEq, Copy, Clone)] +pub enum TileMapArea { + X9800 = 0x1800, + X9C00 = 0x1C00, +} + +#[derive(Eq, PartialEq)] +pub enum ObjectSize { + OS8X8, + OS8X16, +} + +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum GpuMode { + HorizontalBlank, + VerticalBlank, + OAMScan, + DrawPixel, +} + +pub struct Gpu { + // ***** GPU PARAMETERS ****** + // VRAM is a memory area used to store graphics such as backgrounds and sprites + pub vram: [u8; VRAM_SIZE as usize], + // OAM is a memory area used to store sprites attributes + // Sprites data are stored in VRAM memory $8000-8FFF + oam: [u8; OAM_SIZE as usize], + + // ****** LCD DISPLAY PARAMETERS ******* + // 0xFF40: LCD control register + pub lcd_display_enabled: bool, + pub window_tile_map_area: TileMapArea, + pub window_display_enabled: bool, + pub background_tile_data_area: bool, + pub background_tile_map_area: TileMapArea, + pub object_size: ObjectSize, + pub object_display_enabled: bool, + pub background_display_enabled: bool, + + // 0xFF41: LCD status register + pub line_compare_it_enable: bool, + pub oam_interrupt_enabled: bool, + pub vblank_interrupt_enabled: bool, + pub hblank_interrupt_enabled: bool, + pub line_compare_state: bool, + pub mode: GpuMode, + + // 0xFF42 - 0xFF43: SCY viewport Y offset + pub viewport_y_offset: u8, + pub viewport_x_offset: u8, + + // 0xFF44: LY + pub current_line: u8, + + // 0xFF45: LY compare + pub compare_line: u8, + + // 0xFF47: Background palette + pub background_palette: Palette, + + // 0xFF48: Objects palette 0 + pub object_palette_0: Palette, + + // 0xFF49: Objects palette 1 + pub object_palette_1: Palette, + + // 0xFF4A - 0xFF4B: window position + window_x_offset: u8, + window_y_offset: u8, + + // ****** GPU INTERNAL PARAMETERS ******* + cycles: u16, + new_mode_flag: bool, + vblank_line: u16, + window_flag: bool, + window_line_counter: u8, + + // ****** OUTPUT FRAME BUFFER ******* + pub frame_buffer: [u8; SCREEN_WIDTH * SCREEN_HEIGHT], +} + +impl Gpu { + pub fn new() -> Gpu { + Gpu { + vram: [0xFF; VRAM_SIZE as usize], + oam: [0xFF; OAM_SIZE as usize], + + lcd_display_enabled: false, + window_tile_map_area: TileMapArea::X9800, + window_display_enabled: false, + background_tile_data_area: false, + background_tile_map_area: TileMapArea::X9800, + object_size: ObjectSize::OS8X8, + object_display_enabled: false, + background_display_enabled: false, + + line_compare_it_enable: false, + oam_interrupt_enabled: false, + vblank_interrupt_enabled: false, + hblank_interrupt_enabled: false, + line_compare_state: false, + mode: GpuMode::OAMScan, + + viewport_y_offset: 0, + viewport_x_offset: 0, + + current_line: 0, + compare_line: 0, + + background_palette: Palette::new(), + object_palette_0: Palette::new(), + object_palette_1: Palette::new(), + + window_x_offset: 0, + window_y_offset: 0, + + cycles: 0, + new_mode_flag: true, + vblank_line: 0, + window_flag: false, + window_line_counter: 0, + + frame_buffer: [0; SCREEN_WIDTH * SCREEN_HEIGHT], + } + } + + pub fn read_vram(&self, address: u16) -> u8 { + self.vram[address as usize] + } + + pub fn write_vram(&mut self, address: u16, data: u8) { + self.vram[address as usize] = data; + } + + pub fn read_oam(&self, address: usize) -> u8 { + self.oam[address] + } + + pub fn write_oam(&mut self, address: usize, data: u8) { + self.oam[address] = data; + } + + pub fn run(&mut self, cycles: u8, nvic: &mut Nvic) { + if self.lcd_display_enabled { + // update GPU cycles counter + self.cycles += cycles as u16; + + match self.mode { + GpuMode::HorizontalBlank => { + // handle interrupts generation + if self.new_mode_flag && self.hblank_interrupt_enabled{ + self.new_mode_flag = false; + nvic.set_interrupt(InterruptSources::STAT); + } + + // we reached the end of the mode + if self.cycles >= HORIZONTAL_BLANK_CYCLES { + self.cycles = self.cycles % HORIZONTAL_BLANK_CYCLES; + // we detected the end of a line + if self.current_line < (SCREEN_HEIGHT - 1) as u8 { + self.current_line += 1; + if self.window_flag { self.window_line_counter += 1 } + // run the compare line circuitry + self.compare_line(nvic); + // reset new mode flag + self.new_mode_flag = true; + // go to next gpu mode + self.mode = GpuMode::OAMScan; + } else { + // reset new mode flag + self.new_mode_flag = true; + // go to next gpu mode + self.mode = GpuMode::VerticalBlank; + } + } + } + GpuMode::VerticalBlank => { + // handle interrupts generation + if self.new_mode_flag { + self.new_mode_flag = false; + nvic.set_interrupt(InterruptSources::VBLANK); + + if self.vblank_interrupt_enabled { + nvic.set_interrupt(InterruptSources::STAT); + } + } + + // if we reached a new line in vblank mode, run compare line circuitry + if (self.cycles / ((self.vblank_line + 1) * ONE_LINE_CYCLES)) != 0 { + self.vblank_line += 1; + self.current_line += 1; + if self.window_flag { self.window_line_counter += 1 } + + self.compare_line(nvic); + } + + // we reached the end of the mode + if self.cycles >= VERTICAL_BLANK_CYCLES { + self.cycles = self.cycles % VERTICAL_BLANK_CYCLES; + // reset the line counter to draw a new frame + self.current_line = 0; + self.window_line_counter = 0; + // reset the vblank line counter + self.vblank_line = 0; + // reset new mode flag + self.new_mode_flag = true; + // go to next gpu mode + self.mode = GpuMode::OAMScan; + } + } + GpuMode::OAMScan => { + // handle interrupts generation + if self.new_mode_flag && self.oam_interrupt_enabled{ + self.new_mode_flag = false; + nvic.set_interrupt(InterruptSources::STAT); + } + + // we reached the end of the mode + if self.cycles >= OAM_SCAN_CYCLES { + self.cycles = self.cycles % OAM_SCAN_CYCLES; + // reset new mode flag + self.new_mode_flag = true; + // go to next gpu mode + self.mode = GpuMode::DrawPixel; + } + } + GpuMode::DrawPixel => { + // we reached the end of the mode + if self.cycles >= DRAW_PIXEL_CYCLES { + self.cycles = self.cycles % DRAW_PIXEL_CYCLES; + // draw the line at the end of the draw pixel mode + self.draw_line(); + // go to next gpu mode + self.mode = GpuMode::HorizontalBlank; + } + } + } + } + } + + + fn draw_line(&mut self) { + let mut bg_line = [0x00; SCREEN_WIDTH as usize]; + let pixel_y_index = self.current_line; + + if self.background_display_enabled { + for pixel_x_index in 0..SCREEN_WIDTH { + // check if we display the background or the window + let (tile_map_area, y_offset, x_offset) = + if self.window_display_enabled + && self.window_y_offset <= self.current_line + && self.window_x_offset.wrapping_sub(WINDOW_X_OFFSET) <= pixel_x_index as u8 { + self.window_flag = true; + // window display mode + (self.window_tile_map_area, + self.window_line_counter, + (pixel_x_index as u8).wrapping_sub(self.window_x_offset.wrapping_sub(WINDOW_X_OFFSET))) + } else { + self.window_flag = false; + // background display mode + (self.background_tile_map_area, + pixel_y_index.wrapping_add(self.viewport_y_offset), + (pixel_x_index as u8).wrapping_add(self.viewport_x_offset)) + }; + + // compute the tile index in tile map + let tile_map_y_index = (y_offset / TILE_ROW_SIZE_IN_PIXEL) as u16; + let tile_map_x_index = (x_offset / TILE_ROW_SIZE_IN_PIXEL) as u16; + let tile_map_index = tile_map_y_index * (TILE_MAP_SIZE as u16) + tile_map_x_index; + + // get the tile memory address from the tile map + let tile_mem_index = self.read_vram((tile_map_area as u16) + tile_map_index); + + // convert a 8 bits tile index into a 16 bits tile memory addr + let tile_mem_addr = (tile_mem_index as u16) * TILE_SIZE_IN_BYTES; + + // get the row offset in the tile + let tile_row_offset = y_offset % TILE_ROW_SIZE_IN_PIXEL * BYTES_PER_TILE_ROM; + + // get tile row data from vram + let (data_1, data_0) = self.get_bg_tile_data(tile_mem_addr, tile_row_offset as u16); + + // get pixel bits from data + let bit_0 = data_0 >> (7 - (x_offset % TILE_ROW_SIZE_IN_PIXEL)) & 0x01; + let bit_1 = data_1 >> (7 - (x_offset % TILE_ROW_SIZE_IN_PIXEL)) & 0x01; + + // find pixel color + let pixel_value = (bit_1 << 1) | bit_0; + let pixel_color = self.get_bg_pixel_color_from_palette(pixel_value); + + // fill frame buffer + self.frame_buffer[(pixel_y_index as usize) * SCREEN_WIDTH + pixel_x_index] = pixel_color; + // save the line for sprite rendering + bg_line[pixel_x_index] = pixel_value; + } + } + + if self.object_display_enabled { + // sprites array wich will contain sprites address to display + let mut sprites: Vec = Vec::new(); + // find 10 sprites to display on the current line + let mut nb_sprites_to_display = 0; + for sprites_idx in 0..NB_SPRITES_IN_OAM { + if nb_sprites_to_display < NB_SRITES_TO_DISPLAY_MAX { + let sprite_addr = sprites_idx * SPRITE_ATTRIBUTES_SIZE_IN_BYTES; + // get the srite first line + let sprite_y_pos_start = self.read_oam((sprite_addr + SPRITE_Y_POS_OFFSET) as usize) as u16 as i16 - SPRITE_Y_OFFSET; + // get the sprite last line + let sprite_y_pos_end = match self.object_size { + ObjectSize::OS8X8 => sprite_y_pos_start + TILE_ROW_SIZE_IN_PIXEL as i16 - 1, + ObjectSize::OS8X16 => sprite_y_pos_start + TILE_ROW_SIZE_IN_PIXEL as i16 * 2 - 1, + }; + // check if the current line hits the sprite + if (self.current_line as i16 >= sprite_y_pos_start) && (self.current_line as i16 <= sprite_y_pos_end) { + // add the sprite to the list + sprites.push(sprite_addr); + // increase sprites counter + nb_sprites_to_display += 1; + } + } else { + // we reached the maximum sprites number to display + // go out of the loop + break + } + } + // sort objects to draw : + // from lower priority in first positions + // to higher priority in last positions + let mut sprites_sorted: Vec = Vec::new(); + for sprite_idx in 0..nb_sprites_to_display { + sprites_sorted.push(sprites[(nb_sprites_to_display - 1 - sprite_idx) as usize]); + } + // draw sorted sprites + // higher priority sprites are drawn in last positions + // so it can override lower priority sprites values + for sprite in sprites_sorted { + let pixel_y_index: u8 = self.current_line; + // get sprite's attributes + let sprite_y_pos = self.read_oam((sprite + SPRITE_Y_POS_OFFSET) as usize) as u16 as i16 - SPRITE_Y_OFFSET; + let sprite_x_pos = self.read_oam((sprite + SPRITE_X_POS_OFFSET) as usize) as i16; + let sprite_tile_addr = match self.object_size { + ObjectSize::OS8X8 => { + self.read_oam((sprite + SPRITE_TILE_INDEX_OFFSET) as usize) as u16 * TILE_SIZE_IN_BYTES + }, + ObjectSize::OS8X16 => { + // ignore bit 0 for tile index in 8x16 object size mode + (self.read_oam((sprite + SPRITE_TILE_INDEX_OFFSET) as usize) as u16 * TILE_SIZE_IN_BYTES) & 0xFFE0 + }, + }; + let sprite_attr = self.read_oam((sprite + SPRITE_ATTRIBUTES_OFFSET) as usize); + let sprite_bg_over = (sprite_attr & 0x80) != 0; + let sprite_y_flip = (sprite_attr & 0x40) != 0; + let sprite_x_flip = (sprite_attr & 0x20) != 0; + let sprite_palette_idx = (sprite_attr & 0x10) != 0; + let sprite_size_offset = match self.object_size { + ObjectSize::OS8X8 => 1, + ObjectSize::OS8X16 => 2, + }; + // get tile addr + let sprite_row_offset = (pixel_y_index as i16 - sprite_y_pos) as u16; + let tile_addr = if sprite_y_flip == false { + sprite_tile_addr + sprite_row_offset * BYTES_PER_TILE_ROM as u16 + } else { + let row = ((TILE_ROW_SIZE_IN_PIXEL * sprite_size_offset) as u16).wrapping_sub(1).wrapping_sub(sprite_row_offset); + sprite_tile_addr + row * BYTES_PER_TILE_ROM as u16 + }; + // get one row of sprite data + let data_0 = self.read_vram(tile_addr); + let data_1 = self.read_vram(tile_addr + 1); + // draw each pixel of the sprite's row + for pixel_x_offset in 0..TILE_ROW_SIZE_IN_PIXEL { + // get pixel bits from data + let (bit_1, bit_0) = if sprite_x_flip == false { + let bit_0 = (data_0 >> (7 - pixel_x_offset)) & 0x01; + let bit_1 = (data_1 >> (7 - pixel_x_offset)) & 0x01; + + (bit_1, bit_0) + } else { + let bit_0 = (data_0 >> pixel_x_offset) & 0x01; + let bit_1 = (data_1 >> pixel_x_offset) & 0x01; + + (bit_1, bit_0) + }; + // deduce pixel value + let pixel_value = (bit_1 << 1) | bit_0; + // compute the x coordinate of the pixel in the frame buffer + let pixel_x_index = sprite_x_pos - SPRITE_X_OFFSET + pixel_x_offset as i16; + // don't draw the pixel if it's not in the viewport + if pixel_x_index >= 0 + && pixel_x_index < SCREEN_WIDTH as i16 + && pixel_value != PIXEL_TRANSPARENT { + // check if bg overlap sprites + if !sprite_bg_over || bg_line[pixel_x_index as usize] == PIXEL_TRANSPARENT { + // find sprite pixel color + let pixel_color = self.get_object_pixel_color_from_palette(pixel_value, sprite_palette_idx); + // fill frame buffer + self.frame_buffer[(pixel_y_index as usize) * SCREEN_WIDTH + (pixel_x_index as usize)] = pixel_color; + } else { + // find bg pixel color + let pixel_color = self.get_bg_pixel_color_from_palette(bg_line[pixel_x_index as usize]); + // fill frame buffer + self.frame_buffer[(pixel_y_index as usize) * SCREEN_WIDTH + (pixel_x_index as usize)] = pixel_color; + } + } + } + } + } + } + + fn get_bg_tile_data(&self, tile_mem_addr: u16, tile_row_offset: u16) -> (u8, u8) { + + if self.background_tile_data_area { + // $8000 method addressing + let data_0 = self.read_vram(tile_mem_addr + tile_row_offset); + let data_1 = self.read_vram(tile_mem_addr + tile_row_offset + 1); + + return (data_1, data_0); + } else { + // $8800 method adressing + if (tile_mem_addr + tile_row_offset) < 0x0800 { + let data_0 = self.read_vram(0x1000 + tile_mem_addr + tile_row_offset); + let data_1 = self.read_vram(0x1000 + tile_mem_addr + tile_row_offset + 1); + + return (data_1, data_0); + } else { + let data_0 = self.read_vram(tile_mem_addr + tile_row_offset); + let data_1 = self.read_vram(tile_mem_addr + tile_row_offset + 1); + + return (data_1, data_0); + } + } + } + + pub fn get_bg_pixel_color_from_palette(&self, pixel_value: u8) -> u8 { + match pixel_value { + 0 => self.background_palette.color_0 as u8, + 1 => self.background_palette.color_1 as u8, + 2 => self.background_palette.color_2 as u8, + _ => self.background_palette.color_3 as u8, + } + } + + fn get_object_pixel_color_from_palette(&self, pixel_value: u8, sprite_palette_idx: bool) -> u8 { + if sprite_palette_idx { + match pixel_value { + 0 => self.object_palette_1.color_0 as u8, + 1 => self.object_palette_1.color_1 as u8, + 2 => self.object_palette_1.color_2 as u8, + _ => self.object_palette_1.color_3 as u8, + } + } else { + match pixel_value { + 0 => self.object_palette_0.color_0 as u8, + 1 => self.object_palette_0.color_1 as u8, + 2 => self.object_palette_0.color_2 as u8, + _ => self.object_palette_0.color_3 as u8, + } + } + } + + fn compare_line(&mut self, nvic: &mut Nvic) { + if self.current_line == self.compare_line { + self.line_compare_state = true; + + // managed interrupt + if self.line_compare_it_enable { + nvic.set_interrupt(InterruptSources::STAT); + } + } else { + self.line_compare_state = false; + } + } + + pub fn control_from_byte(&mut self, data: u8) { + // bit 7 + self.lcd_display_enabled = ((data >> 7) & 0x01) != 0; + // bit 6 + if((data >> 6) & 0x01) != 0 { + self.window_tile_map_area = TileMapArea::X9C00; + } else { + self.window_tile_map_area = TileMapArea::X9800; + } + // bit 5 + self.window_display_enabled = ((data >> 5) & 0x01) != 0; + // bit 4 + self.background_tile_data_area = ((data >> 4) & 0x01) != 0; + // bit 3 + if((data >> 3) & 0x01) != 0 { + self.background_tile_map_area = TileMapArea::X9C00; + } else { + self.background_tile_map_area = TileMapArea::X9800; + } + // bit 2 + if((data >> 2) & 0x01) != 0 { + self.object_size = ObjectSize::OS8X16; + } else { + self.object_size = ObjectSize::OS8X8; + } + // bit 1 + self.object_display_enabled = ((data >> 1) & 0x01) != 0; + // bit 0 + self.background_display_enabled = (data & 0x01) != 0; + } + + pub fn control_to_byte(&self) -> u8 { + let window_tile_map_area_bit: u8 = if self.window_tile_map_area == TileMapArea::X9C00 { + 1 + } else { + 0 + }; + + let background_tile_map_area_bit: u8 = if self.background_tile_map_area == TileMapArea::X9C00 { + 1 + } else { + 0 + }; + + let object_size_bit: u8 = if self.object_size == ObjectSize::OS8X16 { + 1 + } else { + 0 + }; + + ((self.lcd_display_enabled as u8) << 7) + | (window_tile_map_area_bit << 6) + | ((self.window_display_enabled as u8) << 5) + | ((self.background_tile_data_area as u8) << 4) + | (background_tile_map_area_bit << 3) + | (object_size_bit << 2) + | ((self.object_display_enabled as u8) << 1) + | (self.background_display_enabled as u8) + } + + pub fn status_from_byte(&mut self, data: u8) { + self.line_compare_it_enable = ((data >> 6) & 0x01) != 0; + self.oam_interrupt_enabled = ((data >> 5) & 0x01) != 0; + self.vblank_interrupt_enabled = ((data >> 4) & 0x01) != 0; + self.hblank_interrupt_enabled = ((data >> 3) & 0x01) != 0; + } + + pub fn status_to_byte(&self) -> u8 { + let gpu_mode_bits = match self.mode { + GpuMode::HorizontalBlank => 0, + GpuMode::VerticalBlank => 1, + GpuMode::OAMScan => 2, + GpuMode::DrawPixel => 3, + }; + + 0x80 + | ((self.line_compare_it_enable as u8) << 6) + | ((self.oam_interrupt_enabled as u8) << 5) + | ((self.vblank_interrupt_enabled as u8) << 4) + | ((self.hblank_interrupt_enabled as u8) << 3) + | ((self.line_compare_state as u8) << 2) + | ((gpu_mode_bits as u8) & 0x11) + } + + pub fn get_scy(&self) -> u8 { + self.viewport_y_offset + } + + pub fn get_scx(&self) -> u8 { + self.viewport_x_offset + } + + pub fn get_current_line(&self) -> u8 { + self.current_line + } + + pub fn get_compare_line(&self) -> u8 { + self.compare_line + } + + pub fn set_scy(&mut self, data: u8) { + self.viewport_y_offset = data; + } + + pub fn set_scx(&mut self, data: u8) { + self.viewport_x_offset = data; + } + + pub fn set_compare_line(&mut self, data: u8) { + self.compare_line = data; + } + + pub fn set_window_y(&mut self, data: u8) { + self.window_y_offset = data; + } + + pub fn set_window_x(&mut self, data: u8) { + self.window_x_offset = data; + } + + pub fn get_window_y(&self) -> u8 { + self.window_y_offset + } + + pub fn get_window_x(&self) -> u8 { + self.window_x_offset + } + + pub fn set_background_palette(&mut self, data: u8) { + set_palette!(self.background_palette.color_0, data, 0); + set_palette!(self.background_palette.color_1, data, 1); + set_palette!(self.background_palette.color_2, data, 2); + set_palette!(self.background_palette.color_3, data, 3); + } + + pub fn set_object_palette_0(&mut self, data: u8) { + set_palette!(self.object_palette_0.color_0, data, 0); + set_palette!(self.object_palette_0.color_1, data, 1); + set_palette!(self.object_palette_0.color_2, data, 2); + set_palette!(self.object_palette_0.color_3, data, 3); + } + + pub fn set_object_palette_1(&mut self, data: u8) { + set_palette!(self.object_palette_1.color_0, data, 0); + set_palette!(self.object_palette_1.color_1, data, 1); + set_palette!(self.object_palette_1.color_2, data, 2); + set_palette!(self.object_palette_1.color_3, data, 3); + } +} + +#[cfg(test)] +mod gpu_tests { + use super::*; + + #[test] + fn test_read_write_vram() { + let mut gpu = Gpu::new(); + gpu.write_vram(0x0001, 0xAA); + gpu.write_vram(0x0002, 0x55); + gpu.write_vram(0x0010, 0xAA); + assert_eq!(gpu.read_vram(0x0001), 0xAA); + assert_eq!(gpu.read_vram(0x0002), 0x55); + assert_eq!(gpu.read_vram(0x0010), 0xAA); + } + + #[test] + fn test_draw_line() { + let mut gpu = Gpu::new(); + + // init GPU + gpu.background_display_enabled = true; + gpu.background_tile_data_area = true; + gpu.background_tile_map_area = TileMapArea::X9800; + gpu.current_line = 8; // first line of the second tile row + + // init VRAM + // here we're looking for tile at index 32 and 33 + gpu.write_vram(0x0200, 0x80); + gpu.write_vram(0x0201, 0x80); + gpu.write_vram(0x0210, 0x80); + gpu.write_vram(0x0211, 0x80); + + // set tile map + // here we're looking for tile at index 32 and 33 + gpu.write_vram(0x1820, 0x20); + gpu.write_vram(0x1821, 0x21); + + // draw the line in the frame buffer + gpu.draw_line(); + + // check frame buffer + // line 8 * 160 = 1280 / 0x0500 + assert_eq!(gpu.frame_buffer[0x0500], PixelColor::BLACK as u8); + assert_eq!(gpu.frame_buffer[0x0508], PixelColor::BLACK as u8); + } + + #[test] + fn test_tile_data_area() { + let mut gpu = Gpu::new(); + + // init GPU + gpu.background_display_enabled = true; + gpu.background_tile_data_area = false; + gpu.background_tile_map_area = TileMapArea::X9800; + + // init VRAM + // here we're looking for tile at index 32 and 33 + gpu.write_vram(0x1200, 0x80); + gpu.write_vram(0x1201, 0x80); + gpu.write_vram(0x1210, 0x80); + gpu.write_vram(0x1211, 0x80); + + // here we're looking for tile at index 128 and 129 + gpu.write_vram(0x0800, 0x80); + gpu.write_vram(0x0801, 0x80); + gpu.write_vram(0x0810, 0x80); + gpu.write_vram(0x0811, 0x80); + + // set tile map + // here we're looking for tile map at index 32 and 33 / line 9 + // which redirects to tile data index 32 and 33 + gpu.write_vram(0x1820, 0x20); + gpu.write_vram(0x1821, 0x21); + // here we're looking for tile map at index 512 and 513 / line 129 + // which redirects to tile data index 128 and 129 + gpu.write_vram(0x1A00, 0x80); + gpu.write_vram(0x1A01, 0x81); + + // draw the line in the frame buffer + gpu.current_line = 8; // first line of the second tile row -> line 9 + gpu.draw_line(); + + gpu.current_line = 128; // first line of the 16th tile row -> line 129 + gpu.draw_line(); + + // check frame buffer + // line 8 * 160 = 1280 / 0x0500 + assert_eq!(gpu.frame_buffer[0x0500], PixelColor::BLACK as u8); + assert_eq!(gpu.frame_buffer[0x0508], PixelColor::BLACK as u8); + // line 128 * 160 = 20480 / 0x5000 + assert_eq!(gpu.frame_buffer[0x5000], PixelColor::BLACK as u8); + assert_eq!(gpu.frame_buffer[0x5008], PixelColor::BLACK as u8); + } + + #[test] + fn test_tile_map_area() { + let mut gpu = Gpu::new(); + + // init GPU + gpu.background_display_enabled = true; + gpu.background_tile_data_area = true; + gpu.background_tile_map_area = TileMapArea::X9800; + gpu.current_line = 8; // first line of the second tile row + + // init VRAM + // here we're looking for tile at index 32 and 33 + gpu.write_vram(0x0200, 0x80); + gpu.write_vram(0x0201, 0x80); + gpu.write_vram(0x0210, 0x80); + gpu.write_vram(0x0211, 0x80); + + // set tile map + // here we're looking for tile at index 32 and 33 + gpu.write_vram(0x1820, 0x20); + gpu.write_vram(0x1821, 0x21); + + // draw the line in the frame buffer + gpu.draw_line(); + + // check frame buffer + // line 8 * 160 = 1280 / 0x0500 + assert_eq!(gpu.frame_buffer[0x0500], PixelColor::BLACK as u8); + assert_eq!(gpu.frame_buffer[0x0508], PixelColor::BLACK as u8); + } + + #[test] + fn test_scrolling() { + let mut gpu = Gpu::new(); + + // init GPU + gpu.background_display_enabled = true; + gpu.background_tile_data_area = true; + gpu.background_tile_map_area = TileMapArea::X9C00; + + // init VRAM + // here we're looking for tile at index 32 and 33 + gpu.write_vram(0x0200, 0x80); + gpu.write_vram(0x0201, 0x80); + gpu.write_vram(0x0210, 0x80); + gpu.write_vram(0x0211, 0x80); + + // set tile map + // here we're looking for tile at index 32 and 33 + gpu.write_vram(0x1C20, 0x20); + gpu.write_vram(0x1C21, 0x21); + + // scroll on y axis and draw the line + gpu.viewport_y_offset = 1; + gpu.viewport_x_offset = 0; + gpu.current_line = 7; // line 8 now corresponds to line 9 + gpu.draw_line(); + + // check frame buffer + // line 9 * 160 = 1440 / 0x05A0 + assert_eq!(gpu.frame_buffer[0x05A0], PixelColor::BLACK as u8); + assert_eq!(gpu.frame_buffer[0x05A8], PixelColor::BLACK as u8); + + // scroll on x axis and draw the line + gpu.viewport_y_offset = 0; + gpu.viewport_x_offset = 1; + gpu.current_line = 8; + gpu.draw_line(); + + // check frame buffer + // line 8 * 160 = 1280 / 0x0500 + assert_eq!(gpu.frame_buffer[0x0507], PixelColor::BLACK as u8); + } + + #[test] + fn test_draw_frame() { + let mut gpu = Gpu::new(); + let mut nvic = Nvic::new(); + + // init GPU + gpu.background_display_enabled = true; + gpu.background_tile_data_area = true; + gpu.background_tile_map_area = TileMapArea::X9800; + gpu.lcd_display_enabled = true; + + // init VRAM + // here we're looking for tile at index 32 and 33 + gpu.write_vram(0x0200, 0x80); + gpu.write_vram(0x0201, 0x80); + + // set tile map + // here we're looking for tile at index 0 and 1 + gpu.write_vram(0x1800, 0x20); + gpu.write_vram(0x1801, 0x20); + // here we're looking for tile at index 32 and 33 + gpu.write_vram(0x1820, 0x20); + gpu.write_vram(0x1821, 0x20); + // here we're looking for tile map at index 512 and 513 / line 129 + gpu.write_vram(0x1A00, 0x20); + gpu.write_vram(0x1A01, 0x20); + + // draw the line in the frame buffer + while gpu.current_line < SCREEN_HEIGHT as u8 { + gpu.run(1, &mut nvic); + } + + // check frame buffer + // line 0 * 160 = 0 / 0x0000 + assert_eq!(gpu.frame_buffer[0x0000], PixelColor::BLACK as u8); + assert_eq!(gpu.frame_buffer[0x0008], PixelColor::BLACK as u8); + // line 8 * 160 = 1280 / 0x0500 + assert_eq!(gpu.frame_buffer[0x0500], PixelColor::BLACK as u8); + assert_eq!(gpu.frame_buffer[0x0508], PixelColor::BLACK as u8); + // line 128 * 160 = 20480 / 0x5000 + assert_eq!(gpu.frame_buffer[0x5000], PixelColor::BLACK as u8); + assert_eq!(gpu.frame_buffer[0x5008], PixelColor::BLACK as u8); + } + + #[test] + fn test_vblank_interrupts() { + let mut gpu = Gpu::new(); + let mut nvic = Nvic::new(); + + gpu.lcd_display_enabled = true; + + nvic.master_enable(true); + nvic.enable_interrupt(InterruptSources::VBLANK, true); + + let mut runned_cycles: u32 = 0; + + // run GPU + while runned_cycles < (SCREEN_HEIGHT * (ONE_LINE_CYCLES as usize) + 1) as u32 { + gpu.run(1, &mut nvic); + runned_cycles += 1; + } + + // check that we are in vblank mode and vblank interrupt has been asserted + assert_eq!(gpu.mode, GpuMode::VerticalBlank); + assert_eq!(nvic.get_interrupt().unwrap(), InterruptSources::VBLANK); + } + + #[test] + fn test_stat_interrupts() { + let mut gpu = Gpu::new(); + let mut nvic = Nvic::new(); + + nvic.master_enable(true); + nvic.enable_interrupt(InterruptSources::STAT, true); + gpu.oam_interrupt_enabled = true; + gpu.hblank_interrupt_enabled = true; + gpu.vblank_interrupt_enabled = false; + gpu.lcd_display_enabled = true; + + let mut runned_cycles: u32 = 0; + + // run GPU + while runned_cycles < (OAM_SCAN_CYCLES + DRAW_PIXEL_CYCLES + 1) as u32 { + gpu.run(1, &mut nvic); + runned_cycles += 1; + } + + // check that we are in vblank mode and vblank interrupt has been asserted + assert_eq!(gpu.mode, GpuMode::HorizontalBlank); + assert_eq!(nvic.get_interrupt().unwrap(), InterruptSources::STAT); + + // run GPU + runned_cycles = 0; + while runned_cycles < (HORIZONTAL_BLANK_CYCLES) as u32 { + gpu.run(1, &mut nvic); + runned_cycles += 1; + } + + // check that we are in vblank mode and vblank interrupt has been asserted + assert_eq!(gpu.mode, GpuMode::OAMScan); + assert_eq!(nvic.get_interrupt().unwrap(), InterruptSources::STAT); + assert_eq!(nvic.get_interrupt(), None); + } + + #[test] + fn test_vblank_stat_interrupts() { + let mut gpu = Gpu::new(); + let mut nvic = Nvic::new(); + + nvic.master_enable(true); + nvic.enable_interrupt(InterruptSources::STAT, true); + gpu.oam_interrupt_enabled = false; + gpu.hblank_interrupt_enabled = false; + gpu.vblank_interrupt_enabled = true; + gpu.lcd_display_enabled = true; + + let mut runned_cycles: u32 = 0; + + // run the gpu + while runned_cycles < (SCREEN_HEIGHT * (ONE_LINE_CYCLES as usize) + (HORIZONTAL_BLANK_CYCLES as usize) + 1) as u32 { + gpu.run(1, &mut nvic); + runned_cycles += 1; + } + + // check that we are in vblank mode and vblank interrupt has been asserted + assert_eq!(gpu.mode, GpuMode::VerticalBlank); + assert_eq!(nvic.get_interrupt().unwrap(), InterruptSources::STAT); + } + + #[test] + fn test_compare_line() { + let mut gpu = Gpu::new(); + let mut nvic = Nvic::new(); + + nvic.master_enable(true); + nvic.enable_interrupt(InterruptSources::STAT, true); + gpu.line_compare_it_enable = true; + gpu.compare_line = 2; + gpu.lcd_display_enabled = true; + + assert_eq!(gpu.line_compare_state, false); + + let mut runned_cycles: u32 = 0; + + // run the gpu for 3 lines + while runned_cycles < (ONE_LINE_CYCLES * 2 + HORIZONTAL_BLANK_CYCLES) as u32 { + gpu.run(1, &mut nvic); + runned_cycles += 1; + } + + // check that we are in vblank mode and vblank interrupt has been asserted + assert_eq!(gpu.current_line, 2); + assert_eq!(gpu.line_compare_state, true); + assert_eq!(nvic.get_interrupt().unwrap(), InterruptSources::STAT); + } + + #[test] + fn test_control_reg() { + let mut gpu = Gpu::new(); + + gpu.control_from_byte(0xDB); + let reg = gpu.control_to_byte(); + + assert_eq!(reg, 0xDB); + } + + #[test] + fn test_status_reg() { + let mut gpu = Gpu::new(); + + gpu.status_from_byte(0xDF); + let reg = gpu.status_to_byte(); + + assert_eq!(reg, 0xD8); + } + + #[test] + fn test_set_background_palette() { + let mut gpu = Gpu::new(); + + gpu.set_background_palette(0b10010011); + + assert_eq!(gpu.background_palette.color_3, PixelColor::DARK_GRAY); + assert_eq!(gpu.background_palette.color_2, PixelColor::LIGHT_GRAY); + assert_eq!(gpu.background_palette.color_1, PixelColor::WHITE); + assert_eq!(gpu.background_palette.color_0, PixelColor::BLACK); + } + + #[test] + fn test_set_object_palette() { + let mut gpu = Gpu::new(); + + gpu.set_object_palette_0(0b10010011); + + assert_eq!(gpu.object_palette_0.color_3, PixelColor::DARK_GRAY); + assert_eq!(gpu.object_palette_0.color_2, PixelColor::LIGHT_GRAY); + assert_eq!(gpu.object_palette_0.color_1, PixelColor::WHITE); + assert_eq!(gpu.object_palette_0.color_0, PixelColor::BLACK); + + gpu.set_object_palette_1(0b11010010); + + assert_eq!(gpu.object_palette_1.color_3, PixelColor::BLACK); + assert_eq!(gpu.object_palette_1.color_2, PixelColor::LIGHT_GRAY); + assert_eq!(gpu.object_palette_1.color_1, PixelColor::WHITE); + assert_eq!(gpu.object_palette_1.color_0, PixelColor::DARK_GRAY); + } +} \ No newline at end of file diff --git a/src/soc/peripheral/keypad.rs b/src/soc/peripheral/keypad.rs new file mode 100644 index 0000000..bacae74 --- /dev/null +++ b/src/soc/peripheral/keypad.rs @@ -0,0 +1,117 @@ +pub enum GameBoyKey { + START, + SELECT, + B, + A, + DOWN, + UP, + LEFT, + RIGHT, +} + +pub struct Keypad { + action_buttons: bool, + direction_buttons: bool, + // action buttons + start: bool, + select: bool, + b: bool, + a: bool, + // direction buttons + down: bool, + up: bool, + left: bool, + right: bool, +} + +impl Keypad { + pub fn new() -> Keypad { + Keypad { + action_buttons: false, + direction_buttons: false, + // action buttons + start: false, + select: false, + b: false, + a: false, + // direction buttons + down: false, + up: false, + left: false, + right: false, + } + } + + pub fn control(&mut self, data: u8) { + self.action_buttons = ((data >> 5) & 0x01) == 0; + self.direction_buttons = ((data >> 4) & 0x01) == 0; + } + + pub fn get(&self) -> u8 { + match (self.action_buttons, self.direction_buttons) { + (true, false) => { + (!self.action_buttons as u8) << 5 + | (!self.direction_buttons as u8) << 4 + | (!self.start as u8) << 3 + | (!self.select as u8) << 2 + | (!self.b as u8) << 1 + | (!self.a as u8) << 0 + }, + (false, true) => { + (!self.action_buttons as u8) << 5 + | (!self.direction_buttons as u8) << 4 + | (!self.down as u8) << 3 + | (!self.up as u8) << 2 + | (!self.left as u8) << 1 + | (!self.right as u8) << 0 + }, + (false, false) => 0x00, // nothing to return + (true, true) => panic!("Cannot read action and direction buttons at the same time"), + } + } + + pub fn set(&mut self, key: GameBoyKey, value: bool) { + match key { + GameBoyKey::START => self.start = value, + GameBoyKey::SELECT => self.select = value, + GameBoyKey::B => self.b = value, + GameBoyKey::A => self.a = value, + GameBoyKey::DOWN => self.down = value, + GameBoyKey::UP => self.up = value, + GameBoyKey::LEFT => self.left = value, + GameBoyKey::RIGHT => self.right = value, + } + } +} + +#[cfg(test)] +mod keypad_tests { + use super::*; + + #[test] + fn test_set_get_gameboykey() { + let mut keypad = Keypad::new(); + + keypad.control(0x10); + keypad.set(GameBoyKey::START, true); + assert_eq!(keypad.get(), 0x17); + keypad.set(GameBoyKey::START, false); + keypad.set(GameBoyKey::B, true); + assert_eq!(keypad.get(), 0x1D); + + keypad.control(0x20); + assert_eq!(keypad.get(), 0x2F); + + keypad.set(GameBoyKey::DOWN, false); + keypad.set(GameBoyKey::UP, true); + keypad.set(GameBoyKey::LEFT, false); + keypad.set(GameBoyKey::RIGHT, true); + assert_eq!(keypad.get(), 0x2A); + + keypad.set(GameBoyKey::DOWN, true); + keypad.set(GameBoyKey::UP, false); + keypad.set(GameBoyKey::LEFT, true); + keypad.set(GameBoyKey::RIGHT, false); + assert_eq!(keypad.get(), 0x25); + } +} \ No newline at end of file diff --git a/src/soc/peripheral/mod.rs b/src/soc/peripheral/mod.rs new file mode 100644 index 0000000..53f63ea --- /dev/null +++ b/src/soc/peripheral/mod.rs @@ -0,0 +1,376 @@ +pub mod gpu; +pub mod nvic; +mod timer; +pub mod keypad; +mod bootrom; + +use gpu::Gpu; +use nvic::{Nvic, InterruptSources}; +use timer::Timer; +use bootrom::BootRom; +use keypad::Keypad; + +use crate::cartridge::Cartridge; + +pub const BOOT_ROM_BEGIN: u16 = 0x0000; +pub const BOOT_ROM_END: u16 = 0x00FF; +pub const BOOT_ROM_SIZE: u16 = BOOT_ROM_END - BOOT_ROM_BEGIN + 1; + +pub const ROM_BANK_0_BEGIN: u16 = 0x0000; +pub const ROM_BANK_0_END: u16 = 0x3FFF; +pub const ROM_BANK_0_SIZE: u16 = ROM_BANK_0_END - ROM_BANK_0_BEGIN + 1; + +pub const ROM_BANK_N_BEGIN: u16 = 0x4000; +pub const ROM_BANK_N_END: u16 = 0x7FFF; +pub const ROM_BANK_N_SIZE: u16 = ROM_BANK_N_END - ROM_BANK_N_BEGIN + 1; + +pub const VRAM_BEGIN: u16 = 0x8000; +pub const VRAM_END: u16 = 0x9FFF; +pub const VRAM_SIZE: u16 = VRAM_END - VRAM_BEGIN + 1; + +pub const EXTERNAL_RAM_BEGIN: u16 = 0xA000; +pub const EXTERNAL_RAM_END: u16 = 0xBFFF; + +pub const WORKING_RAM_BEGIN: u16 = 0xC000; +pub const WORKING_RAM_END: u16 = 0xDFFF; +pub const WORKING_RAM_SIZE: u16 = WORKING_RAM_END - WORKING_RAM_BEGIN + 1; + +pub const ECHO_RAM_BEGIN: u16 = 0xE000; +pub const ECHO_RAM_END: u16 = 0xFDFF; + +pub const OAM_BEGIN: u16 = 0xFE00; +pub const OAM_END: u16 = 0xFE9F; +pub const OAM_SIZE: u16 = OAM_END - OAM_BEGIN + 1; + +pub const UNUSED_BEGIN: u16 = 0xFEA0; +pub const UNUSED_END: u16 = 0xFEFF; + +pub const IO_REGISTERS_BEGIN: u16 = 0xFF00; +pub const IO_REGISTERS_END: u16 = 0xFF7F; + +pub const ZERO_PAGE_BEGIN: u16 = 0xFF80; +pub const ZERO_PAGE_END: u16 = 0xFFFE; +pub const ZERO_PAGE_SIZE: u16 = ZERO_PAGE_END - ZERO_PAGE_BEGIN + 1; + +pub const INTERRUPT_ENABLE_REGISTER: u16 = 0xFFFF; + +pub const VBLANK_VECTOR: u16 = 0x40; +pub const LCDSTAT_VECTOR: u16 = 0x48; +pub const TIMER_VECTOR: u16 = 0x50; + +pub trait IoAccess { + fn read(&self, address: u16) -> u8; + + fn write(&mut self, address: u16, data: u8); +} + +pub trait Interrupt { + fn is_an_interrupt_to_run(&self) -> bool; + + fn is_an_interrupt_pending(&self) -> bool; + + fn get_interrupt(&mut self) -> Option; + + fn master_enable(&mut self, enable: bool); +} + +pub struct Peripheral { + boot_rom: BootRom, + cartridge: Cartridge, + working_ram: [u8; WORKING_RAM_SIZE as usize], + zero_page: [u8; ZERO_PAGE_SIZE as usize], + pub gpu: Gpu, + pub nvic: Nvic, + timer: Timer, + pub keypad: Keypad, + // dma + dma_cycles: u8, + dma_start_adress: u16, + dma_enabled: bool, +} + +impl Peripheral { + pub fn new(cartridge: Cartridge) -> Peripheral { + Peripheral { + boot_rom: BootRom::new(), + cartridge: cartridge, + working_ram: [0xFF; WORKING_RAM_SIZE as usize], + zero_page: [0xFF; ZERO_PAGE_SIZE as usize], + gpu: Gpu::new(), + nvic: Nvic::new(), + timer: Timer::new(), + keypad: Keypad::new(), + dma_cycles: 0, + dma_start_adress: 0xFFFF, + dma_enabled: false, + } + } + + pub fn run(&mut self, runned_cycles: u8) { + // run the timer + self.timer.run(runned_cycles, &mut self.nvic); + + // run the DMA + if self.dma_enabled { + // copy data + for mem_index in 0..runned_cycles { + if self.dma_cycles + mem_index < OAM_SIZE as u8 { + let data = self.read(self.dma_start_adress + (self.dma_cycles + mem_index) as u16); + self.gpu.write_oam((mem_index + self.dma_cycles) as usize, data); + } + } + // update internal timer + self.dma_cycles += runned_cycles; + // check if we reached the end of the dma transfert + if self.dma_cycles >= OAM_SIZE as u8{ + // disable dma + self.dma_enabled = false; + self.dma_cycles = 0; + } + } + + // run the GPU + self.gpu.run(runned_cycles, &mut self.nvic); + + // run the cartridge + self.cartridge.run(runned_cycles); + } + + pub fn load_bootrom(&mut self, boot_rom: &[u8]){ + self.boot_rom.load(boot_rom); + } + + fn read_io_register(&self, address: usize) -> u8 { + match address { + 0xFF00 => self.keypad.get(), + 0xFF01 => 0, // TODO: serial + 0xFF02 => 0, // TODO: serial + 0xFF04 => self.timer.get_divider(), + 0xFF05 => self.timer.get_value(), + 0xFF06 => self.timer.get_modulo(), + 0xFF0F => self.nvic.get_it_flag(), + 0xFF10 => 0xFF, // Channel 1 Sweep register + 0xFF11 => 0xFF, /* Channel 1 Sound Length and Wave */ + 0xFF12 => 0xFF, /* Channel 1 Sound Control */ + 0xFF13 => 0xFF, /* Channel 1 Frequency lo */ + 0xFF14 => 0xFF, /* Channel 1 Control */ + 0xFF16 => 0xFF, /* Channel 2 Sound Control */ + 0xFF17 => 0xFF, /* Channel 2 Sound Control */ + 0xFF18 => 0xFF, /* Channel 2 Sound Control */ + 0xFF19 => 0xFF, /* Channel 2 Frequency hi data*/ + 0xFF1A => 0xFF, /* Channel 3 Sound on/off */ + 0xFF1B => 0xFF, /* Channel 3 Sound on/off */ + 0xFF1C => 0xFF, /* Channel 3 Sound on/off */ + 0xFF1D => 0xFF, /* Channel 3 Sound on/off */ + 0xFF1E => 0xFF, /* Channel 3 Sound on/off */ + 0xFF20 => 0xFF, /* Channel 4 Volumn */ + 0xFF21 => 0xFF, /* Channel 4 Volumn */ + 0xFF22 => 0xFF, /* Channel 4 Volumn */ + 0xFF23 => 0xFF, /* Channel 4 Counter/consecutive */ + 0xFF24 => 0xFF, /* Sound Volume */ + 0xFF25 => 0xFF, /* Sound output terminal selection */ + 0xFF26 => 0xFF, /* Sound on/off */ + 0xff30..=0xff3f => 0xFF, //Wave Pattern RAM + 0xFF40 => self.gpu.control_to_byte(), + 0xFF41 => self.gpu.status_to_byte(), + 0xFF42 => self.gpu.get_scy(), + 0xFF43 => self.gpu.get_scx(), + 0xFF44 => self.gpu.get_current_line(), + 0xFF45 => self.gpu.get_compare_line(), + 0xFF4A => self.gpu.get_window_y(), + 0xFF4B => self.gpu.get_window_x(), + 0xFF4D => 0xFF, // CGB SPEED SWITCH register, not supported + 0xFF48 => 0xFF, // pokemon tries to read this registers + 0xFF49 => 0xFF, // pokemon tries to read this registers + _ => panic!("Reading from an unknown I/O register {:x}", address), + } + } + + fn write_io_register(&mut self, address: usize, data: u8) { + match address { + 0xFF00 => self.keypad.control(data), + 0xFF01 => { /* Serial Transfer */ } + 0xFF02 => { /* Serial Transfer Control */ } + 0xFF04 => self.timer.set_divider(), + 0xFF05 => self.timer.set_value(data), + 0xFF06 => self.timer.set_modulo(data), + 0xFF07 => self.timer.settings_from_byte(data), + 0xFF0F => self.nvic.set_it_flag(data), + 0xFF10 => { /* Channel 1 Sweep register */ } + 0xFF11 => { /* Channel 1 Sound Length and Wave */ } + 0xFF12 => { /* Channel 1 Sound Control */ } + 0xFF13 => { /* Channel 1 Frequency lo */ } + 0xFF14 => { /* Channel 1 Control */ } + 0xFF16 => { /* Channel 2 Sound Control */ } + 0xFF17 => { /* Channel 2 Sound Control */ } + 0xFF18 => { /* Channel 2 Sound Control */ } + 0xFF19 => { /* Channel 2 Frequency hi data*/ } + 0xFF1A => { /* Channel 3 Sound on/off */ } + 0xFF1B => { /* Channel 3 Sound on/off */ } + 0xFF1C => { /* Channel 3 Sound on/off */ } + 0xFF1D => { /* Channel 3 Sound on/off */ } + 0xFF1E => { /* Channel 3 Sound on/off */ } + 0xFF20 => { /* Channel 4 Volumn */ } + 0xFF21 => { /* Channel 4 Volumn */ } + 0xFF22 => { /* Channel 4 Volumn */ } + 0xFF23 => { /* Channel 4 Counter/consecutive */ } + 0xFF24 => { /* Sound Volume */ } + 0xFF25 => { /* Sound output terminal selection */ } + 0xFF26 => { /* Sound on/off */ } + 0xff30..=0xff3f => { /* Wave Pattern RAM */ } + 0xFF40 => self.gpu.control_from_byte(data), + 0xFF41 => self.gpu.status_from_byte(data), + 0xFF42 => self.gpu.set_scy(data), + 0xFF43 => self.gpu.set_scx(data), + 0xFF45 => self.gpu.set_compare_line(data), + 0xFF46 => { + self.dma_start_adress = (data as u16) << 8; + self.dma_enabled = true; + } + 0xFF47 => self.gpu.set_background_palette(data), + 0xFF48 => self.gpu.set_object_palette_0(data), + 0xFF49 => self.gpu.set_object_palette_1(data), + 0xFF4A => self.gpu.set_window_y(data), + 0xFF4B => self.gpu.set_window_x(data), + 0xFF50 => self.boot_rom.set_state(false), + 0xFF7f => { + // Writing to here does nothing + } + _ => panic!( + "Writting '0b{:b}' to an unknown I/O register {:x}", + data, address + ), + } + } +} + +impl IoAccess for Peripheral { + fn read(&self, address: u16) -> u8 { + match address { + ROM_BANK_0_BEGIN..=ROM_BANK_0_END => { + match address { + BOOT_ROM_BEGIN..=BOOT_ROM_END => + if self.boot_rom.get_state() { + self.boot_rom.read(address) + } else { + self.cartridge.read_bank_0(address as usize) + } + _ => self.cartridge.read_bank_0(address as usize) + } + } + ROM_BANK_N_BEGIN..=ROM_BANK_N_END => self.cartridge.read_bank_n(address as usize), + VRAM_BEGIN..=VRAM_END => self.gpu.read_vram(address - VRAM_BEGIN), + EXTERNAL_RAM_BEGIN..=EXTERNAL_RAM_END => self.cartridge.read_ram(address as usize), + WORKING_RAM_BEGIN..=WORKING_RAM_END => self.working_ram[(address - WORKING_RAM_BEGIN) as usize], + ECHO_RAM_BEGIN..=ECHO_RAM_END => self.working_ram[(address - ECHO_RAM_BEGIN) as usize], + OAM_BEGIN..=OAM_END => self.gpu.read_oam((address - OAM_BEGIN) as usize), + IO_REGISTERS_BEGIN..=IO_REGISTERS_END => self.read_io_register(address as usize), + UNUSED_BEGIN..=UNUSED_END => 0, // unused memory + ZERO_PAGE_BEGIN..=ZERO_PAGE_END => self.zero_page[(address - ZERO_PAGE_BEGIN) as usize], + INTERRUPT_ENABLE_REGISTER => self.nvic.get_it_enable(), + } + } + + fn write(&mut self, address: u16, data: u8) { + match address { + ROM_BANK_0_BEGIN..=ROM_BANK_0_END => self.cartridge.write_bank_0(address as usize, data), + ROM_BANK_N_BEGIN..=ROM_BANK_N_END => self.cartridge.write_bank_n(address as usize, data), + VRAM_BEGIN..=VRAM_END => self.gpu.write_vram(address - VRAM_BEGIN, data), + EXTERNAL_RAM_BEGIN..=EXTERNAL_RAM_END => self.cartridge.write_ram(address as usize, data), + WORKING_RAM_BEGIN..=WORKING_RAM_END => { + self.working_ram[(address - WORKING_RAM_BEGIN) as usize] = data; + } + ECHO_RAM_BEGIN..=ECHO_RAM_END => { + self.working_ram[(address - ECHO_RAM_BEGIN) as usize] = data; + } + OAM_BEGIN..=OAM_END => self.gpu.write_oam((address - OAM_BEGIN) as usize, data), + IO_REGISTERS_BEGIN..=IO_REGISTERS_END => self.write_io_register(address as usize, data), + UNUSED_BEGIN..=UNUSED_END => { /* Writing to here does nothing */ } + ZERO_PAGE_BEGIN..=ZERO_PAGE_END => { + self.zero_page[(address - ZERO_PAGE_BEGIN) as usize] = data; + } + INTERRUPT_ENABLE_REGISTER => self.nvic.set_it_enable(data), + } + } +} + +impl Interrupt for Peripheral { + fn is_an_interrupt_to_run(&self) -> bool { + self.nvic.is_an_interrupt_to_run() + } + + fn is_an_interrupt_pending(&self) -> bool { + self.nvic.is_an_interrupt_pending() + } + + fn get_interrupt(&mut self) -> Option { + self.nvic.get_interrupt() + } + + fn master_enable(&mut self, enable: bool) { + self.nvic.master_enable(enable); + } +} + +#[cfg(test)] +mod peripheral_tests { + use super::*; + use crate::cartridge::{Cartridge, CARTRIDGE_TYPE_OFFSET, CARTRIDGE_RAM_SIZE_OFFSET, CARTRIDGE_ROM_SIZE_OFFSET}; + + #[test] + fn test_read_write() { + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + peripheral.write(0x0001 + 0xC000, 0xAA); + peripheral.write(0x0002 + 0xC000, 0x55); + peripheral.write(0x0010 + 0xC000, 0xAA); + assert_eq!(peripheral.read(0x0001 + 0xC000), 0xAA); + assert_eq!(peripheral.read(0x0002 + 0xC000), 0x55); + assert_eq!(peripheral.read(0x0010 + 0xC000), 0xAA); + } + + #[test] + fn test_read_write_vram() { + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + peripheral.write(0x0001 + VRAM_BEGIN, 0xAA); + peripheral.write(0x0002 + VRAM_BEGIN, 0x55); + peripheral.write(0x0010 + VRAM_BEGIN, 0xAA); + assert_eq!(peripheral.read(0x0001 + VRAM_BEGIN), 0xAA); + assert_eq!(peripheral.read(0x0002 + VRAM_BEGIN), 0x55); + assert_eq!(peripheral.read(0x0010 + VRAM_BEGIN), 0xAA); + } + + #[test] + fn test_oam_dma() { + let mut rom = [0xFF; 0x8000]; + rom[CARTRIDGE_TYPE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_ROM_SIZE_OFFSET as usize] = 0x00; + rom[CARTRIDGE_RAM_SIZE_OFFSET as usize] = 0x00; + let mut peripheral = Peripheral::new(Cartridge::new(&rom)); + let address = 0xC000; + // init data + peripheral.write(address, 0xAA); + peripheral.write(address + 0x007F, 0xAA); + peripheral.write(address + 0x009F, 0x55); + + // set dma + peripheral.write(0xFF46, (address >> 8) as u8); + + // run peripheral for 160 cycles + for _ in 0..OAM_SIZE { + peripheral.run(1); + } + + // check oam memory + assert_eq!(peripheral.gpu.read_oam(0x00), 0xAA); + assert_eq!(peripheral.gpu.read_oam(0x7F), 0xAA); + assert_eq!(peripheral.gpu.read_oam(0x9F), 0x55); + } +} \ No newline at end of file diff --git a/src/soc/peripheral/nvic.rs b/src/soc/peripheral/nvic.rs new file mode 100644 index 0000000..a72c33f --- /dev/null +++ b/src/soc/peripheral/nvic.rs @@ -0,0 +1,260 @@ +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum InterruptSources { + VBLANK, + STAT, + TIMER, + SERIAL, + JOYPAD, +} + +const FIRST_INTERRUPT_SOURCE: u8 = InterruptSources::VBLANK as u8; +const LAST_INTERRUPT_SOURCE: u8 = InterruptSources::JOYPAD as u8; + +pub struct Nvic { + pub interrupt_master_enable: bool, + pub interrupt_enable: u8, + pub interrupt_flag: u8, +} + +impl Nvic { + pub fn new() -> Nvic { + Nvic { + interrupt_master_enable: false, + interrupt_enable: 0, + interrupt_flag: 0, + } + } + + pub fn master_enable(&mut self, enable: bool) { + self.interrupt_master_enable = enable; + } + + pub fn enable_interrupt(&mut self, source: InterruptSources, enable: bool) { + if enable { + self.interrupt_enable |= (1 as u8) << (source as u8); + } else { + self.interrupt_enable &= !((1 as u8) << (source as u8)); + } + } + + pub fn set_interrupt(&mut self, source: InterruptSources) { + self.interrupt_flag |= (1 as u8) << (source as u8); + } + + pub fn get_interrupt(&mut self) -> Option { + // find the interrupt source and clear the bit flag + for interrupt_index in FIRST_INTERRUPT_SOURCE..=LAST_INTERRUPT_SOURCE { + if (self.interrupt_enable & self.interrupt_flag & (1 << interrupt_index)) != 0 { + // clear the interrupt flag + self.interrupt_flag &= !(1 << interrupt_index); + + // high priority interrupt found + let interrupt_source = match interrupt_index { + 0 => InterruptSources::VBLANK, + 1 => InterruptSources::STAT, + 2 => InterruptSources::TIMER, + 3 => InterruptSources::SERIAL, + 4 => InterruptSources::JOYPAD, + _ => panic!("Interrupt index exceeded interrupt max number") + }; + + return Some(interrupt_source); + } + } + + return None; + } + + pub fn is_an_interrupt_to_run(&self) -> bool { + if self.interrupt_master_enable { + if self.is_an_interrupt_pending() { + // we detected an interrupt + true + } else { + false + } + } else { + false + } + } + + pub fn is_an_interrupt_pending(&self) -> bool { + if (self.interrupt_enable & self.interrupt_flag) != 0 { + true + } else { + false + } + } + + pub fn set_it_enable(&mut self, data: u8) { + self.interrupt_enable = data; + } + + pub fn get_it_enable(&self) -> u8 { + 0b11100000 | self.interrupt_enable + } + + pub fn set_it_flag(&mut self, data: u8) { + self.interrupt_flag = data; + } + + pub fn get_it_flag(&self) -> u8 { + 0b11100000 | self.interrupt_flag + } +} + +#[cfg(test)] +mod nvic_tests { + use super::*; + + #[test] + fn test_enable_interrupt() { + let mut nvic = Nvic::new(); + + nvic.enable_interrupt(InterruptSources::VBLANK, true); + assert_eq!(nvic.interrupt_enable, 0x01); + + nvic.enable_interrupt(InterruptSources::STAT, true); + assert_eq!(nvic.interrupt_enable, 0x03); + + nvic.enable_interrupt(InterruptSources::JOYPAD, true); + assert_eq!(nvic.interrupt_enable, 0x13); + + nvic.enable_interrupt(InterruptSources::STAT, false); + assert_eq!(nvic.interrupt_enable, 0x11); + } + + #[test] + fn test_set_interrupt() { + let mut nvic = Nvic::new(); + + nvic.master_enable(true); + nvic.enable_interrupt(InterruptSources::VBLANK, true); + assert_eq!(nvic.interrupt_enable, 0x01); + nvic.enable_interrupt(InterruptSources::STAT, true); + assert_eq!(nvic.interrupt_enable, 0x03); + + nvic.set_interrupt(InterruptSources::SERIAL); + assert_eq!(nvic.is_an_interrupt_to_run(), false); + assert_eq!(nvic.is_an_interrupt_pending(), false); + let mut interrupt = nvic.get_interrupt(); + match interrupt { + Some(InterruptSources::VBLANK) => assert!(false), + Some(InterruptSources::STAT) => assert!(false), + Some(InterruptSources::TIMER) => assert!(false), + Some(InterruptSources::SERIAL) => assert!(false), + Some(InterruptSources::JOYPAD) => assert!(false), + None => assert!(true) + } + + nvic.set_interrupt(InterruptSources::STAT); + assert_eq!(nvic.is_an_interrupt_to_run(), true); + assert_eq!(nvic.is_an_interrupt_pending(), true); + interrupt = nvic.get_interrupt(); + match interrupt { + Some(InterruptSources::VBLANK) => assert!(false), + Some(InterruptSources::STAT) => assert!(true), + Some(InterruptSources::TIMER) => assert!(false), + Some(InterruptSources::SERIAL) => assert!(false), + Some(InterruptSources::JOYPAD) => assert!(false), + None => assert!(false) + } + + // check that interrupt has been cleared + interrupt = nvic.get_interrupt(); + assert_eq!(nvic.is_an_interrupt_to_run(), false); + assert_eq!(nvic.is_an_interrupt_pending(), false); + match interrupt { + Some(InterruptSources::VBLANK) => assert!(false), + Some(InterruptSources::STAT) => assert!(false), + Some(InterruptSources::TIMER) => assert!(false), + Some(InterruptSources::SERIAL) => assert!(false), + Some(InterruptSources::JOYPAD) => assert!(false), + None => assert!(true) + } + + // check interrupt priority + nvic.set_interrupt(InterruptSources::STAT); + nvic.set_interrupt(InterruptSources::VBLANK); + assert_eq!(nvic.is_an_interrupt_to_run(), true); + assert_eq!(nvic.is_an_interrupt_pending(), true); + interrupt = nvic.get_interrupt(); + match interrupt { + Some(InterruptSources::VBLANK) => assert!(true), + Some(InterruptSources::STAT) => assert!(false), + Some(InterruptSources::TIMER) => assert!(false), + Some(InterruptSources::SERIAL) => assert!(false), + Some(InterruptSources::JOYPAD) => assert!(false), + None => assert!(false) + } + + assert_eq!(nvic.is_an_interrupt_to_run(), true); + assert_eq!(nvic.is_an_interrupt_pending(), true); + interrupt = nvic.get_interrupt(); + match interrupt { + Some(InterruptSources::VBLANK) => assert!(false), + Some(InterruptSources::STAT) => assert!(true), + Some(InterruptSources::TIMER) => assert!(false), + Some(InterruptSources::SERIAL) => assert!(false), + Some(InterruptSources::JOYPAD) => assert!(false), + None => assert!(false) + } + + assert_eq!(nvic.is_an_interrupt_to_run(), false); + assert_eq!(nvic.is_an_interrupt_pending(), false); + interrupt = nvic.get_interrupt(); + match interrupt { + Some(InterruptSources::VBLANK) => assert!(false), + Some(InterruptSources::STAT) => assert!(false), + Some(InterruptSources::TIMER) => assert!(false), + Some(InterruptSources::SERIAL) => assert!(false), + Some(InterruptSources::JOYPAD) => assert!(false), + None => assert!(true) + } + } + + #[test] + fn test_last_interrupt() { + let mut nvic = Nvic::new(); + + nvic.master_enable(true); + nvic.enable_interrupt(InterruptSources::VBLANK, true); + assert_eq!(nvic.interrupt_enable, 0x01); + nvic.enable_interrupt(InterruptSources::JOYPAD, true); + assert_eq!(nvic.interrupt_enable, 0x11); + + nvic.set_interrupt(InterruptSources::JOYPAD); + assert_eq!(nvic.is_an_interrupt_to_run(), true); + assert_eq!(nvic.is_an_interrupt_pending(), true); + let mut interrupt = nvic.get_interrupt(); + match interrupt { + Some(InterruptSources::VBLANK) => assert!(false), + Some(InterruptSources::STAT) => assert!(false), + Some(InterruptSources::TIMER) => assert!(false), + Some(InterruptSources::SERIAL) => assert!(false), + Some(InterruptSources::JOYPAD) => assert!(true), + None => assert!(false) + } + + assert_eq!(nvic.is_an_interrupt_to_run(), false); + assert_eq!(nvic.is_an_interrupt_pending(), false); + interrupt = nvic.get_interrupt(); + match interrupt { + Some(InterruptSources::VBLANK) => assert!(false), + Some(InterruptSources::STAT) => assert!(false), + Some(InterruptSources::TIMER) => assert!(false), + Some(InterruptSources::SERIAL) => assert!(false), + Some(InterruptSources::JOYPAD) => assert!(false), + None => assert!(true) + } + } + + + #[test] + fn test_enable_it_from_byte() { + let mut nvic = Nvic::new(); + + nvic.set_it_enable(0b00001100); + assert_eq!(nvic.get_it_enable(), 0b11101100); + } +} \ No newline at end of file diff --git a/src/soc/peripheral/timer.rs b/src/soc/peripheral/timer.rs new file mode 100644 index 0000000..e72c2ba --- /dev/null +++ b/src/soc/peripheral/timer.rs @@ -0,0 +1,201 @@ +use crate::soc::peripheral::nvic::{Nvic, InterruptSources}; +use crate::soc::CLOCK_TICK_PER_MACHINE_CYCLE; + +pub enum Frequency { + F4096, + F16384, + F262144, + F65536, +} + +impl Frequency { + // The number of CPU cycles that occur per tick of the clock. + // This is equal to the number of cpu cycles per second (4194304) + // divided by the timer frequency. + fn cycles_per_tick(&self) -> usize { + match self { + Frequency::F4096 => 1024, + Frequency::F16384 => 256, + Frequency::F262144 => 16, + Frequency::F65536 => 64, + } + } +} + +pub struct Timer { + // internal parameters + main_timer_cycles: usize, + divider_timer_cycles: usize, + tima_overflow: bool, + // DIV / TIMA / TMA registers + pub divider: u8, + pub value: u8, + pub modulo: u8, + // TAC registers values + pub main_timer_frequency: Frequency, + pub divider_timer_frequency: Frequency, + pub enabled: bool, +} + +impl Timer { + pub fn new() -> Timer { + Timer { + // internal parameters + main_timer_cycles: 0, + divider_timer_cycles: 0, + tima_overflow: false, + // DIV / TIMA / TMA registers + divider: 0, + value: 0, + modulo: 0, + // TAC registers values + main_timer_frequency: Frequency::F4096, + divider_timer_frequency: Frequency::F16384, + enabled: false, + } + } + + pub fn run(&mut self, cycles: u8, nvic: &mut Nvic) { + + // update internal divider timer clock + self.divider_timer_cycles += cycles as usize; + // check if the divider timer reached its maximum value + let divider_cycles_per_tick = self.divider_timer_frequency.cycles_per_tick(); + if self.divider_timer_cycles > divider_cycles_per_tick { + let add_divider = (self.divider_timer_cycles / divider_cycles_per_tick) as u8; + self.divider_timer_cycles = self.divider_timer_cycles % divider_cycles_per_tick; + + // check if the main timer reached its maximum value + let (new_divider, _overflow) = self.divider.overflowing_add(add_divider); + self.divider = new_divider; + } + + if self.enabled { + // update internal main timer clock + self.main_timer_cycles += cycles as usize; + + // delay interrupt by 1 machine cycle / 4 clocks + if self.tima_overflow && self.has_timer_passed_1_cycle() { + self.tima_overflow = false; + nvic.set_interrupt(InterruptSources::TIMER); + self.value = self.modulo; + } + + // divide the main cpu clock + let main_cycles_per_tick = self.main_timer_frequency.cycles_per_tick(); + if self.main_timer_cycles > main_cycles_per_tick { + let add_timer = (self.main_timer_cycles / main_cycles_per_tick) as u8; + self.main_timer_cycles = self.main_timer_cycles % main_cycles_per_tick; + + // check if the main timer reached its maximum value + let (new_value, overflow) = self.value.overflowing_add(add_timer); + self.value = new_value; + + // register overflow for next cycle if any + // see https://gbdev.io/pandocs/Timer_Obscure_Behaviour.html + if overflow { + self.tima_overflow = true; + self.value = 0; + } + + // delay interrupt by 1 machine cycle / 4 clocks + if self.tima_overflow && self.has_timer_passed_1_cycle() { + self.tima_overflow = false; + nvic.set_interrupt(InterruptSources::TIMER); + self.value = self.modulo; + } + } + } + } + + pub fn has_timer_passed_1_cycle(&self) -> bool { + self.main_timer_cycles / CLOCK_TICK_PER_MACHINE_CYCLE as usize > 0 + } + + pub fn set_divider(&mut self) { + self.divider = 0; + } + + pub fn get_divider(&self) -> u8 { + self.divider + } + + pub fn set_value(&mut self, data: u8) { + self.value = data; + } + + pub fn get_value(&self) -> u8 { + self.value + } + + pub fn set_modulo(&mut self, data: u8) { + self.modulo = data; + } + + pub fn get_modulo(&self) -> u8 { + self.modulo + } + + pub fn settings_from_byte(&mut self, data: u8) { + // timer enable + self.enabled = ((data >> 2) & 0x01) != 0; + + // main timer frequency + self.main_timer_frequency = match data & 0x03 { + 0x00 => Frequency::F4096, + 0x01 => Frequency::F262144, + 0x10 => Frequency::F65536, + _ => Frequency::F16384, + }; + } +} + +#[cfg(test)] +mod timer_tests { + use super::*; + + #[test] + fn test_timer_inc() { + let mut timer = Timer::new(); + let mut nvic = Nvic::new(); + + timer.enabled = true; + + for _ in 0..=1024 { + timer.run(1, &mut nvic); + } + + assert_eq!(timer.value, 1); + } + + #[test] + fn test_timer_overflow() { + let mut timer = Timer::new(); + let mut nvic = Nvic::new(); + + nvic.master_enable(true); + nvic.enable_interrupt(InterruptSources::TIMER, true); + timer.enabled = true; + timer.value = 0xFF; + + for _ in 0..=1024 { + timer.run(1, &mut nvic); + } + + assert_eq!(timer.value, 0x00); + assert_eq!(nvic.get_interrupt(), None); + + // run 1 more machine cycle (4 clocks) to rise the interrupt + timer.run(CLOCK_TICK_PER_MACHINE_CYCLE, &mut nvic); + assert_eq!(nvic.get_interrupt().unwrap(), InterruptSources::TIMER); + + timer.modulo = 0xF5; + timer.value = 0xFF; + + for _ in 0..=1024 { + timer.run(1, &mut nvic); + } + + assert_eq!(timer.value, 0xF5); + } +} \ No newline at end of file