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
|
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"
|
||||||
|
@ -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"
|
||||||
|
274
src/bus.rs
274
src/bus.rs
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
304
src/cpu.rs
304
src/cpu.rs
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
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,
|
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;
|
|
||||||
}
|
}
|
||||||
|
195
src/main.rs
195
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::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(())
|
|
||||||
}
|
}
|
||||||
|
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