From dc61bd00878f705e143e64dd7b999e8890854981 Mon Sep 17 00:00:00 2001 From: John Breaux Date: Wed, 22 Mar 2023 15:03:53 -0500 Subject: [PATCH] I/O: KISS the bus, attach a screen, plug in a controller Chip-8 has no ROM, nor memory management. - It's much easier to just use contiguous memory. - Then we can return references to slices of that memory - ~3x speed increase Screen exists now, uses 24-bit framebuffer - We have a 1-bit framebuffer - I chose colors that look good to me Controller exists as well, has 16 buttons - Mapped "0 123 456 789 ab cdef" to (QWERTY) "X 123 QWE ASD zC 4RFV" - Other chip-8 interpreters may use a different layout - This is good enough for now. - F1-F9 map to control functions - F1, F2: Dump CPU registers/screen contents - F3, F4: Toggle disassembly/pause - F5: Single-step the CPU, pausing after - F6, F7: Set/Unset breakpoint - F8, F9: Soft/Hard Reset CPU --- Cargo.lock | 833 ++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 6 +- src/bus.rs | 274 ++++---------- src/cpu.rs | 304 ++++++++++----- src/cpu/disassemble.rs | 72 ++-- src/error.rs | 8 +- src/io.rs | 125 +++++++ src/lib.rs | 11 +- src/main.rs | 195 +++++++--- src/mem.rs | 153 -------- src/screen.rs | 30 -- 11 files changed, 1434 insertions(+), 577 deletions(-) create mode 100644 src/io.rs delete mode 100644 src/mem.rs delete mode 100644 src/screen.rs diff --git a/Cargo.lock b/Cargo.lock index 94ad43f..6c8bb11 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,21 +3,431 @@ version = 3 [[package]] -name = "chumpulator" +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 = "chirp" version = "0.1.0" dependencies = [ + "gumdrop", + "hex-wrapper", + "minifb", "owo-colors", + "rand", "rhexdump", "serde", "thiserror", ] +[[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 = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "futures" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "531ac96c6ff5fd7c62263c5e3c67a603af4fcaee2e1a0ae5565ba3a11e69e549" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "164713a5a0dcc3e7b4b1ed7d3b433cabc18025386f9339346e8daf15963cf7ac" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86d7a0c1aa76363dac491de0ee99faf6941128376f1cf96f07db7603b7de69dd" + +[[package]] +name = "futures-executor" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1997dd9df74cdac935c76252744c1ed5794fac083242ea4fe77ef3ed60ba0f83" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89d422fa3cbe3b40dca574ab087abb5bc98258ea57eea3fd6f1fa7162c778b91" + +[[package]] +name = "futures-macro" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb14ed937631bd8b8b8977f2c198443447a8355b6e3ca599f38c975e5a963b6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec93083a4aecafb2a80a885c9de1f0ccae9dbd32c2bb54b0c3a65690e0b8d2f2" + +[[package]] +name = "futures-task" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd65540d33b37b16542a0438c12e6aeead10d4ac5d05bd3f805b8f35ab592879" + +[[package]] +name = "futures-util" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ef6b17e481503ec85211fed8f39d1970f128935ca1f814cd32ac4a6842e84ab" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gumdrop" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bc700f989d2f6f0248546222d9b4258f5b02a171a431f8285a81c08142629e3" +dependencies = [ + "gumdrop_derive", +] + +[[package]] +name = "gumdrop_derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "729f9bd3449d77e7831a18abfb7ba2f99ee813dfd15b8c2167c9a54ba20aa99d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + +[[package]] +name = "hex-wrapper" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21ee81c2a2396bc6afc4206272515c808a9306d885d0dc018b55da1236c1ed1a" + +[[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 = "io-lifetimes" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76e86b86ae312accbf05ade23ce76b625e0e47a255712b7414037385a1c05380" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.45.0", +] + +[[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.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" + +[[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 = "linux-raw-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" + +[[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.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66a1fdd7e946fe33fe9725012e25836bba3655769bee9ee347cce7de3f396df" +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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[package]] +name = "orbclient" +version = "0.3.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "974465c5e83cf9df05c1e4137b271d29035c902e39e5ad4c1939837e22160af8" +dependencies = [ + "cfg-if", + "libc", + "raw-window-handle 0.3.4", + "redox_syscall", + "sdl2", + "sdl2-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "owo-colors" version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" +[[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 = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro2" version = "1.0.51" @@ -36,12 +446,115 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[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 = "rhexdump" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5e9af64574935e39f24d1c0313a997c8b880ca0e087c888bc6af8af31579847" +[[package]] +name = "rustix" +version = "0.36.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd5c6ff11fecd55b40746d1995a02f2eb375bf8c00d192d521ee09f42bef37bc" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.45.0", +] + +[[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.154" @@ -62,6 +575,21 @@ dependencies = [ "syn", ] +[[package]] +name = "slab" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +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.109" @@ -73,6 +601,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys 0.42.0", +] + [[package]] name = "thiserror" version = "1.0.39" @@ -98,3 +639,293 @@ name = "unicode-ident" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + +[[package]] +name = "version-compare" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[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 = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[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 58b32b0..182930e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,16 @@ [package] -name = "chumpulator" +name = "chirp" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +gumdrop = "0.8.1" +hex-wrapper = "1.3.2" +minifb = "0.24.0" owo-colors = "^3" +rand = "0.8.5" rhexdump = "0.1.1" serde = { version = "^1.0", features = ["derive"] } thiserror = "1.0.39" diff --git a/src/bus.rs b/src/bus.rs index aa517c3..8e67482 100644 --- a/src/bus.rs +++ b/src/bus.rs @@ -1,13 +1,9 @@ //! The Bus connects the CPU to Memory -mod bus_device; -mod iterator; -use crate::dump::{BinDumpable, Dumpable}; -use bus_device::BusDevice; -use iterator::BusIterator; +use crate::error::Result; use std::{ collections::HashMap, - fmt::{Debug, Display, Formatter, Result}, + fmt::{Debug, Display, Formatter}, ops::Range, slice::SliceIndex, }; @@ -17,24 +13,14 @@ use std::{ /// ```rust /// # use chumpulator::prelude::*; /// let mut bus = bus! { -/// "RAM" [0x0000..0x8000] Mem::new(0x8000), -/// "ROM" [0x8000..0xFFFF] Mem::new(0x8000).w(false), +/// "RAM" [0x0000..0x8000], +/// "ROM" [0x8000..0xFFFF], /// }; /// ``` #[macro_export] macro_rules! bus { - ($($name:literal $(:)? [$range:expr] $(=)? $d:expr) ,* $(,)?) => { - $crate::bus::Bus::new() - $( - .connect($name, $range, Box::new($d)) - )* - }; -} - -#[macro_export] -macro_rules! newbus { ($($name:literal $(:)? [$range:expr] $(= $data:expr)?) ,* $(,)?) => { - $crate::bus::NewBus::new() + $crate::bus::Bus::new() $( .add_region($name, $range) $( @@ -44,17 +30,33 @@ macro_rules! newbus { }; } +// Traits Read and Write are here purely to make implementing other things more bearable +/// Do whatever `Read` means to you +pub trait Read { + /// Read a T from address `addr` + fn read(&self, addr: impl Into) -> T; +} + +/// Write "some data" to the Bus +pub trait Write { + /// Write a T to address `addr` + fn write(&mut self, addr: impl Into, data: T); +} + +#[derive(Clone, Copy, Debug)] +pub enum Region {} + /// Store memory in a series of named regions with ranges #[derive(Debug, Default)] -pub struct NewBus { +pub struct Bus { memory: Vec, region: HashMap<&'static str, Range>, } -impl NewBus { +impl Bus { /// Construct a new bus pub fn new() -> Self { - NewBus::default() + Bus::default() } /// Gets the length of the bus' backing memory pub fn len(&self) -> usize { @@ -65,7 +67,7 @@ impl NewBus { self.memory.is_empty() } /// Grows the NewBus backing memory to at least size bytes, but does not truncate - pub fn with_size(&mut self, size: usize){ + pub fn with_size(&mut self, size: usize) { if self.len() < size { self.memory.resize(size, 0); } @@ -82,6 +84,12 @@ impl NewBus { } self } + pub fn clear_region(&mut self, name: &str) -> &mut Self { + if let Some(region) = self.get_region_mut(name) { + region.fill(0) + } + self + } /// Gets a slice of bus memory pub fn get(&self, index: I) -> Option<&>::Output> where @@ -104,15 +112,38 @@ impl NewBus { pub fn get_region_mut(&mut self, name: &str) -> Option<&mut [u8]> { self.get_mut(self.region.get(name)?.clone()) } -} - -impl Read for NewBus { - fn read(&self, addr: impl Into) -> u8 { - *self.memory.get(addr.into()).unwrap_or(&0xc5) + pub fn print_screen(&self) -> Result<()> { + const REGION: &str = "screen"; + if let Some(screen) = self.get_region(REGION) { + for (index, byte) in screen.iter().enumerate() { + if index % 8 == 0 { + print!("|"); + } + print!( + "{}", + format!("{byte:08b}").replace('0', " ").replace('1', "██") + ); + if index % 8 == 7 { + println!("|"); + } + } + } else { + return Err(crate::error::Error::MissingRegion { + region: REGION.to_string(), + }); + } + Ok(()) } } -impl Read for NewBus { +impl Read for Bus { + fn read(&self, addr: impl Into) -> u8 { + let addr: usize = addr.into(); + *self.memory.get(addr).unwrap_or(&0xc5) + } +} + +impl Read for Bus { fn read(&self, addr: impl Into) -> u16 { let addr: usize = addr.into(); if let Some(bytes) = self.memory.get(addr..addr + 2) { @@ -120,11 +151,10 @@ impl Read for NewBus { } else { 0xc5c5 } - //u16::from_le_bytes(self.memory.get([addr;2])) } } -impl Write for NewBus { +impl Write for Bus { fn write(&mut self, addr: impl Into, data: u8) { let addr: usize = addr.into(); if let Some(byte) = self.get_mut(addr) { @@ -133,183 +163,29 @@ impl Write for NewBus { } } -impl Write for NewBus { +impl Write for Bus { fn write(&mut self, addr: impl Into, data: u16) { let addr: usize = addr.into(); if let Some(slice) = self.get_mut(addr..addr + 2) { - slice.swap_with_slice(data.to_be_bytes().as_mut()) - } - } -} - -impl Display for NewBus { - fn fmt(&self, f: &mut Formatter<'_>) -> Result { - use rhexdump::Rhexdump; - let mut rhx = Rhexdump::default(); - rhx.set_bytes_per_group(2).expect("2 <= MAX_BYTES_PER_GROUP (8)"); - rhx.display_duplicate_lines(false); - write!(f, "{}", rhx.hexdump(&self.memory)) - } -} - -/// BusConnectable objects can be connected to a bus with `Bus::connect()` -/// -/// The bus performs address translation, so your object will receive -/// reads and writes relative to offset 0 -pub trait BusConnectible: Debug + Display { - fn read_at(&self, addr: u16) -> Option; - fn write_to(&mut self, addr: u16, data: u8); - fn get_mut(&mut self, addr: u16) -> Option<&mut u8>; -} - -// Traits Read and Write are here purely to make implementing other things more bearable -/// Do whatever `Read` means to you -pub trait Read { - /// Read a T from address `addr` - fn read(&self, addr: impl Into) -> T; -} - -/// Write "some data" to the Bus -pub trait Write { - /// Write a T to address `addr` - fn write(&mut self, addr: impl Into, data: T); -} - -/// The Bus connects bus readers with bus writers. -/// The design assumes single-threaded operation. -#[derive(Debug, Default)] -pub struct Bus { - devices: Vec, -} - -impl Bus { - /// Construct a new bus - pub fn new() -> Self { - Bus::default() - } - /// Connect a BusConnectible object to the bus - pub fn connect( - mut self, - name: &str, - range: Range, - device: Box, - ) -> Self { - self.devices.push(BusDevice::new(name, range, device)); - self - } - pub fn get_region_by_name(&mut self, name: &str) -> Option<&mut BusDevice> { - self.devices.iter_mut().find(|item| item.name == name) - } -} - -/// lmao -impl BusConnectible for Bus { - fn read_at(&self, addr: u16) -> Option { - let mut result: u8 = 0; - for item in &self.devices { - result |= item.read_at(addr).unwrap_or(0) - } - Some(result) - } - fn write_to(&mut self, addr: u16, data: u8) { - for item in &mut self.devices { - item.write_to(addr, data) - } - } - - fn get_mut(&mut self, addr: u16) -> Option<&mut u8> { - for item in &mut self.devices { - if let Some(mutable) = item.get_mut(addr) { - return Some(mutable); - } - } - None - } -} - -impl Read for Bus { - fn read(&self, addr: impl Into) -> u8 { - let addr = addr.into() as u16; - let mut result: u8 = 0; - for item in &self.devices { - result |= item.read_at(addr).unwrap_or(0) - } - result - } -} - -impl Read for Bus { - fn read(&self, addr: impl Into) -> u16 { - let addr = addr.into() as u16; - let mut result = 0; - result |= (self.read_at(addr).unwrap_or(0) as u16) << 8; - result |= self.read_at(addr.wrapping_add(1)).unwrap_or(0) as u16; - result - } -} - -impl Write for Bus { - fn write(&mut self, addr: impl Into, data: u8) { - let addr = addr.into() as u16; - for item in &mut self.devices { - item.write_to(addr, data) - } - } -} - -impl Write for Bus { - fn write(&mut self, addr: impl Into, data: u16) { - let addr = addr.into() as u16; - self.write_to(addr, (data >> 8) as u8); - self.write_to(addr.wrapping_add(1), data as u8); - } -} - -impl Write for Bus { - fn write(&mut self, addr: impl Into, data: u32) { - let addr = addr.into() as u16; - for i in 0..4 { - self.write_to(addr.wrapping_add(i), (data >> (3 - i * 8)) as u8); + data.to_be_bytes().as_mut().swap_with_slice(slice); } } } impl Display for Bus { - fn fmt(&self, f: &mut Formatter<'_>) -> Result { - for device in &self.devices { - write!(f, "{device}")?; + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + use rhexdump::Rhexdump; + let mut rhx = Rhexdump::default(); + rhx.set_bytes_per_group(2) + .expect("2 <= MAX_BYTES_PER_GROUP (8)"); + rhx.display_duplicate_lines(false); + for (&name, range) in &self.region { + writeln!( + f, + "[{name}]\n{}\n", + rhx.hexdump(&self.memory[range.clone()]) + )? } write!(f, "") } } - -impl Dumpable for Bus { - fn dump(&self, range: Range) { - for (index, byte) in self - .into_iter() - .range(range.start as u16..range.end as u16) // this causes a truncation - .enumerate() - { - crate::dump::as_hexdump(index, byte); - } - } -} - -impl BinDumpable for Bus { - fn bin_dump(&self, range: Range) { - for index in range { - let byte: u8 = self.read(index as u16); - crate::dump::as_bindump(index, byte) - } - } -} - -impl<'a> IntoIterator for &'a Bus { - type Item = u8; - - type IntoIter = iterator::BusIterator<'a>; - - fn into_iter(self) -> Self::IntoIter { - BusIterator::new(0..u16::MAX, self) - } -} diff --git a/src/cpu.rs b/src/cpu.rs index 3bd628a..faf000f 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -3,47 +3,37 @@ pub mod disassemble; use self::disassemble::Disassemble; -use crate::bus::{Read, Write}; +use crate::bus::{Bus, Read, Write}; use owo_colors::OwoColorize; +use rand::random; +use std::time::Instant; type Reg = usize; type Adr = u16; type Nib = u8; -#[derive(Clone, Debug, Default, PartialEq)] -pub struct CPUBuilder { - screen: Option, - font: Option, - pc: Option, - sp: Option, +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ControlFlags { + pub debug: bool, + pub pause: bool, + pub keypause: bool, + pub authentic: bool, } -impl CPUBuilder { - pub fn new() -> Self { - CPUBuilder { - screen: None, - font: None, - pc: None, - sp: None, - } +impl ControlFlags { + pub fn debug(&mut self) { + self.debug = !self.debug } - pub fn build(self) -> CPU { - CPU { - screen: self.screen.unwrap_or(0xF00), - font: self.font.unwrap_or(0x050), - pc: self.pc.unwrap_or(0x200), - sp: self.sp.unwrap_or(0xefe), - i: 0, - v: [0; 16], - delay: 0, - sound: 0, - cycle: 0, - keys: 0, - disassembler: Disassemble::default(), - } + pub fn pause(&mut self) { + self.pause = !self.pause } } +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Keys { + keys: [bool; 16], +} + #[derive(Clone, Debug, PartialEq)] pub struct CPU { // memory map info @@ -57,19 +47,30 @@ pub struct CPU { delay: u8, sound: u8, // I/O - keys: usize, + pub keys: [bool; 16], + pub flags: ControlFlags, // Execution data cycle: usize, + breakpoints: Vec, disassembler: Disassemble, } // public interface impl CPU { /// Press keys (where `keys` is a bitmap of the keys [F-0]) - pub fn press(mut self, keys: u16) -> Self { - self.keys = keys as usize; - self + pub fn press(&mut self, key: usize) { + if (0..16).contains(&key) { + self.keys[key] = true; + self.flags.keypause = false; + } } + /// Release all keys + pub fn release(&mut self) { + for key in &mut self.keys { + *key = false; + } + } + /// Set a general purpose register in the CPU /// # Examples /// ```rust @@ -80,11 +81,10 @@ impl CPU { /// // Dump the CPU registers /// cpu.dump(); /// ``` - pub fn set_gpr(mut self, gpr: Reg, value: u8) -> Self { + pub fn set_gpr(&mut self, gpr: Reg, value: u8) { if let Some(gpr) = self.v.get_mut(gpr) { *gpr = value; } - self } /// Constructs a new CPU with sane defaults @@ -112,23 +112,80 @@ impl CPU { delay: 0, sound: 0, cycle: 0, - keys: 0, + keys: [false; 16], + breakpoints: vec![], + flags: ControlFlags { + debug: true, + ..Default::default() + }, } } - pub fn tick(&mut self, bus: &mut B) - where - B: Read + Write + Read + Write - { - std::print!("{:3} {:03x}: ", self.cycle.bright_black(), self.pc); + /// Get the program counter + pub fn pc(&self) -> Adr { + self.pc + } + + /// Soft resets the CPU, releasing keypause and reinitializing the program counter to 0x200 + pub fn soft_reset(&mut self) { + self.pc = 0x200; + self.flags.keypause = false; + } + + /// Set a breakpoint + pub fn set_break(&mut self, point: Adr) { + if !self.breakpoints.contains(&point) { + self.breakpoints.push(point) + } + } + + /// Unset a breakpoint + pub fn unset_break(&mut self, point: Adr) { + fn linear_find(needle: Adr, haystack: &Vec) -> Option { + for (i, v) in haystack.iter().enumerate() { + if *v == needle { + return Some(i); + } + } + None + } + if let Some(idx) = linear_find(point, &self.breakpoints) { + assert_eq!(point, self.breakpoints.swap_remove(idx)); + } + } + + /// Unpauses the emulator for a single tick + /// NOTE: does not synchronize with delay timers + pub fn singlestep(&mut self, bus: &mut Bus) { + self.flags.pause = false; + self.tick(bus); + self.flags.pause = true; + } + + /// Ticks the delay and sound timers + pub fn tick_timer(&mut self) { + if self.flags.pause { + return; + } + self.delay = self.delay.saturating_sub(1); + self.sound = self.sound.saturating_sub(1); + } + + /// Runs a single instruction + pub fn tick(&mut self, bus: &mut Bus) { + // Do nothing if paused + if self.flags.pause || self.flags.keypause { + return; + } + let time = Instant::now(); // fetch opcode let opcode: u16 = bus.read(self.pc); + let pc = self.pc; + // DINC pc self.pc = self.pc.wrapping_add(2); // decode opcode - // Print opcode disassembly: - std::println!("{}", self.disassembler.instruction(opcode)); use disassemble::{a, b, i, n, x, y}; let (i, x, y, n, b, a) = ( i(opcode), @@ -246,9 +303,24 @@ impl CPU { _ => self.unimplemented(opcode), }, _ => unimplemented!("Extracted nibble from byte, got >nibble?"), + + } + let elapsed = time.elapsed(); + // Print opcode disassembly: + if self.flags.debug { + std::println!( + "{:3} {:03x}: {:<36}{:?}", + self.cycle.bright_black(), + pc, + self.disassembler.instruction(opcode), + elapsed.dimmed() + ); } - self.cycle += 1; + // process breakpoints + if self.breakpoints.contains(&self.pc) { + self.flags.pause = true; + } } pub fn dump(&self) { @@ -275,7 +347,24 @@ impl CPU { impl Default for CPU { fn default() -> Self { - CPUBuilder::new().build() + CPU { + screen: 0xf00, + font: 0x050, + pc: 0x200, + sp: 0xefe, + i: 0, + v: [0; 16], + delay: 0, + sound: 0, + cycle: 0, + keys: [false; 16], + flags: ControlFlags { + debug: true, + ..Default::default() + }, + breakpoints: vec![], + disassembler: Disassemble::default(), + } } } @@ -293,9 +382,11 @@ impl CPU { } /// 00e0: Clears the screen memory to 0 #[inline] - fn clear_screen(&mut self, bus: &mut impl Write) { - for addr in self.screen..self.screen + 0x100 { - bus.write(addr, 0u8); + fn clear_screen(&mut self, bus: &mut Bus) { + if let Some(screen) = bus.get_region_mut("screen") { + for byte in screen { + *byte = 0; + } } //use dump::BinDumpable; //bus.bin_dump(self.screen as usize..self.screen as usize + 0x100); @@ -351,9 +442,9 @@ impl CPU { } /// Set the carry register (vF) after math #[inline] - fn set_carry(&mut self, x: Reg, y: Reg, f: fn(u16, u16) -> u16) -> u8 { + fn set_carry(&mut self, x: Reg, y: Reg, f: fn(u16, u16) -> u16, inv: bool) -> u8 { let sum = f(self.v[x] as u16, self.v[y] as u16); - self.v[0xf] = if sum & 0xff00 != 0 { 1 } else { 0 }; + self.v[0xf] = if (sum & 0xff00 != 0) ^ inv { 1 } else { 0 }; (sum & 0xff) as u8 } /// 8xy0: Loads the value of y into x @@ -379,22 +470,24 @@ impl CPU { /// 8xy4: Performs addition of vX and vY, and stores the result in vX #[inline] fn x_addequals_y(&mut self, x: Reg, y: Reg) { - self.v[x] = self.set_carry(x, y, u16::wrapping_add); + self.v[x] = self.set_carry(x, y, u16::wrapping_add, false); } /// 8xy5: Performs subtraction of vX and vY, and stores the result in vX #[inline] fn x_subequals_y(&mut self, x: Reg, y: Reg) { - self.v[x] = self.set_carry(x, y, u16::wrapping_sub); + self.v[x] = self.set_carry(x, y, u16::wrapping_sub, true); } /// 8xy6: Performs bitwise right shift of vX #[inline] fn shift_right_x(&mut self, x: Reg) { + let shift_out = self.v[x] & 1; self.v[x] >>= 1; + self.v[0xf] = shift_out; } /// 8xy7: Performs subtraction of vY and vX, and stores the result in vX #[inline] fn backwards_subtract(&mut self, x: Reg, y: Reg) { - self.v[x] = self.set_carry(y, x, u16::wrapping_sub); + self.v[x] = self.set_carry(y, x, u16::wrapping_sub, true); } /// 8X_E: Performs bitwise left shift of vX #[inline] @@ -421,48 +514,48 @@ impl CPU { fn jump_indexed(&mut self, a: Adr) { self.pc = a.wrapping_add(self.v[0] as Adr); } - /// Cxbb: Stores a random number + the provided byte into vX - /// Pretty sure the input byte is supposed to be the seed of a LFSR or something + /// Cxbb: Stores a random number & the provided byte into vX #[inline] fn rand(&mut self, x: Reg, b: u8) { - // TODO: Random Number Generator - todo!("{}", format_args!("rand\t#{b:X}, v{x:x}").red()); + self.v[x] = random::() & b; } /// Dxyn: Draws n-byte sprite to the screen at coordinates (vX, vY) #[inline] - fn draw(&mut self, x: Reg, y: Reg, n: Nib, bus: &mut I) - where - I: Read + Read - { - println!("{}", format_args!("draw\t#{n:x}, v{x:x}, v{y:x}").red()); + fn draw(&mut self, x: Reg, y: Reg, n: Nib, bus: &mut Bus) { + // println!("{}", format_args!("draw\t#{n:x}, (x: {:x}, y: {:x})", self.v[x], self.v[y]).green()); + let (x, y) = (self.v[x], self.v[y]); self.v[0xf] = 0; - // TODO: Repeat for all N for byte in 0..n as u16 { - // TODO: Calculate the lower bound address based on the X,Y position on the screen - let lower_bound = ((y as u16 + byte) * 8) + x as u16 / 8; - // TODO: Read a byte of sprite data into a u16, and shift it x % 8 bits - let sprite_line: u8 = bus.read(self.i); - // TODO: Read a u16 from the bus containing the two bytes which might need to be updated - let screen_word: u16 = bus.read(self.screen + lower_bound); - // TODO: Update the screen word by XORing the sprite byte - todo!("{sprite_line}, {screen_word}") + // Calculate the lower bound address based on the X,Y position on the screen + let addr = ((y as u16 + byte) * 8) + (x / 8) as u16 + self.screen; + // Read a byte of sprite data into a u16, and shift it x % 8 bits + let sprite: u8 = bus.read(self.i + byte); + let sprite = (sprite as u16) << 1 + (7 - x % 8); + // Read a u16 from the bus containing the two bytes which might need to be updated + let mut screen: u16 = bus.read(addr); + // Save the bits-toggled-off flag if necessary + if screen & sprite != 0 { + self.v[0xF] = 1 + } + // Update the screen word by XORing the sprite byte + screen ^= sprite; + // Save the result back to the screen + bus.write(addr, screen); } } /// Ex9E: Skip next instruction if key == #X #[inline] fn skip_if_key_equals_x(&mut self, x: Reg) { - std::println!("{}", format_args!("sek\tv{x:x}")); - if self.keys >> x & 1 == 1 { - std::println!("KEY == {x}"); + let x = self.v[x] as usize; + if self.keys[x] { self.pc += 2; } } /// ExaE: Skip next instruction if key != #X #[inline] fn skip_if_key_not_x(&mut self, x: Reg) { - std::println!("{}", format_args!("snek\tv{x:x}")); - if self.keys >> x & 1 == 0 { - std::println!("KEY != {x}"); + let x = self.v[x] as usize; + if !self.keys[x] { self.pc += 2; } } @@ -477,9 +570,17 @@ impl CPU { /// Fx0A: Wait for key, then vX = K #[inline] fn wait_for_key(&mut self, x: Reg) { - // TODO: I/O - - std::println!("{}", format_args!("waitk\tv{x:x}").red()); + let mut pressed = false; + for bit in 0..16 { + if self.keys[bit] { + self.v[x] = bit as u8; + pressed = true; + } + } + if !pressed { + self.pc = self.pc.wrapping_sub(2); + self.flags.keypause = true; + } } /// Fx15: Load vX into DT /// ```py @@ -511,29 +612,46 @@ impl CPU { /// ``` #[inline] fn load_sprite_x(&mut self, x: Reg) { - self.i = self.font + (5 * x as Adr); + self.i = self.font + (5 * (self.v[x] as Adr % 0x10)); } /// Fx33: BCD convert X into I`[0..3]` #[inline] - fn bcd_convert_i(&mut self, x: Reg, _bus: &mut impl Write) { - // TODO: I/O - - std::println!("{}", format_args!("bcd\t{x:x}, &I").red()); + fn bcd_convert_i(&mut self, x: Reg, bus: &mut Bus) { + let x = self.v[x]; + bus.write(self.i.wrapping_add(2), x % 10); + bus.write(self.i.wrapping_add(1), x / 10 % 10); + bus.write(self.i, x / 100 % 10); } /// Fx55: DMA Stor from I to registers 0..X #[inline] - fn dma_store(&mut self, x: Reg, bus: &mut impl Write) { - for reg in 0..=x { - bus.write(self.i + reg as u16, self.v[reg]); + fn dma_store(&mut self, x: Reg, bus: &mut Bus) { + let i = self.i as usize; + for (reg, value) in bus + .get_mut(i..=i + x) + .unwrap_or_default() + .iter_mut() + .enumerate() + { + *value = self.v[reg] + } + if self.flags.authentic { + self.i += x as Adr + 1; } - self.i += x as Adr + 1; } /// Fx65: DMA Load from I to registers 0..X #[inline] - fn dma_load(&mut self, x: Reg, bus: &mut impl Read) { - for reg in 0..=x { - self.v[reg] = bus.read(self.i + reg as u16); + fn dma_load(&mut self, x: Reg, bus: &mut Bus) { + let i = self.i as usize; + for (reg, value) in bus + .get(i + 0..=i + x) + .unwrap_or_default() + .iter() + .enumerate() + { + self.v[reg] = *value; + } + if self.flags.authentic { + self.i += x as Adr + 1; } - self.i += x as Adr + 1; } } diff --git a/src/cpu/disassemble.rs b/src/cpu/disassemble.rs index 6562fc8..421e32e 100644 --- a/src/cpu/disassemble.rs +++ b/src/cpu/disassemble.rs @@ -178,190 +178,190 @@ impl Disassemble { impl Disassemble { /// Unused instructions fn unimplemented(&self, opcode: u16) -> String { - format!("inval\t{opcode:04x}") + format!("inval {opcode:04x}") .style(self.invalid) .to_string() } /// `0aaa`: Handles a "machine language function call" (lmao) pub fn sys(&self, a: Adr) -> String { - format!("sysc\t{a:03x}").style(self.invalid).to_string() + format!("sysc {a:03x}").style(self.invalid).to_string() } /// `00e0`: Clears the screen memory to 0 pub fn clear_screen(&self) -> String { - "cls".style(self.normal).to_string() + "cls ".style(self.normal).to_string() } /// `00ee`: Returns from subroutine pub fn ret(&self) -> String { - "ret".style(self.normal).to_string() + "ret ".style(self.normal).to_string() } /// `1aaa`: Sets the program counter to an absolute address pub fn jump(&self, a: Adr) -> String { - format!("jmp\t{a:03x}").style(self.normal).to_string() + format!("jmp {a:03x}").style(self.normal).to_string() } /// `2aaa`: Pushes pc onto the stack, then jumps to a pub fn call(&self, a: Adr) -> String { - format!("call\t{a:03x}").style(self.normal).to_string() + format!("call {a:03x}").style(self.normal).to_string() } /// `3xbb`: Skips the next instruction if register X == b pub fn skip_if_x_equal_byte(&self, x: Reg, b: u8) -> String { - format!("se\t#{b:02x}, v{x:X}") + format!("se #{b:02x}, v{x:X}") .style(self.normal) .to_string() } /// `4xbb`: Skips the next instruction if register X != b pub fn skip_if_x_not_equal_byte(&self, x: Reg, b: u8) -> String { - format!("sne\t#{b:02x}, v{x:X}") + format!("sne #{b:02x}, v{x:X}") .style(self.normal) .to_string() } /// `5xy0`: Skips the next instruction if register X != register Y pub fn skip_if_x_equal_y(&self, x: Reg, y: Reg) -> String { - format!("se\tv{x:X}, v{y:X}").style(self.normal).to_string() + format!("se v{x:X}, v{y:X}").style(self.normal).to_string() } /// `6xbb`: Loads immediate byte b into register vX pub fn load_immediate(&self, x: Reg, b: u8) -> String { - format!("mov\t#{b:02x}, v{x:X}") + format!("mov #{b:02x}, v{x:X}") .style(self.normal) .to_string() } /// `7xbb`: Adds immediate byte b to register vX pub fn add_immediate(&self, x: Reg, b: u8) -> String { - format!("add\t#{b:02x}, v{x:X}") + format!("add #{b:02x}, v{x:X}") .style(self.normal) .to_string() } /// `8xy0`: Loads the value of y into x pub fn load_y_into_x(&self, x: Reg, y: Reg) -> String { - format!("mov\tv{y:X}, v{x:X}") + format!("mov v{y:X}, v{x:X}") .style(self.normal) .to_string() } /// `8xy1`: Performs bitwise or of vX and vY, and stores the result in vX pub fn x_orequals_y(&self, x: Reg, y: Reg) -> String { - format!("or\tv{y:X}, v{x:X}").style(self.normal).to_string() + format!("or v{y:X}, v{x:X}").style(self.normal).to_string() } /// `8xy2`: Performs bitwise and of vX and vY, and stores the result in vX pub fn x_andequals_y(&self, x: Reg, y: Reg) -> String { - format!("and\tv{y:X}, v{x:X}") + format!("and v{y:X}, v{x:X}") .style(self.normal) .to_string() } /// `8xy3`: Performs bitwise xor of vX and vY, and stores the result in vX pub fn x_xorequals_y(&self, x: Reg, y: Reg) -> String { - format!("xor\tv{y:X}, v{x:X}") + format!("xor v{y:X}, v{x:X}") .style(self.normal) .to_string() } /// `8xy4`: Performs addition of vX and vY, and stores the result in vX pub fn x_addequals_y(&self, x: Reg, y: Reg) -> String { - format!("add\tv{y:X}, v{x:X}") + format!("add v{y:X}, v{x:X}") .style(self.normal) .to_string() } /// `8xy5`: Performs subtraction of vX and vY, and stores the result in vX pub fn x_subequals_y(&self, x: Reg, y: Reg) -> String { - format!("sub\tv{y:X}, v{x:X}") + format!("sub v{y:X}, v{x:X}") .style(self.normal) .to_string() } /// `8xy6`: Performs bitwise right shift of vX pub fn shift_right_x(&self, x: Reg) -> String { - format!("shr\tv{x:X}").style(self.normal).to_string() + format!("shr v{x:X}").style(self.normal).to_string() } /// `8xy7`: Performs subtraction of vY and vX, and stores the result in vX pub fn backwards_subtract(&self, x: Reg, y: Reg) -> String { - format!("bsub\tv{y:X}, v{x:X}") + format!("bsub v{y:X}, v{x:X}") .style(self.normal) .to_string() } /// 8X_E: Performs bitwise left shift of vX pub fn shift_left_x(&self, x: Reg) -> String { - format!("shl\tv{x:X}").style(self.normal).to_string() + format!("shl v{x:X}").style(self.normal).to_string() } /// `9xy0`: Skip next instruction if X != y pub fn skip_if_x_not_equal_y(&self, x: Reg, y: Reg) -> String { - format!("sn\tv{x:X}, v{y:X}").style(self.normal).to_string() + format!("sne v{x:X}, v{y:X}").style(self.normal).to_string() } /// Aadr: Load address #adr into register I pub fn load_indirect_register(&self, a: Adr) -> String { - format!("mov\t${a:03x}, I").style(self.normal).to_string() + format!("mov ${a:03x}, I").style(self.normal).to_string() } /// Badr: Jump to &adr + v0 pub fn jump_indexed(&self, a: Adr) -> String { - format!("jmp\t${a:03x}+v0").style(self.normal).to_string() + format!("jmp ${a:03x}+v0").style(self.normal).to_string() } /// `Cxbb`: Stores a random number + the provided byte into vX /// Pretty sure the input byte is supposed to be the seed of a LFSR or something pub fn rand(&self, x: Reg, b: u8) -> String { - format!("rand\t#{b:X}, v{x:X}") + format!("rand #{b:X}, v{x:X}") .style(self.normal) .to_string() } /// `Dxyn`: Draws n-byte sprite to the screen at coordinates (vX, vY) pub fn draw(&self, x: Reg, y: Reg, n: Nib) -> String { #[rustfmt::skip] - format!("draw\t#{n:x}, v{x:X}, v{y:X}").style(self.normal).to_string() + format!("draw #{n:x}, v{x:X}, v{y:X}").style(self.normal).to_string() } /// `Ex9E`: Skip next instruction if key == #X pub fn skip_if_key_equals_x(&self, x: Reg) -> String { - format!("sek\tv{x:X}").style(self.normal).to_string() + format!("sek v{x:X}").style(self.normal).to_string() } /// `ExaE`: Skip next instruction if key != #X pub fn skip_if_key_not_x(&self, x: Reg) -> String { - format!("snek\tv{x:X}").style(self.normal).to_string() + format!("snek v{x:X}").style(self.normal).to_string() } /// `Fx07`: Get the current DT, and put it in vX /// ```py /// vX = DT /// ``` pub fn get_delay_timer(&self, x: Reg) -> String { - format!("mov\tDT, v{x:X}").style(self.normal).to_string() + format!("mov DT, v{x:X}").style(self.normal).to_string() } /// `Fx0A`: Wait for key, then vX = K pub fn wait_for_key(&self, x: Reg) -> String { - format!("waitk\tv{x:X}").style(self.normal).to_string() + format!("waitk v{x:X}").style(self.normal).to_string() } /// `Fx15`: Load vX into DT /// ```py /// DT = vX /// ``` pub fn load_delay_timer(&self, x: Reg) -> String { - format!("ld\tv{x:X}, DT").style(self.normal).to_string() + format!("ld v{x:X}, DT").style(self.normal).to_string() } /// `Fx18`: Load vX into ST /// ```py /// ST = vX; /// ``` pub fn load_sound_timer(&self, x: Reg) -> String { - format!("ld\tv{x:X}, ST").style(self.normal).to_string() + format!("ld v{x:X}, ST").style(self.normal).to_string() } /// `Fx1e`: Add vX to I, /// ```py /// I += vX; /// ``` pub fn add_to_indirect(&self, x: Reg) -> String { - format!("add\tv{x:X}, I").style(self.normal).to_string() + format!("add v{x:X}, I").style(self.normal).to_string() } /// `Fx29`: Load sprite for character x into I /// ```py /// I = sprite(X); /// ``` pub fn load_sprite_x(&self, x: Reg) -> String { - format!("font\t#{x:X}, I").style(self.normal).to_string() + format!("font #{x:X}, I").style(self.normal).to_string() } /// `Fx33`: BCD convert X into I`[0..3]` pub fn bcd_convert_i(&self, x: Reg) -> String { - format!("bcd\t{x:X}, &I").style(self.normal).to_string() + format!("bcd {x:X}, &I").style(self.normal).to_string() } /// `Fx55`: DMA Stor from I to registers 0..X pub fn dma_store(&self, x: Reg) -> String { - format!("dmao\t{x:X}").style(self.normal).to_string() + format!("dmao {x:X}").style(self.normal).to_string() } /// `Fx65`: DMA Load from I to registers 0..X pub fn dma_load(&self, x: Reg) -> String { - format!("dmai\t{x:X}").style(self.normal).to_string() + format!("dmai {x:X}").style(self.normal).to_string() } } diff --git a/src/error.rs b/src/error.rs index 522ed7a..3e405ae 100644 --- a/src/error.rs +++ b/src/error.rs @@ -3,10 +3,16 @@ use thiserror::Error; pub type Result = std::result::Result; -#[derive(Clone, Debug, Error, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Error)] pub enum Error { #[error("Unrecognized opcode {word}")] UnimplementedInstruction { word: u16 }, #[error("Math was funky when parsing {word}: {explanation}")] FunkyMath { word: u16, explanation: String }, + #[error("No {region} found on bus")] + MissingRegion { region: String }, + #[error(transparent)] + IoError(#[from] std::io::Error), + #[error(transparent)] + WindowError(#[from] minifb::Error) } diff --git a/src/io.rs b/src/io.rs new file mode 100644 index 0000000..0bfb863 --- /dev/null +++ b/src/io.rs @@ -0,0 +1,125 @@ +//! + +use crate::{bus::Bus, cpu::CPU, error::Result}; +use minifb::*; + +#[derive(Clone, Copy, Debug)] +pub struct WindowBuilder { + pub width: usize, + pub height: usize, + pub name: Option<&'static str>, + pub window_options: WindowOptions, +} + +impl WindowBuilder { + pub fn new(height: usize, width: usize) -> Self { + WindowBuilder { + width, + height, + ..Default::default() + } + } + pub fn build(self) -> Result { + Ok(Window::new( + self.name.unwrap_or_default(), + self.width, + self.height, + self.window_options, + )?) + } +} + +impl Default for WindowBuilder { + fn default() -> Self { + WindowBuilder { + width: 64, + height: 32, + name: Some("Chip-8 Interpreter"), + window_options: WindowOptions { + title: true, + resize: false, + scale: Scale::X16, + scale_mode: ScaleMode::AspectRatioStretch, + none: true, + ..Default::default() + }, + } + } +} + +pub struct FrameBufferFormat { + pub fg: u32, + pub bg: u32 +} + +impl Default for FrameBufferFormat { + fn default() -> Self { + FrameBufferFormat { fg: 0x0011a434, bg: 0x001E2431 } + } +} + +pub struct FrameBuffer { + buffer: Vec, + width: usize, + height: usize, + format: FrameBufferFormat, +} + +impl FrameBuffer { + pub fn new(width: usize, height: usize) -> Self { + FrameBuffer { + buffer: vec![0x00be4d; width * height], + width, + height, + format: Default::default(), + } + } + pub fn render(&mut self, window: &mut Window, bus: &Bus) { + if let Some(screen) = bus.get_region("screen") { + for (idx, byte) in screen.iter().enumerate() { + for bit in 0..8 { + self.buffer[8 * idx + bit] = if byte & (1 << 7 - bit) as u8 != 0 { + self.format.fg + } else { + self.format.bg + } + } + } + } + //TODO: NOT THIS + window + .update_with_buffer(&self.buffer, self.width, self.height) + .expect("The window manager has encountered an issue I don't want to deal with"); + } +} + +pub fn identify_key(key: Key) -> usize { + match key { + Key::Key1 => 0x1, + Key::Key2 => 0x2, + Key::Key3 => 0x3, + Key::Key4 => 0xc, + Key::Q => 0x4, + Key::W => 0x5, + Key::E => 0x6, + Key::R => 0xD, + Key::A => 0x7, + Key::S => 0x8, + Key::D => 0x9, + Key::F => 0xE, + Key::Z => 0xA, + Key::X => 0x0, + Key::C => 0xB, + Key::V => 0xF, + _ => 0x10, + } +} + +/// Gets keys from the Window, and feeds them directly to the CPU +pub fn get_keys(window: &mut Window, cpu: &mut CPU) { + cpu.release(); + window + .get_keys() + .iter() + .for_each(|key| cpu.press(identify_key(*key))); +} diff --git a/src/lib.rs b/src/lib.rs index e3eacc7..7c10aa9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -#![feature(let_chains, stmt_expr_attributes)] +#![feature(stmt_expr_attributes)] /*! This crate implements a Chip-8 interpreter as if it were a real CPU architecture, to the best of my current knowledge. As it's the first emulator project I've @@ -9,20 +9,17 @@ Hopefully, though, you'll find some use in it. pub mod bus; pub mod cpu; -pub mod mem; +pub mod io; pub mod dump; pub mod error; -pub mod screen; /// Common imports for chumpulator pub mod prelude { use super::*; pub use crate::bus; - pub use crate::newbus; - pub use bus::{Bus, BusConnectible, Read, Write}; + pub use bus::{Bus, Read, Write}; pub use cpu::{disassemble::Disassemble, CPU}; pub use dump::{BinDumpable, Dumpable}; - pub use mem::Mem; - pub use screen::Screen; + pub use io::{*, WindowBuilder}; } diff --git a/src/main.rs b/src/main.rs index b0db83a..9b1cb3b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,71 +1,154 @@ -use chumpulator::{bus::Read, prelude::*}; +//! Chirp: A chip-8 interpreter in Rust +//! Hello, world! + +use chirp::{error::Result, prelude::*}; +use gumdrop::*; +use minifb::*; use std::fs::read; -use std::time::{Duration, Instant}; +use std::{ + path::PathBuf, + time::{Duration, Instant}, +}; -/// What I want: -/// I want a data bus that stores as much memory as I need to implement a chip 8 emulator -/// I want that data bus to hold named memory ranges and have a way to get a memory region +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Options, Hash)] +struct Arguments { + #[options(help = "Enable behavior incompatible with modern software")] + pub authentic: bool, + #[options( + help = "Set breakpoints for the emulator to stop at", + parse(try_from_str = "parse_hex") + )] + pub breakpoints: Vec, + #[options(help = "Enable debug mode at startup")] + pub debug: bool, + #[options(help = "Enable pause mode at startup")] + pub pause: bool, + #[options(help = "Load a ROM to run on Chirp")] + pub file: PathBuf, +} -fn main() -> Result<(), std::io::Error> { - let mut now; - println!("Building Bus..."); - let mut time = Instant::now(); +fn parse_hex(value: &str) -> std::result::Result { + u16::from_str_radix(value, 16) +} + +fn main() -> Result<()> { + let options = Arguments::parse_args_default_or_exit(); + + // Create the data bus let mut bus = bus! { // Load the charset into ROM - "charset" [0x0050..0x00a0] = Mem::new(0x50).load_charset(0).w(false), + "charset" [0x0050..0x00A0] = include_bytes!("mem/charset.bin"), // Load the ROM file into RAM - "userram" [0x0200..0x0F00] = Mem::new(0xF00 - 0x200).load(0, &read("chip-8/Fishie.ch8")?), - // Create a screen - "screen" [0x0F00..0x1000] = Mem::new(32*64/8), - // Create some stack memory - "stack" [0xF000..0xF800] = Mem::new(0x800).r(true).w(true), - }; - now = time.elapsed(); - println!("Elapsed: {:?}\nBuilding NewBus...", now); - time = Instant::now(); - let mut newbus = newbus! { - // Load the charset into ROM - "charset" [0x0050..0x00a0] = include_bytes!("mem/charset.bin"), - // Load the ROM file into RAM - "userram" [0x0200..0x0F00] = &read("chip-8/Fishie.ch8")?, + "userram" [0x0200..0x0F00] = &read(options.file)?, // Create a screen "screen" [0x0F00..0x1000], // Create some stack memory - "stack" [0x2000..0x2800], + //"stack" [0x2000..0x2100], }; - now = time.elapsed(); - println!("Elapsed: {:?}", now); - println!("{newbus}"); - let disassembler = Disassemble::default(); - if false { - for addr in 0x200..0x290 { - if addr % 2 == 0 { - println!( - "{addr:03x}: {}", - disassembler.instruction(bus.read(addr as usize)) - ); + // let disassembler = Disassemble::default(); + // if false { + // for addr in 0x200..0x290 { + // if addr % 2 == 0 { + // println!( + // "{addr:03x}: {}", + // disassembler.instruction(bus.read(addr as usize)) + // ); + // } + // } + // } + + let mut cpu = CPU::new(0xf00, 0x50, 0x200, 0x20fe, Disassemble::default()); + for point in options.breakpoints { + cpu.set_break(point); + } + cpu.flags.authentic = options.authentic; + cpu.flags.debug = options.debug; + cpu.flags.pause = options.pause; + let mut framebuffer = FrameBuffer::new(64, 32); + let mut window = WindowBuilder::default().build()?; + let mut frame_time = Instant::now(); + let mut step_time = Instant::now(); + + framebuffer.render(&mut window, &mut bus); + + cpu.flags.pause = false; + cpu.flags.debug = true; + + loop { + if !cpu.flags.pause { + cpu.tick(&mut bus); + } + while frame_time.elapsed() > Duration::from_micros(16000) { + if cpu.flags.pause { + window.set_title("Chirp ⏸") + } else { + window.set_title("Chirp ▶") + } + frame_time += Duration::from_micros(16000); + // tick sound and delay timers + cpu.tick_timer(); + // update framebuffer + framebuffer.render(&mut window, &mut bus); + // get key input (has to happen after framebuffer) + get_keys(&mut window, &mut cpu); + // handle keys at the + for key in window.get_keys_pressed(KeyRepeat::No) { + use Key::*; + match key { + F1 => cpu.dump(), + F2 => bus + .print_screen() + .expect("The 'screen' memory region exists"), + F3 => { + println!( + "{}", + endis("Debug", { + cpu.flags.debug(); + cpu.flags.debug + }) + ) + } + F4 => println!( + "{}", + endis("Pause", { + cpu.flags.pause(); + cpu.flags.pause + }) + ), + F5 => { + println!("Step"); + cpu.singlestep(&mut bus) + } + F6 => { + println!("Set breakpoint {:x}", cpu.pc()); + cpu.set_break(cpu.pc()) + } + F7 => { + println!("Unset breakpoint {:x}", cpu.pc()); + cpu.unset_break(cpu.pc()) + } + F8 => { + println!("Soft reset CPU {:x}", cpu.pc()); + cpu.soft_reset(); + bus.clear_region("screen"); + } + F9 => { + println!("Hard reset CPU"); + cpu = CPU::default(); + bus.clear_region("screen"); + } + Escape => return Ok(()), + _ => (), + } } } + std::thread::sleep(Duration::from_micros(1666).saturating_sub(step_time.elapsed())); + step_time = Instant::now(); } - - let mut cpu = CPU::new(0xf00, 0x50, 0x200, 0xf7fe, disassembler); - let mut cpu2 = cpu.clone(); - println!("Old Bus:"); - for _instruction in 0..6 { - time = Instant::now(); - cpu.tick(&mut bus); - now = time.elapsed(); - println!(" Elapsed: {:?}", now); - std::thread::sleep(Duration::from_micros(2000).saturating_sub(time.elapsed())); - } - println!("New Bus:"); - for _instruction in 0..6 { - time = Instant::now(); - cpu2.tick(&mut newbus); - now = time.elapsed(); - println!(" Elapsed: {:?}", now); - std::thread::sleep(Duration::from_micros(2000).saturating_sub(time.elapsed())); - } - Ok(()) + //Ok(()) +} + +fn endis(name: &str, state: bool) -> String { + format!("{name} {}", if state { "enabled" } else { "disabled" }) } diff --git a/src/mem.rs b/src/mem.rs deleted file mode 100644 index e46e8c9..0000000 --- a/src/mem.rs +++ /dev/null @@ -1,153 +0,0 @@ -//! Mem covers WOM, ROM, and RAM - -use crate::{bus::BusConnectible, dump::Dumpable}; -use owo_colors::{OwoColorize, Style}; -use std::{ - fmt::{Display, Formatter, Result}, - ops::Range, -}; - -const MSIZE: usize = 0x1000; - -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -struct Attr { - pub r: bool, - pub w: bool, -} - -pub struct MemWindow<'a> { - mem: &'a [u8], -} - -impl<'a> Display for MemWindow<'a> { - fn fmt(&self, f: &mut Formatter<'_>) -> Result { - // Green phosphor style formatting, for taste - let term: Style = Style::new().bold().green().on_black(); - for (index, byte) in self.mem.iter().enumerate() { - if index % 16 == 0 { - write!(f, "{:>03x}{} ", index.style(term), ":".style(term))? - } - write!(f, "{byte:02x}")?; - write!( - f, - "{}", - match index % 16 { - 0xf => "\n", - 0x7 => " ", - _ if index % 2 == 1 => " ", - _ => "", - } - )? - } - write!(f, "") - } -} - -/// Represents some kind of abstract memory chip -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct Mem { - mem: Vec, - attr: Attr, -} - -impl Mem { - pub fn r(mut self, readable: bool) -> Self { - self.attr.r = readable; - self - } - pub fn w(mut self, writable: bool) -> Self { - self.attr.w = writable; - self - } - /// Returns the number of bytes in the `Mem`, also referred to as its length. - /// - /// # Examples - /// ``` rust - /// # use chumpulator::prelude::*; - /// let mem = Mem::new(0x100); - /// assert_eq!(mem.len(), 0x100) - /// ``` - pub fn len(&self) -> usize { - self.mem.len() - } - - /// Because clippy is so kind: - pub fn is_empty(&self) -> bool { - self.mem.is_empty() - } - - /// Loads data into a `Mem` - pub fn load(mut self, addr: u16, bytes: &[u8]) -> Self { - let addr = addr as usize; - let end = self.mem.len().min(addr + bytes.len()); - self.mem[addr..end].copy_from_slice(&bytes[0..bytes.len()]); - self - } - - /// Load a character set from chumpulator/src/mem/charset.bin into this memory section - pub fn load_charset(self, addr: u16) -> Self { - let charset = include_bytes!("mem/charset.bin"); - self.load(addr, charset) - } - - /// Creates a new `mem` with the specified length - /// - /// # Examples - /// ```rust - /// # use chumpulator::prelude::*; - /// let length = 0x100; - /// let mem = Mem::new(length); - /// ``` - pub fn new(len: usize) -> Self { - Mem { - mem: vec![0; len], - attr: Attr { r: true, w: true }, - } - } - - /// Creates a window into the Mem which implements Display - pub fn window(&self, range: Range) -> MemWindow { - MemWindow { - mem: &self.mem[range], - } - } -} - -impl Default for Mem { - fn default() -> Self { - Self::new(MSIZE) - } -} - -impl Display for Mem { - fn fmt(&self, f: &mut Formatter<'_>) -> Result { - write!(f, "{}", self.window(0..self.len())) - } -} - -impl Dumpable for Mem { - fn dump(&self, range: Range) { - print!("Mem {range:2x?}: "); - print!("{}", self.window(range)); - } -} - -impl BusConnectible for Mem { - fn read_at(&self, addr: u16) -> Option { - if !self.attr.r { - return None; - } - self.mem.get(addr as usize).copied() - } - fn write_to(&mut self, addr: u16, data: u8) { - if self.attr.w && let Some(value) = self.mem.get_mut(addr as usize) { - *value = data - } - } - fn get_mut(&mut self, addr: u16) -> Option<&mut u8> { - if !self.attr.w { - return None; - } - self.mem.get_mut(addr as usize) - } -} diff --git a/src/screen.rs b/src/screen.rs deleted file mode 100644 index 2ab84e8..0000000 --- a/src/screen.rs +++ /dev/null @@ -1,30 +0,0 @@ -//! Stores and displays the Chip-8's screen memory - -#![allow(unused_imports)] - -use crate::{bus::BusConnectible, dump::Dumpable, mem::Mem}; -use std::{ - fmt::{Display, Formatter, Result}, - ops::Range, -}; - -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct Screen { - pub width: usize, - pub height: usize, -} - -impl Screen { - pub fn new(width: usize, height: usize) -> Screen { - Screen { width, height } - } -} - -impl Default for Screen { - fn default() -> Self { - Screen { - width: 64, - height: 32, - } - } -}