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 version = 3
[[package]] [[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" version = "0.1.0"
dependencies = [ dependencies = [
"gumdrop",
"hex-wrapper",
"minifb",
"owo-colors", "owo-colors",
"rand",
"rhexdump", "rhexdump",
"serde", "serde",
"thiserror", "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]] [[package]]
name = "owo-colors" name = "owo-colors"
version = "3.5.0" version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" 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]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.51" version = "1.0.51"
@ -36,12 +446,115 @@ dependencies = [
"proc-macro2", "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]] [[package]]
name = "rhexdump" name = "rhexdump"
version = "0.1.1" version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5e9af64574935e39f24d1c0313a997c8b880ca0e087c888bc6af8af31579847" 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]] [[package]]
name = "serde" name = "serde"
version = "1.0.154" version = "1.0.154"
@ -62,6 +575,21 @@ dependencies = [
"syn", "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]] [[package]]
name = "syn" name = "syn"
version = "1.0.109" version = "1.0.109"
@ -73,6 +601,19 @@ dependencies = [
"unicode-ident", "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]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.39" version = "1.0.39"
@ -98,3 +639,293 @@ name = "unicode-ident"
version = "1.0.8" version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" 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] [package]
name = "chumpulator" name = "chirp"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
gumdrop = "0.8.1"
hex-wrapper = "1.3.2"
minifb = "0.24.0"
owo-colors = "^3" owo-colors = "^3"
rand = "0.8.5"
rhexdump = "0.1.1" rhexdump = "0.1.1"
serde = { version = "^1.0", features = ["derive"] } serde = { version = "^1.0", features = ["derive"] }
thiserror = "1.0.39" thiserror = "1.0.39"

View File

@ -1,13 +1,9 @@
//! The Bus connects the CPU to Memory //! The Bus connects the CPU to Memory
mod bus_device;
mod iterator;
use crate::dump::{BinDumpable, Dumpable}; use crate::error::Result;
use bus_device::BusDevice;
use iterator::BusIterator;
use std::{ use std::{
collections::HashMap, collections::HashMap,
fmt::{Debug, Display, Formatter, Result}, fmt::{Debug, Display, Formatter},
ops::Range, ops::Range,
slice::SliceIndex, slice::SliceIndex,
}; };
@ -17,24 +13,14 @@ use std::{
/// ```rust /// ```rust
/// # use chumpulator::prelude::*; /// # use chumpulator::prelude::*;
/// let mut bus = bus! { /// let mut bus = bus! {
/// "RAM" [0x0000..0x8000] Mem::new(0x8000), /// "RAM" [0x0000..0x8000],
/// "ROM" [0x8000..0xFFFF] Mem::new(0x8000).w(false), /// "ROM" [0x8000..0xFFFF],
/// }; /// };
/// ``` /// ```
#[macro_export] #[macro_export]
macro_rules! bus { 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)?) ,* $(,)?) => { ($($name:literal $(:)? [$range:expr] $(= $data:expr)?) ,* $(,)?) => {
$crate::bus::NewBus::new() $crate::bus::Bus::new()
$( $(
.add_region($name, $range) .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 /// Store memory in a series of named regions with ranges
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct NewBus { pub struct Bus {
memory: Vec<u8>, memory: Vec<u8>,
region: HashMap<&'static str, Range<usize>>, region: HashMap<&'static str, Range<usize>>,
} }
impl NewBus { impl Bus {
/// Construct a new bus /// Construct a new bus
pub fn new() -> Self { pub fn new() -> Self {
NewBus::default() Bus::default()
} }
/// Gets the length of the bus' backing memory /// Gets the length of the bus' backing memory
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
@ -65,7 +67,7 @@ impl NewBus {
self.memory.is_empty() self.memory.is_empty()
} }
/// Grows the NewBus backing memory to at least size bytes, but does not truncate /// 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 { if self.len() < size {
self.memory.resize(size, 0); self.memory.resize(size, 0);
} }
@ -82,6 +84,12 @@ impl NewBus {
} }
self 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 /// Gets a slice of bus memory
pub fn get<I>(&self, index: I) -> Option<&<I as SliceIndex<[u8]>>::Output> pub fn get<I>(&self, index: I) -> Option<&<I as SliceIndex<[u8]>>::Output>
where where
@ -104,15 +112,38 @@ impl NewBus {
pub fn get_region_mut(&mut self, name: &str) -> Option<&mut [u8]> { pub fn get_region_mut(&mut self, name: &str) -> Option<&mut [u8]> {
self.get_mut(self.region.get(name)?.clone()) self.get_mut(self.region.get(name)?.clone())
} }
} pub fn print_screen(&self) -> Result<()> {
const REGION: &str = "screen";
impl Read<u8> for NewBus { if let Some(screen) = self.get_region(REGION) {
fn read(&self, addr: impl Into<usize>) -> u8 { for (index, byte) in screen.iter().enumerate() {
*self.memory.get(addr.into()).unwrap_or(&0xc5) 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 { fn read(&self, addr: impl Into<usize>) -> u16 {
let addr: usize = addr.into(); let addr: usize = addr.into();
if let Some(bytes) = self.memory.get(addr..addr + 2) { if let Some(bytes) = self.memory.get(addr..addr + 2) {
@ -120,11 +151,10 @@ impl Read<u16> for NewBus {
} else { } else {
0xc5c5 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) { fn write(&mut self, addr: impl Into<usize>, data: u8) {
let addr: usize = addr.into(); let addr: usize = addr.into();
if let Some(byte) = self.get_mut(addr) { 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) { fn write(&mut self, addr: impl Into<usize>, data: u16) {
let addr: usize = addr.into(); let addr: usize = addr.into();
if let Some(slice) = self.get_mut(addr..addr + 2) { if let Some(slice) = self.get_mut(addr..addr + 2) {
slice.swap_with_slice(data.to_be_bytes().as_mut()) data.to_be_bytes().as_mut().swap_with_slice(slice);
}
}
}
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);
} }
} }
} }
impl Display for Bus { impl Display for Bus {
fn fmt(&self, f: &mut Formatter<'_>) -> Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
for device in &self.devices { use rhexdump::Rhexdump;
write!(f, "{device}")?; 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, "") 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; pub mod disassemble;
use self::disassemble::Disassemble; use self::disassemble::Disassemble;
use crate::bus::{Read, Write}; use crate::bus::{Bus, Read, Write};
use owo_colors::OwoColorize; use owo_colors::OwoColorize;
use rand::random;
use std::time::Instant;
type Reg = usize; type Reg = usize;
type Adr = u16; type Adr = u16;
type Nib = u8; type Nib = u8;
#[derive(Clone, Debug, Default, PartialEq)] #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct CPUBuilder { pub struct ControlFlags {
screen: Option<Adr>, pub debug: bool,
font: Option<Adr>, pub pause: bool,
pc: Option<Adr>, pub keypause: bool,
sp: Option<Adr>, pub authentic: bool,
} }
impl CPUBuilder { impl ControlFlags {
pub fn new() -> Self { pub fn debug(&mut self) {
CPUBuilder { self.debug = !self.debug
screen: None,
font: None,
pc: None,
sp: None,
}
} }
pub fn build(self) -> CPU { pub fn pause(&mut self) {
CPU { self.pause = !self.pause
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(),
}
} }
} }
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Keys {
keys: [bool; 16],
}
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct CPU { pub struct CPU {
// memory map info // memory map info
@ -57,19 +47,30 @@ pub struct CPU {
delay: u8, delay: u8,
sound: u8, sound: u8,
// I/O // I/O
keys: usize, pub keys: [bool; 16],
pub flags: ControlFlags,
// Execution data // Execution data
cycle: usize, cycle: usize,
breakpoints: Vec<Adr>,
disassembler: Disassemble, disassembler: Disassemble,
} }
// public interface // public interface
impl CPU { impl CPU {
/// Press keys (where `keys` is a bitmap of the keys [F-0]) /// Press keys (where `keys` is a bitmap of the keys [F-0])
pub fn press(mut self, keys: u16) -> Self { pub fn press(&mut self, key: usize) {
self.keys = keys as usize; if (0..16).contains(&key) {
self 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 /// Set a general purpose register in the CPU
/// # Examples /// # Examples
/// ```rust /// ```rust
@ -80,11 +81,10 @@ impl CPU {
/// // Dump the CPU registers /// // Dump the CPU registers
/// cpu.dump(); /// 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) { if let Some(gpr) = self.v.get_mut(gpr) {
*gpr = value; *gpr = value;
} }
self
} }
/// Constructs a new CPU with sane defaults /// Constructs a new CPU with sane defaults
@ -112,23 +112,80 @@ impl CPU {
delay: 0, delay: 0,
sound: 0, sound: 0,
cycle: 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) /// Get the program counter
where pub fn pc(&self) -> Adr {
B: Read<u8> + Write<u8> + Read<u16> + Write<u16> self.pc
{ }
std::print!("{:3} {:03x}: ", self.cycle.bright_black(), 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 // fetch opcode
let opcode: u16 = bus.read(self.pc); let opcode: u16 = bus.read(self.pc);
let pc = self.pc;
// DINC pc // DINC pc
self.pc = self.pc.wrapping_add(2); self.pc = self.pc.wrapping_add(2);
// decode opcode // decode opcode
// Print opcode disassembly:
std::println!("{}", self.disassembler.instruction(opcode));
use disassemble::{a, b, i, n, x, y}; use disassemble::{a, b, i, n, x, y};
let (i, x, y, n, b, a) = ( let (i, x, y, n, b, a) = (
i(opcode), i(opcode),
@ -246,9 +303,24 @@ impl CPU {
_ => self.unimplemented(opcode), _ => self.unimplemented(opcode),
}, },
_ => unimplemented!("Extracted nibble from byte, got >nibble?"), _ => 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; self.cycle += 1;
// process breakpoints
if self.breakpoints.contains(&self.pc) {
self.flags.pause = true;
}
} }
pub fn dump(&self) { pub fn dump(&self) {
@ -275,7 +347,24 @@ impl CPU {
impl Default for CPU { impl Default for CPU {
fn default() -> Self { 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 /// 00e0: Clears the screen memory to 0
#[inline] #[inline]
fn clear_screen(&mut self, bus: &mut impl Write<u8>) { fn clear_screen(&mut self, bus: &mut Bus) {
for addr in self.screen..self.screen + 0x100 { if let Some(screen) = bus.get_region_mut("screen") {
bus.write(addr, 0u8); for byte in screen {
*byte = 0;
}
} }
//use dump::BinDumpable; //use dump::BinDumpable;
//bus.bin_dump(self.screen as usize..self.screen as usize + 0x100); //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 /// Set the carry register (vF) after math
#[inline] #[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); 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 (sum & 0xff) as u8
} }
/// 8xy0: Loads the value of y into x /// 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 /// 8xy4: Performs addition of vX and vY, and stores the result in vX
#[inline] #[inline]
fn x_addequals_y(&mut self, x: Reg, y: Reg) { 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 /// 8xy5: Performs subtraction of vX and vY, and stores the result in vX
#[inline] #[inline]
fn x_subequals_y(&mut self, x: Reg, y: Reg) { 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 /// 8xy6: Performs bitwise right shift of vX
#[inline] #[inline]
fn shift_right_x(&mut self, x: Reg) { fn shift_right_x(&mut self, x: Reg) {
let shift_out = self.v[x] & 1;
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 /// 8xy7: Performs subtraction of vY and vX, and stores the result in vX
#[inline] #[inline]
fn backwards_subtract(&mut self, x: Reg, y: Reg) { 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 /// 8X_E: Performs bitwise left shift of vX
#[inline] #[inline]
@ -421,48 +514,48 @@ impl CPU {
fn jump_indexed(&mut self, a: Adr) { fn jump_indexed(&mut self, a: Adr) {
self.pc = a.wrapping_add(self.v[0] as Adr); self.pc = a.wrapping_add(self.v[0] as Adr);
} }
/// Cxbb: Stores a random number + the provided byte into vX /// 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
#[inline] #[inline]
fn rand(&mut self, x: Reg, b: u8) { fn rand(&mut self, x: Reg, b: u8) {
// TODO: Random Number Generator self.v[x] = random::<u8>() & b;
todo!("{}", format_args!("rand\t#{b:X}, v{x:x}").red());
} }
/// Dxyn: Draws n-byte sprite to the screen at coordinates (vX, vY) /// Dxyn: Draws n-byte sprite to the screen at coordinates (vX, vY)
#[inline] #[inline]
fn draw<I>(&mut self, x: Reg, y: Reg, n: Nib, bus: &mut I) fn draw(&mut self, x: Reg, y: Reg, n: Nib, bus: &mut Bus) {
where // println!("{}", format_args!("draw\t#{n:x}, (x: {:x}, y: {:x})", self.v[x], self.v[y]).green());
I: Read<u8> + Read<u16> let (x, y) = (self.v[x], self.v[y]);
{
println!("{}", format_args!("draw\t#{n:x}, v{x:x}, v{y:x}").red());
self.v[0xf] = 0; self.v[0xf] = 0;
// TODO: Repeat for all N
for byte in 0..n as u16 { for byte in 0..n as u16 {
// TODO: Calculate the lower bound address based on the X,Y position on the screen // 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; let addr = ((y as u16 + byte) * 8) + (x / 8) as u16 + self.screen;
// TODO: Read a byte of sprite data into a u16, and shift it x % 8 bits // Read a byte of sprite data into a u16, and shift it x % 8 bits
let sprite_line: u8 = bus.read(self.i); let sprite: u8 = bus.read(self.i + byte);
// TODO: Read a u16 from the bus containing the two bytes which might need to be updated let sprite = (sprite as u16) << 1 + (7 - x % 8);
let screen_word: u16 = bus.read(self.screen + lower_bound); // Read a u16 from the bus containing the two bytes which might need to be updated
// TODO: Update the screen word by XORing the sprite byte let mut screen: u16 = bus.read(addr);
todo!("{sprite_line}, {screen_word}") // 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 /// Ex9E: Skip next instruction if key == #X
#[inline] #[inline]
fn skip_if_key_equals_x(&mut self, x: Reg) { fn skip_if_key_equals_x(&mut self, x: Reg) {
std::println!("{}", format_args!("sek\tv{x:x}")); let x = self.v[x] as usize;
if self.keys >> x & 1 == 1 { if self.keys[x] {
std::println!("KEY == {x}");
self.pc += 2; self.pc += 2;
} }
} }
/// ExaE: Skip next instruction if key != #X /// ExaE: Skip next instruction if key != #X
#[inline] #[inline]
fn skip_if_key_not_x(&mut self, x: Reg) { fn skip_if_key_not_x(&mut self, x: Reg) {
std::println!("{}", format_args!("snek\tv{x:x}")); let x = self.v[x] as usize;
if self.keys >> x & 1 == 0 { if !self.keys[x] {
std::println!("KEY != {x}");
self.pc += 2; self.pc += 2;
} }
} }
@ -477,9 +570,17 @@ impl CPU {
/// Fx0A: Wait for key, then vX = K /// Fx0A: Wait for key, then vX = K
#[inline] #[inline]
fn wait_for_key(&mut self, x: Reg) { fn wait_for_key(&mut self, x: Reg) {
// TODO: I/O let mut pressed = false;
for bit in 0..16 {
std::println!("{}", format_args!("waitk\tv{x:x}").red()); 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 /// Fx15: Load vX into DT
/// ```py /// ```py
@ -511,29 +612,46 @@ impl CPU {
/// ``` /// ```
#[inline] #[inline]
fn load_sprite_x(&mut self, x: Reg) { 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]` /// Fx33: BCD convert X into I`[0..3]`
#[inline] #[inline]
fn bcd_convert_i(&mut self, x: Reg, _bus: &mut impl Write<u8>) { fn bcd_convert_i(&mut self, x: Reg, bus: &mut Bus) {
// TODO: I/O let x = self.v[x];
bus.write(self.i.wrapping_add(2), x % 10);
std::println!("{}", format_args!("bcd\t{x:x}, &I").red()); 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 /// Fx55: DMA Stor from I to registers 0..X
#[inline] #[inline]
fn dma_store(&mut self, x: Reg, bus: &mut impl Write<u8>) { fn dma_store(&mut self, x: Reg, bus: &mut Bus) {
for reg in 0..=x { let i = self.i as usize;
bus.write(self.i + reg as u16, self.v[reg]); 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 /// Fx65: DMA Load from I to registers 0..X
#[inline] #[inline]
fn dma_load(&mut self, x: Reg, bus: &mut impl Read<u8>) { fn dma_load(&mut self, x: Reg, bus: &mut Bus) {
for reg in 0..=x { let i = self.i as usize;
self.v[reg] = bus.read(self.i + reg as u16); 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 { impl Disassemble {
/// Unused instructions /// Unused instructions
fn unimplemented(&self, opcode: u16) -> String { fn unimplemented(&self, opcode: u16) -> String {
format!("inval\t{opcode:04x}") format!("inval {opcode:04x}")
.style(self.invalid) .style(self.invalid)
.to_string() .to_string()
} }
/// `0aaa`: Handles a "machine language function call" (lmao) /// `0aaa`: Handles a "machine language function call" (lmao)
pub fn sys(&self, a: Adr) -> String { 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 /// `00e0`: Clears the screen memory to 0
pub fn clear_screen(&self) -> String { pub fn clear_screen(&self) -> String {
"cls".style(self.normal).to_string() "cls ".style(self.normal).to_string()
} }
/// `00ee`: Returns from subroutine /// `00ee`: Returns from subroutine
pub fn ret(&self) -> String { 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 /// `1aaa`: Sets the program counter to an absolute address
pub fn jump(&self, a: Adr) -> String { 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 /// `2aaa`: Pushes pc onto the stack, then jumps to a
pub fn call(&self, a: Adr) -> String { 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 /// `3xbb`: Skips the next instruction if register X == b
pub fn skip_if_x_equal_byte(&self, x: Reg, b: u8) -> String { 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) .style(self.normal)
.to_string() .to_string()
} }
/// `4xbb`: Skips the next instruction if register X != b /// `4xbb`: Skips the next instruction if register X != b
pub fn skip_if_x_not_equal_byte(&self, x: Reg, b: u8) -> String { 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) .style(self.normal)
.to_string() .to_string()
} }
/// `5xy0`: Skips the next instruction if register X != register Y /// `5xy0`: Skips the next instruction if register X != register Y
pub fn skip_if_x_equal_y(&self, x: Reg, y: Reg) -> String { 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 /// `6xbb`: Loads immediate byte b into register vX
pub fn load_immediate(&self, x: Reg, b: u8) -> String { 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) .style(self.normal)
.to_string() .to_string()
} }
/// `7xbb`: Adds immediate byte b to register vX /// `7xbb`: Adds immediate byte b to register vX
pub fn add_immediate(&self, x: Reg, b: u8) -> String { 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) .style(self.normal)
.to_string() .to_string()
} }
/// `8xy0`: Loads the value of y into x /// `8xy0`: Loads the value of y into x
pub fn load_y_into_x(&self, x: Reg, y: Reg) -> String { 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) .style(self.normal)
.to_string() .to_string()
} }
/// `8xy1`: Performs bitwise or of vX and vY, and stores the result in vX /// `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 { 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 /// `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 { 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) .style(self.normal)
.to_string() .to_string()
} }
/// `8xy3`: Performs bitwise xor of vX and vY, and stores the result in vX /// `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 { 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) .style(self.normal)
.to_string() .to_string()
} }
/// `8xy4`: Performs addition of vX and vY, and stores the result in vX /// `8xy4`: Performs addition of vX and vY, and stores the result in vX
pub fn x_addequals_y(&self, x: Reg, y: Reg) -> String { 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) .style(self.normal)
.to_string() .to_string()
} }
/// `8xy5`: Performs subtraction of vX and vY, and stores the result in vX /// `8xy5`: Performs subtraction of vX and vY, and stores the result in vX
pub fn x_subequals_y(&self, x: Reg, y: Reg) -> String { 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) .style(self.normal)
.to_string() .to_string()
} }
/// `8xy6`: Performs bitwise right shift of vX /// `8xy6`: Performs bitwise right shift of vX
pub fn shift_right_x(&self, x: Reg) -> String { 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 /// `8xy7`: Performs subtraction of vY and vX, and stores the result in vX
pub fn backwards_subtract(&self, x: Reg, y: Reg) -> String { 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) .style(self.normal)
.to_string() .to_string()
} }
/// 8X_E: Performs bitwise left shift of vX /// 8X_E: Performs bitwise left shift of vX
pub fn shift_left_x(&self, x: Reg) -> String { 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 /// `9xy0`: Skip next instruction if X != y
pub fn skip_if_x_not_equal_y(&self, x: Reg, y: Reg) -> String { 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 /// Aadr: Load address #adr into register I
pub fn load_indirect_register(&self, a: Adr) -> String { 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 /// Badr: Jump to &adr + v0
pub fn jump_indexed(&self, a: Adr) -> String { 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 /// `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 /// 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 { 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) .style(self.normal)
.to_string() .to_string()
} }
/// `Dxyn`: Draws n-byte sprite to the screen at coordinates (vX, vY) /// `Dxyn`: Draws n-byte sprite to the screen at coordinates (vX, vY)
pub fn draw(&self, x: Reg, y: Reg, n: Nib) -> String { pub fn draw(&self, x: Reg, y: Reg, n: Nib) -> String {
#[rustfmt::skip] #[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 /// `Ex9E`: Skip next instruction if key == #X
pub fn skip_if_key_equals_x(&self, x: Reg) -> String { 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 /// `ExaE`: Skip next instruction if key != #X
pub fn skip_if_key_not_x(&self, x: Reg) -> String { 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 /// `Fx07`: Get the current DT, and put it in vX
/// ```py /// ```py
/// vX = DT /// vX = DT
/// ``` /// ```
pub fn get_delay_timer(&self, x: Reg) -> String { 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 /// `Fx0A`: Wait for key, then vX = K
pub fn wait_for_key(&self, x: Reg) -> String { 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 /// `Fx15`: Load vX into DT
/// ```py /// ```py
/// DT = vX /// DT = vX
/// ``` /// ```
pub fn load_delay_timer(&self, x: Reg) -> String { 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 /// `Fx18`: Load vX into ST
/// ```py /// ```py
/// ST = vX; /// ST = vX;
/// ``` /// ```
pub fn load_sound_timer(&self, x: Reg) -> String { 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, /// `Fx1e`: Add vX to I,
/// ```py /// ```py
/// I += vX; /// I += vX;
/// ``` /// ```
pub fn add_to_indirect(&self, x: Reg) -> String { 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 /// `Fx29`: Load sprite for character x into I
/// ```py /// ```py
/// I = sprite(X); /// I = sprite(X);
/// ``` /// ```
pub fn load_sprite_x(&self, x: Reg) -> String { 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]` /// `Fx33`: BCD convert X into I`[0..3]`
pub fn bcd_convert_i(&self, x: Reg) -> String { 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 /// `Fx55`: DMA Stor from I to registers 0..X
pub fn dma_store(&self, x: Reg) -> String { 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 /// `Fx65`: DMA Load from I to registers 0..X
pub fn dma_load(&self, x: Reg) -> String { 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; use thiserror::Error;
pub type Result<T> = std::result::Result<T, Error>; pub type Result<T> = std::result::Result<T, Error>;
#[derive(Clone, Debug, Error, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Error)]
pub enum Error { pub enum Error {
#[error("Unrecognized opcode {word}")] #[error("Unrecognized opcode {word}")]
UnimplementedInstruction { word: u16 }, UnimplementedInstruction { word: u16 },
#[error("Math was funky when parsing {word}: {explanation}")] #[error("Math was funky when parsing {word}: {explanation}")]
FunkyMath { word: u16, explanation: String }, 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, 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 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 bus;
pub mod cpu; pub mod cpu;
pub mod mem; pub mod io;
pub mod dump; pub mod dump;
pub mod error; pub mod error;
pub mod screen;
/// Common imports for chumpulator /// Common imports for chumpulator
pub mod prelude { pub mod prelude {
use super::*; use super::*;
pub use crate::bus; pub use crate::bus;
pub use crate::newbus; pub use bus::{Bus, Read, Write};
pub use bus::{Bus, BusConnectible, Read, Write};
pub use cpu::{disassemble::Disassemble, CPU}; pub use cpu::{disassemble::Disassemble, CPU};
pub use dump::{BinDumpable, Dumpable}; pub use dump::{BinDumpable, Dumpable};
pub use mem::Mem; pub use io::{*, WindowBuilder};
pub use screen::Screen;
} }

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::fs::read;
use std::time::{Duration, Instant}; use std::{
path::PathBuf,
time::{Duration, Instant},
};
/// What I want: #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Options, Hash)]
/// I want a data bus that stores as much memory as I need to implement a chip 8 emulator struct Arguments {
/// I want that data bus to hold named memory ranges and have a way to get a memory region #[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> { fn parse_hex(value: &str) -> std::result::Result<u16, std::num::ParseIntError> {
let mut now; u16::from_str_radix(value, 16)
println!("Building Bus..."); }
let mut time = Instant::now();
fn main() -> Result<()> {
let options = Arguments::parse_args_default_or_exit();
// Create the data bus
let mut bus = bus! { let mut bus = bus! {
// Load the charset into ROM // 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 // Load the ROM file into RAM
"userram" [0x0200..0x0F00] = Mem::new(0xF00 - 0x200).load(0, &read("chip-8/Fishie.ch8")?), "userram" [0x0200..0x0F00] = &read(options.file)?,
// 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")?,
// Create a screen // Create a screen
"screen" [0x0F00..0x1000], "screen" [0x0F00..0x1000],
// Create some stack memory // Create some stack memory
"stack" [0x2000..0x2800], //"stack" [0x2000..0x2100],
}; };
now = time.elapsed();
println!("Elapsed: {:?}", now);
println!("{newbus}");
let disassembler = Disassemble::default(); // let disassembler = Disassemble::default();
if false { // if false {
for addr in 0x200..0x290 { // for addr in 0x200..0x290 {
if addr % 2 == 0 { // if addr % 2 == 0 {
println!( // println!(
"{addr:03x}: {}", // "{addr:03x}: {}",
disassembler.instruction(bus.read(addr as usize)) // 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();
} }
//Ok(())
let mut cpu = CPU::new(0xf00, 0x50, 0x200, 0xf7fe, disassembler); }
let mut cpu2 = cpu.clone();
println!("Old Bus:"); fn endis(name: &str, state: bool) -> String {
for _instruction in 0..6 { format!("{name} {}", if state { "enabled" } else { "disabled" })
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(())
} }

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