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:
parent
ef3d765651
commit
dc61bd0087
833
Cargo.lock
generated
833
Cargo.lock
generated
@ -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"
|
||||
|
@ -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"
|
||||
|
266
src/bus.rs
266
src/bus.rs
@ -1,13 +1,9 @@
|
||||
//! The Bus connects the CPU to Memory
|
||||
mod bus_device;
|
||||
mod iterator;
|
||||
|
||||
use crate::dump::{BinDumpable, Dumpable};
|
||||
use bus_device::BusDevice;
|
||||
use iterator::BusIterator;
|
||||
use crate::error::Result;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt::{Debug, Display, Formatter, Result},
|
||||
fmt::{Debug, Display, Formatter},
|
||||
ops::Range,
|
||||
slice::SliceIndex,
|
||||
};
|
||||
@ -17,24 +13,14 @@ use std::{
|
||||
/// ```rust
|
||||
/// # use chumpulator::prelude::*;
|
||||
/// let mut bus = bus! {
|
||||
/// "RAM" [0x0000..0x8000] Mem::new(0x8000),
|
||||
/// "ROM" [0x8000..0xFFFF] Mem::new(0x8000).w(false),
|
||||
/// "RAM" [0x0000..0x8000],
|
||||
/// "ROM" [0x8000..0xFFFF],
|
||||
/// };
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! bus {
|
||||
($($name:literal $(:)? [$range:expr] $(=)? $d:expr) ,* $(,)?) => {
|
||||
$crate::bus::Bus::new()
|
||||
$(
|
||||
.connect($name, $range, Box::new($d))
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! newbus {
|
||||
($($name:literal $(:)? [$range:expr] $(= $data:expr)?) ,* $(,)?) => {
|
||||
$crate::bus::NewBus::new()
|
||||
$crate::bus::Bus::new()
|
||||
$(
|
||||
.add_region($name, $range)
|
||||
$(
|
||||
@ -44,17 +30,33 @@ macro_rules! newbus {
|
||||
};
|
||||
}
|
||||
|
||||
// Traits Read and Write are here purely to make implementing other things more bearable
|
||||
/// Do whatever `Read` means to you
|
||||
pub trait Read<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 {
|
||||
@ -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())
|
||||
}
|
||||
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<u8> for NewBus {
|
||||
impl Read<u8> for Bus {
|
||||
fn read(&self, addr: impl Into<usize>) -> u8 {
|
||||
*self.memory.get(addr.into()).unwrap_or(&0xc5)
|
||||
let addr: usize = addr.into();
|
||||
*self.memory.get(addr).unwrap_or(&0xc5)
|
||||
}
|
||||
}
|
||||
|
||||
impl Read<u16> for NewBus {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
300
src/cpu.rs
300
src/cpu.rs
@ -3,45 +3,35 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
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(),
|
||||
impl ControlFlags {
|
||||
pub fn debug(&mut self) {
|
||||
self.debug = !self.debug
|
||||
}
|
||||
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)]
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
/// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -178,13 +178,13 @@ 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 {
|
||||
@ -196,172 +196,172 @@ impl Disassemble {
|
||||
}
|
||||
/// `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()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
125
src/io.rs
Normal 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)));
|
||||
}
|
11
src/lib.rs
11
src/lib.rs
@ -1,4 +1,4 @@
|
||||
#![feature(let_chains, stmt_expr_attributes)]
|
||||
#![feature(stmt_expr_attributes)]
|
||||
/*!
|
||||
This crate implements a Chip-8 interpreter as if it were a real CPU architecture,
|
||||
to the best of my current knowledge. As it's the first emulator project I've
|
||||
@ -9,20 +9,17 @@ Hopefully, though, you'll find some use in it.
|
||||
|
||||
pub mod bus;
|
||||
pub mod cpu;
|
||||
pub mod mem;
|
||||
pub mod io;
|
||||
|
||||
pub mod dump;
|
||||
pub mod error;
|
||||
pub mod screen;
|
||||
|
||||
/// Common imports for chumpulator
|
||||
pub mod prelude {
|
||||
use super::*;
|
||||
pub use crate::bus;
|
||||
pub use crate::newbus;
|
||||
pub use bus::{Bus, BusConnectible, Read, Write};
|
||||
pub use bus::{Bus, Read, Write};
|
||||
pub use cpu::{disassemble::Disassemble, CPU};
|
||||
pub use dump::{BinDumpable, Dumpable};
|
||||
pub use mem::Mem;
|
||||
pub use screen::Screen;
|
||||
pub use io::{*, WindowBuilder};
|
||||
}
|
||||
|
193
src/main.rs
193
src/main.rs
@ -1,71 +1,154 @@
|
||||
use chumpulator::{bus::Read, prelude::*};
|
||||
//! Chirp: A chip-8 interpreter in Rust
|
||||
//! Hello, world!
|
||||
|
||||
use chirp::{error::Result, prelude::*};
|
||||
use gumdrop::*;
|
||||
use minifb::*;
|
||||
use std::fs::read;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
/// What I want:
|
||||
/// I want a data bus that stores as much memory as I need to implement a chip 8 emulator
|
||||
/// I want that data bus to hold named memory ranges and have a way to get a memory region
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Options, Hash)]
|
||||
struct Arguments {
|
||||
#[options(help = "Enable behavior incompatible with modern software")]
|
||||
pub authentic: bool,
|
||||
#[options(
|
||||
help = "Set breakpoints for the emulator to stop at",
|
||||
parse(try_from_str = "parse_hex")
|
||||
)]
|
||||
pub breakpoints: Vec<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, 0xf7fe, disassembler);
|
||||
let mut cpu2 = cpu.clone();
|
||||
println!("Old Bus:");
|
||||
for _instruction in 0..6 {
|
||||
time = Instant::now();
|
||||
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);
|
||||
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()));
|
||||
while frame_time.elapsed() > Duration::from_micros(16000) {
|
||||
if cpu.flags.pause {
|
||||
window.set_title("Chirp ⏸")
|
||||
} else {
|
||||
window.set_title("Chirp ▶")
|
||||
}
|
||||
Ok(())
|
||||
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(())
|
||||
}
|
||||
|
||||
fn endis(name: &str, state: bool) -> String {
|
||||
format!("{name} {}", if state { "enabled" } else { "disabled" })
|
||||
}
|
||||
|
153
src/mem.rs
153
src/mem.rs
@ -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)
|
||||
}
|
||||
}
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user