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
This commit is contained in:
John 2023-03-22 15:03:53 -05:00
parent ef3d765651
commit dc61bd0087
11 changed files with 1434 additions and 577 deletions

833
Cargo.lock generated
View File

@ -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"

View File

@ -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"

View File

@ -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<T> {
/// Read a T from address `addr`
fn read(&self, addr: impl Into<usize>) -> T;
}
/// Write "some data" to the Bus
pub trait Write<T> {
/// Write a T to address `addr`
fn write(&mut self, addr: impl Into<usize>, 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<u8>,
region: HashMap<&'static str, Range<usize>>,
}
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<I>(&self, index: I) -> Option<&<I as SliceIndex<[u8]>>::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<u8> for NewBus {
fn read(&self, addr: impl Into<usize>) -> 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<u16> for NewBus {
impl Read<u8> for Bus {
fn read(&self, addr: impl Into<usize>) -> u8 {
let addr: usize = addr.into();
*self.memory.get(addr).unwrap_or(&0xc5)
}
}
impl Read<u16> for Bus {
fn read(&self, addr: impl Into<usize>) -> u16 {
let addr: usize = addr.into();
if let Some(bytes) = self.memory.get(addr..addr + 2) {
@ -120,11 +151,10 @@ impl Read<u16> for NewBus {
} else {
0xc5c5
}
//u16::from_le_bytes(self.memory.get([addr;2]))
}
}
impl Write<u8> for NewBus {
impl Write<u8> for Bus {
fn write(&mut self, addr: impl Into<usize>, data: u8) {
let addr: usize = addr.into();
if let Some(byte) = self.get_mut(addr) {
@ -133,183 +163,29 @@ impl Write<u8> for NewBus {
}
}
impl Write<u16> for NewBus {
impl Write<u16> for Bus {
fn write(&mut self, addr: impl Into<usize>, 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<u8>;
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<T> {
/// Read a T from address `addr`
fn read(&self, addr: impl Into<usize>) -> T;
}
/// Write "some data" to the Bus
pub trait Write<T> {
/// Write a T to address `addr`
fn write(&mut self, addr: impl Into<usize>, data: T);
}
/// The Bus connects bus readers with bus writers.
/// The design assumes single-threaded operation.
#[derive(Debug, Default)]
pub struct Bus {
devices: Vec<BusDevice>,
}
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<u16>,
device: Box<dyn BusConnectible>,
) -> 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<u8> {
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<u8> for Bus {
fn read(&self, addr: impl Into<usize>) -> 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<u16> for Bus {
fn read(&self, addr: impl Into<usize>) -> 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<u8> for Bus {
fn write(&mut self, addr: impl Into<usize>, data: u8) {
let addr = addr.into() as u16;
for item in &mut self.devices {
item.write_to(addr, data)
}
}
}
impl Write<u16> for Bus {
fn write(&mut self, addr: impl Into<usize>, 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<u32> for Bus {
fn write(&mut self, addr: impl Into<usize>, 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<usize>) {
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<usize>) {
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)
}
}

View File

@ -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<Adr>,
font: Option<Adr>,
pc: Option<Adr>,
sp: Option<Adr>,
#[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<Adr>,
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<B>(&mut self, bus: &mut B)
where
B: Read<u8> + Write<u8> + Read<u16> + Write<u16>
{
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<Adr>) -> Option<usize> {
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<u8>) {
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::<u8>() & b;
}
/// Dxyn: Draws n-byte sprite to the screen at coordinates (vX, vY)
#[inline]
fn draw<I>(&mut self, x: Reg, y: Reg, n: Nib, bus: &mut I)
where
I: Read<u8> + Read<u16>
{
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<u8>) {
// 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<u8>) {
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<u8>) {
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;
}
}

View File

@ -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()
}
}

View File

@ -3,10 +3,16 @@
use thiserror::Error;
pub type Result<T> = std::result::Result<T, Error>;
#[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)
}

125
src/io.rs Normal file
View File

@ -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<Window> {
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<u32>,
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)));
}

View File

@ -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};
}

View File

@ -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<u16>,
#[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, std::num::ParseIntError> {
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" })
}

View File

@ -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<u8>,
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<usize>) -> 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<usize>) {
print!("Mem {range:2x?}: ");
print!("{}", self.window(range));
}
}
impl BusConnectible for Mem {
fn read_at(&self, addr: u16) -> Option<u8> {
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)
}
}

View File

@ -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,
}
}
}