progress: new assembler works, instruction set fully implemented with call/ret and push/pop, bf.dsa works which means the dsa assembly language works reliably. still a few bugs to fix. might be able to squeeze out a tiny bit more performance
This commit is contained in:
@@ -0,0 +1,8 @@
|
|||||||
|
[workspace]
|
||||||
|
members = ["dsa/common", "dsa/assembler_old", "dsa/emulator", "dsa/linker", "dsa/compiler"]
|
||||||
|
resolver = "3"
|
||||||
|
|
||||||
|
[workspace.package]
|
||||||
|
edition = "2024"
|
||||||
|
version = "0.3.0"
|
||||||
|
authors = ["zxq5"]
|
||||||
+2
-1
@@ -73,7 +73,8 @@ subi <sr1: Reg> <dst: Reg> <imm: u16>
|
|||||||
|
|
||||||
// Misc
|
// Misc
|
||||||
nop (zero/do nothing)
|
nop (zero/do nothing)
|
||||||
int <imm: u6>
|
int <imm16: u16>
|
||||||
|
irt
|
||||||
hlt
|
hlt
|
||||||
|
|
||||||
// Atomic
|
// Atomic
|
||||||
|
|||||||
Generated
-714
@@ -1,714 +0,0 @@
|
|||||||
# This file is automatically @generated by Cargo.
|
|
||||||
# It is not intended for manual editing.
|
|
||||||
version = 4
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "aho-corasick"
|
|
||||||
version = "1.1.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
|
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anes"
|
|
||||||
version = "0.1.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstream"
|
|
||||||
version = "0.6.21"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
|
|
||||||
dependencies = [
|
|
||||||
"anstyle",
|
|
||||||
"anstyle-parse",
|
|
||||||
"anstyle-query",
|
|
||||||
"anstyle-wincon",
|
|
||||||
"colorchoice",
|
|
||||||
"is_terminal_polyfill",
|
|
||||||
"utf8parse",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstyle"
|
|
||||||
version = "1.0.13"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstyle-parse"
|
|
||||||
version = "0.2.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
|
|
||||||
dependencies = [
|
|
||||||
"utf8parse",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstyle-query"
|
|
||||||
version = "1.1.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
|
||||||
dependencies = [
|
|
||||||
"windows-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstyle-wincon"
|
|
||||||
version = "3.0.11"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
|
||||||
dependencies = [
|
|
||||||
"anstyle",
|
|
||||||
"once_cell_polyfill",
|
|
||||||
"windows-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "arc-swap"
|
|
||||||
version = "1.8.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f9f3647c145568cec02c42054e07bdf9a5a698e15b466fb2341bfc393cd24aa5"
|
|
||||||
dependencies = [
|
|
||||||
"rustversion",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "autocfg"
|
|
||||||
version = "1.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bitflags"
|
|
||||||
version = "2.11.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
|
|
||||||
dependencies = [
|
|
||||||
"serde_core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bumpalo"
|
|
||||||
version = "3.20.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "byteorder"
|
|
||||||
version = "1.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cast"
|
|
||||||
version = "0.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cfg-if"
|
|
||||||
version = "1.0.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ciborium"
|
|
||||||
version = "0.2.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
|
|
||||||
dependencies = [
|
|
||||||
"ciborium-io",
|
|
||||||
"ciborium-ll",
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ciborium-io"
|
|
||||||
version = "0.2.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ciborium-ll"
|
|
||||||
version = "0.2.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
|
|
||||||
dependencies = [
|
|
||||||
"ciborium-io",
|
|
||||||
"half",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "clap"
|
|
||||||
version = "4.5.60"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a"
|
|
||||||
dependencies = [
|
|
||||||
"clap_builder",
|
|
||||||
"clap_derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "clap_builder"
|
|
||||||
version = "4.5.60"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876"
|
|
||||||
dependencies = [
|
|
||||||
"anstream",
|
|
||||||
"anstyle",
|
|
||||||
"clap_lex",
|
|
||||||
"strsim",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "clap_derive"
|
|
||||||
version = "4.5.55"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5"
|
|
||||||
dependencies = [
|
|
||||||
"heck",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "clap_lex"
|
|
||||||
version = "1.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "colorchoice"
|
|
||||||
version = "1.0.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "criterion"
|
|
||||||
version = "0.5.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f"
|
|
||||||
dependencies = [
|
|
||||||
"anes",
|
|
||||||
"cast",
|
|
||||||
"ciborium",
|
|
||||||
"clap",
|
|
||||||
"criterion-plot",
|
|
||||||
"is-terminal",
|
|
||||||
"itertools",
|
|
||||||
"num-traits",
|
|
||||||
"once_cell",
|
|
||||||
"oorandom",
|
|
||||||
"plotters",
|
|
||||||
"rayon",
|
|
||||||
"regex",
|
|
||||||
"serde",
|
|
||||||
"serde_derive",
|
|
||||||
"serde_json",
|
|
||||||
"tinytemplate",
|
|
||||||
"walkdir",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "criterion-plot"
|
|
||||||
version = "0.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
|
|
||||||
dependencies = [
|
|
||||||
"cast",
|
|
||||||
"itertools",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "crossbeam-deque"
|
|
||||||
version = "0.8.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
|
|
||||||
dependencies = [
|
|
||||||
"crossbeam-epoch",
|
|
||||||
"crossbeam-utils",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "crossbeam-epoch"
|
|
||||||
version = "0.9.18"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
|
||||||
dependencies = [
|
|
||||||
"crossbeam-utils",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "crossbeam-utils"
|
|
||||||
version = "0.8.21"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "crunchy"
|
|
||||||
version = "0.2.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "dsa-emu"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"arc-swap",
|
|
||||||
"clap",
|
|
||||||
"criterion",
|
|
||||||
"fxhash",
|
|
||||||
"ron",
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "either"
|
|
||||||
version = "1.15.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "fxhash"
|
|
||||||
version = "0.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
|
|
||||||
dependencies = [
|
|
||||||
"byteorder",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "half"
|
|
||||||
version = "2.7.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"crunchy",
|
|
||||||
"zerocopy",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "heck"
|
|
||||||
version = "0.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hermit-abi"
|
|
||||||
version = "0.5.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "is-terminal"
|
|
||||||
version = "0.4.17"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46"
|
|
||||||
dependencies = [
|
|
||||||
"hermit-abi",
|
|
||||||
"libc",
|
|
||||||
"windows-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "is_terminal_polyfill"
|
|
||||||
version = "1.70.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "itertools"
|
|
||||||
version = "0.10.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
|
||||||
dependencies = [
|
|
||||||
"either",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "itoa"
|
|
||||||
version = "1.0.17"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "js-sys"
|
|
||||||
version = "0.3.91"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c"
|
|
||||||
dependencies = [
|
|
||||||
"once_cell",
|
|
||||||
"wasm-bindgen",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "libc"
|
|
||||||
version = "0.2.182"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "memchr"
|
|
||||||
version = "2.8.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "num-traits"
|
|
||||||
version = "0.2.19"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
|
||||||
dependencies = [
|
|
||||||
"autocfg",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "once_cell"
|
|
||||||
version = "1.21.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "once_cell_polyfill"
|
|
||||||
version = "1.70.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "oorandom"
|
|
||||||
version = "11.1.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "plotters"
|
|
||||||
version = "0.3.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747"
|
|
||||||
dependencies = [
|
|
||||||
"num-traits",
|
|
||||||
"plotters-backend",
|
|
||||||
"plotters-svg",
|
|
||||||
"wasm-bindgen",
|
|
||||||
"web-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "plotters-backend"
|
|
||||||
version = "0.3.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "plotters-svg"
|
|
||||||
version = "0.3.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670"
|
|
||||||
dependencies = [
|
|
||||||
"plotters-backend",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "proc-macro2"
|
|
||||||
version = "1.0.106"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
|
||||||
dependencies = [
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "quote"
|
|
||||||
version = "1.0.44"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rayon"
|
|
||||||
version = "1.11.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f"
|
|
||||||
dependencies = [
|
|
||||||
"either",
|
|
||||||
"rayon-core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rayon-core"
|
|
||||||
version = "1.13.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
|
|
||||||
dependencies = [
|
|
||||||
"crossbeam-deque",
|
|
||||||
"crossbeam-utils",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "regex"
|
|
||||||
version = "1.12.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
|
|
||||||
dependencies = [
|
|
||||||
"aho-corasick",
|
|
||||||
"memchr",
|
|
||||||
"regex-automata",
|
|
||||||
"regex-syntax",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "regex-automata"
|
|
||||||
version = "0.4.14"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
|
|
||||||
dependencies = [
|
|
||||||
"aho-corasick",
|
|
||||||
"memchr",
|
|
||||||
"regex-syntax",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "regex-syntax"
|
|
||||||
version = "0.8.10"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ron"
|
|
||||||
version = "0.12.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "fd490c5b18261893f14449cbd28cb9c0b637aebf161cd77900bfdedaff21ec32"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
"once_cell",
|
|
||||||
"serde",
|
|
||||||
"serde_derive",
|
|
||||||
"typeid",
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustversion"
|
|
||||||
version = "1.0.22"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "same-file"
|
|
||||||
version = "1.0.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
|
||||||
dependencies = [
|
|
||||||
"winapi-util",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde"
|
|
||||||
version = "1.0.228"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
|
||||||
dependencies = [
|
|
||||||
"serde_core",
|
|
||||||
"serde_derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_core"
|
|
||||||
version = "1.0.228"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
|
||||||
dependencies = [
|
|
||||||
"serde_derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_derive"
|
|
||||||
version = "1.0.228"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_json"
|
|
||||||
version = "1.0.149"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
|
|
||||||
dependencies = [
|
|
||||||
"itoa",
|
|
||||||
"memchr",
|
|
||||||
"serde",
|
|
||||||
"serde_core",
|
|
||||||
"zmij",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "strsim"
|
|
||||||
version = "0.11.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "syn"
|
|
||||||
version = "2.0.117"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tinytemplate"
|
|
||||||
version = "1.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
|
|
||||||
dependencies = [
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "typeid"
|
|
||||||
version = "1.0.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unicode-ident"
|
|
||||||
version = "1.0.24"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "utf8parse"
|
|
||||||
version = "0.2.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "walkdir"
|
|
||||||
version = "2.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
|
||||||
dependencies = [
|
|
||||||
"same-file",
|
|
||||||
"winapi-util",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen"
|
|
||||||
version = "0.2.114"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"once_cell",
|
|
||||||
"rustversion",
|
|
||||||
"wasm-bindgen-macro",
|
|
||||||
"wasm-bindgen-shared",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen-macro"
|
|
||||||
version = "0.2.114"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6"
|
|
||||||
dependencies = [
|
|
||||||
"quote",
|
|
||||||
"wasm-bindgen-macro-support",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen-macro-support"
|
|
||||||
version = "0.2.114"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3"
|
|
||||||
dependencies = [
|
|
||||||
"bumpalo",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
"wasm-bindgen-shared",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen-shared"
|
|
||||||
version = "0.2.114"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16"
|
|
||||||
dependencies = [
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "web-sys"
|
|
||||||
version = "0.3.91"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9"
|
|
||||||
dependencies = [
|
|
||||||
"js-sys",
|
|
||||||
"wasm-bindgen",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winapi-util"
|
|
||||||
version = "0.1.11"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
|
||||||
dependencies = [
|
|
||||||
"windows-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-link"
|
|
||||||
version = "0.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-sys"
|
|
||||||
version = "0.61.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
|
||||||
dependencies = [
|
|
||||||
"windows-link",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zerocopy"
|
|
||||||
version = "0.8.40"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5"
|
|
||||||
dependencies = [
|
|
||||||
"zerocopy-derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zerocopy-derive"
|
|
||||||
version = "0.8.40"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zmij"
|
|
||||||
version = "1.0.21"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
|
||||||
@@ -1,177 +0,0 @@
|
|||||||
#[derive(Clone, Copy)]
|
|
||||||
pub struct InstructionWord(pub u32);
|
|
||||||
|
|
||||||
impl InstructionWord {
|
|
||||||
#[inline]
|
|
||||||
pub fn opcode(self) -> u8 {
|
|
||||||
((self.0 >> 26) & 0x3F) as u8
|
|
||||||
}
|
|
||||||
|
|
||||||
// Src shared between R type and I type
|
|
||||||
#[inline]
|
|
||||||
pub fn src1(self) -> Reg {
|
|
||||||
Reg::from_u8_unchecked(((self.0 >> 21) & 0x1F) as u8)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dest shared between R type and I type
|
|
||||||
#[inline]
|
|
||||||
pub fn dest(self) -> Reg {
|
|
||||||
Reg::from_u8_unchecked(((self.0 >> 16) & 0x1F) as u8)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Secondary Src for R type only
|
|
||||||
#[inline]
|
|
||||||
pub fn src2(self) -> Reg {
|
|
||||||
Reg::from_u8_unchecked(((self.0 >> 11) & 0x1F) as u8)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Misc/Conditional reg for R type only
|
|
||||||
#[inline]
|
|
||||||
pub fn misc(self) -> Reg {
|
|
||||||
Reg::from_u8_unchecked(((self.0 >> 6) & 0x1F) as u8)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shift amount / misc data for R type only
|
|
||||||
#[inline]
|
|
||||||
pub fn shamt(self) -> u8 {
|
|
||||||
(self.0 & 0x3F) as u8
|
|
||||||
}
|
|
||||||
|
|
||||||
// 16 Bit immediate for I type only
|
|
||||||
#[inline]
|
|
||||||
pub fn imm16(self) -> u16 {
|
|
||||||
self.0 as u16
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(u8)]
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
||||||
pub enum Opcode {
|
|
||||||
Nop = 0,
|
|
||||||
|
|
||||||
// move
|
|
||||||
Mov = 1,
|
|
||||||
CMov = 2,
|
|
||||||
|
|
||||||
// load
|
|
||||||
Ldb = 3,
|
|
||||||
Ldbs = 4,
|
|
||||||
Ldh = 5,
|
|
||||||
Ldhs = 6,
|
|
||||||
Ldw = 7,
|
|
||||||
|
|
||||||
// store
|
|
||||||
Stb = 8,
|
|
||||||
Sth = 9,
|
|
||||||
Stw = 10,
|
|
||||||
|
|
||||||
// load immediate
|
|
||||||
Lli = 11,
|
|
||||||
Lui = 12,
|
|
||||||
|
|
||||||
// comparison
|
|
||||||
Ieq = 13,
|
|
||||||
Ine = 14,
|
|
||||||
Ilt = 15,
|
|
||||||
Ile = 16,
|
|
||||||
Igt = 17,
|
|
||||||
Ige = 18,
|
|
||||||
|
|
||||||
// jump
|
|
||||||
Jmp = 19,
|
|
||||||
Jez = 20,
|
|
||||||
Jnz = 21,
|
|
||||||
Jic = 22,
|
|
||||||
Jnc = 23,
|
|
||||||
|
|
||||||
// bitwise
|
|
||||||
And = 24,
|
|
||||||
Nand = 25,
|
|
||||||
Or = 26,
|
|
||||||
Nor = 27,
|
|
||||||
Xor = 28,
|
|
||||||
Xnor = 29,
|
|
||||||
Not = 30,
|
|
||||||
|
|
||||||
// arithmetic
|
|
||||||
Add = 31,
|
|
||||||
Sub = 32,
|
|
||||||
Shl = 33,
|
|
||||||
Shr = 34,
|
|
||||||
Addi = 35,
|
|
||||||
Subi = 36,
|
|
||||||
|
|
||||||
// system
|
|
||||||
Int = 37,
|
|
||||||
Hlt = 38,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Opcode {
|
|
||||||
#[inline]
|
|
||||||
pub fn from_u8(val: u8) -> Option<Self> {
|
|
||||||
if val <= Self::Hlt as u8 {
|
|
||||||
Some(unsafe { std::mem::transmute(val) })
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
|
|
||||||
#[repr(u8)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub enum Reg {
|
|
||||||
// general purpose
|
|
||||||
Rg0 = 0,
|
|
||||||
Rg1 = 1,
|
|
||||||
Rg2 = 2,
|
|
||||||
Rg3 = 3,
|
|
||||||
Rg4 = 4,
|
|
||||||
Rg5 = 5,
|
|
||||||
Rg6 = 6,
|
|
||||||
Rg7 = 7,
|
|
||||||
Rg8 = 8,
|
|
||||||
Rg9 = 9,
|
|
||||||
Rga = 10,
|
|
||||||
Rgb = 11,
|
|
||||||
Rgc = 12,
|
|
||||||
Rgd = 13,
|
|
||||||
Rge = 14,
|
|
||||||
Rgf = 15,
|
|
||||||
|
|
||||||
// special purpose
|
|
||||||
Zero = 16,
|
|
||||||
Acc = 17,
|
|
||||||
Spr = 18,
|
|
||||||
Bpr = 19,
|
|
||||||
Ret = 20,
|
|
||||||
Idr = 21,
|
|
||||||
Mmr = 22,
|
|
||||||
|
|
||||||
// system - read only
|
|
||||||
Mar = 23,
|
|
||||||
Mdr = 24,
|
|
||||||
Sts = 25,
|
|
||||||
Cir = 26,
|
|
||||||
Pcx = 27,
|
|
||||||
|
|
||||||
#[default]
|
|
||||||
Null = 28,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Reg {
|
|
||||||
#[must_use]
|
|
||||||
#[inline]
|
|
||||||
pub fn from_u8_unchecked(idx: u8) -> Self {
|
|
||||||
debug_assert!(idx <= 28);
|
|
||||||
unsafe { std::mem::transmute(idx) }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn from_u8(idx: u8) -> Result<Self, ()> {
|
|
||||||
if idx > 28 {
|
|
||||||
return Err(());
|
|
||||||
}
|
|
||||||
Ok(Self::from_u8_unchecked(idx))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
pub mod instructions;
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
use clap::Parser;
|
|
||||||
use dsa::{
|
|
||||||
BulkAllocStore, Emulator, MemoryMap, SharedState,
|
|
||||||
args::DsaArgs,
|
|
||||||
io::display::{DisplayDevice, DisplayHandle},
|
|
||||||
};
|
|
||||||
use std::{sync::Arc, thread};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let args = DsaArgs::parse();
|
|
||||||
|
|
||||||
let mmap = args.get_memory_map();
|
|
||||||
let iomap = args.get_io_map();
|
|
||||||
|
|
||||||
let store = BulkAllocStore::new();
|
|
||||||
let (display, handle) = DisplayDevice::new(80, 25);
|
|
||||||
|
|
||||||
let mut emulator = Emulator::new(store)
|
|
||||||
.with_device(display)
|
|
||||||
.apply_memory_map(mmap)
|
|
||||||
.apply_io_map(iomap)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let state = emulator.state_handle();
|
|
||||||
|
|
||||||
const STACK_SIZE: usize = 1024 * 1024 * 16;
|
|
||||||
let runner = thread::Builder::new()
|
|
||||||
.stack_size(STACK_SIZE)
|
|
||||||
.spawn(move || emulator.run().unwrap())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let observer = thread::spawn(|| observe(state, handle));
|
|
||||||
|
|
||||||
runner.join().unwrap();
|
|
||||||
observer.join().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// todo: remove this!
|
|
||||||
fn observe(state: Arc<SharedState>, handle: DisplayHandle) {
|
|
||||||
loop {
|
|
||||||
thread::sleep(std::time::Duration::from_millis(100));
|
|
||||||
let state = state.proc.load();
|
|
||||||
println!("GP Registers: {:?}", state.registers);
|
|
||||||
|
|
||||||
if handle.is_dirty() {
|
|
||||||
for line in handle.read_all().chunks(80) {
|
|
||||||
for (i, byte) in line.iter().enumerate() {
|
|
||||||
print!("{}", *byte as char);
|
|
||||||
}
|
|
||||||
println!();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,558 +0,0 @@
|
|||||||
use std::{
|
|
||||||
hint::unlikely,
|
|
||||||
sync::{
|
|
||||||
Arc,
|
|
||||||
atomic::Ordering,
|
|
||||||
mpsc::{self, TryRecvError},
|
|
||||||
},
|
|
||||||
time::Duration,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
Page,
|
|
||||||
common::instructions::{InstructionWord, Opcode, Reg},
|
|
||||||
config::{IoMapping, MemoryMap, RegionType},
|
|
||||||
io::{IoDevice, MappedDevice},
|
|
||||||
memory::{mmu::MMU, ram::RandomAccessMemory},
|
|
||||||
processor::{interrupts::Interrupt, state::SharedState},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct Emulator<Mem: RandomAccessMemory> {
|
|
||||||
internal_state: ProcessorSnapshot,
|
|
||||||
shared_state: Arc<SharedState>,
|
|
||||||
|
|
||||||
// Interrupts
|
|
||||||
interrupts: mpsc::Receiver<u8>,
|
|
||||||
pending_fault: Option<Interrupt>,
|
|
||||||
|
|
||||||
// memory
|
|
||||||
mmu: MMU,
|
|
||||||
mainstore: Mem,
|
|
||||||
|
|
||||||
// IO
|
|
||||||
mmio: Vec<MappedDevice>,
|
|
||||||
mmio_region: Option<(u32, u32)>, // start end end of segment
|
|
||||||
|
|
||||||
// optionals - not necessarily set on boot.
|
|
||||||
memory_map: Option<MemoryMap>,
|
|
||||||
|
|
||||||
// Config params
|
|
||||||
__mmap_configured: bool,
|
|
||||||
__io_configured: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl<Mem: RandomAccessMemory> Send for Emulator<Mem> {}
|
|
||||||
impl<Mem: RandomAccessMemory> Emulator<Mem> {
|
|
||||||
pub fn new(mem: Mem) -> Self {
|
|
||||||
let (sender, receiver) = mpsc::channel::<u8>();
|
|
||||||
|
|
||||||
Self {
|
|
||||||
interrupts: receiver,
|
|
||||||
pending_fault: None,
|
|
||||||
|
|
||||||
internal_state: ProcessorSnapshot::default(),
|
|
||||||
shared_state: Arc::new(SharedState::new(sender)),
|
|
||||||
|
|
||||||
memory_map: None,
|
|
||||||
mmu: MMU::new(),
|
|
||||||
mainstore: mem,
|
|
||||||
|
|
||||||
// mmio
|
|
||||||
mmio: Vec::new(),
|
|
||||||
mmio_region: None,
|
|
||||||
|
|
||||||
// Config params
|
|
||||||
__io_configured: false,
|
|
||||||
__mmap_configured: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_device(mut self, device: impl IoDevice + 'static) -> Self {
|
|
||||||
self.mmio.push(MappedDevice {
|
|
||||||
base: 0,
|
|
||||||
device: Box::new(device),
|
|
||||||
});
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn state_handle(&self) -> Arc<SharedState> {
|
|
||||||
self.shared_state.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn mmu_mut(&mut self) -> &mut MMU {
|
|
||||||
&mut self.mmu
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn memory_mut(&mut self) -> &mut impl RandomAccessMemory {
|
|
||||||
&mut self.mainstore
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
#[inline]
|
|
||||||
pub fn reg(&self, reg: Reg) -> u32 {
|
|
||||||
if reg as u8 == Reg::Zero as u8 {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
debug_assert!((reg as usize) < ProcessorSnapshot::REG_COUNT);
|
|
||||||
|
|
||||||
unsafe { *self.internal_state.registers.get_unchecked(reg as usize) }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn mut_reg(&mut self, reg: Reg) -> &mut u32 {
|
|
||||||
debug_assert!((reg as usize) < ProcessorSnapshot::REG_COUNT);
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
self.internal_state
|
|
||||||
.registers
|
|
||||||
.get_unchecked_mut(reg as usize)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cold]
|
|
||||||
pub fn apply_memory_map(mut self, map: MemoryMap) -> Self {
|
|
||||||
self.mmio_region = map
|
|
||||||
.regions
|
|
||||||
.iter()
|
|
||||||
.find(|r| matches!(r.region_type, RegionType::MMIO))
|
|
||||||
.map(|r| (r.base, r.base + r.size));
|
|
||||||
|
|
||||||
map.apply(&mut self);
|
|
||||||
self.memory_map = Some(map);
|
|
||||||
self.__mmap_configured = true;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn apply_io_map(mut self, map: Vec<IoMapping>) -> Result<Self, String> {
|
|
||||||
if !self.__mmap_configured {
|
|
||||||
return Err("You must map memory before applying I/O mappings".to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
for entry in map {
|
|
||||||
if let Some(idx) = self.mmio.iter().position(|d| d.device.id() == entry.device) {
|
|
||||||
self.mmio[idx].base = entry.base;
|
|
||||||
} else {
|
|
||||||
eprintln!("WARN: no device registgered for {:?}", entry.device);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.__io_configured = true;
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cold]
|
|
||||||
fn update(&mut self) {
|
|
||||||
self.shared_state
|
|
||||||
.proc
|
|
||||||
.store(Arc::new(self.internal_state.clone()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cold]
|
|
||||||
fn boot(&mut self) -> Result<(), String> {
|
|
||||||
if !(self.__io_configured && self.__mmap_configured) {
|
|
||||||
return Err("Processor not configured".to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(map) = &self.memory_map {
|
|
||||||
MemoryMap::identity_map(&mut self.mmu, map.regions.get(0).unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
// self.mmu.paging_enabled(true);
|
|
||||||
|
|
||||||
self.internal_state.running = true;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cold]
|
|
||||||
fn shutdown(&mut self) {
|
|
||||||
self.internal_state.running = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cold]
|
|
||||||
pub fn idle_wait(&mut self) {
|
|
||||||
self.internal_state.running = false;
|
|
||||||
|
|
||||||
// Wait for an interrupt or state update to continue.
|
|
||||||
loop {
|
|
||||||
// Check for interrupts.
|
|
||||||
if let Ok(code) = self.interrupts.recv_timeout(Duration::from_millis(100)) {
|
|
||||||
self.interrupt(Interrupt::Software(code));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we've received a request to continue running.
|
|
||||||
if self.shared_state.running.load(Ordering::Relaxed) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// UI requested a state update.
|
|
||||||
if self.shared_state.update_req.load(Ordering::Relaxed) {
|
|
||||||
self.update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.internal_state.running = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run(&mut self) -> Result<(), String> {
|
|
||||||
self.boot()?;
|
|
||||||
|
|
||||||
'emu: loop {
|
|
||||||
// Update UI thread (roughly every 512k cycles targeting UPS)
|
|
||||||
if unlikely(self.internal_state.clock & 0x7FFFF == 0) {
|
|
||||||
if self.shared_state.update_req.load(Ordering::Relaxed) {
|
|
||||||
self.update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for commands or hardware Interrupts every 32k cycles
|
|
||||||
if self.internal_state.clock % 0x7FFF == 0 {
|
|
||||||
if self.shared_state.running.load(Ordering::Relaxed) == false {
|
|
||||||
self.idle_wait();
|
|
||||||
}
|
|
||||||
|
|
||||||
match self.interrupts.try_recv() {
|
|
||||||
Ok(code) => self.interrupt(Interrupt::Software(code)),
|
|
||||||
Err(TryRecvError::Disconnected) => break 'emu,
|
|
||||||
Err(TryRecvError::Empty) => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// let instruction = self.mmu.lookup(self.internal_state.reg(Reg::Pcx));
|
|
||||||
let pc = self.reg(Reg::Pcx);
|
|
||||||
let instruction = self.mem_read_word(pc);
|
|
||||||
self.execute(InstructionWord(instruction));
|
|
||||||
|
|
||||||
// Check if executing the interrupt caused a fault.
|
|
||||||
if let Some(fault) = self.pending_fault {
|
|
||||||
self.interrupt(fault);
|
|
||||||
}
|
|
||||||
|
|
||||||
// always increment clock.
|
|
||||||
self.internal_state.clock += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.shutdown();
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn interrupt(&mut self, int: Interrupt) {
|
|
||||||
let idt = self.reg(Reg::Idr);
|
|
||||||
|
|
||||||
*self.mut_reg(Reg::Spr) -= 4;
|
|
||||||
let spr = self.reg(Reg::Spr);
|
|
||||||
let pcx = self.reg(Reg::Pcx);
|
|
||||||
self.mem_write_word(spr, pcx);
|
|
||||||
|
|
||||||
*self.mut_reg(Reg::Pcx) = self.mem_read_word(idt + int.code() as u32 * 4)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn execute(&mut self, word: InstructionWord) {
|
|
||||||
match Opcode::from_u8(word.opcode()) {
|
|
||||||
// Nothing
|
|
||||||
Some(Opcode::Nop) => {}
|
|
||||||
|
|
||||||
// move
|
|
||||||
Some(Opcode::Mov) => {
|
|
||||||
*self.mut_reg(word.dest()) = self.reg(word.src1());
|
|
||||||
}
|
|
||||||
Some(Opcode::CMov) => {
|
|
||||||
if self.reg(word.misc()) != 0 {
|
|
||||||
*self.mut_reg(word.dest()) = self.reg(word.src1());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// load
|
|
||||||
Some(Opcode::Ldbs) => todo!(),
|
|
||||||
Some(Opcode::Ldhs) => todo!(),
|
|
||||||
Some(Opcode::Ldb) => {
|
|
||||||
*self.mut_reg(word.dest()) =
|
|
||||||
u32::from(self.mem_read_byte(self.reg(word.src1()) + word.imm16() as u32))
|
|
||||||
}
|
|
||||||
Some(Opcode::Ldh) => {
|
|
||||||
*self.mut_reg(word.dest()) =
|
|
||||||
u32::from(self.mem_read_word(self.reg(word.src1()) + word.imm16() as u32) >> 16)
|
|
||||||
}
|
|
||||||
Some(Opcode::Ldw) => {
|
|
||||||
*self.mut_reg(word.dest()) =
|
|
||||||
u32::from(self.mem_read_word(self.reg(word.src1()) + word.imm16() as u32))
|
|
||||||
}
|
|
||||||
|
|
||||||
// store
|
|
||||||
Some(Opcode::Stb) => {
|
|
||||||
self.mem_write_byte(
|
|
||||||
self.reg(word.dest()) + word.imm16() as u32,
|
|
||||||
self.reg(word.src1()) as u8,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Some(Opcode::Sth) => {
|
|
||||||
self.mem_write_byte(
|
|
||||||
self.reg(word.dest()) + word.imm16() as u32,
|
|
||||||
(self.reg(word.src1()) as u16 >> 8) as u8,
|
|
||||||
);
|
|
||||||
self.mem_write_byte(
|
|
||||||
self.reg(word.dest()) + word.imm16() as u32 + 1,
|
|
||||||
self.reg(word.src1()) as u8,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Some(Opcode::Stw) => {
|
|
||||||
self.mem_write_word(
|
|
||||||
self.reg(word.dest()) + word.imm16() as u32,
|
|
||||||
self.reg(word.src1()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// load immediate
|
|
||||||
Some(Opcode::Lli) => {
|
|
||||||
*self.mut_reg(word.dest()) = word.imm16() as u32;
|
|
||||||
}
|
|
||||||
Some(Opcode::Lui) => {
|
|
||||||
*self.mut_reg(word.dest()) = (word.imm16() as u32) << 16 & self.reg(word.dest());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Comparison
|
|
||||||
Some(Opcode::Ieq) => {
|
|
||||||
*self.mut_reg(word.dest()) =
|
|
||||||
(self.reg(word.src1()) == self.reg(word.src2())) as u32;
|
|
||||||
}
|
|
||||||
Some(Opcode::Ine) => {
|
|
||||||
*self.mut_reg(word.dest()) =
|
|
||||||
(self.reg(word.src1()) != self.reg(word.src2())) as u32;
|
|
||||||
}
|
|
||||||
Some(Opcode::Ilt) => {
|
|
||||||
*self.mut_reg(word.dest()) = (self.reg(word.src1()) < self.reg(word.src2())) as u32;
|
|
||||||
}
|
|
||||||
Some(Opcode::Ile) => {
|
|
||||||
*self.mut_reg(word.dest()) =
|
|
||||||
(self.reg(word.src1()) <= self.reg(word.src2())) as u32;
|
|
||||||
}
|
|
||||||
Some(Opcode::Igt) => {
|
|
||||||
*self.mut_reg(word.dest()) = (self.reg(word.src1()) > self.reg(word.src2())) as u32;
|
|
||||||
}
|
|
||||||
Some(Opcode::Ige) => {
|
|
||||||
*self.mut_reg(word.dest()) =
|
|
||||||
(self.reg(word.src1()) >= self.reg(word.src2())) as u32;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Jump
|
|
||||||
Some(Opcode::Jmp) => *self.mut_reg(Reg::Pcx) = self.reg(word.dest()),
|
|
||||||
|
|
||||||
Some(Opcode::Jez) => {
|
|
||||||
if self.reg(word.src1()) == 0 {
|
|
||||||
*self.mut_reg(Reg::Pcx) = self.reg(word.dest())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some(Opcode::Jnz) => {
|
|
||||||
if self.reg(word.src1()) != 0 {
|
|
||||||
*self.mut_reg(Reg::Pcx) = self.reg(word.dest())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some(Opcode::Jnc) => todo!(),
|
|
||||||
Some(Opcode::Jic) => todo!(),
|
|
||||||
|
|
||||||
// Bitwise
|
|
||||||
Some(Opcode::And) => {
|
|
||||||
*self.mut_reg(word.dest()) = self.reg(word.src1()) & self.reg(word.src2());
|
|
||||||
}
|
|
||||||
Some(Opcode::Nand) => {
|
|
||||||
*self.mut_reg(word.dest()) = !(self.reg(word.src1()) & self.reg(word.src2()));
|
|
||||||
}
|
|
||||||
Some(Opcode::Or) => {
|
|
||||||
*self.mut_reg(word.dest()) = self.reg(word.src1()) | self.reg(word.src2());
|
|
||||||
}
|
|
||||||
Some(Opcode::Nor) => {
|
|
||||||
*self.mut_reg(word.dest()) = !(self.reg(word.src1()) | self.reg(word.src2()));
|
|
||||||
}
|
|
||||||
Some(Opcode::Xor) => {
|
|
||||||
*self.mut_reg(word.dest()) = self.reg(word.src1()) ^ self.reg(word.src2());
|
|
||||||
}
|
|
||||||
Some(Opcode::Xnor) => {
|
|
||||||
*self.mut_reg(word.dest()) = !(self.reg(word.src1()) ^ self.reg(word.src2()));
|
|
||||||
}
|
|
||||||
Some(Opcode::Not) => {
|
|
||||||
*self.mut_reg(word.dest()) = !self.reg(word.src1());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Arithmetic
|
|
||||||
Some(Opcode::Add) => {
|
|
||||||
*self.mut_reg(word.dest()) = self.reg(word.src1()) + self.reg(word.src2());
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(Opcode::Sub) => {
|
|
||||||
*self.mut_reg(word.dest()) = self.reg(word.src1()) - self.reg(word.src2());
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(Opcode::Shl) => {
|
|
||||||
*self.mut_reg(word.dest()) =
|
|
||||||
self.reg(word.src1()) << (self.reg(word.src2()) + word.shamt() as u32);
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(Opcode::Shr) => {
|
|
||||||
*self.mut_reg(word.dest()) =
|
|
||||||
self.reg(word.src1()) >> (self.reg(word.src2()) + word.shamt() as u32);
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(Opcode::Addi) => {
|
|
||||||
*self.mut_reg(word.dest()) = self.reg(word.src1()) + word.imm16() as u32;
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(Opcode::Subi) => {
|
|
||||||
*self.mut_reg(word.dest()) = self.reg(word.src1()) - word.imm16() as u32;
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(Opcode::Int) => {
|
|
||||||
self.interrupt(Interrupt::Software(word.shamt()));
|
|
||||||
}
|
|
||||||
Some(Opcode::Hlt) => self.internal_state.running = false,
|
|
||||||
None => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn mem_read_byte(&mut self, addr: u32) -> u8 {
|
|
||||||
if unlikely(self.is_mmio(addr)) {
|
|
||||||
return self.io_read_byte(addr);
|
|
||||||
}
|
|
||||||
self.mainstore.read_byte(addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn mem_write_byte(&mut self, addr: u32, val: u8) {
|
|
||||||
if unlikely(self.is_mmio(addr)) {
|
|
||||||
self.io_write_byte(addr, val);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
self.mainstore.write_byte(addr, val);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn mem_read_word(&mut self, addr: u32) -> u32 {
|
|
||||||
if unlikely(self.is_mmio(addr)) {
|
|
||||||
return self.io_read_word(addr);
|
|
||||||
}
|
|
||||||
self.mainstore.read_word(addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn mem_write_word(&mut self, addr: u32, val: u32) {
|
|
||||||
if unlikely(self.is_mmio(addr)) {
|
|
||||||
self.io_write_word(addr, val);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
self.mainstore.write_word(addr, val);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn mem_read_page(&mut self, addr: u32) -> &Page {
|
|
||||||
if unlikely(self.is_mmio(addr)) {
|
|
||||||
// pages spanning MMIO don't really make sense
|
|
||||||
// treat as fault
|
|
||||||
self.pending_fault = Some(Interrupt::ProtectionFault);
|
|
||||||
return &Page::ZERO;
|
|
||||||
}
|
|
||||||
self.mainstore.read_page(addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn mem_write_page(&mut self, addr: u32, val: &Page) {
|
|
||||||
if unlikely(self.is_mmio(addr)) {
|
|
||||||
self.pending_fault = Some(Interrupt::ProtectionFault);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
self.mainstore.write_page(addr, val);
|
|
||||||
}
|
|
||||||
|
|
||||||
// single MMIO check reused by all of the above
|
|
||||||
#[inline]
|
|
||||||
fn is_mmio(&self, addr: u32) -> bool {
|
|
||||||
self.mmio_region
|
|
||||||
.map(|(base, end)| addr >= base && addr < end)
|
|
||||||
.unwrap_or(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- cold IO paths ---
|
|
||||||
|
|
||||||
#[cold]
|
|
||||||
fn io_read_byte(&mut self, addr: u32) -> u8 {
|
|
||||||
// if let Some(device) = self.mmio.iter().find(|d| d.contains(addr)) {
|
|
||||||
// match device.read_byte(addr) {
|
|
||||||
// Some(val) => val,
|
|
||||||
// None => {
|
|
||||||
// self.pending_fault = Some(Interrupt::ReadFromWriteOnly);
|
|
||||||
// 0
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// self.pending_fault = Some(Interrupt::UnmappedIo);
|
|
||||||
// 0
|
|
||||||
// }
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cold]
|
|
||||||
fn io_read_word(&mut self, addr: u32) -> u32 {
|
|
||||||
// if let Some(device) = self.mmio.iter().find(|d| d.contains(addr)) {
|
|
||||||
// match device.read_word(addr) {
|
|
||||||
// Some(val) => val,
|
|
||||||
// None => {
|
|
||||||
// self.pending_fault = Some(Interrupt::ReadFromWriteOnly);
|
|
||||||
// 0
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// self.pending_fault = Some(Interrupt::UnmappedIo);
|
|
||||||
// 0
|
|
||||||
// }
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cold]
|
|
||||||
fn io_write_byte(&mut self, addr: u32, val: u8) {
|
|
||||||
// if let Some(device) = self.mmio.iter_mut().find(|d| d.contains(addr)) {
|
|
||||||
// if !device.write_byte(addr, val) {
|
|
||||||
// self.pending_fault = Some(Interrupt::WriteToReadOnly);
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// self.pending_fault = Some(Interrupt::UnmappedIo);
|
|
||||||
// }
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cold]
|
|
||||||
fn io_write_word(&mut self, addr: u32, val: u32) {
|
|
||||||
// if let Some(device) = self.mmio.iter_mut().find(|d| d.contains(addr)) {
|
|
||||||
// if !device.write_word(addr, val) {
|
|
||||||
// self.pending_fault = Some(Interrupt::WriteToReadOnly);
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// self.pending_fault = Some(Interrupt::UnmappedIo);
|
|
||||||
// }
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct ProcessorSnapshot {
|
|
||||||
pub running: bool,
|
|
||||||
pub clock: usize,
|
|
||||||
pub registers: [u32; Self::REG_COUNT],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ProcessorSnapshot {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
running: false,
|
|
||||||
clock: 0,
|
|
||||||
registers: [0; Self::REG_COUNT],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ProcessorSnapshot {
|
|
||||||
const REG_COUNT: usize = 28;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
The DSA libraries form the foundation of the entire project, acting as a bridge between high‑level software and the specialized DSA hardware. Their primary responsibilities are:
|
||||||
|
|
||||||
|
1. **Instruction Set Core Library**
|
||||||
|
- Encapsulates all architectural primitives: opcodes, operand formats, and execution semantics.
|
||||||
|
- Supplies utility functions to build, validate, and serialize instruction streams.
|
||||||
|
|
||||||
|
2. **Assembler Support**
|
||||||
|
- Translates DSA assembly language into binary instruction packets that the hardware can execute.
|
||||||
|
- Implements directives for sectioning, alignment, and relocation handling.
|
||||||
|
|
||||||
|
3. **High‑Level Language Compiler (DSC) Runtime**
|
||||||
|
- Generates DSA bytecode from DSC source files, leveraging the core library for instruction encoding.
|
||||||
|
- Offers debugging hooks, profiling counters, and exception handling mechanisms.
|
||||||
|
|
||||||
|
4. **Testing & Verification Utilities**
|
||||||
|
- Contains unit tests that run against both emulated and real hardware to ensure correctness.
|
||||||
|
- Provides logging facilities for low‑level bus transactions and performance metrics.
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
[package]
|
||||||
|
name = "assembler_old"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "dsa-a"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "assembler"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
common = { path = "../common" }
|
||||||
|
|
||||||
|
num_cpus = "1.17.0"
|
||||||
|
threadpool = "1.8.1"
|
||||||
@@ -0,0 +1,399 @@
|
|||||||
|
use common::prelude::*;
|
||||||
|
|
||||||
|
use crate::assembler::Token;
|
||||||
|
use crate::assembler::model::{Node, Opcode};
|
||||||
|
use crate::{assembler::AssembleError, expect_token};
|
||||||
|
|
||||||
|
fn log(message: &str) {
|
||||||
|
println!("\x1b[32mINFO:\x1b[0m {message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn codegen(nodes: Vec<Node>) -> Result<Vec<Instruction>, AssembleError> {
|
||||||
|
let mut instructions = vec![];
|
||||||
|
|
||||||
|
for node in nodes {
|
||||||
|
println!("{:?}", node);
|
||||||
|
instructions.push(build_instruction(&node)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
log("Assembly Successful ✅");
|
||||||
|
|
||||||
|
Ok(instructions)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_instruction(node: &Node) -> Result<Instruction, AssembleError> {
|
||||||
|
let opcode = node.opcode();
|
||||||
|
let args = node.args();
|
||||||
|
|
||||||
|
match opcode {
|
||||||
|
Opcode::Nop => Ok(Instruction::nop()),
|
||||||
|
Opcode::Mov => ins_mov(opcode, &args),
|
||||||
|
Opcode::CMov => ins_cmov(opcode, &args),
|
||||||
|
Opcode::Ldb
|
||||||
|
| Opcode::Ldw
|
||||||
|
| Opcode::Ldh
|
||||||
|
| Opcode::Ldbs
|
||||||
|
| Opcode::Ldhs
|
||||||
|
| Opcode::Stb
|
||||||
|
| Opcode::Stw
|
||||||
|
| Opcode::Sth => ins_ldx_stx(opcode, &args),
|
||||||
|
Opcode::Lli | Opcode::Lui => ins_load_imm(opcode, &args),
|
||||||
|
Opcode::Push | Opcode::Pop => ins_stack(opcode, &args),
|
||||||
|
Opcode::Ieq | Opcode::Ine | Opcode::Igt | Opcode::Ige | Opcode::Ile | Opcode::Ilt => {
|
||||||
|
ins_comparison(opcode, &args)
|
||||||
|
}
|
||||||
|
Opcode::Jmp | Opcode::Call | Opcode::Jic | Opcode::Jnc => {
|
||||||
|
ins_jump_unconditional(opcode, &args)
|
||||||
|
}
|
||||||
|
Opcode::Jez | Opcode::Jnz => ins_jump_conditional(opcode, &args),
|
||||||
|
Opcode::Shl | Opcode::Shr => ins_bitshift(opcode, &args),
|
||||||
|
Opcode::Add
|
||||||
|
| Opcode::Sub
|
||||||
|
| Opcode::And
|
||||||
|
| Opcode::Or
|
||||||
|
| Opcode::Xor
|
||||||
|
| Opcode::Nand
|
||||||
|
| Opcode::Nor
|
||||||
|
| Opcode::Xnor => ins_arithmetic(opcode, &args),
|
||||||
|
Opcode::AddI | Opcode::SubI => ins_imm_arithmetic(opcode, &args),
|
||||||
|
Opcode::Not => ins_not(&args),
|
||||||
|
Opcode::Int => ins_interrupt(&args),
|
||||||
|
Opcode::Ret => Ok(Instruction::ret()),
|
||||||
|
Opcode::IRet => Ok(Instruction::irt()),
|
||||||
|
Opcode::Hlt => Ok(Instruction::hlt()),
|
||||||
|
Opcode::Data => build_data_instruction(&args),
|
||||||
|
Opcode::Segment => build_segment_instruction(&args),
|
||||||
|
Opcode::Db
|
||||||
|
| Opcode::Dh
|
||||||
|
| Opcode::Dw
|
||||||
|
| Opcode::Resb
|
||||||
|
| Opcode::Resh
|
||||||
|
| Opcode::Resw
|
||||||
|
| Opcode::Lwi
|
||||||
|
| Opcode::Include
|
||||||
|
| Opcode::Func
|
||||||
|
| Opcode::Return => Err(AssembleError::InvalidArg),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ins_mov(
|
||||||
|
opcode: Opcode,
|
||||||
|
args: &[crate::assembler::model::Token],
|
||||||
|
) -> Result<Instruction, AssembleError> {
|
||||||
|
let Some(src_token) = args.first() else {
|
||||||
|
return Err(AssembleError::MissingArgument(0));
|
||||||
|
};
|
||||||
|
let Some(dest_token) = args.get(1) else {
|
||||||
|
return Err(AssembleError::MissingArgument(1));
|
||||||
|
};
|
||||||
|
|
||||||
|
let src = expect_token!(src_token, Register)?;
|
||||||
|
let dest = expect_token!(dest_token, Register)?;
|
||||||
|
|
||||||
|
match opcode {
|
||||||
|
Opcode::Mov => Ok(Instruction::mov(src, dest)),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ins_cmov(
|
||||||
|
opcode: Opcode,
|
||||||
|
args: &[crate::assembler::model::Token],
|
||||||
|
) -> Result<Instruction, AssembleError> {
|
||||||
|
let Some(src_token) = args.first() else {
|
||||||
|
return Err(AssembleError::MissingArgument(0));
|
||||||
|
};
|
||||||
|
let Some(dest_token) = args.get(1) else {
|
||||||
|
return Err(AssembleError::MissingArgument(1));
|
||||||
|
};
|
||||||
|
let Some(cmp_token) = args.get(2) else {
|
||||||
|
return Err(AssembleError::MissingArgument(1));
|
||||||
|
};
|
||||||
|
|
||||||
|
let src = expect_token!(src_token, Register)?;
|
||||||
|
let dest = expect_token!(dest_token, Register)?;
|
||||||
|
let cmp = expect_token!(cmp_token, Register)?;
|
||||||
|
|
||||||
|
match opcode {
|
||||||
|
Opcode::CMov => Ok(Instruction::cmov(src, dest, cmp)),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ins_ldx_stx(
|
||||||
|
opcode: Opcode,
|
||||||
|
args: &[crate::assembler::model::Token],
|
||||||
|
) -> Result<Instruction, AssembleError> {
|
||||||
|
let Some(src_token) = args.first() else {
|
||||||
|
return Err(AssembleError::MissingArgument(0));
|
||||||
|
};
|
||||||
|
let Some(dest_token) = args.get(1) else {
|
||||||
|
return Err(AssembleError::MissingArgument(1));
|
||||||
|
};
|
||||||
|
let Some(offset_token) = args.get(2) else {
|
||||||
|
return Err(AssembleError::MissingArgument(2));
|
||||||
|
};
|
||||||
|
|
||||||
|
let src = expect_token!(src_token, Register)?;
|
||||||
|
let dest = expect_token!(dest_token, Register)?;
|
||||||
|
let offset = expect_token!(offset_token, Immediate)?;
|
||||||
|
|
||||||
|
match opcode {
|
||||||
|
Opcode::Ldb => Ok(Instruction::ldb(src, dest, offset as u16)),
|
||||||
|
Opcode::Ldbs => Ok(Instruction::ldbs(src, dest, offset as u16)),
|
||||||
|
Opcode::Ldh => Ok(Instruction::ldh(src, dest, offset as u16)),
|
||||||
|
Opcode::Ldhs => Ok(Instruction::ldhs(src, dest, offset as u16)),
|
||||||
|
Opcode::Ldw => Ok(Instruction::ldw(src, dest, offset as u16)),
|
||||||
|
Opcode::Stb => Ok(Instruction::stb(src, dest, offset as u16)),
|
||||||
|
Opcode::Sth => Ok(Instruction::sth(src, dest, offset as u16)),
|
||||||
|
Opcode::Stw => Ok(Instruction::stw(src, dest, offset as u16)),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ins_stack(
|
||||||
|
opcode: Opcode,
|
||||||
|
args: &[crate::assembler::model::Token],
|
||||||
|
) -> Result<Instruction, AssembleError> {
|
||||||
|
let Some(reg_token) = args.first() else {
|
||||||
|
return Err(AssembleError::MissingArgument(0));
|
||||||
|
};
|
||||||
|
|
||||||
|
let reg = expect_token!(reg_token, Register)?;
|
||||||
|
match opcode {
|
||||||
|
Opcode::Push => Ok(Instruction::push(reg)),
|
||||||
|
Opcode::Pop => Ok(Instruction::pop(reg)),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ins_load_imm(
|
||||||
|
opcode: Opcode,
|
||||||
|
args: &[crate::assembler::model::Token],
|
||||||
|
) -> Result<Instruction, AssembleError> {
|
||||||
|
let Some(value_token) = args.first() else {
|
||||||
|
return Err(AssembleError::MissingArgument(0));
|
||||||
|
};
|
||||||
|
let Some(dest_token) = args.get(1) else {
|
||||||
|
return Err(AssembleError::MissingArgument(1));
|
||||||
|
};
|
||||||
|
|
||||||
|
let value = expect_token!(value_token, Immediate)?;
|
||||||
|
let dest = expect_token!(dest_token, Register)?;
|
||||||
|
|
||||||
|
match opcode {
|
||||||
|
Opcode::Lli => Ok(Instruction::lli(dest, value as u16)),
|
||||||
|
Opcode::Lui => Ok(Instruction::lui(dest, (value >> 16) as u16)),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ins_jump_unconditional(
|
||||||
|
opcode: Opcode,
|
||||||
|
args: &[crate::assembler::model::Token],
|
||||||
|
) -> Result<Instruction, AssembleError> {
|
||||||
|
let Some(addr_imm_token) = args.first() else {
|
||||||
|
return Err(AssembleError::MissingArgument(0));
|
||||||
|
};
|
||||||
|
// addr_reg is optional; default to Register::Zero if missing
|
||||||
|
let addr_reg_token = args.get(1).unwrap_or(&Token::Register(Register::Zero));
|
||||||
|
|
||||||
|
let addr_imm = expect_token!(addr_imm_token, Immediate)?;
|
||||||
|
let addr_reg = expect_token!(addr_reg_token, Register)?;
|
||||||
|
|
||||||
|
match opcode {
|
||||||
|
Opcode::Jmp => Ok(Instruction::jmp(addr_reg, addr_imm as u16)),
|
||||||
|
Opcode::Jic => Ok(Instruction::jic(addr_reg, addr_imm as u16)),
|
||||||
|
Opcode::Jnc => Ok(Instruction::jnc(addr_reg, addr_imm as u16)),
|
||||||
|
Opcode::Call => Ok(Instruction::call(addr_reg, addr_imm as u16)),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ins_jump_conditional(
|
||||||
|
opcode: Opcode,
|
||||||
|
args: &[crate::assembler::model::Token],
|
||||||
|
) -> Result<Instruction, AssembleError> {
|
||||||
|
let Some(condition_token) = args.first() else {
|
||||||
|
return Err(AssembleError::MissingArgument(0));
|
||||||
|
};
|
||||||
|
let Some(addr_imm_token) = args.get(1) else {
|
||||||
|
return Err(AssembleError::MissingArgument(1));
|
||||||
|
};
|
||||||
|
let addr_reg_token = if let Some(token) = args.get(2) {
|
||||||
|
token
|
||||||
|
} else {
|
||||||
|
&Token::Register(Register::Zero)
|
||||||
|
};
|
||||||
|
|
||||||
|
let condition = expect_token!(condition_token, Register)?;
|
||||||
|
let addr_imm = expect_token!(addr_imm_token, Immediate)?;
|
||||||
|
let addr_reg = expect_token!(addr_reg_token, Register)?;
|
||||||
|
|
||||||
|
Ok(match opcode {
|
||||||
|
Opcode::Jez => Instruction::jez(condition, addr_reg, addr_imm as u16),
|
||||||
|
Opcode::Jnz => Instruction::jnz(condition, addr_reg, addr_imm as u16),
|
||||||
|
_ => unreachable!(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ins_comparison(
|
||||||
|
opcode: Opcode,
|
||||||
|
args: &[crate::assembler::model::Token],
|
||||||
|
) -> Result<Instruction, AssembleError> {
|
||||||
|
let Some(left_token) = args.first() else {
|
||||||
|
return Err(AssembleError::MissingArgument(0));
|
||||||
|
};
|
||||||
|
let Some(right_token) = args.get(1) else {
|
||||||
|
return Err(AssembleError::MissingArgument(1));
|
||||||
|
};
|
||||||
|
let Some(dest_token) = args.get(2) else {
|
||||||
|
return Err(AssembleError::MissingArgument(2));
|
||||||
|
};
|
||||||
|
|
||||||
|
let left = expect_token!(left_token, Register)?;
|
||||||
|
let right = expect_token!(right_token, Register)?;
|
||||||
|
let dest = expect_token!(dest_token, Register)?;
|
||||||
|
Ok(match opcode {
|
||||||
|
Opcode::Ieq => Instruction::ieq(left, right, dest),
|
||||||
|
Opcode::Ine => Instruction::ine(left, right, dest),
|
||||||
|
Opcode::Igt => Instruction::igt(left, right, dest),
|
||||||
|
Opcode::Ige => Instruction::ige(left, right, dest),
|
||||||
|
Opcode::Ile => Instruction::ile(left, right, dest),
|
||||||
|
Opcode::Ilt => Instruction::ilt(left, right, dest),
|
||||||
|
_ => unreachable!(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ins_bitshift(
|
||||||
|
opcode: Opcode,
|
||||||
|
args: &[crate::assembler::model::Token],
|
||||||
|
) -> Result<Instruction, AssembleError> {
|
||||||
|
let Some(src_reg) = args.first() else {
|
||||||
|
return Err(AssembleError::MissingArgument(0));
|
||||||
|
};
|
||||||
|
let Some(r_shamt) = args.get(1) else {
|
||||||
|
return Err(AssembleError::MissingArgument(0));
|
||||||
|
};
|
||||||
|
let Some(i_shamt) = args.get(2) else {
|
||||||
|
return Err(AssembleError::MissingArgument(1));
|
||||||
|
};
|
||||||
|
let Some(dest_reg) = args.get(3) else {
|
||||||
|
return Err(AssembleError::MissingArgument(1));
|
||||||
|
};
|
||||||
|
|
||||||
|
let src = expect_token!(src_reg, Register)?;
|
||||||
|
let r_shamt = expect_token!(r_shamt, Register)?;
|
||||||
|
let i_shamt = expect_token!(i_shamt, Immediate)? as u8;
|
||||||
|
let dest = expect_token!(dest_reg, Register)?;
|
||||||
|
|
||||||
|
Ok(match opcode {
|
||||||
|
Opcode::Shl => Instruction::shl(src, r_shamt, dest, i_shamt),
|
||||||
|
Opcode::Shr => Instruction::shr(src, r_shamt, dest, i_shamt),
|
||||||
|
_ => unreachable!(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ins_arithmetic(
|
||||||
|
opcode: Opcode,
|
||||||
|
args: &[crate::assembler::model::Token],
|
||||||
|
) -> Result<Instruction, AssembleError> {
|
||||||
|
let Some(left_token) = args.first() else {
|
||||||
|
return Err(AssembleError::MissingArgument(0));
|
||||||
|
};
|
||||||
|
let Some(right_token) = args.get(1) else {
|
||||||
|
return Err(AssembleError::MissingArgument(1));
|
||||||
|
};
|
||||||
|
let Some(dest_token) = args.get(2) else {
|
||||||
|
return Err(AssembleError::MissingArgument(2));
|
||||||
|
};
|
||||||
|
|
||||||
|
let left = expect_token!(left_token, Register)?;
|
||||||
|
let right = expect_token!(right_token, Register)?;
|
||||||
|
let dest = expect_token!(dest_token, Register)?;
|
||||||
|
|
||||||
|
Ok(match opcode {
|
||||||
|
Opcode::Add => Instruction::add(left, right, dest),
|
||||||
|
Opcode::Sub => Instruction::sub(left, right, dest),
|
||||||
|
Opcode::And => Instruction::and(left, right, dest),
|
||||||
|
Opcode::Or => Instruction::or(left, right, dest),
|
||||||
|
Opcode::Xor => Instruction::xor(left, right, dest),
|
||||||
|
Opcode::Nand => Instruction::nand(left, right, dest),
|
||||||
|
Opcode::Nor => Instruction::nor(left, right, dest),
|
||||||
|
Opcode::Xnor => Instruction::xnor(left, right, dest),
|
||||||
|
_ => unreachable!(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ins_imm_arithmetic(
|
||||||
|
opcode: Opcode,
|
||||||
|
args: &[crate::assembler::model::Token],
|
||||||
|
) -> Result<Instruction, AssembleError> {
|
||||||
|
let Some(reg_token) = args.first() else {
|
||||||
|
return Err(AssembleError::MissingArgument(0));
|
||||||
|
};
|
||||||
|
let Some(immediate_token) = args.get(1) else {
|
||||||
|
return Err(AssembleError::MissingArgument(1));
|
||||||
|
};
|
||||||
|
let Some(dest_token) = args.get(2) else {
|
||||||
|
return Err(AssembleError::MissingArgument(2));
|
||||||
|
};
|
||||||
|
|
||||||
|
let reg = expect_token!(reg_token, Register)?;
|
||||||
|
let immediate = expect_token!(immediate_token, Immediate)? as u16;
|
||||||
|
let dest = expect_token!(dest_token, Register)?;
|
||||||
|
|
||||||
|
Ok(match opcode {
|
||||||
|
Opcode::AddI => Instruction::addi(reg, dest, immediate),
|
||||||
|
Opcode::SubI => Instruction::subi(reg, dest, immediate),
|
||||||
|
_ => unreachable!(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ins_not(args: &[crate::assembler::model::Token]) -> Result<Instruction, AssembleError> {
|
||||||
|
let Some(reg_token) = args.first() else {
|
||||||
|
return Err(AssembleError::MissingArgument(0));
|
||||||
|
};
|
||||||
|
let Some(dest_token) = args.get(1) else {
|
||||||
|
return Err(AssembleError::MissingArgument(1));
|
||||||
|
};
|
||||||
|
|
||||||
|
let src = expect_token!(reg_token, Register)?;
|
||||||
|
let dest = expect_token!(dest_token, Register)?;
|
||||||
|
Ok(Instruction::not(src, dest))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ins_interrupt(args: &[crate::assembler::model::Token]) -> Result<Instruction, AssembleError> {
|
||||||
|
let Some(code_token) = args.first() else {
|
||||||
|
return Err(AssembleError::MissingArgument(0));
|
||||||
|
};
|
||||||
|
|
||||||
|
let code = expect_token!(code_token, Immediate)? as u8;
|
||||||
|
Ok(Instruction::int(code))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_data_instruction(
|
||||||
|
args: &[crate::assembler::model::Token],
|
||||||
|
) -> Result<Instruction, AssembleError> {
|
||||||
|
let Some(immediate_token) = args.first() else {
|
||||||
|
return Err(AssembleError::MissingArgument(0));
|
||||||
|
};
|
||||||
|
|
||||||
|
let immediate = expect_token!(immediate_token, Immediate)?;
|
||||||
|
Ok(Instruction::data(immediate))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_segment_instruction(
|
||||||
|
args: &[crate::assembler::model::Token],
|
||||||
|
) -> Result<Instruction, AssembleError> {
|
||||||
|
let Some(immediate_token) = args.first() else {
|
||||||
|
return Err(AssembleError::MissingArgument(0));
|
||||||
|
};
|
||||||
|
|
||||||
|
let immediate = expect_token!(immediate_token, Immediate)?;
|
||||||
|
Ok(Instruction::data(
|
||||||
|
// mask the first 6 bits for the opcode
|
||||||
|
// this is really deprecated tbh. may remove in future.
|
||||||
|
Opcode::Segment as u32 | (immediate & 0x05FFFFFF),
|
||||||
|
))
|
||||||
|
}
|
||||||
@@ -0,0 +1,252 @@
|
|||||||
|
use common::prelude::Register;
|
||||||
|
|
||||||
|
use crate::assembler::model::{Node, Opcode, Token};
|
||||||
|
use crate::{assembler::AssembleError, expect_token, expect_type, node};
|
||||||
|
|
||||||
|
pub fn expand_pseudo_ops(mut nodes: Vec<Node>, module: u64) -> Result<Vec<Node>, AssembleError> {
|
||||||
|
let mut result = Vec::<Node>::with_capacity(nodes.len());
|
||||||
|
|
||||||
|
for node in &mut nodes {
|
||||||
|
if try_expand(node.clone(), &mut result, module).is_err() {
|
||||||
|
result.push(node.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_expand(node: Node, result: &mut Vec<Node>, _module: u64) -> Result<(), AssembleError> {
|
||||||
|
match node.opcode() {
|
||||||
|
Opcode::Func => expand_func(&node, result),
|
||||||
|
Opcode::Return => expand_return(&node, result),
|
||||||
|
Opcode::Ldb | Opcode::Ldbs | Opcode::Ldh | Opcode::Ldhs | Opcode::Ldw => {
|
||||||
|
expand_ldx(&node, result)?;
|
||||||
|
}
|
||||||
|
Opcode::Stb | Opcode::Sth | Opcode::Stw => expand_stx(&node, result)?,
|
||||||
|
|
||||||
|
Opcode::Lwi => expand_lwi(&node, result)?,
|
||||||
|
Opcode::Resb | Opcode::Resh | Opcode::Resw => expand_resx(&node, result)?,
|
||||||
|
Opcode::Db | Opcode::Dh | Opcode::Dw => expand_dx(&node, result)?,
|
||||||
|
_ => result.push(node),
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Function stack frame initialisation
|
||||||
|
fn expand_func(current: &Node, nodes: &mut Vec<Node>) {
|
||||||
|
let label = current.label();
|
||||||
|
let spr = Token::Register(Register::Spr);
|
||||||
|
let bpr = Token::Register(Register::Bpr);
|
||||||
|
|
||||||
|
nodes.extend(vec![
|
||||||
|
node!(label, Opcode::Push, bpr),
|
||||||
|
node!(None, Opcode::Mov, spr, bpr),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return from a function
|
||||||
|
fn expand_return(current: &Node, nodes: &mut Vec<Node>) {
|
||||||
|
let label = current.label();
|
||||||
|
let spr = Token::Register(Register::Spr);
|
||||||
|
let ret = Token::Register(Register::Ret);
|
||||||
|
let bpr = Token::Register(Register::Bpr);
|
||||||
|
|
||||||
|
nodes.extend(vec![
|
||||||
|
node!(label, Opcode::Mov, bpr, spr),
|
||||||
|
node!(None, Opcode::Pop, bpr),
|
||||||
|
node!(None, Opcode::Ret),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expand_ldx(current: &Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
|
||||||
|
let opcode = current.opcode();
|
||||||
|
let args: Vec<Token> = current.args().into_iter().take(3).collect();
|
||||||
|
|
||||||
|
let Some(name) = args.first() else {
|
||||||
|
return Err(AssembleError::MissingArgument(0));
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(reg) = args.get(1) else {
|
||||||
|
return Err(AssembleError::MissingArgument(1));
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(offset) = args.get(2) else {
|
||||||
|
return Err(AssembleError::MissingArgument(2));
|
||||||
|
};
|
||||||
|
|
||||||
|
let name = expect_type!(name, Symbol)?;
|
||||||
|
let reg = expect_type!(reg, Register)?;
|
||||||
|
let offset = expect_type!(offset, Immediate)?;
|
||||||
|
|
||||||
|
nodes.extend(vec![
|
||||||
|
node!(current.label(), Opcode::Lli, name, reg),
|
||||||
|
node!(None, Opcode::Lui, name, reg),
|
||||||
|
node!(None, opcode, reg, reg, offset),
|
||||||
|
]);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expand_stx(current: &Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
|
||||||
|
let opcode = current.opcode();
|
||||||
|
|
||||||
|
let args: Vec<Token> = current.args().into_iter().take(3).collect();
|
||||||
|
|
||||||
|
let Some(base) = args.first() else {
|
||||||
|
return Err(AssembleError::MissingArgument(0));
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(dest) = args.get(1) else {
|
||||||
|
return Err(AssembleError::MissingArgument(1));
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(offset) = args.get(2) else {
|
||||||
|
return Err(AssembleError::MissingArgument(2));
|
||||||
|
};
|
||||||
|
|
||||||
|
let base = expect_type!(base, Register)?;
|
||||||
|
let dest = expect_type!(dest, Symbol)?;
|
||||||
|
let offset = expect_type!(offset, Immediate)?;
|
||||||
|
let temp = Token::Register(Register::Acc);
|
||||||
|
|
||||||
|
nodes.extend(vec![
|
||||||
|
node!(current.label(), Opcode::Lli, dest, temp),
|
||||||
|
node!(None, Opcode::Lui, dest, temp),
|
||||||
|
node!(None, opcode, base, temp, offset),
|
||||||
|
]);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expand_lwi(current: &Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
|
||||||
|
let Ok(val) = current.arg(0) else {
|
||||||
|
return Err(AssembleError::MissingArgument(0));
|
||||||
|
};
|
||||||
|
|
||||||
|
let Ok(reg) = current.arg(1) else {
|
||||||
|
return Err(AssembleError::MissingArgument(1));
|
||||||
|
};
|
||||||
|
|
||||||
|
let val = expect_type!(val, Symbol, Immediate)?;
|
||||||
|
let reg = expect_type!(reg, Register)?;
|
||||||
|
|
||||||
|
nodes.extend(vec![
|
||||||
|
node!(current.label(), Opcode::Lli, val, reg),
|
||||||
|
node!(None, Opcode::Lui, val, reg),
|
||||||
|
]);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expand_resx(current: &Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
|
||||||
|
let Ok(region_label) = current.arg(0) else {
|
||||||
|
return Err(AssembleError::MissingArgument(0));
|
||||||
|
};
|
||||||
|
|
||||||
|
let Ok(size) = current.arg(1) else {
|
||||||
|
return Err(AssembleError::MissingArgument(1));
|
||||||
|
};
|
||||||
|
|
||||||
|
let region_label = expect_token!(region_label, Symbol)?;
|
||||||
|
let size = expect_token!(size, Immediate)?;
|
||||||
|
|
||||||
|
let units_per = match current.opcode() {
|
||||||
|
Opcode::Resb => 4,
|
||||||
|
Opcode::Resh => 2,
|
||||||
|
Opcode::Resw => 1,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut buffer = vec![];
|
||||||
|
// push the inital node with the label
|
||||||
|
for _ in 0..size.div_ceil(units_per) {
|
||||||
|
// push the rest of the nodes
|
||||||
|
buffer.push(node!(None, Opcode::Data, 0));
|
||||||
|
}
|
||||||
|
buffer[0].symbol = Some(region_label);
|
||||||
|
nodes.extend(buffer);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expand_dx(current: &Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
|
||||||
|
let Ok(region_label) = current.arg(0) else {
|
||||||
|
return Err(AssembleError::MissingArgument(0));
|
||||||
|
};
|
||||||
|
|
||||||
|
let region_label = expect_token!(region_label, Symbol)?;
|
||||||
|
let size = match current.opcode() {
|
||||||
|
Opcode::Db => 4,
|
||||||
|
Opcode::Dh => 2,
|
||||||
|
Opcode::Dw => 1,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut buffer = vec![];
|
||||||
|
|
||||||
|
let mut args = current.args();
|
||||||
|
let _label = args.remove(0);
|
||||||
|
|
||||||
|
for word in process_dx_data(args, size)? {
|
||||||
|
buffer.push(node!(None, Opcode::Data, Token::Immediate(word)));
|
||||||
|
}
|
||||||
|
buffer[0].symbol = Some(region_label);
|
||||||
|
|
||||||
|
nodes.extend(buffer);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_dx_data(args: Vec<Token>, size: usize) -> Result<Vec<u32>, AssembleError> {
|
||||||
|
assert!(matches!(size, 1 | 2 | 4));
|
||||||
|
|
||||||
|
let mut buffer = Vec::<u8>::new();
|
||||||
|
|
||||||
|
// Process each token
|
||||||
|
for token in args {
|
||||||
|
match token {
|
||||||
|
Token::StringLit(mut s) => {
|
||||||
|
s.push('\0');
|
||||||
|
// Split string into chars and write as bytes
|
||||||
|
for ch in s.chars() {
|
||||||
|
// Convert char to bytes (UTF-8 encoding)
|
||||||
|
let mut char_buf = [0u8; 4];
|
||||||
|
let char_bytes = ch.encode_utf8(&mut char_buf);
|
||||||
|
buffer.extend_from_slice(char_bytes.as_bytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Token::Immediate(value) => {
|
||||||
|
// Split u32 into bytes (little-endian)
|
||||||
|
buffer.extend_from_slice(&value.to_le_bytes());
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(AssembleError::Generic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pad buffer to alignment boundary with zeros
|
||||||
|
let remainder = buffer.len() % size;
|
||||||
|
if remainder != 0 {
|
||||||
|
let padding = size - remainder;
|
||||||
|
buffer.resize(buffer.len() + padding, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert byte buffer to u32 chunks
|
||||||
|
// Pad final buffer to u32 boundary if needed
|
||||||
|
let remainder = buffer.len() % 4;
|
||||||
|
if remainder != 0 {
|
||||||
|
let padding = 4 - remainder;
|
||||||
|
buffer.resize(buffer.len() + padding, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert bytes to u32s efficiently using chunks_exact
|
||||||
|
let result = buffer
|
||||||
|
.chunks_exact(4)
|
||||||
|
.map(|chunk| {
|
||||||
|
// Convert 4 bytes to u32 (little-endian)
|
||||||
|
u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]])
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use crate::assembler::AssembleError;
|
||||||
|
use crate::assembler::model::{Module, Opcode, Symbol, Token};
|
||||||
|
use common::prelude::Register;
|
||||||
|
|
||||||
|
pub fn lexer(mut program: String, module: u64) -> Result<Vec<Token>, AssembleError> {
|
||||||
|
let mut tokens = Vec::new();
|
||||||
|
|
||||||
|
let lines = program.lines();
|
||||||
|
let mut literal = String::new();
|
||||||
|
|
||||||
|
for line in lines {
|
||||||
|
for (i, token) in line.split_whitespace().enumerate() {
|
||||||
|
if token.starts_with("//") {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(stripped) = token.strip_prefix('"') {
|
||||||
|
literal.push_str(stripped);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !literal.is_empty() {
|
||||||
|
if !token.starts_with('"') {
|
||||||
|
if i > 0 {
|
||||||
|
literal.push(' ');
|
||||||
|
}
|
||||||
|
literal.push_str(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
if token.ends_with('"') {
|
||||||
|
literal.pop(); // remove the closing quote
|
||||||
|
|
||||||
|
tokens.push(Token::StringLit(literal));
|
||||||
|
literal = String::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let token = token.trim_end_matches(',');
|
||||||
|
if token.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(token) = parse_register(token)? {
|
||||||
|
tokens.push(token);
|
||||||
|
} else if let Some(token) = parse_opcode(token)? {
|
||||||
|
tokens.push(token);
|
||||||
|
} else if let Some(token) = parse_hex(token)? {
|
||||||
|
tokens.push(token);
|
||||||
|
} else if let Some(token) = parse_octal(token)? {
|
||||||
|
tokens.push(token);
|
||||||
|
} else if let Some(token) = parse_binary(token)? {
|
||||||
|
tokens.push(token);
|
||||||
|
} else if let Some(token) = parse_decimal(token)? {
|
||||||
|
tokens.push(token);
|
||||||
|
} else if let Some(token) = parse_label(token, module)? {
|
||||||
|
tokens.push(token);
|
||||||
|
} else if let Some(token) = parse_symbol(token, module)? {
|
||||||
|
tokens.push(token);
|
||||||
|
} else {
|
||||||
|
return Err(AssembleError::Generic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// println!("{:#?}", tokens);
|
||||||
|
|
||||||
|
Ok(tokens)
|
||||||
|
}
|
||||||
|
pub fn parse_register(token: &str) -> Result<Option<Token>, AssembleError> {
|
||||||
|
Ok(Register::from_str(token).map(Token::Register).ok())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_opcode(token: &str) -> Result<Option<Token>, AssembleError> {
|
||||||
|
Ok(Opcode::from_str(token).ok().map(Token::Opcode))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_hex(token: &str) -> Result<Option<Token>, AssembleError> {
|
||||||
|
if (token.len() < 3) | !token.starts_with("0x") {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(lit) = &token.get(2..) else {
|
||||||
|
return Err(AssembleError::InvalidArg);
|
||||||
|
};
|
||||||
|
|
||||||
|
u32::from_str_radix(lit, 16).map_or(Err(AssembleError::Generic), |value| {
|
||||||
|
Ok(Some(Token::Immediate(value)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_octal(token: &str) -> Result<Option<Token>, AssembleError> {
|
||||||
|
if (token.len() < 3) | !token.starts_with("0o") {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(lit) = &token.get(2..) else {
|
||||||
|
return Err(AssembleError::InvalidArg);
|
||||||
|
};
|
||||||
|
|
||||||
|
u32::from_str_radix(lit, 8).map_or(Err(AssembleError::Generic), |value| {
|
||||||
|
Ok(Some(Token::Immediate(value)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_binary(token: &str) -> Result<Option<Token>, AssembleError> {
|
||||||
|
if (token.len() < 3) | !token.starts_with("0b") {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(lit) = &token.get(2..) else {
|
||||||
|
return Err(AssembleError::InvalidArg);
|
||||||
|
};
|
||||||
|
|
||||||
|
u32::from_str_radix(lit, 2).map_or(Err(AssembleError::Generic), |value| {
|
||||||
|
Ok(Some(Token::Immediate(value)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_decimal(token: &str) -> Result<Option<Token>, AssembleError> {
|
||||||
|
let Ok(tok) = token.parse::<u32>() else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Some(Token::Immediate(tok)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_label(token: &str, module: u64) -> Result<Option<Token>, AssembleError> {
|
||||||
|
if token.ends_with(':') {
|
||||||
|
Ok(Some(Token::Symbol(Symbol {
|
||||||
|
name: token[0..token.len() - 1].to_string(),
|
||||||
|
module: Module::Resolved(module),
|
||||||
|
})))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_symbol(token: &str, module: u64) -> Result<Option<Token>, AssembleError> {
|
||||||
|
let Some(tokc) = token.chars().next() else {
|
||||||
|
return Err(AssembleError::Generic); // TODO: What is this error?
|
||||||
|
};
|
||||||
|
|
||||||
|
if tokc.is_numeric() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut split = token.splitn(2, "::");
|
||||||
|
let Some(symbol1) = split.next() else {
|
||||||
|
return Err(AssembleError::InvalidArg);
|
||||||
|
};
|
||||||
|
let symbol1 = symbol1.to_string();
|
||||||
|
|
||||||
|
if let Some(symbol2) = split.next() {
|
||||||
|
Ok(Some(Token::Symbol(Symbol {
|
||||||
|
name: symbol2.to_string(),
|
||||||
|
module: Module::Unresolved(symbol1),
|
||||||
|
})))
|
||||||
|
} else {
|
||||||
|
Ok(Some(Token::Symbol(Symbol {
|
||||||
|
name: symbol1,
|
||||||
|
module: Module::Resolved(module),
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
//! Macros used throughout the assembler
|
||||||
|
|
||||||
|
use crate::assembler::model::{Node, Opcode, Symbol, Token};
|
||||||
|
/// Parse DSA assembly code with optional formatting
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```rs
|
||||||
|
/// use assembler::macros::dsa;
|
||||||
|
/// // With formatting:
|
||||||
|
/// let nodes = dsa!(hash, "mov r1, {}", 42)?;
|
||||||
|
///
|
||||||
|
/// // Without formatting:
|
||||||
|
/// let nodes = dsa!(hash, "mov r1, 42")?;
|
||||||
|
/// ```
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! dsa {
|
||||||
|
// Version with formatting arguments
|
||||||
|
($hash:expr, $input:expr, $($args:expr),+) => {{
|
||||||
|
let input = format!($input, $($args),+);
|
||||||
|
let tokens = $crate::lexer::lexer(input, $hash)?;
|
||||||
|
let parsed = $crate::parser::Parser::parse_nodes(tokens)?;
|
||||||
|
parsed
|
||||||
|
}};
|
||||||
|
// Version without formatting
|
||||||
|
($hash:expr, $input:expr) => {{
|
||||||
|
let input = String::from($input);
|
||||||
|
let tokens = $crate::lexer::lexer(input, $hash)?;
|
||||||
|
let parsed = $crate::parser::Parser::parse_nodes(tokens)?;
|
||||||
|
parsed
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new Node with the given symbol, opcode, and tokens
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! node {
|
||||||
|
($symbol: expr, $opcode: expr, args: $tokens: expr) => {
|
||||||
|
$crate::assembler::model::Node::new($symbol.clone(), $opcode.clone(), $tokens.clone())
|
||||||
|
};
|
||||||
|
|
||||||
|
($symbol: expr, $opcode: expr, $($tokens: expr),+) => {
|
||||||
|
$crate::assembler::model::Node::new(
|
||||||
|
$symbol.clone(),
|
||||||
|
$opcode.clone(),
|
||||||
|
vec![$(node!(@convert_token $tokens)),+]
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
($symbol: expr, $opcode: expr) => {
|
||||||
|
$crate::assembler::model::Node::new(
|
||||||
|
$symbol.clone(),
|
||||||
|
$opcode.clone(),
|
||||||
|
Vec::new()
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
(@convert_token $token: literal) => {
|
||||||
|
$crate::assembler::model::Token::Immediate($token)
|
||||||
|
};
|
||||||
|
|
||||||
|
(@convert_token $token: expr) => {
|
||||||
|
$token.clone()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extracts a specific token type from a token
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! expect_token {
|
||||||
|
($token:expr, Symbol) => {
|
||||||
|
match $token {
|
||||||
|
$crate::assembler::model::Token::Symbol(value) => Ok(value.clone()),
|
||||||
|
other => Err($crate::assembler::AssembleError::UnexpectedToken(
|
||||||
|
other.clone(),
|
||||||
|
$crate::assembler::model::TokenType::Symbol,
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($token:expr, Register) => {
|
||||||
|
match $token {
|
||||||
|
$crate::assembler::model::Token::Register(value) => Ok(value.clone()),
|
||||||
|
other => Err($crate::assembler::AssembleError::UnexpectedToken(
|
||||||
|
other.clone(),
|
||||||
|
$crate::assembler::model::TokenType::Register,
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($token:expr, Immediate) => {
|
||||||
|
match $token {
|
||||||
|
$crate::assembler::model::Token::Immediate(value) => Ok(value.clone()),
|
||||||
|
other => Err($crate::assembler::AssembleError::UnexpectedToken(
|
||||||
|
other.clone(),
|
||||||
|
$crate::assembler::model::TokenType::Immediate,
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($token:expr, StringLit) => {
|
||||||
|
match $token {
|
||||||
|
$crate::assembler::model::Token::StringLit(value) => Ok(value.clone()),
|
||||||
|
other => Err($crate::assembler::AssembleError::UnexpectedToken(
|
||||||
|
other.clone(),
|
||||||
|
$crate::assembler::model::TokenType::StringLit,
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($token:expr, Opcode) => {
|
||||||
|
match $token {
|
||||||
|
$crate::assembler::model::Token::Opcode(value) => Ok(value.clone()),
|
||||||
|
other => Err($crate::assembler::AssembleError::UnexpectedToken(
|
||||||
|
other.clone(),
|
||||||
|
$crate::assembler::model::TokenType::Opcode,
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if a token matches any of the specified types
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! expect_type {
|
||||||
|
($token:expr, $($variant:ident),+) => {{
|
||||||
|
let token = $token;
|
||||||
|
match &token {
|
||||||
|
$(
|
||||||
|
$crate::assembler::model::Token::$variant(_) => Ok(token.clone()),
|
||||||
|
)+
|
||||||
|
other => {
|
||||||
|
let expected_type = expect_type!(@get_first_type $($variant),+);
|
||||||
|
Err($crate::assembler::AssembleError::UnexpectedToken(
|
||||||
|
other.clone().clone(),
|
||||||
|
expected_type,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
|
||||||
|
(@get_first_type Symbol $(, $rest:ident)*) => { $crate::assembler::model::TokenType::Symbol };
|
||||||
|
(@get_first_type Register $(, $rest:ident)*) => { $crate::assembler::model::TokenType::Register };
|
||||||
|
(@get_first_type Immediate $(, $rest:ident)*) => { $crate::assembler::model::TokenType::Immediate };
|
||||||
|
(@get_first_type StringLit $(, $rest:ident)*) => { $crate::assembler::model::TokenType::StringLit };
|
||||||
|
(@get_first_type Opcode $(, $rest:ident)*) => { $crate::assembler::model::TokenType::Opcode };
|
||||||
|
}
|
||||||
@@ -0,0 +1,263 @@
|
|||||||
|
#![allow(dead_code, unused)]
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
collections::HashSet,
|
||||||
|
fmt, fs,
|
||||||
|
hash::{DefaultHasher, Hash, Hasher},
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
sync::{
|
||||||
|
Arc, Mutex,
|
||||||
|
mpsc::{self, Receiver, Sender},
|
||||||
|
},
|
||||||
|
thread,
|
||||||
|
};
|
||||||
|
|
||||||
|
use common::prelude::Instruction;
|
||||||
|
|
||||||
|
// Module declarations
|
||||||
|
#[macro_use]
|
||||||
|
pub mod macros;
|
||||||
|
|
||||||
|
#[allow(clippy::module_inception)]
|
||||||
|
pub mod codegen;
|
||||||
|
pub mod expand;
|
||||||
|
pub mod lexer;
|
||||||
|
pub mod model;
|
||||||
|
pub mod parser;
|
||||||
|
pub mod resolver;
|
||||||
|
|
||||||
|
// Re-exports
|
||||||
|
pub use self::{
|
||||||
|
codegen::codegen,
|
||||||
|
expand::expand_pseudo_ops,
|
||||||
|
lexer::lexer,
|
||||||
|
model::{Module, Node, Opcode, Symbol, Token, TokenType},
|
||||||
|
parser::{Parser, Program},
|
||||||
|
resolver::{create_sections, resolve_dependencies, resolve_symbols},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Assembler {
|
||||||
|
src_path: PathBuf,
|
||||||
|
result_tx: mpsc::Sender<Result<Vec<u8>, AssembleError>>,
|
||||||
|
result_rx: Option<mpsc::Receiver<Result<Vec<u8>, AssembleError>>>,
|
||||||
|
is_running: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Assembler {
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(src_path: impl Into<PathBuf>) -> Self {
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
Self {
|
||||||
|
src_path: src_path.into(),
|
||||||
|
result_tx: tx,
|
||||||
|
result_rx: Some(rx),
|
||||||
|
is_running: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start the compilation process in a separate thread
|
||||||
|
pub fn start(&mut self, args: ()) {
|
||||||
|
if self.is_running {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let src = self.src_path.clone();
|
||||||
|
let tx = self.result_tx.clone();
|
||||||
|
|
||||||
|
thread::spawn(move || match assemble(&src) {
|
||||||
|
Ok(res) => {
|
||||||
|
let buffer: Vec<u8> = res
|
||||||
|
.iter()
|
||||||
|
.flat_map(|instruction| instruction.to_le_bytes())
|
||||||
|
.collect();
|
||||||
|
tx.send(Ok(buffer))
|
||||||
|
.expect("Failed to send compilation result from worker thread");
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
tx.send(Err(err))
|
||||||
|
.expect("Failed to send compilation error from worker thread");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
self.is_running = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn poll(&mut self) -> Option<Result<Vec<u8>, String>> {
|
||||||
|
if !self.is_running {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
match self
|
||||||
|
.result_rx
|
||||||
|
.as_ref()
|
||||||
|
.expect("result_rx should be Some while compilation is running")
|
||||||
|
.try_recv()
|
||||||
|
{
|
||||||
|
Ok(result) => {
|
||||||
|
self.is_running = false;
|
||||||
|
Some(result.map_err(|e| e.to_string()))
|
||||||
|
}
|
||||||
|
Err(mpsc::TryRecvError::Empty) => None,
|
||||||
|
Err(mpsc::TryRecvError::Disconnected) => {
|
||||||
|
self.is_running = false;
|
||||||
|
Some(Err(String::from(
|
||||||
|
"Compilation terminated before a result was returned",
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Block until compilation is complete and return the result
|
||||||
|
pub fn output(&mut self) -> Result<Vec<u8>, String> {
|
||||||
|
if let Ok(result) = self
|
||||||
|
.result_rx
|
||||||
|
.take()
|
||||||
|
.expect("result_rx should be Some while waiting for compilation result")
|
||||||
|
.recv()
|
||||||
|
{
|
||||||
|
self.is_running = false;
|
||||||
|
result.map_err(|e| e.to_string())
|
||||||
|
} else {
|
||||||
|
self.is_running = false;
|
||||||
|
Err(String::from(
|
||||||
|
"Compilation terminated before a result was returned",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Assembler {}
|
||||||
|
|
||||||
|
fn assemble(src: &Path) -> Result<Vec<Instruction>, AssembleError> {
|
||||||
|
let mut modules = HashSet::new();
|
||||||
|
let mut program = Program::new();
|
||||||
|
|
||||||
|
let hash = quick_hash(src);
|
||||||
|
|
||||||
|
if modules.contains(&hash) {
|
||||||
|
return Ok(vec![]);
|
||||||
|
}
|
||||||
|
|
||||||
|
prepare_dependency(src, &mut modules, &mut program)?;
|
||||||
|
|
||||||
|
let mut nodes = program.nodes.clone();
|
||||||
|
|
||||||
|
create_sections(&mut nodes)?;
|
||||||
|
resolve_symbols(&mut nodes)?;
|
||||||
|
|
||||||
|
println!("Generating assembly output...");
|
||||||
|
|
||||||
|
let instructions = codegen(nodes)?;
|
||||||
|
|
||||||
|
println!("Compilation Successful");
|
||||||
|
Ok(instructions)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare_dependency(
|
||||||
|
path: &Path,
|
||||||
|
modules: &mut HashSet<u64>,
|
||||||
|
program: &mut Program,
|
||||||
|
) -> Result<(), AssembleError> {
|
||||||
|
let filename = path
|
||||||
|
.file_name()
|
||||||
|
.and_then(|n| n.to_str())
|
||||||
|
.expect("Failed to get file name from path");
|
||||||
|
|
||||||
|
if let Ok(path) = path.canonicalize() {
|
||||||
|
println!("{:20} {:20} [{}]", "Building", filename, path.display());
|
||||||
|
}
|
||||||
|
|
||||||
|
let src =
|
||||||
|
fs::read_to_string(path).map_err(|_| AssembleError::InvalidFile(path.to_path_buf()))?;
|
||||||
|
let file_hash = quick_hash(path);
|
||||||
|
|
||||||
|
println!("{:20} {:20}", "Tokenising", filename);
|
||||||
|
let tokens = lexer::lexer(src, file_hash)?;
|
||||||
|
|
||||||
|
println!("{:20} {:20}", "Parsing", filename);
|
||||||
|
let parsed = Parser::parse_nodes(tokens)?;
|
||||||
|
|
||||||
|
println!("{:20} {:20}", "Resolving Deps", filename);
|
||||||
|
// Get the parent directory of the source file to use as the base directory
|
||||||
|
let base_dir = path
|
||||||
|
.parent()
|
||||||
|
.ok_or_else(|| AssembleError::InvalidFile(path.to_path_buf()))?;
|
||||||
|
let mut nodes = expand_pseudo_ops(parsed, file_hash)?;
|
||||||
|
nodes = resolve_dependencies(nodes, base_dir)?;
|
||||||
|
|
||||||
|
let deps = Parser::get_dependencies(&nodes, path)?;
|
||||||
|
|
||||||
|
println!("{:20} {:20}", "Expanding Pseudo-ops", filename);
|
||||||
|
|
||||||
|
// add a section instruction
|
||||||
|
nodes.insert(
|
||||||
|
0,
|
||||||
|
node!(None, Opcode::Segment, Token::Immediate(file_hash as u32)),
|
||||||
|
);
|
||||||
|
|
||||||
|
// for n in &nodes {
|
||||||
|
// println!("{n}");
|
||||||
|
// }
|
||||||
|
|
||||||
|
program.add_module(nodes);
|
||||||
|
|
||||||
|
for dep in deps {
|
||||||
|
println!(
|
||||||
|
"{:20} {:20}",
|
||||||
|
"Including",
|
||||||
|
dep.file_name()
|
||||||
|
.and_then(|f| f.to_str())
|
||||||
|
.expect("Dependency path has no file name or is not valid UTF-8")
|
||||||
|
);
|
||||||
|
|
||||||
|
let dep_hash = quick_hash(&dep);
|
||||||
|
if modules.insert(dep_hash) {
|
||||||
|
prepare_dependency(dep.as_path(), modules, program)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum AssembleError {
|
||||||
|
Generic,
|
||||||
|
UnexpectedEof,
|
||||||
|
InvalidFile(PathBuf),
|
||||||
|
UnexpectedToken(Token, TokenType),
|
||||||
|
InvalidArg,
|
||||||
|
UndefinedSymbol(Symbol),
|
||||||
|
/// Contains the nth element missing from the instruction.
|
||||||
|
MissingArgument(u8),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for AssembleError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Generic => write!(f, "Generic error"),
|
||||||
|
Self::UnexpectedToken(tok, expected) => {
|
||||||
|
write!(f, "Unexpected token {tok:?}, expected {expected:?}")
|
||||||
|
}
|
||||||
|
Self::UnexpectedEof => write!(f, "Unexpected end of file"),
|
||||||
|
Self::InvalidFile(path) => write!(f, "Invalid file `{}`", path.display()),
|
||||||
|
Self::InvalidArg => write!(f, "Invalid argument"),
|
||||||
|
Self::UndefinedSymbol(symbol) => {
|
||||||
|
write!(f, "Undefined symbol {symbol}")
|
||||||
|
}
|
||||||
|
Self::MissingArgument(n) => {
|
||||||
|
write!(f, "Missing argument #{n} from instruction arguments.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn quick_hash(value: &Path) -> u64 {
|
||||||
|
let mut hasher = DefaultHasher::new();
|
||||||
|
value
|
||||||
|
.canonicalize()
|
||||||
|
.expect("Failed to canonicalize path for quick_hash")
|
||||||
|
.to_str()
|
||||||
|
.hash(&mut hasher);
|
||||||
|
|
||||||
|
hasher.finish()
|
||||||
|
}
|
||||||
@@ -0,0 +1,480 @@
|
|||||||
|
use std::{fmt, str::FromStr};
|
||||||
|
|
||||||
|
use common::prelude::Register;
|
||||||
|
|
||||||
|
use crate::assembler::AssembleError;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Node {
|
||||||
|
pub symbol: Option<Symbol>,
|
||||||
|
pub opcode: Opcode,
|
||||||
|
pub tokens: Vec<Token>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Node {
|
||||||
|
#[must_use]
|
||||||
|
pub const fn new(symbol: Option<Symbol>, opcode: Opcode, tokens: Vec<Token>) -> Self {
|
||||||
|
Self {
|
||||||
|
symbol,
|
||||||
|
opcode,
|
||||||
|
tokens,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn label(&self) -> Option<Symbol> {
|
||||||
|
self.symbol.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub const fn opcode(&self) -> Opcode {
|
||||||
|
self.opcode
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn args(&self) -> Vec<Token> {
|
||||||
|
self.tokens.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn arg(&self, index: usize) -> Result<Token, AssembleError> {
|
||||||
|
self.args()
|
||||||
|
.get(index)
|
||||||
|
.cloned()
|
||||||
|
.ok_or(AssembleError::InvalidArg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Node {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
let symbol = self
|
||||||
|
.label()
|
||||||
|
.as_ref()
|
||||||
|
.map_or_else(String::new, |symbol| format!("{symbol}:\n"));
|
||||||
|
|
||||||
|
let args = self
|
||||||
|
.args()
|
||||||
|
.into_iter()
|
||||||
|
.map(|arg| arg.to_string())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(" ");
|
||||||
|
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"\x1b[93m{} \t\x1b[94m{} \x1b[37m{} \x1b[0m",
|
||||||
|
symbol,
|
||||||
|
self.opcode(),
|
||||||
|
args,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Symbol {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{} [ID:{}]", self.name, self.module)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Module {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Unresolved(name) => write!(f, "{name}"),
|
||||||
|
Self::Resolved(name) => write!(f, "{name}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Opcode {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Nop => write!(f, "nop"),
|
||||||
|
Self::Mov => write!(f, "mov"),
|
||||||
|
Self::CMov => write!(f, "movs"),
|
||||||
|
Self::Ldb => write!(f, "ldb"),
|
||||||
|
Self::Ldbs => write!(f, "ldbs"),
|
||||||
|
Self::Ldh => write!(f, "ldh"),
|
||||||
|
Self::Ldhs => write!(f, "ldhs"),
|
||||||
|
Self::Ldw => write!(f, "ldw"),
|
||||||
|
Self::Stb => write!(f, "stb"),
|
||||||
|
Self::Sth => write!(f, "sth"),
|
||||||
|
Self::Stw => write!(f, "stw"),
|
||||||
|
Self::Lli => write!(f, "lli"),
|
||||||
|
Self::Lui => write!(f, "lui"),
|
||||||
|
|
||||||
|
Self::Jmp => write!(f, "jmp"),
|
||||||
|
Self::Jez => write!(f, "jez"),
|
||||||
|
Self::Jnz => write!(f, "jnz"),
|
||||||
|
Self::Jic => write!(f, "jic"),
|
||||||
|
Self::Jnc => write!(f, "jnc"),
|
||||||
|
|
||||||
|
Self::Ieq => write!(f, "ieq"),
|
||||||
|
Self::Ine => write!(f, "ine"),
|
||||||
|
Self::Igt => write!(f, "igt"),
|
||||||
|
Self::Ige => write!(f, "ige"),
|
||||||
|
Self::Ilt => write!(f, "ilt"),
|
||||||
|
Self::Ile => write!(f, "ile"),
|
||||||
|
|
||||||
|
Self::Shl => write!(f, "shl"),
|
||||||
|
Self::Shr => write!(f, "shr"),
|
||||||
|
Self::Add => write!(f, "add"),
|
||||||
|
Self::Sub => write!(f, "sub"),
|
||||||
|
Self::AddI => write!(f, "addi"),
|
||||||
|
Self::SubI => write!(f, "subi"),
|
||||||
|
|
||||||
|
Self::And => write!(f, "and"),
|
||||||
|
Self::Or => write!(f, "or"),
|
||||||
|
Self::Not => write!(f, "not"),
|
||||||
|
Self::Xor => write!(f, "xor"),
|
||||||
|
Self::Nand => write!(f, "nand"),
|
||||||
|
Self::Nor => write!(f, "nor"),
|
||||||
|
Self::Xnor => write!(f, "xnor"),
|
||||||
|
|
||||||
|
Self::Int => write!(f, "int"),
|
||||||
|
Self::IRet => write!(f, "irt"),
|
||||||
|
Self::Hlt => write!(f, "hlt"),
|
||||||
|
|
||||||
|
Self::Db => write!(f, "db"),
|
||||||
|
Self::Dh => write!(f, "dh"),
|
||||||
|
Self::Dw => write!(f, "dw"),
|
||||||
|
Self::Resb => write!(f, "resb"),
|
||||||
|
Self::Resh => write!(f, "resh"),
|
||||||
|
Self::Resw => write!(f, "resw"),
|
||||||
|
|
||||||
|
Self::Push => write!(f, "push"),
|
||||||
|
Self::Pop => write!(f, "pop"),
|
||||||
|
Self::Lwi => write!(f, "lwi"),
|
||||||
|
|
||||||
|
Self::Func => write!(f, "func"),
|
||||||
|
Self::Call => write!(f, "call"),
|
||||||
|
Self::Ret => write!(f, "ret"),
|
||||||
|
Self::Return => write!(f, "return"),
|
||||||
|
|
||||||
|
Self::Include => write!(f, "include"),
|
||||||
|
Self::Data => write!(f, "data"),
|
||||||
|
Self::Segment => write!(f, "[SEGMENT]"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Eq)]
|
||||||
|
pub struct Symbol {
|
||||||
|
pub name: String,
|
||||||
|
pub module: Module,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::hash::Hash for Symbol {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
self.name.hash(state);
|
||||||
|
self.module.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Symbol {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.name == other.name && self.module == other.module
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub enum Module {
|
||||||
|
Resolved(u64),
|
||||||
|
Unresolved(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Token {
|
||||||
|
Symbol(Symbol),
|
||||||
|
Register(Register),
|
||||||
|
Immediate(u32),
|
||||||
|
StringLit(String),
|
||||||
|
Opcode(Opcode),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Token {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Symbol(symbol) => write!(f, "{symbol}"),
|
||||||
|
Self::Register(register) => write!(f, "{register}",),
|
||||||
|
Self::Immediate(immediate) => write!(f, "{immediate}",),
|
||||||
|
Self::StringLit(string_lit) => write!(f, "{string_lit}",),
|
||||||
|
Self::Opcode(opcode) => write!(f, "{opcode}",),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
|
pub enum TokenType {
|
||||||
|
Symbol,
|
||||||
|
Register,
|
||||||
|
Immediate,
|
||||||
|
StringLit,
|
||||||
|
Opcode,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TokenType {
|
||||||
|
#[must_use]
|
||||||
|
pub const fn from_token(token: &Token) -> Self {
|
||||||
|
match token {
|
||||||
|
Token::Symbol(_) => Self::Symbol,
|
||||||
|
Token::Register(_) => Self::Register,
|
||||||
|
Token::Immediate(_) => Self::Immediate,
|
||||||
|
Token::StringLit(_) => Self::StringLit,
|
||||||
|
Token::Opcode(_) => Self::Opcode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum Opcode {
|
||||||
|
// Real instructions (0x00-0x26)
|
||||||
|
Nop,
|
||||||
|
Mov,
|
||||||
|
CMov,
|
||||||
|
Ldb,
|
||||||
|
Ldbs,
|
||||||
|
Ldh,
|
||||||
|
Ldhs,
|
||||||
|
Ldw,
|
||||||
|
Stb,
|
||||||
|
Sth,
|
||||||
|
Stw,
|
||||||
|
Lli,
|
||||||
|
Lui,
|
||||||
|
|
||||||
|
Jmp,
|
||||||
|
Jez,
|
||||||
|
Jnz,
|
||||||
|
Jic,
|
||||||
|
Jnc,
|
||||||
|
|
||||||
|
Ieq,
|
||||||
|
Ine,
|
||||||
|
Igt,
|
||||||
|
Ige,
|
||||||
|
Ilt,
|
||||||
|
Ile,
|
||||||
|
|
||||||
|
Shl,
|
||||||
|
Shr,
|
||||||
|
Add,
|
||||||
|
Sub,
|
||||||
|
AddI,
|
||||||
|
SubI,
|
||||||
|
|
||||||
|
And,
|
||||||
|
Or,
|
||||||
|
Not,
|
||||||
|
Xor,
|
||||||
|
Nand,
|
||||||
|
Nor,
|
||||||
|
Xnor,
|
||||||
|
|
||||||
|
Int,
|
||||||
|
IRet,
|
||||||
|
Hlt,
|
||||||
|
|
||||||
|
// Function instructions
|
||||||
|
Call,
|
||||||
|
Ret,
|
||||||
|
|
||||||
|
// Stack ops
|
||||||
|
Push,
|
||||||
|
Pop,
|
||||||
|
|
||||||
|
// Pseudo-instructions
|
||||||
|
Db,
|
||||||
|
Dh,
|
||||||
|
Dw,
|
||||||
|
Resb,
|
||||||
|
Resh,
|
||||||
|
Resw,
|
||||||
|
Lwi,
|
||||||
|
Func,
|
||||||
|
Return,
|
||||||
|
|
||||||
|
// meta instructions (these aren't present in the binary as instructions)
|
||||||
|
Include,
|
||||||
|
Data,
|
||||||
|
Segment,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum OpcodeFromStrError {
|
||||||
|
InvalidRegister(&'static str),
|
||||||
|
InvalidOpcode(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for OpcodeFromStrError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::InvalidRegister(reg) => write!(f, "register does not exist: {reg}"),
|
||||||
|
Self::InvalidOpcode(op) => write!(f, "instruction does not exist: {op}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for OpcodeFromStrError {}
|
||||||
|
|
||||||
|
impl FromStr for Opcode {
|
||||||
|
type Err = OpcodeFromStrError;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s.to_lowercase().as_str() {
|
||||||
|
"nop" => Ok(Self::Nop),
|
||||||
|
"mov" => Ok(Self::Mov),
|
||||||
|
"cmov" => Ok(Self::CMov),
|
||||||
|
"ldb" => Ok(Self::Ldb),
|
||||||
|
"ldbs" => Ok(Self::Ldbs),
|
||||||
|
"ldh" => Ok(Self::Ldh),
|
||||||
|
"ldhs" => Ok(Self::Ldhs),
|
||||||
|
"ldw" => Ok(Self::Ldw),
|
||||||
|
"stb" => Ok(Self::Stb),
|
||||||
|
"sth" => Ok(Self::Sth),
|
||||||
|
"stw" => Ok(Self::Stw),
|
||||||
|
"lli" => Ok(Self::Lli),
|
||||||
|
"lui" => Ok(Self::Lui),
|
||||||
|
|
||||||
|
// comparison
|
||||||
|
"ieq" => Ok(Self::Ieq),
|
||||||
|
"ine" => Ok(Self::Ine),
|
||||||
|
"igt" => Ok(Self::Igt),
|
||||||
|
"ige" => Ok(Self::Ige),
|
||||||
|
"ilt" => Ok(Self::Ilt),
|
||||||
|
"ile" => Ok(Self::Ile),
|
||||||
|
|
||||||
|
// jumps
|
||||||
|
"jmp" => Ok(Self::Jmp),
|
||||||
|
"jez" => Ok(Self::Jez),
|
||||||
|
"jnz" => Ok(Self::Jnz),
|
||||||
|
"jic" => Ok(Self::Jic),
|
||||||
|
"jnc" => Ok(Self::Jnc),
|
||||||
|
|
||||||
|
"shl" => Ok(Self::Shl),
|
||||||
|
"shr" => Ok(Self::Shr),
|
||||||
|
"add" => Ok(Self::Add),
|
||||||
|
"sub" => Ok(Self::Sub),
|
||||||
|
"and" => Ok(Self::And),
|
||||||
|
"or" => Ok(Self::Or),
|
||||||
|
"not" => Ok(Self::Not),
|
||||||
|
"xor" => Ok(Self::Xor),
|
||||||
|
"nand" => Ok(Self::Nand),
|
||||||
|
"nor" => Ok(Self::Nor),
|
||||||
|
"xnor" => Ok(Self::Xnor),
|
||||||
|
"addi" => Ok(Self::AddI),
|
||||||
|
"subi" => Ok(Self::SubI),
|
||||||
|
|
||||||
|
// stack ops
|
||||||
|
"push" => Ok(Self::Push),
|
||||||
|
"pop" => Ok(Self::Pop),
|
||||||
|
|
||||||
|
// function instructions
|
||||||
|
"call" => Ok(Self::Call),
|
||||||
|
"ret" => Ok(Self::Ret),
|
||||||
|
|
||||||
|
"int" => Ok(Self::Int),
|
||||||
|
"irt" | "iret" => Ok(Self::IRet),
|
||||||
|
"hlt" => Ok(Self::Hlt),
|
||||||
|
|
||||||
|
// pseudoinstructions
|
||||||
|
"func" => Ok(Self::Func),
|
||||||
|
"return" => Ok(Self::Return),
|
||||||
|
"lwi" => Ok(Self::Lwi),
|
||||||
|
|
||||||
|
// directives
|
||||||
|
"include" => Ok(Self::Include),
|
||||||
|
"db" => Ok(Self::Db),
|
||||||
|
"dh" => Ok(Self::Dh),
|
||||||
|
"dw" => Ok(Self::Dw),
|
||||||
|
"resb" => Ok(Self::Resb),
|
||||||
|
"resh" => Ok(Self::Resh),
|
||||||
|
"resw" => Ok(Self::Resw),
|
||||||
|
|
||||||
|
// function pseudoinstructions
|
||||||
|
_ => Err(OpcodeFromStrError::InvalidOpcode(s.to_string())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Opcode {
|
||||||
|
pub const OPCODES: &[&str] = &[
|
||||||
|
// Real instructions (0x00-0x26)
|
||||||
|
"nop", "mov", "movs", "ldb", "ldbs", "ldh", "ldhs", "ldw", "stb", "sth", "stw", "lli",
|
||||||
|
"lui", "jmp", "jeq", "jne", "jgt", "jge", "jlt", "jle", "cmp", "inc", "dec", "shl", "shr",
|
||||||
|
"add", "sub", "and", "or", "not", "xor", "nand", "nor", "xnor", "int", "iret", "hlt",
|
||||||
|
"addi", "subi", "call", "ret", // Pseudo-instructions
|
||||||
|
"db", "dh", "dw", "resb", "resh", "resw", "lwi", "func", "return",
|
||||||
|
// meta instructions
|
||||||
|
"include",
|
||||||
|
];
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub const fn to_opcode_value(&self) -> Option<u8> {
|
||||||
|
match self {
|
||||||
|
Self::Nop => Some(0x00),
|
||||||
|
Self::Mov => Some(0x01),
|
||||||
|
Self::CMov => Some(0x02),
|
||||||
|
Self::Ldb => Some(0x03),
|
||||||
|
Self::Ldbs => Some(0x04),
|
||||||
|
Self::Ldh => Some(0x05),
|
||||||
|
Self::Ldhs => Some(0x06),
|
||||||
|
Self::Ldw => Some(0x07),
|
||||||
|
Self::Stb => Some(0x08),
|
||||||
|
Self::Sth => Some(0x09),
|
||||||
|
Self::Stw => Some(0x0A),
|
||||||
|
Self::Lli => Some(0x0B),
|
||||||
|
Self::Lui => Some(0x0C),
|
||||||
|
Self::Ieq => Some(0x0D),
|
||||||
|
Self::Ine => Some(0x0E),
|
||||||
|
Self::Ilt => Some(0x0F),
|
||||||
|
Self::Ile => Some(0x10),
|
||||||
|
Self::Igt => Some(0x11),
|
||||||
|
Self::Ige => Some(0x12),
|
||||||
|
Self::Jmp => Some(0x13),
|
||||||
|
Self::Jez => Some(0x14),
|
||||||
|
Self::Jnz => Some(0x15),
|
||||||
|
Self::Jic => Some(0x16),
|
||||||
|
Self::Jnc => Some(0x17),
|
||||||
|
Self::And => Some(0x18),
|
||||||
|
Self::Nand => Some(0x19),
|
||||||
|
Self::Or => Some(0x1A),
|
||||||
|
Self::Nor => Some(0x1B),
|
||||||
|
Self::Xor => Some(0x1C),
|
||||||
|
Self::Xnor => Some(0x1D),
|
||||||
|
Self::Not => Some(0x1E),
|
||||||
|
Self::Add => Some(0x1F),
|
||||||
|
Self::Sub => Some(0x20),
|
||||||
|
Self::Shl => Some(0x21),
|
||||||
|
Self::Shr => Some(0x22),
|
||||||
|
Self::AddI => Some(0x23),
|
||||||
|
Self::SubI => Some(0x24),
|
||||||
|
|
||||||
|
Self::Push => Some(0x25),
|
||||||
|
Self::Pop => Some(0x26),
|
||||||
|
Self::Call => Some(0x27),
|
||||||
|
Self::Ret => Some(0x28),
|
||||||
|
|
||||||
|
Self::Int => Some(0x29),
|
||||||
|
Self::IRet => Some(0x2A),
|
||||||
|
Self::Hlt => Some(0x2B),
|
||||||
|
|
||||||
|
Self::Segment => Some(0x2C),
|
||||||
|
// Pseudo-instructions don't have opcode values
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub const fn is_pseudo_instruction(&self) -> bool {
|
||||||
|
matches!(
|
||||||
|
self,
|
||||||
|
Self::Db
|
||||||
|
| Self::Dh
|
||||||
|
| Self::Dw
|
||||||
|
| Self::Resb
|
||||||
|
| Self::Resh
|
||||||
|
| Self::Resw
|
||||||
|
| Self::Lwi
|
||||||
|
| Self::Func
|
||||||
|
| Self::Return
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,427 @@
|
|||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use crate::assembler::TokenType;
|
||||||
|
use crate::{assembler::AssembleError, expect_token, expect_type, node};
|
||||||
|
|
||||||
|
use crate::assembler::model::{Node, Opcode, Token};
|
||||||
|
use common::prelude::*;
|
||||||
|
|
||||||
|
pub struct Parser {
|
||||||
|
tokens: Vec<Token>,
|
||||||
|
nodes: Vec<Node>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Program {
|
||||||
|
pub nodes: Vec<Node>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Program {
|
||||||
|
#[must_use]
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self { nodes: vec![] }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_module(&mut self, module: Vec<Node>) {
|
||||||
|
self.nodes.extend(module);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parser(&mut self) -> Parser {
|
||||||
|
Parser {
|
||||||
|
tokens: vec![],
|
||||||
|
nodes: self.nodes.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Program {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parser {
|
||||||
|
pub fn parse_nodes(tokens: Vec<Token>) -> Result<Vec<Node>, AssembleError> {
|
||||||
|
println!("parsing");
|
||||||
|
|
||||||
|
let mut self_ = Self {
|
||||||
|
tokens: tokens.into_iter().rev().collect(),
|
||||||
|
nodes: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
while !self_.tokens.is_empty() {
|
||||||
|
println!("NEXT {}", self_.peek_next().unwrap());
|
||||||
|
|
||||||
|
let ins = self_.parse_instruction()?;
|
||||||
|
self_.nodes.push(ins);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(self_.nodes.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_dependencies(
|
||||||
|
nodes: &Vec<Node>,
|
||||||
|
source_path: &Path,
|
||||||
|
) -> Result<Vec<PathBuf>, AssembleError> {
|
||||||
|
let mut dependencies = Vec::new();
|
||||||
|
// Get the parent directory of the source file to use as the base directory
|
||||||
|
let base_dir = source_path
|
||||||
|
.parent()
|
||||||
|
.ok_or_else(|| AssembleError::InvalidFile(source_path.to_path_buf()))?;
|
||||||
|
|
||||||
|
for node in nodes {
|
||||||
|
if node.opcode() == Opcode::Include {
|
||||||
|
let path_str =
|
||||||
|
expect_token!(node.args().get(1).ok_or(AssembleError::Generic)?, StringLit)?;
|
||||||
|
let path = PathBuf::from(path_str);
|
||||||
|
|
||||||
|
// If the path is not absolute, make it relative to the base directory
|
||||||
|
let full_path = if path.is_absolute() {
|
||||||
|
path
|
||||||
|
} else {
|
||||||
|
base_dir.join(path)
|
||||||
|
};
|
||||||
|
|
||||||
|
dependencies.push(full_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(dependencies)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[expect(clippy::too_many_lines, clippy::cognitive_complexity)]
|
||||||
|
fn parse_instruction(&mut self) -> Result<Node, AssembleError> {
|
||||||
|
if self.tokens.is_empty() {
|
||||||
|
unreachable!();
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the Node starts with a label
|
||||||
|
let label = expect_token!(self.peek_next()?, Symbol).ok();
|
||||||
|
if label.is_some() {
|
||||||
|
self.tokens.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
let opcode = expect_token!(self.next()?, Opcode)?;
|
||||||
|
let args: Vec<Token>;
|
||||||
|
|
||||||
|
#[allow(clippy::match_same_arms)]
|
||||||
|
match opcode {
|
||||||
|
// R-type instructions
|
||||||
|
Opcode::Mov | Opcode::CMov => {
|
||||||
|
let reg1 = expect_type!(self.next()?, Register, Symbol)?;
|
||||||
|
let reg2 = expect_type!(self.next()?, Register, Symbol)?;
|
||||||
|
args = vec![reg1, reg2];
|
||||||
|
}
|
||||||
|
|
||||||
|
Opcode::Ldb | Opcode::Ldbs | Opcode::Ldh | Opcode::Ldhs | Opcode::Ldw => {
|
||||||
|
let base = expect_type!(self.next()?, Register, Symbol)?;
|
||||||
|
let dest = expect_type!(self.next()?, Register)?;
|
||||||
|
|
||||||
|
let offset = match self.peek_next() {
|
||||||
|
Ok(next) if expect_type!(next.clone(), Immediate).is_ok() => self.next()?,
|
||||||
|
_ => Token::Immediate(0),
|
||||||
|
};
|
||||||
|
|
||||||
|
args = vec![base, dest, offset];
|
||||||
|
}
|
||||||
|
Opcode::Stb | Opcode::Sth | Opcode::Stw => {
|
||||||
|
let base = expect_type!(self.next()?, Register)?;
|
||||||
|
let dest = expect_type!(self.next()?, Register, Symbol)?;
|
||||||
|
|
||||||
|
let offset = match self.peek_next() {
|
||||||
|
Ok(next) if expect_type!(next.clone(), Immediate).is_ok() => self.next()?,
|
||||||
|
_ => Token::Immediate(0),
|
||||||
|
};
|
||||||
|
args = vec![base, dest, offset];
|
||||||
|
}
|
||||||
|
|
||||||
|
Opcode::Add
|
||||||
|
| Opcode::Sub
|
||||||
|
| Opcode::And
|
||||||
|
| Opcode::Or
|
||||||
|
| Opcode::Xor
|
||||||
|
| Opcode::Nand
|
||||||
|
| Opcode::Nor
|
||||||
|
| Opcode::Xnor => {
|
||||||
|
let src1 = expect_type!(self.next()?, Register, Symbol)?;
|
||||||
|
let src2 = expect_type!(self.next()?, Register, Symbol)?;
|
||||||
|
let dest = expect_type!(self.next()?, Register, Symbol)?;
|
||||||
|
args = vec![src1, src2, dest];
|
||||||
|
}
|
||||||
|
|
||||||
|
Opcode::Not => {
|
||||||
|
let src = expect_type!(self.next()?, Register, Symbol)?;
|
||||||
|
let dest = expect_type!(self.next()?, Register, Symbol)?;
|
||||||
|
args = vec![src, dest];
|
||||||
|
}
|
||||||
|
Opcode::Shl | Opcode::Shr => {
|
||||||
|
let src = expect_type!(self.next()?, Register, Symbol)?;
|
||||||
|
|
||||||
|
// First operand after src: could be immediate or register
|
||||||
|
let first = self.next()?;
|
||||||
|
|
||||||
|
let (r_shamt, i_shamt) = match first {
|
||||||
|
Token::Register(_) => (
|
||||||
|
first,
|
||||||
|
if let Ok(tok) = self.peek_next() {
|
||||||
|
if expect_type!(tok, Immediate).is_ok() {
|
||||||
|
self.next()?
|
||||||
|
} else {
|
||||||
|
Token::Immediate(0)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Token::Immediate(0)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Token::Immediate(_) => (Token::Register(Register::Zero), first),
|
||||||
|
_ => {
|
||||||
|
return Err(AssembleError::UnexpectedToken(first, TokenType::Immediate));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let dest = if let Ok(tok) = self.peek_next() {
|
||||||
|
if expect_type!(tok, Register).is_ok() {
|
||||||
|
self.next()?
|
||||||
|
} else {
|
||||||
|
src.clone() // Default to src if no dest specified
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
src.clone() // Default to src if no dest specified
|
||||||
|
};
|
||||||
|
|
||||||
|
args = vec![src, r_shamt, i_shamt, dest];
|
||||||
|
}
|
||||||
|
|
||||||
|
Opcode::Include => {
|
||||||
|
let mod_name = expect_type!(self.next()?, Symbol)?;
|
||||||
|
let path = expect_type!(self.next()?, StringLit)?;
|
||||||
|
args = vec![mod_name, path];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unconditional jump
|
||||||
|
Opcode::Jmp | Opcode::Jic | Opcode::Jnc => {
|
||||||
|
let imm = expect_type!(self.next()?, Immediate, Symbol)?;
|
||||||
|
let offset = match self.peek_next() {
|
||||||
|
Ok(token) => {
|
||||||
|
if expect_type!(token, Register).is_ok() {
|
||||||
|
self.next()?
|
||||||
|
} else {
|
||||||
|
Token::Register(Register::Zero)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => Token::Register(Register::Zero),
|
||||||
|
};
|
||||||
|
args = vec![imm, offset];
|
||||||
|
}
|
||||||
|
|
||||||
|
Opcode::Ieq | Opcode::Ine | Opcode::Ilt | Opcode::Igt | Opcode::Ile | Opcode::Ige => {
|
||||||
|
println!("yes");
|
||||||
|
let src1 = expect_type!(self.next()?, Register, Symbol)?;
|
||||||
|
let src2 = expect_type!(self.next()?, Register, Symbol)?;
|
||||||
|
let dest = expect_type!(self.next()?, Register, Symbol)?;
|
||||||
|
args = vec![src1, src2, dest];
|
||||||
|
|
||||||
|
println!("done");
|
||||||
|
}
|
||||||
|
|
||||||
|
Opcode::Jez | Opcode::Jnz => {
|
||||||
|
let condition = expect_type!(self.next()?, Register)?;
|
||||||
|
let imm = expect_type!(self.next()?, Immediate, Symbol)?;
|
||||||
|
let offset = match self.peek_next() {
|
||||||
|
Ok(token) => {
|
||||||
|
if expect_type!(token, Register).is_ok() {
|
||||||
|
self.next()?
|
||||||
|
} else {
|
||||||
|
Token::Register(Register::Zero)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => Token::Register(Register::Zero),
|
||||||
|
};
|
||||||
|
args = vec![condition, imm, offset];
|
||||||
|
}
|
||||||
|
|
||||||
|
Opcode::Call => {
|
||||||
|
let addr = expect_type!(self.next()?, Symbol)?;
|
||||||
|
args = vec![addr];
|
||||||
|
}
|
||||||
|
|
||||||
|
// I-type instructions
|
||||||
|
Opcode::Lui | Opcode::Lli | Opcode::Lwi => {
|
||||||
|
let imm = expect_type!(self.next()?, Immediate, Symbol)?;
|
||||||
|
let reg = expect_type!(self.next()?, Register)?;
|
||||||
|
args = vec![imm, reg];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Immediate Arithmetic
|
||||||
|
Opcode::AddI | Opcode::SubI => {
|
||||||
|
let reg = expect_type!(self.next()?, Register)?;
|
||||||
|
let imm = expect_type!(self.next()?, Immediate)?;
|
||||||
|
let reg2 = if expect_type!(self.peek_next()?, Register).is_ok() {
|
||||||
|
self.next()?
|
||||||
|
} else {
|
||||||
|
reg.clone()
|
||||||
|
};
|
||||||
|
args = vec![reg, imm, reg2];
|
||||||
|
}
|
||||||
|
|
||||||
|
// D-type pseudoinstructions (data definition)
|
||||||
|
Opcode::Resb | Opcode::Resh | Opcode::Resw => {
|
||||||
|
let name = expect_type!(self.next()?, Symbol)?;
|
||||||
|
let num = expect_type!(self.next()?, Immediate)?;
|
||||||
|
args = vec![name, num];
|
||||||
|
}
|
||||||
|
|
||||||
|
Opcode::Db | Opcode::Dh | Opcode::Dw => {
|
||||||
|
args = self.parse_data_definition(opcode)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// E-type pseudoinstructions (stack operations)
|
||||||
|
Opcode::Push | Opcode::Pop => {
|
||||||
|
let reg = expect_type!(self.next()?, Register, Symbol)?;
|
||||||
|
args = vec![reg];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special instructions
|
||||||
|
Opcode::Int => {
|
||||||
|
let val = expect_type!(self.next()?, Immediate)?;
|
||||||
|
args = vec![val];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instructions with no arguments
|
||||||
|
Opcode::Hlt
|
||||||
|
| Opcode::Nop
|
||||||
|
| Opcode::Ret
|
||||||
|
| Opcode::IRet
|
||||||
|
| Opcode::Return
|
||||||
|
| Opcode::Func => {
|
||||||
|
args = vec![];
|
||||||
|
}
|
||||||
|
|
||||||
|
Opcode::Data | Opcode::Segment => {
|
||||||
|
return Err(AssembleError::Generic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(node!(label, opcode, args: args))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_data_definition(&mut self, opcode: Opcode) -> Result<Vec<Token>, AssembleError> {
|
||||||
|
let mut values = Vec::new();
|
||||||
|
|
||||||
|
println!("e {:?}", self.peek_next());
|
||||||
|
|
||||||
|
let name = expect_type!(self.next()?, Symbol)?;
|
||||||
|
values.push(name);
|
||||||
|
|
||||||
|
match opcode {
|
||||||
|
Opcode::Db => {
|
||||||
|
// db can take string literals or u8 immediates
|
||||||
|
while !self.tokens.is_empty() {
|
||||||
|
let token = self
|
||||||
|
.tokens
|
||||||
|
.last()
|
||||||
|
.expect("Expected a token for data definition, but found none");
|
||||||
|
|
||||||
|
match token {
|
||||||
|
Token::StringLit(_) => {
|
||||||
|
values.push(
|
||||||
|
self.tokens
|
||||||
|
.pop()
|
||||||
|
.expect("Expected a token for data definition, but found none"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Token::Immediate(val) if u8::try_from(*val).is_ok() => {
|
||||||
|
values.push(
|
||||||
|
self.tokens
|
||||||
|
.pop()
|
||||||
|
.expect("Expected a token for data definition, but found none"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Opcode::Dh => {
|
||||||
|
// dh can take u16 immediates
|
||||||
|
while !self.tokens.is_empty() {
|
||||||
|
let token = self
|
||||||
|
.tokens
|
||||||
|
.last()
|
||||||
|
.expect("Expected a token for data definition, but found none");
|
||||||
|
|
||||||
|
match token {
|
||||||
|
Token::StringLit(_) => {
|
||||||
|
values.push(
|
||||||
|
self.tokens
|
||||||
|
.pop()
|
||||||
|
.expect("Expected a token for data definition, but found none"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Token::Immediate(val) if u16::try_from(*val).is_ok() => {
|
||||||
|
values.push(
|
||||||
|
self.tokens
|
||||||
|
.pop()
|
||||||
|
.expect("Expected a token for data definition, but found none"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Opcode::Dw => {
|
||||||
|
// dw can take u32 immediates
|
||||||
|
while !self.tokens.is_empty() {
|
||||||
|
match self
|
||||||
|
.tokens
|
||||||
|
.last()
|
||||||
|
.expect("Expected a token for data definition, but found none")
|
||||||
|
{
|
||||||
|
Token::StringLit(_) => {
|
||||||
|
values.push(
|
||||||
|
self.tokens
|
||||||
|
.pop()
|
||||||
|
.expect("Expected a token for data definition, but found none"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Token::Immediate(val) => {
|
||||||
|
values.push(
|
||||||
|
self.tokens
|
||||||
|
.pop()
|
||||||
|
.expect("Expected a token for data definition, but found none"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(values)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next(&mut self) -> Result<Token, AssembleError> {
|
||||||
|
if self.tokens.is_empty() {
|
||||||
|
Err(AssembleError::UnexpectedEof)
|
||||||
|
} else {
|
||||||
|
Ok(self
|
||||||
|
.tokens
|
||||||
|
.pop()
|
||||||
|
.expect("tokens vector was unexpectedly empty in next()"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn peek_next(&self) -> Result<Token, AssembleError> {
|
||||||
|
if self.tokens.is_empty() {
|
||||||
|
Err(AssembleError::UnexpectedEof)
|
||||||
|
} else {
|
||||||
|
Ok(self
|
||||||
|
.tokens
|
||||||
|
.last()
|
||||||
|
.expect("peek_next called on empty tokens vector")
|
||||||
|
.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
fs::canonicalize,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
use common::prelude::Register;
|
||||||
|
|
||||||
|
use crate::assembler::model::{Module, Node, Opcode, Symbol, Token};
|
||||||
|
use crate::assembler::quick_hash;
|
||||||
|
use crate::{assembler::AssembleError, node};
|
||||||
|
|
||||||
|
pub fn resolve_symbols(nodes: &mut [Node]) -> Result<(), AssembleError> {
|
||||||
|
let symbol_table = generate_symbol_table(nodes);
|
||||||
|
|
||||||
|
for node in nodes.iter_mut() {
|
||||||
|
match node.opcode() {
|
||||||
|
Opcode::Jmp | Opcode::Call | Opcode::Jic | Opcode::Jnc | Opcode::Lli | Opcode::Lui => {
|
||||||
|
if let Token::Symbol(symbol) = node
|
||||||
|
.arg(0)
|
||||||
|
.expect("Expected argument 0 for jump-like opcode")
|
||||||
|
{
|
||||||
|
if let Some(address) = symbol_table.get(&symbol) {
|
||||||
|
node.tokens[0] = Token::Immediate(*address);
|
||||||
|
} else {
|
||||||
|
return Err(AssembleError::UndefinedSymbol(symbol));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Opcode::Jez | Opcode::Jnz => {
|
||||||
|
if let Token::Symbol(symbol) = node
|
||||||
|
.arg(1)
|
||||||
|
.expect("Expected argument 0 for jump-like opcode")
|
||||||
|
{
|
||||||
|
if let Some(address) = symbol_table.get(&symbol) {
|
||||||
|
node.tokens[1] = Token::Immediate(*address);
|
||||||
|
} else {
|
||||||
|
return Err(AssembleError::UndefinedSymbol(symbol));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_symbol_table(nodes: &[Node]) -> HashMap<Symbol, u32> {
|
||||||
|
let mut table = HashMap::new();
|
||||||
|
|
||||||
|
for (i, node) in nodes.iter().enumerate() {
|
||||||
|
if let Some(symbol) = node.label() {
|
||||||
|
table.insert(symbol, 4 * i as u32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resolve_dependencies(
|
||||||
|
mut nodes: Vec<Node>,
|
||||||
|
base_dir: &Path,
|
||||||
|
) -> Result<Vec<Node>, AssembleError> {
|
||||||
|
// First we get a list of imports.
|
||||||
|
let mut dependencies = Vec::new();
|
||||||
|
for node in &nodes {
|
||||||
|
if node.opcode() == Opcode::Include {
|
||||||
|
// we want the path, and the name
|
||||||
|
let name = if let Token::Symbol(name) = node
|
||||||
|
.arg(0)
|
||||||
|
.expect("Expected argument #0 for Include directive.")
|
||||||
|
{
|
||||||
|
name.name.clone()
|
||||||
|
} else {
|
||||||
|
unreachable!()
|
||||||
|
}; //node.2.get(0).unwrap()
|
||||||
|
|
||||||
|
let Ok(Token::StringLit(path)) = node.arg(1) else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
|
let full_path = base_dir.join(path);
|
||||||
|
let canonical_path = full_path
|
||||||
|
.canonicalize()
|
||||||
|
.map_err(|_| AssembleError::InvalidFile(full_path.clone()))?;
|
||||||
|
|
||||||
|
let hash = quick_hash(&canonical_path);
|
||||||
|
|
||||||
|
dependencies.push((name, hash));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut changes = Vec::<(u32, u32, Symbol)>::new();
|
||||||
|
// now we resolve the symbols on all the nodes
|
||||||
|
// we need to check all operands for unresolved signals
|
||||||
|
for (i, node) in nodes.clone().iter().enumerate() {
|
||||||
|
let Node {
|
||||||
|
tokens: operands, ..
|
||||||
|
} = node;
|
||||||
|
for (j, token) in operands.iter().enumerate() {
|
||||||
|
if let Token::Symbol(symbol) = token {
|
||||||
|
for d in &dependencies {
|
||||||
|
if let Module::Unresolved(name) = symbol.module.clone() {
|
||||||
|
if name != d.0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let symbol = Symbol {
|
||||||
|
name: symbol.name.clone(),
|
||||||
|
module: Module::Resolved(d.1),
|
||||||
|
};
|
||||||
|
changes.push((i as u32, j as u32, symbol));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i, j, symbol) in changes {
|
||||||
|
nodes[i as usize].tokens[j as usize] = Token::Symbol(symbol);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(nodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_sections(nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
|
||||||
|
let mut res = Vec::<Node>::with_capacity(nodes.len());
|
||||||
|
|
||||||
|
res.push(node!(None, Opcode::Segment, Token::Immediate(0)));
|
||||||
|
|
||||||
|
for n in nodes.iter() {
|
||||||
|
if n.opcode() == Opcode::Data {
|
||||||
|
res.push(n.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let start = res.len() + 2;
|
||||||
|
res.insert(
|
||||||
|
0,
|
||||||
|
node!(
|
||||||
|
None,
|
||||||
|
Opcode::Jmp,
|
||||||
|
Token::Immediate(start as u32 * 4),
|
||||||
|
Token::Register(Register::Zero)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
for n in nodes.iter() {
|
||||||
|
if !matches!(n.opcode(), Opcode::Data | Opcode::Include) {
|
||||||
|
res.push(n.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*nodes = res;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#![deny(
|
||||||
|
clippy::unwrap_used,
|
||||||
|
clippy::nursery,
|
||||||
|
clippy::perf,
|
||||||
|
clippy::pedantic,
|
||||||
|
clippy::complexity
|
||||||
|
)]
|
||||||
|
#![allow(
|
||||||
|
clippy::cast_possible_truncation,
|
||||||
|
clippy::missing_panics_doc,
|
||||||
|
clippy::missing_errors_doc,
|
||||||
|
clippy::match_wildcard_for_single_variants
|
||||||
|
)]
|
||||||
|
|
||||||
|
pub mod assembler;
|
||||||
|
// mod util;
|
||||||
|
|
||||||
|
pub mod prelude {
|
||||||
|
pub use crate::assembler::Assembler;
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
use common::{self as _};
|
||||||
|
|
||||||
|
use assembler::prelude::*;
|
||||||
|
use std::{fs, io::Write, path::PathBuf};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// Parse command line arguments
|
||||||
|
let args: Vec<String> = std::env::args().collect();
|
||||||
|
|
||||||
|
let input_path = &args[1];
|
||||||
|
let output_path = &args[2];
|
||||||
|
|
||||||
|
let mut engine = Assembler::new(PathBuf::from(input_path));
|
||||||
|
engine.start(());
|
||||||
|
let result = engine.output().expect("assembler failed.");
|
||||||
|
|
||||||
|
if let Err(e) = fs::write(output_path, result) {
|
||||||
|
eprintln!("Failed to write to output file: {e}");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[package]
|
||||||
|
name = "common"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
use crate::{instructions::Instruction, register::Register};
|
||||||
|
|
||||||
|
impl Instruction {
|
||||||
|
#[inline]
|
||||||
|
pub fn opcode(self) -> u8 {
|
||||||
|
((self.0 >> 26) & 0x3F) as u8
|
||||||
|
}
|
||||||
|
|
||||||
|
// Src shared between R type and I type
|
||||||
|
#[inline]
|
||||||
|
pub unsafe fn src1_uc(self) -> Register {
|
||||||
|
unsafe { Register::from_u8_unchecked(((self.0 >> 21) & 0x1F) as u8) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn src1_checked(self) -> Register {
|
||||||
|
Register::from_u8(((self.0 >> 21) & 0x1F) as u8).unwrap_or(Register::Null)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dest shared between R type and I type
|
||||||
|
#[inline]
|
||||||
|
pub unsafe fn dest_uc(self) -> Register {
|
||||||
|
unsafe { Register::from_u8_unchecked(((self.0 >> 16) & 0x1F) as u8) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn dest_checked(self) -> Register {
|
||||||
|
Register::from_u8(((self.0 >> 16) & 0x1F) as u8).unwrap_or(Register::Null)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Secondary Src for R type only
|
||||||
|
#[inline]
|
||||||
|
pub unsafe fn src2_uc(self) -> Register {
|
||||||
|
unsafe { Register::from_u8_unchecked(((self.0 >> 11) & 0x1F) as u8) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn src2_checked(self) -> Register {
|
||||||
|
Register::from_u8(((self.0 >> 11) & 0x1F) as u8).unwrap_or(Register::Null)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Misc/Conditional reg for R type only
|
||||||
|
#[inline]
|
||||||
|
pub unsafe fn misc_uc(self) -> Register {
|
||||||
|
unsafe { Register::from_u8_unchecked(((self.0 >> 6) & 0x1F) as u8) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn misc_checked(self) -> Register {
|
||||||
|
Register::from_u8(((self.0 >> 6) & 0x1F) as u8).unwrap_or(Register::Null)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shift amount / misc data for R type only
|
||||||
|
#[inline]
|
||||||
|
pub fn shamt(self) -> u8 {
|
||||||
|
(self.0 & 0x3F) as u8
|
||||||
|
}
|
||||||
|
|
||||||
|
// 16 Bit immediate for I type only
|
||||||
|
#[inline]
|
||||||
|
pub fn imm16(self) -> u16 {
|
||||||
|
self.0 as u16
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,334 @@
|
|||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
instructions::{Instruction, Opcode},
|
||||||
|
register::Register,
|
||||||
|
};
|
||||||
|
|
||||||
|
impl From<u32> for Instruction {
|
||||||
|
#[inline]
|
||||||
|
fn from(word: u32) -> Self {
|
||||||
|
Self(word)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Instruction {
|
||||||
|
type Target = u32;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for Instruction {
|
||||||
|
#[inline]
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Instruction {
|
||||||
|
// ---- R Type Builders ----
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn build_r(
|
||||||
|
opcode: Opcode,
|
||||||
|
src1: Register,
|
||||||
|
dest: Register,
|
||||||
|
src2: Register,
|
||||||
|
misc: Register,
|
||||||
|
shamt: u8,
|
||||||
|
) -> Self {
|
||||||
|
Self(
|
||||||
|
((opcode as u32) << 26)
|
||||||
|
| ((src1 as u32) << 21)
|
||||||
|
| ((dest as u32) << 16)
|
||||||
|
| ((src2 as u32) << 11)
|
||||||
|
| ((misc as u32) << 6)
|
||||||
|
| ((shamt as u32) & 0x3F),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn build_i(opcode: Opcode, src: Register, dest: Register, imm: u16) -> Self {
|
||||||
|
Self(((opcode as u32) << 26) | ((src as u32) << 21) | ((dest as u32) << 16) | (imm as u32))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn build_noarg(opcode: Opcode) -> Self {
|
||||||
|
Self((opcode as u32) << 26)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// No operation.
|
||||||
|
pub fn nop() -> Self {
|
||||||
|
Self::build_noarg(Opcode::Nop)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Move ----
|
||||||
|
|
||||||
|
/// Move a value from `src` to `dest`.
|
||||||
|
pub fn mov(src: Register, dest: Register) -> Self {
|
||||||
|
Self::build_r(Opcode::Mov, src, dest, Register::Zero, Register::Zero, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Conditional move: if `cmp` is true then move `src` to `dest`.
|
||||||
|
pub fn cmov(src: Register, dest: Register, cmp: Register) -> Self {
|
||||||
|
Self::build_r(Opcode::CMov, src, dest, cmp, Register::Zero, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Load ----
|
||||||
|
|
||||||
|
/// Load byte from memory at `src + offset` into `dest`.
|
||||||
|
pub fn ldb(src: Register, dest: Register, offset: u16) -> Self {
|
||||||
|
Self::build_i(Opcode::Ldb, src, dest, offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load signed byte (sign‑extended) from memory at `src + offset` into `dest`.
|
||||||
|
pub fn ldbs(src: Register, dest: Register, offset: u16) -> Self {
|
||||||
|
Self::build_i(Opcode::Ldbs, src, dest, offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load halfword from memory at `src + offset` into `dest`.
|
||||||
|
pub fn ldh(src: Register, dest: Register, offset: u16) -> Self {
|
||||||
|
Self::build_i(Opcode::Ldh, src, dest, offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load signed halfword (sign‑extended) from memory at `src + offset` into `dest`.
|
||||||
|
pub fn ldhs(src: Register, dest: Register, offset: u16) -> Self {
|
||||||
|
Self::build_i(Opcode::Ldhs, src, dest, offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load word from memory at `src + offset` into `dest`.
|
||||||
|
pub fn ldw(src: Register, dest: Register, offset: u16) -> Self {
|
||||||
|
Self::build_i(Opcode::Ldw, src, dest, offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Store ----
|
||||||
|
|
||||||
|
/// Store byte from `src` to memory at `dest + offset`.
|
||||||
|
pub fn stb(src: Register, dest: Register, offset: u16) -> Self {
|
||||||
|
Self::build_i(Opcode::Stb, src, dest, offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Store halfword from `src` to memory at `dest + offset`.
|
||||||
|
pub fn sth(src: Register, dest: Register, offset: u16) -> Self {
|
||||||
|
Self::build_i(Opcode::Sth, src, dest, offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Store word from `src` to memory at `dest + offset`.
|
||||||
|
pub fn stw(src: Register, dest: Register, offset: u16) -> Self {
|
||||||
|
Self::build_i(Opcode::Stw, src, dest, offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Load Immediate ----
|
||||||
|
|
||||||
|
/// Load lower 16 bits of an immediate into `dest`.
|
||||||
|
pub fn lli(dest: Register, imm: u16) -> Self {
|
||||||
|
Self::build_i(Opcode::Lli, Register::Zero, dest, imm)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load upper 16 bits of an immediate into `dest`.
|
||||||
|
pub fn lui(dest: Register, imm: u16) -> Self {
|
||||||
|
Self::build_i(Opcode::Lui, Register::Zero, dest, imm)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load a full 32‑bit immediate using an `lli` + `lui` pair.
|
||||||
|
pub fn load_imm32(dest: Register, imm: u32) -> [Self; 2] {
|
||||||
|
[
|
||||||
|
Self::lli(dest, imm as u16),
|
||||||
|
Self::lui(dest, (imm >> 16) as u16),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Comparison ----
|
||||||
|
|
||||||
|
/// Set `dest` to 1 if `sr1 == sr2`, else 0.
|
||||||
|
pub fn ieq(sr1: Register, sr2: Register, dest: Register) -> Self {
|
||||||
|
Self::build_r(Opcode::Ieq, sr1, dest, sr2, Register::Zero, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set `dest` to 1 if `sr1 != sr2`, else 0.
|
||||||
|
pub fn ine(sr1: Register, sr2: Register, dest: Register) -> Self {
|
||||||
|
Self::build_r(Opcode::Ine, sr1, dest, sr2, Register::Zero, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set `dest` to 1 if `sr1 < sr2`, else 0.
|
||||||
|
pub fn ilt(sr1: Register, sr2: Register, dest: Register) -> Self {
|
||||||
|
Self::build_r(Opcode::Ilt, sr1, dest, sr2, Register::Zero, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set `dest` to 1 if `sr1 <= sr2`, else 0.
|
||||||
|
pub fn ile(sr1: Register, sr2: Register, dest: Register) -> Self {
|
||||||
|
Self::build_r(Opcode::Ile, sr1, dest, sr2, Register::Zero, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set `dest` to 1 if `sr1 > sr2`, else 0.
|
||||||
|
pub fn igt(sr1: Register, sr2: Register, dest: Register) -> Self {
|
||||||
|
Self::build_r(Opcode::Igt, sr1, dest, sr2, Register::Zero, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set `dest` to 1 if `sr1 >= sr2`, else 0.
|
||||||
|
pub fn ige(sr1: Register, sr2: Register, dest: Register) -> Self {
|
||||||
|
Self::build_r(Opcode::Ige, sr1, dest, sr2, Register::Zero, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Jump ----
|
||||||
|
|
||||||
|
/// Unconditional jump to address in `addr` with offset.
|
||||||
|
pub fn jmp(addr: Register, offset: u16) -> Self {
|
||||||
|
Self::build_i(Opcode::Jmp, Register::Zero, addr, offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Jump if zero flag set (`jez`).
|
||||||
|
pub fn jez(cmp: Register, addr: Register, offset: u16) -> Self {
|
||||||
|
Self::build_i(Opcode::Jez, cmp, addr, offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Jump if zero flag clear (`jnz`).
|
||||||
|
pub fn jnz(cmp: Register, addr: Register, offset: u16) -> Self {
|
||||||
|
Self::build_i(Opcode::Jnz, cmp, addr, offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Jump if carry set (`jic`).
|
||||||
|
pub fn jic(addr: Register, offset: u16) -> Self {
|
||||||
|
Self::build_i(Opcode::Jic, Register::Zero, addr, offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Jump if carry clear (`jnc`).
|
||||||
|
pub fn jnc(addr: Register, offset: u16) -> Self {
|
||||||
|
Self::build_i(Opcode::Jnc, Register::Zero, addr, offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Bitwise ----
|
||||||
|
|
||||||
|
/// Bitwise AND of `sr1` and `sr2`, result in `dest`.
|
||||||
|
pub fn and(sr1: Register, sr2: Register, dest: Register) -> Self {
|
||||||
|
Self::build_r(Opcode::And, sr1, dest, sr2, Register::Zero, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bitwise NAND of `sr1` and `sr2`, result in `dest`.
|
||||||
|
pub fn nand(sr1: Register, sr2: Register, dest: Register) -> Self {
|
||||||
|
Self::build_r(Opcode::Nand, sr1, dest, sr2, Register::Zero, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bitwise OR of `sr1` and `sr2`, result in `dest`.
|
||||||
|
pub fn or(sr1: Register, sr2: Register, dest: Register) -> Self {
|
||||||
|
Self::build_r(Opcode::Or, sr1, dest, sr2, Register::Zero, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bitwise NOR of `sr1` and `sr2`, result in `dest`.
|
||||||
|
pub fn nor(sr1: Register, sr2: Register, dest: Register) -> Self {
|
||||||
|
Self::build_r(Opcode::Nor, sr1, dest, sr2, Register::Zero, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bitwise XOR of `sr1` and `sr2`, result in `dest`.
|
||||||
|
pub fn xor(sr1: Register, sr2: Register, dest: Register) -> Self {
|
||||||
|
Self::build_r(Opcode::Xor, sr1, dest, sr2, Register::Zero, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bitwise XNOR of `sr1` and `sr2`, result in `dest`.
|
||||||
|
pub fn xnor(sr1: Register, sr2: Register, dest: Register) -> Self {
|
||||||
|
Self::build_r(Opcode::Xnor, sr1, dest, sr2, Register::Zero, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bitwise NOT of `src`, result in `dest`.
|
||||||
|
pub fn not(src: Register, dest: Register) -> Self {
|
||||||
|
Self::build_r(Opcode::Not, src, dest, Register::Zero, Register::Zero, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Arithmetic ----
|
||||||
|
|
||||||
|
/// Add `sr1` and `sr2`, store result in `dest`.
|
||||||
|
pub fn add(sr1: Register, sr2: Register, dest: Register) -> Self {
|
||||||
|
Self::build_r(Opcode::Add, sr1, dest, sr2, Register::Zero, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Subtract `sr2` from `sr1`, store result in `dest`.
|
||||||
|
pub fn sub(sr1: Register, sr2: Register, dest: Register) -> Self {
|
||||||
|
Self::build_r(Opcode::Sub, sr1, dest, sr2, Register::Zero, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shift left logical: shift `src` by (`rshamt` + `ishamt`) bits into `dest`.
|
||||||
|
pub fn shl(src: Register, rshamt: Register, dest: Register, ishamt: u8) -> Self {
|
||||||
|
debug_assert!(ishamt < 64);
|
||||||
|
Self::build_r(Opcode::Shl, src, dest, rshamt, Register::Zero, ishamt)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shift right logical: shift `src` by (`rshamt` + `ishamt`) bits into `dest`.
|
||||||
|
pub fn shr(src: Register, rshamt: Register, dest: Register, ishamt: u8) -> Self {
|
||||||
|
debug_assert!(ishamt < 64);
|
||||||
|
Self::build_r(Opcode::Shr, src, dest, rshamt, Register::Zero, ishamt)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add immediate `imm` to `sr1`, store result in `dest`.
|
||||||
|
pub fn addi(sr1: Register, dest: Register, imm: u16) -> Self {
|
||||||
|
Self::build_i(Opcode::Addi, sr1, dest, imm)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Subtract immediate `imm` from `sr1`, store result in `dest`.
|
||||||
|
pub fn subi(sr1: Register, dest: Register, imm: u16) -> Self {
|
||||||
|
Self::build_i(Opcode::Subi, sr1, dest, imm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Util -----
|
||||||
|
pub fn call(addr: Register, offset: u16) -> Self {
|
||||||
|
Self::build_i(Opcode::Call, Register::Zero, addr, offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ret() -> Self {
|
||||||
|
Self::build_noarg(Opcode::Ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push(reg: Register) -> Self {
|
||||||
|
Self::build_r(
|
||||||
|
Opcode::Push,
|
||||||
|
reg,
|
||||||
|
Register::Zero,
|
||||||
|
Register::Zero,
|
||||||
|
Register::Zero,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pop(reg: Register) -> Self {
|
||||||
|
Self::build_r(
|
||||||
|
Opcode::Pop,
|
||||||
|
Register::Zero,
|
||||||
|
reg,
|
||||||
|
Register::Zero,
|
||||||
|
Register::Zero,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- System ----
|
||||||
|
|
||||||
|
/// Trigger an interrupt with the given 6‑bit code.
|
||||||
|
pub fn int(code: u8) -> Self {
|
||||||
|
debug_assert!(code < 64);
|
||||||
|
Self::build_i(Opcode::Int, Register::Zero, Register::Zero, code as u16)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return from interrupt.
|
||||||
|
pub fn irt() -> Self {
|
||||||
|
Self::build_noarg(Opcode::IRet)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Halt execution.
|
||||||
|
pub fn hlt() -> Self {
|
||||||
|
Self::build_r(
|
||||||
|
Opcode::Hlt,
|
||||||
|
Register::Zero,
|
||||||
|
Register::Zero,
|
||||||
|
Register::Zero,
|
||||||
|
Register::Zero,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Raw data (not an instruction)
|
||||||
|
pub fn data(value: u32) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,235 @@
|
|||||||
|
pub mod decode;
|
||||||
|
pub mod encode;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct Instruction(pub u32);
|
||||||
|
|
||||||
|
#[repr(u8)]
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum Opcode {
|
||||||
|
Nop = 0,
|
||||||
|
|
||||||
|
// move
|
||||||
|
Mov = 1,
|
||||||
|
CMov = 2,
|
||||||
|
|
||||||
|
// load
|
||||||
|
Ldb = 3,
|
||||||
|
Ldbs = 4,
|
||||||
|
Ldh = 5,
|
||||||
|
Ldhs = 6,
|
||||||
|
Ldw = 7,
|
||||||
|
|
||||||
|
// store
|
||||||
|
Stb = 8,
|
||||||
|
Sth = 9,
|
||||||
|
Stw = 10,
|
||||||
|
|
||||||
|
// load immediate
|
||||||
|
Lli = 11,
|
||||||
|
Lui = 12,
|
||||||
|
|
||||||
|
// comparison
|
||||||
|
Ieq = 13,
|
||||||
|
Ine = 14,
|
||||||
|
Ilt = 15,
|
||||||
|
Ile = 16,
|
||||||
|
Igt = 17,
|
||||||
|
Ige = 18,
|
||||||
|
|
||||||
|
// jump
|
||||||
|
Jmp = 19,
|
||||||
|
Jez = 20,
|
||||||
|
Jnz = 21,
|
||||||
|
Jic = 22,
|
||||||
|
Jnc = 23,
|
||||||
|
|
||||||
|
// bitwise
|
||||||
|
And = 24,
|
||||||
|
Nand = 25,
|
||||||
|
Or = 26,
|
||||||
|
Nor = 27,
|
||||||
|
Xor = 28,
|
||||||
|
Xnor = 29,
|
||||||
|
Not = 30,
|
||||||
|
|
||||||
|
// arithmetic
|
||||||
|
Add = 31,
|
||||||
|
Sub = 32,
|
||||||
|
Shl = 33,
|
||||||
|
Shr = 34,
|
||||||
|
Addi = 35,
|
||||||
|
Subi = 36,
|
||||||
|
|
||||||
|
// utility
|
||||||
|
Push = 37,
|
||||||
|
Pop = 38,
|
||||||
|
Call = 39,
|
||||||
|
Ret = 40,
|
||||||
|
|
||||||
|
// system
|
||||||
|
Int = 41,
|
||||||
|
IRet = 42,
|
||||||
|
Hlt = 43,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Opcode {
|
||||||
|
#[inline]
|
||||||
|
pub fn from_u8(val: u8) -> Option<Self> {
|
||||||
|
if val <= Self::Hlt as u8 {
|
||||||
|
Some(unsafe { std::mem::transmute(val) })
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
impl fmt::Debug for Instruction {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let opcode = Opcode::from_u8(self.opcode());
|
||||||
|
|
||||||
|
match opcode {
|
||||||
|
None => write!(f, "Instruction(INVALID OPCODE {:#04x})", self.opcode()),
|
||||||
|
|
||||||
|
Some(op @ (Opcode::Nop | Opcode::Hlt | Opcode::Ret | Opcode::IRet)) => {
|
||||||
|
write!(f, "{op:?}")
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(op @ Opcode::Push) => {
|
||||||
|
write!(f, "{op:?} {src1:?}", op = op, src1 = self.src1_checked())
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(op @ Opcode::Pop) => {
|
||||||
|
write!(f, "{op:?} {dest:?}", op = op, dest = self.dest_checked())
|
||||||
|
}
|
||||||
|
|
||||||
|
// I-type: single reg + immediate
|
||||||
|
Some(op @ (Opcode::Lli | Opcode::Lui)) => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{op:?} {dest:?}, {imm:#06x}",
|
||||||
|
op = op,
|
||||||
|
dest = self.dest_checked(),
|
||||||
|
imm = self.imm16()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// I-type: no reg, immediate only
|
||||||
|
Some(op @ Opcode::Int) => {
|
||||||
|
write!(f, "{op:?} {imm:#06x}", op = op, imm = self.imm16())
|
||||||
|
}
|
||||||
|
|
||||||
|
// I-type: src + dest + immediate
|
||||||
|
Some(
|
||||||
|
op @ (Opcode::Ldb
|
||||||
|
| Opcode::Ldbs
|
||||||
|
| Opcode::Ldh
|
||||||
|
| Opcode::Ldhs
|
||||||
|
| Opcode::Ldw
|
||||||
|
| Opcode::Stb
|
||||||
|
| Opcode::Sth
|
||||||
|
| Opcode::Stw
|
||||||
|
| Opcode::Addi
|
||||||
|
| Opcode::Subi),
|
||||||
|
) => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{op:?} {src:?}, {dest:?}, {imm:#06x}",
|
||||||
|
op = op,
|
||||||
|
src = self.src1_checked(),
|
||||||
|
dest = self.dest_checked(),
|
||||||
|
imm = self.imm16()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// I-type: cmp + addr + immediate (conditional jumps)
|
||||||
|
Some(op @ (Opcode::Jez | Opcode::Jnz)) => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{op:?} {cmp:?}, {addr:?}, {imm:#06x}",
|
||||||
|
op = op,
|
||||||
|
cmp = self.src1_checked(),
|
||||||
|
addr = self.dest_checked(),
|
||||||
|
imm = self.imm16()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// I-type: addr + immediate only (unconditional/carry jumps)
|
||||||
|
Some(op @ (Opcode::Jmp | Opcode::Call | Opcode::Jic | Opcode::Jnc)) => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{op:?} {addr:?}, {imm:#06x}",
|
||||||
|
op = op,
|
||||||
|
addr = self.dest_checked(),
|
||||||
|
imm = self.imm16()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// R-type: src + dest (two reg)
|
||||||
|
Some(op @ (Opcode::Mov | Opcode::Not)) => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{op:?} {src:?}, {dest:?}",
|
||||||
|
op = op,
|
||||||
|
src = self.src1_checked(),
|
||||||
|
dest = self.dest_checked()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// R-type: src + dest + cmp (cmov)
|
||||||
|
Some(op @ Opcode::CMov) => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{op:?} {src:?}, {dest:?}, {cmp:?}",
|
||||||
|
op = op,
|
||||||
|
src = self.src1_checked(),
|
||||||
|
dest = self.dest_checked(),
|
||||||
|
cmp = self.src2_checked()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// R-type: sr1 + sr2 + dest (arithmetic/bitwise/comparison)
|
||||||
|
Some(
|
||||||
|
op @ (Opcode::Add
|
||||||
|
| Opcode::Sub
|
||||||
|
| Opcode::And
|
||||||
|
| Opcode::Nand
|
||||||
|
| Opcode::Or
|
||||||
|
| Opcode::Nor
|
||||||
|
| Opcode::Xor
|
||||||
|
| Opcode::Xnor
|
||||||
|
| Opcode::Ieq
|
||||||
|
| Opcode::Ine
|
||||||
|
| Opcode::Ilt
|
||||||
|
| Opcode::Ile
|
||||||
|
| Opcode::Igt
|
||||||
|
| Opcode::Ige),
|
||||||
|
) => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{op:?} {sr1:?}, {sr2:?}, {dest:?}",
|
||||||
|
op = op,
|
||||||
|
sr1 = self.src1_checked(),
|
||||||
|
sr2 = self.src2_checked(),
|
||||||
|
dest = self.dest_checked()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// R-type: src + rshamt + dest + ishamt
|
||||||
|
Some(op @ (Opcode::Shl | Opcode::Shr)) => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{op:?} {src:?}, {dest:?}, r:{rshamt:?} + i:{ishamt}",
|
||||||
|
op = op,
|
||||||
|
src = self.src1_checked(),
|
||||||
|
dest = self.dest_checked(),
|
||||||
|
rshamt = self.src2_checked(),
|
||||||
|
ishamt = self.shamt()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
pub mod instructions;
|
||||||
|
pub mod register;
|
||||||
|
|
||||||
|
pub mod prelude {
|
||||||
|
pub use crate::instructions::Instruction;
|
||||||
|
pub use crate::register::Register;
|
||||||
|
}
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
|
||||||
|
#[repr(u8)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum Register {
|
||||||
|
// general purpose
|
||||||
|
Rg0 = 0,
|
||||||
|
Rg1 = 1,
|
||||||
|
Rg2 = 2,
|
||||||
|
Rg3 = 3,
|
||||||
|
Rg4 = 4,
|
||||||
|
Rg5 = 5,
|
||||||
|
Rg6 = 6,
|
||||||
|
Rg7 = 7,
|
||||||
|
Rg8 = 8,
|
||||||
|
Rg9 = 9,
|
||||||
|
Rga = 10,
|
||||||
|
Rgb = 11,
|
||||||
|
Rgc = 12,
|
||||||
|
Rgd = 13,
|
||||||
|
Rge = 14,
|
||||||
|
Rgf = 15,
|
||||||
|
|
||||||
|
// special purpose
|
||||||
|
Zero = 16,
|
||||||
|
Acc = 17,
|
||||||
|
Spr = 18,
|
||||||
|
Bpr = 19,
|
||||||
|
Ret = 20,
|
||||||
|
Idr = 21,
|
||||||
|
Mmr = 22,
|
||||||
|
|
||||||
|
// system - read only
|
||||||
|
Mar = 23,
|
||||||
|
Mdr = 24,
|
||||||
|
Sts = 25,
|
||||||
|
Cir = 26,
|
||||||
|
Pcx = 27,
|
||||||
|
|
||||||
|
#[default]
|
||||||
|
Null = 28,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Register {
|
||||||
|
#[must_use]
|
||||||
|
#[inline]
|
||||||
|
pub unsafe fn from_u8_unchecked(idx: u8) -> Self {
|
||||||
|
debug_assert!(idx <= Self::Null as u8);
|
||||||
|
unsafe { std::mem::transmute(idx) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn from_u8(idx: u8) -> Result<Self, ()> {
|
||||||
|
if idx > 28 {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
Ok(unsafe { Self::from_u8_unchecked(idx) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Register {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
match self {
|
||||||
|
// general purpose
|
||||||
|
Self::Rg0 => "Rg0",
|
||||||
|
Self::Rg1 => "Rg1",
|
||||||
|
Self::Rg2 => "Rg2",
|
||||||
|
Self::Rg3 => "Rg3",
|
||||||
|
Self::Rg4 => "Rg4",
|
||||||
|
Self::Rg5 => "Rg5",
|
||||||
|
Self::Rg6 => "Rg6",
|
||||||
|
Self::Rg7 => "Rg7",
|
||||||
|
Self::Rg8 => "Rg8",
|
||||||
|
Self::Rg9 => "Rg9",
|
||||||
|
Self::Rga => "Rga",
|
||||||
|
Self::Rgb => "Rgb",
|
||||||
|
Self::Rgc => "Rgc",
|
||||||
|
Self::Rgd => "Rgd",
|
||||||
|
Self::Rge => "Rge",
|
||||||
|
Self::Rgf => "Rgf",
|
||||||
|
|
||||||
|
// special purpose
|
||||||
|
Self::Zero => "Zero",
|
||||||
|
Self::Acc => "Acc",
|
||||||
|
Self::Spr => "Spr",
|
||||||
|
Self::Bpr => "Bpr",
|
||||||
|
Self::Ret => "Ret",
|
||||||
|
Self::Idr => "Idr",
|
||||||
|
Self::Mmr => "Mmr",
|
||||||
|
|
||||||
|
// system - read only
|
||||||
|
Self::Mar => "Mar",
|
||||||
|
Self::Mdr => "Mdr",
|
||||||
|
Self::Sts => "Sts",
|
||||||
|
Self::Cir => "Cir",
|
||||||
|
Self::Pcx => "Pcx",
|
||||||
|
|
||||||
|
Self::Null => "Null",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::str::FromStr for Register {
|
||||||
|
type Err = ();
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s.to_ascii_lowercase().as_str() {
|
||||||
|
// general purpose
|
||||||
|
"rg0" => Ok(Self::Rg0),
|
||||||
|
"rg1" => Ok(Self::Rg1),
|
||||||
|
"rg2" => Ok(Self::Rg2),
|
||||||
|
"rg3" => Ok(Self::Rg3),
|
||||||
|
"rg4" => Ok(Self::Rg4),
|
||||||
|
"rg5" => Ok(Self::Rg5),
|
||||||
|
"rg6" => Ok(Self::Rg6),
|
||||||
|
"rg7" => Ok(Self::Rg7),
|
||||||
|
"rg8" => Ok(Self::Rg8),
|
||||||
|
"rg9" => Ok(Self::Rg9),
|
||||||
|
"rga" => Ok(Self::Rga),
|
||||||
|
"rgb" => Ok(Self::Rgb),
|
||||||
|
"rgc" => Ok(Self::Rgc),
|
||||||
|
"rgd" => Ok(Self::Rgd),
|
||||||
|
"rge" => Ok(Self::Rge),
|
||||||
|
"rgf" => Ok(Self::Rgf),
|
||||||
|
|
||||||
|
// special purpose
|
||||||
|
"zero" => Ok(Self::Zero),
|
||||||
|
"acc" => Ok(Self::Acc),
|
||||||
|
"spr" => Ok(Self::Spr),
|
||||||
|
"bpr" => Ok(Self::Bpr),
|
||||||
|
// "ret" => Ok(Self::Ret),
|
||||||
|
"idr" => Ok(Self::Idr),
|
||||||
|
"mmr" => Ok(Self::Mmr),
|
||||||
|
|
||||||
|
// system - read only
|
||||||
|
"mar" => Ok(Self::Mar),
|
||||||
|
"mdr" => Ok(Self::Mdr),
|
||||||
|
"sts" => Ok(Self::Sts),
|
||||||
|
"cir" => Ok(Self::Cir),
|
||||||
|
"pcx" => Ok(Self::Pcx),
|
||||||
|
|
||||||
|
"null" => Ok(Self::Null),
|
||||||
|
_ => Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
[package]
|
||||||
|
name = "compiler"
|
||||||
|
edition.workspace = true
|
||||||
|
version.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
pub fn add(left: u64, right: u64) -> u64 {
|
||||||
|
left + right
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_works() {
|
||||||
|
let result = add(2, 2);
|
||||||
|
assert_eq!(result, 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,12 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "dsa-emu"
|
name = "emulator"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "dsa"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "dsa"
|
name = "dsa"
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
@@ -17,6 +21,10 @@ fxhash = "0.2.1"
|
|||||||
ron = "0.12.0"
|
ron = "0.12.0"
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
|
|
||||||
|
common = { path = "../common" }
|
||||||
|
#assembler = { path = "../assembler" }
|
||||||
|
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "bench_mainstore"
|
name = "bench_mainstore"
|
||||||
harness = false
|
harness = false
|
||||||
@@ -13,6 +13,9 @@ pub struct DsaArgs {
|
|||||||
#[arg(long = "mmap")]
|
#[arg(long = "mmap")]
|
||||||
memory_map: Option<PathBuf>,
|
memory_map: Option<PathBuf>,
|
||||||
|
|
||||||
|
#[arg(long = "bin")]
|
||||||
|
binary: Option<PathBuf>,
|
||||||
|
|
||||||
#[arg(long = "iomap")]
|
#[arg(long = "iomap")]
|
||||||
io_map: Option<PathBuf>,
|
io_map: Option<PathBuf>,
|
||||||
|
|
||||||
@@ -34,6 +37,12 @@ impl DsaArgs {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_binary(&self) -> Option<Vec<u8>> {
|
||||||
|
self.binary
|
||||||
|
.clone()
|
||||||
|
.and_then(|path| Some(fs::read(path).unwrap()))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_io_map(&self) -> Vec<IoMapping> {
|
pub fn get_io_map(&self) -> Vec<IoMapping> {
|
||||||
self.io_map
|
self.io_map
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -0,0 +1,330 @@
|
|||||||
|
use common::instructions::Instruction;
|
||||||
|
use dsa::{BulkAllocStore, Emulator, Page, RandomAccessMemory};
|
||||||
|
/// DSA Emulator Benchmark
|
||||||
|
///
|
||||||
|
/// Place this in `dsa/src/bin/bench.rs` and run with:
|
||||||
|
/// cargo run --bin bench --release
|
||||||
|
///
|
||||||
|
/// Three workloads are tested:
|
||||||
|
/// 1. ALU-heavy — tight arithmetic loop, no memory, no branches
|
||||||
|
/// 2. Memory-heavy — repeated load/store to a working set of addresses
|
||||||
|
/// 3. Branch-heavy — the bf.dsa dispatch pattern (ieq + jnz chains)
|
||||||
|
///
|
||||||
|
/// Each workload is a hand-assembled flat binary loaded directly into
|
||||||
|
/// the emulator, bypassing all the args/display/IO plumbing.
|
||||||
|
use std::{
|
||||||
|
thread,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
u16,
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Minimal no-op memory map + IO so the emulator boots without a config file.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
fn make_emulator() -> Emulator<BulkAllocStore> {
|
||||||
|
use dsa::{
|
||||||
|
config::{MemoryMap, Region, RegionType},
|
||||||
|
io::display::DisplayDevice,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mmap = MemoryMap {
|
||||||
|
table_addr: 0x40000,
|
||||||
|
regions: vec![
|
||||||
|
Region {
|
||||||
|
base: 0x0000_0000,
|
||||||
|
size: 0x0010_0000, // 1 MiB RAM
|
||||||
|
region_type: RegionType::Usable,
|
||||||
|
identity_map_on_boot: true,
|
||||||
|
},
|
||||||
|
Region {
|
||||||
|
base: 0x0020_0000,
|
||||||
|
size: 0x0002_0000, // 128 KiB MMIO (display)
|
||||||
|
region_type: RegionType::MMIO,
|
||||||
|
identity_map_on_boot: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
let iomap = vec![dsa::config::IoMapping {
|
||||||
|
device: dsa::io::DeviceId::Display,
|
||||||
|
base: 0x0020_0000,
|
||||||
|
size: 0x0000_07D0,
|
||||||
|
}];
|
||||||
|
|
||||||
|
let (display, _handle) = DisplayDevice::new(80, 25);
|
||||||
|
Emulator::new(BulkAllocStore::new())
|
||||||
|
.with_device(display)
|
||||||
|
.apply_memory_map(mmap)
|
||||||
|
.apply_io_map(iomap)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Instruction encoding helpers (little-endian words)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Encode an I-type instruction: [op:6][src:5][dst:5][imm:16]
|
||||||
|
fn enc_i(op: u8, src: u8, dst: u8, imm: u16) -> u32 {
|
||||||
|
((op as u32 & 0x3F) << 26)
|
||||||
|
| ((src as u32 & 0x1F) << 21)
|
||||||
|
| ((dst as u32 & 0x1F) << 16)
|
||||||
|
| imm as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encode an R-type instruction: [op:6][r1:5][r2:5][r3:5][r4:5][u6:6]
|
||||||
|
fn enc_r(op: u8, r1: u8, r2: u8, r3: u8, r4: u8, u6: u8) -> u32 {
|
||||||
|
((op as u32 & 0x3F) << 26)
|
||||||
|
| ((r1 as u32 & 0x1F) << 21)
|
||||||
|
| ((r2 as u32 & 0x1F) << 16)
|
||||||
|
| ((r3 as u32 & 0x1F) << 11)
|
||||||
|
| ((r4 as u32 & 0x1F) << 6)
|
||||||
|
| (u6 as u32 & 0x3F)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register indices — must match your Register enum discriminants
|
||||||
|
const RG0: u8 = 0;
|
||||||
|
const RG1: u8 = 1;
|
||||||
|
const RG2: u8 = 2;
|
||||||
|
const RG3: u8 = 3;
|
||||||
|
const RG4: u8 = 4;
|
||||||
|
const RG5: u8 = 5;
|
||||||
|
const ZERO: u8 = 16;
|
||||||
|
|
||||||
|
// Opcodes — must match your Opcode enum discriminants.
|
||||||
|
// Fill these in from your ISA.md / encode.rs.
|
||||||
|
const OP_NOP: u8 = 0x0D; // adjust to match your actual opcode values
|
||||||
|
const OP_HLT: u8 = 0x2B;
|
||||||
|
const OP_ADD: u8 = 0x22;
|
||||||
|
const OP_ADDI: u8 = 0x23;
|
||||||
|
const OP_SUBI: u8 = 0x24;
|
||||||
|
const OP_LDW: u8 = 0x07;
|
||||||
|
const OP_STW: u8 = 0x0A;
|
||||||
|
const OP_LLI: u8 = 0x0B;
|
||||||
|
const OP_LUI: u8 = 0x0C;
|
||||||
|
const OP_IEQ: u8 = 0x0E;
|
||||||
|
const OP_JNZ: u8 = 0x15;
|
||||||
|
const OP_JMP: u8 = 0x12;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Workload 1: ALU-heavy
|
||||||
|
//
|
||||||
|
// Counts down from 10_000_000 using subi, jumps back to top.
|
||||||
|
// Pure register arithmetic, no memory accesses after init.
|
||||||
|
// Instruction mix: lli, subi, ieq, jnz, hlt
|
||||||
|
//
|
||||||
|
// Layout (each word at address = index * 4):
|
||||||
|
// 0: lli rg0, 0 (upper half of count) [lui follows]
|
||||||
|
// 4: lui rg0, <high>
|
||||||
|
// 8: loop: subi rg0, 1, rg0
|
||||||
|
// 12: ieq rg0, zero, rg1
|
||||||
|
// 16: jnz rg1, zero, 8 [jump to loop]
|
||||||
|
// 20: hlt
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
fn alu_workload() -> Vec<u8> {
|
||||||
|
let count: u32 = 10_000_000;
|
||||||
|
let lo = (count & 0xFFFF) as u16;
|
||||||
|
let hi = ((count >> 16) & 0xFFFF) as u16;
|
||||||
|
|
||||||
|
let words: &[u32] = &[
|
||||||
|
enc_i(OP_LLI, 0, RG0, lo), // 0x00: lli rg0, lo(count)
|
||||||
|
enc_i(OP_LUI, 0, RG0, hi), // 0x04: lui rg0, hi(count)
|
||||||
|
// loop @ 0x08:
|
||||||
|
enc_i(OP_SUBI, RG0, RG0, 1), // 0x08: subi rg0, rg0, 1
|
||||||
|
enc_r(OP_IEQ, RG0, ZERO, RG1, 0, 0), // 0x0C: ieq rg0, zero, rg1
|
||||||
|
enc_i(OP_JNZ, RG1, ZERO, 0x0008), // 0x10: jnz rg1, zero, 0x08 <- BUG: jnz jumps when rg1!=0
|
||||||
|
// rg1=1 when rg0==0, so we want jez here to keep looping
|
||||||
|
// fix: use jez (jump when rg1==0, i.e. rg0!=0)
|
||||||
|
enc_i(OP_HLT, 0, 0, 0), // 0x14: hlt
|
||||||
|
];
|
||||||
|
// Note: replace OP_JNZ above with your JEZ opcode so the loop continues
|
||||||
|
// while rg0 != 0. JNZ exits when rg1 != 0, i.e. when rg0 == 0 (done).
|
||||||
|
// The ieq sets rg1=1 only when rg0==0, so jnz rg1 exits the loop correctly.
|
||||||
|
// Actually this IS correct: loop while ieq returns 0 (rg0 != 0),
|
||||||
|
// exit when ieq returns 1 (rg0 == 0). jnz rg1 = jump when rg1 != 0 = jump when done.
|
||||||
|
// We want to jump BACK while not done, so we need jez:
|
||||||
|
// jez rg1, zero, 0x08 = jump back when rg1==0 (rg0 != 0, not done yet)
|
||||||
|
// Swap OP_JNZ for your JEZ opcode.
|
||||||
|
words_to_bytes(words)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Workload 2: Memory-heavy
|
||||||
|
//
|
||||||
|
// Repeatedly writes and reads a 64-word working set.
|
||||||
|
// Tests page table lookup performance under repeated memory access.
|
||||||
|
//
|
||||||
|
// rg0 = base address (0x1000, well within RAM, page-aligned)
|
||||||
|
// rg1 = loop counter (1000 outer iterations)
|
||||||
|
// rg2 = inner counter (64 words per pass)
|
||||||
|
// rg3 = scratch for store value
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
fn memory_workload() -> Vec<u8> {
|
||||||
|
let base: u32 = 0x1000;
|
||||||
|
let outer: u16 = 1000;
|
||||||
|
let inner: u16 = 64;
|
||||||
|
|
||||||
|
let words: &[u32] = &[
|
||||||
|
// init
|
||||||
|
enc_i(OP_LLI, 0, RG0, (base & 0xFFFF) as u16), // rg0 = base
|
||||||
|
enc_i(OP_LLI, 0, RG1, outer), // rg1 = outer count
|
||||||
|
// outer_loop @ 0x08:
|
||||||
|
enc_i(OP_LLI, 0, RG2, inner), // rg2 = inner count
|
||||||
|
enc_i(OP_LLI, 0, RG3, 0xABCD), // rg3 = value to write
|
||||||
|
// inner_loop @ 0x10:
|
||||||
|
enc_i(OP_STW, RG3, RG0, 0), // stw rg3, rg0, 0
|
||||||
|
enc_i(OP_LDW, RG0, RG3, 0), // ldw rg0, rg3, 0 (read back)
|
||||||
|
enc_i(OP_ADDI, RG0, RG0, 4), // rg0 += 4
|
||||||
|
enc_i(OP_SUBI, RG2, RG2, 1), // rg2 -= 1
|
||||||
|
enc_r(OP_IEQ, RG2, ZERO, RG4, 0, 0), // ieq rg2, zero, rg4
|
||||||
|
enc_i(OP_JNZ, RG4, ZERO, 0x0010), // jez rg4 -> inner_loop (swap opcode)
|
||||||
|
// restore base, dec outer
|
||||||
|
enc_i(OP_LLI, 0, RG0, (base & 0xFFFF) as u16),
|
||||||
|
enc_i(OP_SUBI, RG1, RG1, 1),
|
||||||
|
enc_r(OP_IEQ, RG1, ZERO, RG4, 0, 0),
|
||||||
|
enc_i(OP_JNZ, RG4, ZERO, 0x0008), // jez rg4 -> outer_loop (swap opcode)
|
||||||
|
enc_i(OP_HLT, 0, 0, 0),
|
||||||
|
];
|
||||||
|
words_to_bytes(words)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Workload 3: Branch-heavy
|
||||||
|
//
|
||||||
|
// Mimics the bf.dsa dispatch pattern: compare a value against 8 constants,
|
||||||
|
// branch on each. Worst case for branch predictor.
|
||||||
|
// rg0 = current "instruction" (cycles through 8 values)
|
||||||
|
// rg8-rgf = the 8 bf opcode constants
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
fn branch_workload() -> Vec<u8> {
|
||||||
|
// 8 bf opcodes: + - > < . , [ ]
|
||||||
|
let opcodes: [u16; 8] = [43, 45, 62, 60, 46, 44, 91, 93];
|
||||||
|
// We'll cycle rg0 through these values and do the full dispatch chain.
|
||||||
|
// To keep it self-contained: load opcodes, set outer loop counter,
|
||||||
|
// inner loop cycles rg0 through all 8 values doing ieq+jnz for each.
|
||||||
|
let outer: u16 = u16::MAX;
|
||||||
|
|
||||||
|
let mut w: Vec<u32> = Vec::new();
|
||||||
|
|
||||||
|
// Load 8 opcode constants into rg8-rgf (regs 8-15)
|
||||||
|
for (i, &op) in opcodes.iter().enumerate() {
|
||||||
|
w.push(enc_i(OP_LLI, 0, 8 + i as u8, op));
|
||||||
|
}
|
||||||
|
// rg1 = outer counter
|
||||||
|
w.push(enc_i(OP_LLI, 0, RG1, outer));
|
||||||
|
|
||||||
|
// outer_loop:
|
||||||
|
let outer_loop_addr = (w.len() * 4) as u16;
|
||||||
|
|
||||||
|
// rg5 = inner counter (8 opcodes)
|
||||||
|
w.push(enc_i(OP_LLI, 0, RG5, 8));
|
||||||
|
// rg2 = pointer into opcode table (we'll use addi to step rg0 through values)
|
||||||
|
// For simplicity: just set rg0 to each opcode in sequence via lli each iteration
|
||||||
|
// This is branch-heavy, not arithmetic-heavy, so the lli overhead is fine.
|
||||||
|
for (i, &op) in opcodes.iter().enumerate() {
|
||||||
|
let next_offset = ((w.len() + 4) * 4) as u16; // addr after the jnz
|
||||||
|
w.push(enc_i(OP_LLI, 0, RG0, op)); // rg0 = opcode
|
||||||
|
w.push(enc_r(OP_IEQ, RG0, 8 + i as u8, RG4, 0, 0)); // ieq rg0, rgX, rg4
|
||||||
|
w.push(enc_i(OP_JNZ, RG4, ZERO, next_offset)); // jnz rg4 (jez to skip)
|
||||||
|
w.push(enc_i(OP_NOP, 0, 0, 0)); // "handler" nop
|
||||||
|
}
|
||||||
|
// dec outer, loop
|
||||||
|
let after_dispatch = (w.len() * 4) as u16;
|
||||||
|
w.push(enc_i(OP_SUBI, RG1, RG1, 1));
|
||||||
|
w.push(enc_r(OP_IEQ, RG1, ZERO, RG4, 0, 0));
|
||||||
|
w.push(enc_i(OP_JNZ, RG4, ZERO, outer_loop_addr)); // jez -> outer_loop
|
||||||
|
w.push(enc_i(OP_HLT, 0, 0, 0));
|
||||||
|
|
||||||
|
words_to_bytes(&w)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn words_to_bytes(words: &[u32]) -> Vec<u8> {
|
||||||
|
words.iter().flat_map(|w| w.to_le_bytes()).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Runner
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
struct BenchResult {
|
||||||
|
name: &'static str,
|
||||||
|
instructions: u64,
|
||||||
|
elapsed: Duration,
|
||||||
|
mips: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_bench(name: &'static str, binary: Vec<u8>) -> BenchResult {
|
||||||
|
let mut emu = make_emulator();
|
||||||
|
let handle = emu.state_handle();
|
||||||
|
|
||||||
|
let pages: Vec<Page> = Page::paginate(&binary).collect();
|
||||||
|
for (i, page) in pages.iter().enumerate() {
|
||||||
|
emu.memory_mut().write_page(i as u32 * 4096, page);
|
||||||
|
}
|
||||||
|
|
||||||
|
let start = Instant::now();
|
||||||
|
// run() blocks until HLT
|
||||||
|
emu.run().expect("emulator run failed");
|
||||||
|
let elapsed = start.elapsed();
|
||||||
|
|
||||||
|
// read clock from emulator — we need to expose it.
|
||||||
|
// For now, estimate from elapsed + target MIPS.
|
||||||
|
// TODO: expose emulator.clock() -> u64 and use that directly.
|
||||||
|
let instructions = handle.proc.load().clock as u64;
|
||||||
|
let mips = instructions as f64 / elapsed.as_micros() as f64;
|
||||||
|
|
||||||
|
BenchResult {
|
||||||
|
name,
|
||||||
|
instructions,
|
||||||
|
elapsed,
|
||||||
|
mips,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
println!("DSA Emulator Benchmark");
|
||||||
|
println!("======================");
|
||||||
|
println!();
|
||||||
|
|
||||||
|
// NOTE: you need to expose `pub fn clock(&self) -> usize` on Emulator
|
||||||
|
// and ensure `run()` returns normally on HLT without blocking in idle_wait.
|
||||||
|
// The idle_wait loop currently spins forever after HLT — for benchmarking,
|
||||||
|
// you want run() to return when the program halts. Add a flag or check:
|
||||||
|
// if !self.internal_state.running { break 'emu; }
|
||||||
|
// after the HLT handler sets running=false.
|
||||||
|
|
||||||
|
let workloads: &[(&'static str, fn() -> Vec<u8>)] = &[
|
||||||
|
("ALU-heavy (10M countdown)", alu_workload),
|
||||||
|
("Memory-heavy (64K rw ops)", memory_workload),
|
||||||
|
("Branch-heavy (bf dispatch)", branch_workload),
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut results = Vec::new();
|
||||||
|
for &(name, build) in workloads {
|
||||||
|
print!("Running {}... ", name);
|
||||||
|
let binary = build();
|
||||||
|
let result = run_bench(name, binary);
|
||||||
|
println!("done ({:.1}ms)", result.elapsed.as_secs_f64() * 1000.0);
|
||||||
|
results.push(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!();
|
||||||
|
println!(
|
||||||
|
"{:<35} {:>12} {:>12} {:>10}",
|
||||||
|
"Workload", "Instructions", "Time", "MIPS"
|
||||||
|
);
|
||||||
|
println!("{}", "-".repeat(72));
|
||||||
|
for r in &results {
|
||||||
|
println!(
|
||||||
|
"{:<35} {:>12} {:>10.1}ms {:>10.1}",
|
||||||
|
r.name,
|
||||||
|
r.instructions,
|
||||||
|
r.elapsed.as_secs_f64() * 1000.0,
|
||||||
|
r.mips,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let avg_mips: f64 = results.iter().map(|r| r.mips).sum::<f64>() / results.len() as f64;
|
||||||
|
println!("{}", "-".repeat(72));
|
||||||
|
println!("{:<35} {:>35.1} MIPS avg", "Overall", avg_mips);
|
||||||
|
}
|
||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
[
|
[
|
||||||
IoMapping(
|
IoMapping(
|
||||||
device: Display,
|
device: Display,
|
||||||
base: 0x0000_0000,
|
base: 0x0002_0000,
|
||||||
size: 0x0000_07D0, // 2000 bytes (80x25)
|
size: 0x0000_07D0, // 2000 bytes (80x25)
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
+12
-12
@@ -2,20 +2,20 @@ MemoryMap(
|
|||||||
table_addr: 0x1000,
|
table_addr: 0x1000,
|
||||||
regions: [
|
regions: [
|
||||||
// 0000-1000 = reserved
|
// 0000-1000 = reserved
|
||||||
Region(
|
// Region(
|
||||||
base: 0x0,
|
// base: 0x0,
|
||||||
size: 0x1000,
|
// size: 0x1000,
|
||||||
region_type: Reserved
|
// region_type: Reserved
|
||||||
),
|
// ),
|
||||||
// 1000-4000 = bootloader
|
// // 1000-4000 = bootloader
|
||||||
Region(
|
// Region(
|
||||||
base: 0x1000,
|
// base: 0x1000,
|
||||||
size: 0x3000,
|
// size: 0x3000,
|
||||||
region_type: Bootloader
|
// region_type: Bootloader
|
||||||
),
|
// ),
|
||||||
// 5000-0FFFFFFF = MMIO
|
// 5000-0FFFFFFF = MMIO
|
||||||
Region(
|
Region(
|
||||||
base: 0x5000,
|
base: 0x20000,
|
||||||
size: 0x0FFF_FFFF,
|
size: 0x0FFF_FFFF,
|
||||||
region_type: MMIO
|
region_type: MMIO
|
||||||
),
|
),
|
||||||
@@ -25,12 +25,12 @@ pub struct Region {
|
|||||||
|
|
||||||
// flags for the emulator
|
// flags for the emulator
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
identity_map_on_boot: bool,
|
pub identity_map_on_boot: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct MemoryMap {
|
pub struct MemoryMap {
|
||||||
table_addr: PhysAddr,
|
pub table_addr: PhysAddr,
|
||||||
pub regions: Vec<Region>,
|
pub regions: Vec<Region>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3,7 +3,10 @@ use std::sync::{
|
|||||||
atomic::{AtomicBool, AtomicU8, Ordering},
|
atomic::{AtomicBool, AtomicU8, Ordering},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::io::{IoAccess, IoDevice};
|
use crate::{
|
||||||
|
io::{IoAccess, IoDevice},
|
||||||
|
processor::interrupts::Interrupt,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct DisplayDevice {
|
pub struct DisplayDevice {
|
||||||
buffer: Arc<Box<[AtomicU8]>>,
|
buffer: Arc<Box<[AtomicU8]>>,
|
||||||
@@ -34,27 +37,27 @@ impl IoDevice for DisplayDevice {
|
|||||||
(self.buffer.len()) as u32
|
(self.buffer.len()) as u32
|
||||||
}
|
}
|
||||||
fn access(&self) -> IoAccess {
|
fn access(&self) -> IoAccess {
|
||||||
IoAccess::WriteOnly
|
IoAccess::WRITE
|
||||||
}
|
}
|
||||||
|
|
||||||
fn id(&self) -> super::DeviceId {
|
fn id(&self) -> super::DeviceId {
|
||||||
super::DeviceId::Display
|
super::DeviceId::Display
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_word(&mut self, offset: u32, val: u32) -> bool {
|
fn write_word(&mut self, offset: u32, val: u32) -> Result<(), Interrupt> {
|
||||||
let bytes = val.to_le_bytes();
|
let bytes = val.to_le_bytes();
|
||||||
self.buffer[offset as usize].store(bytes[0], Ordering::Relaxed);
|
self.buffer[offset as usize].store(bytes[0], Ordering::Relaxed);
|
||||||
self.buffer[offset as usize + 1].store(bytes[1], Ordering::Relaxed);
|
self.buffer[offset as usize + 1].store(bytes[1], Ordering::Relaxed);
|
||||||
self.buffer[offset as usize + 2].store(bytes[2], Ordering::Relaxed);
|
self.buffer[offset as usize + 2].store(bytes[2], Ordering::Relaxed);
|
||||||
self.buffer[offset as usize + 3].store(bytes[3], Ordering::Relaxed);
|
self.buffer[offset as usize + 3].store(bytes[3], Ordering::Relaxed);
|
||||||
self.dirty.store(true, Ordering::Relaxed);
|
self.dirty.store(true, Ordering::Relaxed);
|
||||||
true
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_byte(&mut self, offset: u32, val: u8) -> bool {
|
fn write_byte(&mut self, offset: u32, val: u8) -> Result<(), Interrupt> {
|
||||||
self.buffer[offset as usize].store(val, Ordering::Relaxed);
|
self.buffer[offset as usize].store(val, Ordering::Relaxed);
|
||||||
self.dirty.store(true, Ordering::Relaxed);
|
self.dirty.store(true, Ordering::Relaxed);
|
||||||
true
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::RandomAccessMemory;
|
use crate::{Emulator, RandomAccessMemory, processor::interrupts::Interrupt};
|
||||||
|
|
||||||
pub mod display;
|
pub mod display;
|
||||||
|
|
||||||
@@ -9,10 +9,28 @@ pub struct MappedDevice {
|
|||||||
pub device: Box<dyn IoDevice>,
|
pub device: Box<dyn IoDevice>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum IoAccess {
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
ReadOnly,
|
pub struct IoAccess(u8);
|
||||||
WriteOnly,
|
|
||||||
ReadWrite,
|
impl IoAccess {
|
||||||
|
pub const READ: Self = Self(0b01);
|
||||||
|
pub const WRITE: Self = Self(0b10);
|
||||||
|
|
||||||
|
/// Convenience alias for Read + Write.
|
||||||
|
pub const READWRITE: Self = Self(Self::READ.0 | Self::WRITE.0);
|
||||||
|
pub const NONE: Self = Self(0);
|
||||||
|
|
||||||
|
/// Bitwise OR – returns a new `IoAccess` that contains all flags from both operands.
|
||||||
|
#[inline]
|
||||||
|
pub fn or(self, other: Self) -> Self {
|
||||||
|
Self(self.0 | other.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if a flag is present.
|
||||||
|
#[inline]
|
||||||
|
pub fn contains(&self, flag: Self) -> bool {
|
||||||
|
self.0 & flag.0 != 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
@@ -42,10 +60,11 @@ pub trait IoDevice: Send {
|
|||||||
/// By default, write‑only devices return `None`. All other devices
|
/// By default, write‑only devices return `None`. All other devices
|
||||||
/// return `Some(0)` as placeholder data. Implementations should override
|
/// return `Some(0)` as placeholder data. Implementations should override
|
||||||
/// this method to provide actual read logic.
|
/// this method to provide actual read logic.
|
||||||
fn read_byte(&self, offset: u32) -> Option<u8> {
|
fn read_byte(&self, offset: u32) -> Result<u8, Interrupt> {
|
||||||
match self.access() {
|
if self.access().contains(IoAccess::READ) {
|
||||||
IoAccess::WriteOnly => None,
|
Ok(0)
|
||||||
_ => Some(0),
|
} else {
|
||||||
|
Err(Interrupt::ReadFromWriteOnly)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,10 +75,11 @@ pub trait IoDevice: Send {
|
|||||||
/// All other devices return `true`. Implementations should override
|
/// All other devices return `true`. Implementations should override
|
||||||
/// this method to perform real write operations and return whether
|
/// this method to perform real write operations and return whether
|
||||||
/// the operation succeeded.
|
/// the operation succeeded.
|
||||||
fn write_byte(&mut self, offset: u32, val: u8) -> bool {
|
fn write_byte(&mut self, offset: u32, val: u8) -> Result<(), Interrupt> {
|
||||||
match self.access() {
|
if self.access().contains(IoAccess::WRITE) {
|
||||||
IoAccess::ReadOnly => false,
|
Ok(())
|
||||||
_ => true,
|
} else {
|
||||||
|
Err(Interrupt::WriteToReadOnly)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,18 +90,17 @@ pub trait IoDevice: Send {
|
|||||||
/// Write‑only devices return `None` immediately. Override this method for
|
/// Write‑only devices return `None` immediately. Override this method for
|
||||||
/// more efficient word reads.
|
/// more efficient word reads.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn read_word(&self, offset: u32) -> Option<u32> {
|
fn read_word(&self, offset: u32) -> Result<u32, Interrupt> {
|
||||||
match self.access() {
|
if self.access().contains(IoAccess::READ) {
|
||||||
IoAccess::WriteOnly => None,
|
|
||||||
_ => {
|
|
||||||
// default: compose from bytes
|
// default: compose from bytes
|
||||||
let b0 = self.read_byte(offset)? as u32;
|
let b0 = self.read_byte(offset)? as u32;
|
||||||
let b1 = self.read_byte(offset + 1)? as u32;
|
let b1 = self.read_byte(offset + 1)? as u32;
|
||||||
let b2 = self.read_byte(offset + 2)? as u32;
|
let b2 = self.read_byte(offset + 2)? as u32;
|
||||||
let b3 = self.read_byte(offset + 3)? as u32;
|
let b3 = self.read_byte(offset + 3)? as u32;
|
||||||
Some(b0 | b1 << 8 | b2 << 16 | b3 << 24)
|
return Ok(b0 | b1 << 8 | b2 << 16 | b3 << 24);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Err(Interrupt::ReadFromWriteOnly)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write a 32‑bit word to the specified address in little‑endian order.
|
/// Write a 32‑bit word to the specified address in little‑endian order.
|
||||||
@@ -90,17 +109,16 @@ pub trait IoDevice: Send {
|
|||||||
/// using `write_byte`. Read‑only devices return `false`; otherwise returns
|
/// using `write_byte`. Read‑only devices return `false`; otherwise returns
|
||||||
/// `true` after attempting all byte writes. Override for more efficient
|
/// `true` after attempting all byte writes. Override for more efficient
|
||||||
/// word writes or to handle partial failures.
|
/// word writes or to handle partial failures.
|
||||||
fn write_word(&mut self, offset: u32, val: u32) -> bool {
|
fn write_word(&mut self, offset: u32, val: u32) -> Result<(), Interrupt> {
|
||||||
match self.access() {
|
if self.access().contains(IoAccess::WRITE) {
|
||||||
IoAccess::ReadOnly => false,
|
|
||||||
_ => {
|
|
||||||
let bytes = val.to_le_bytes();
|
let bytes = val.to_le_bytes();
|
||||||
self.write_byte(offset, bytes[0]);
|
self.write_byte(offset, bytes[0]);
|
||||||
self.write_byte(offset + 1, bytes[1]);
|
self.write_byte(offset + 1, bytes[1]);
|
||||||
self.write_byte(offset + 2, bytes[2]);
|
self.write_byte(offset + 2, bytes[2]);
|
||||||
self.write_byte(offset + 3, bytes[3]);
|
self.write_byte(offset + 3, bytes[3]);
|
||||||
true
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
Err(Interrupt::WriteToReadOnly)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
#![feature(likely_unlikely)]
|
#![feature(likely_unlikely)]
|
||||||
|
|
||||||
pub mod args;
|
pub mod args;
|
||||||
mod common;
|
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod io;
|
pub mod io;
|
||||||
mod memory;
|
mod memory;
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
use clap::Parser;
|
||||||
|
use common::{instructions::Instruction, register::Register};
|
||||||
|
use dsa::{
|
||||||
|
BulkAllocStore, Emulator, Page, RandomAccessMemory, SharedState,
|
||||||
|
args::DsaArgs,
|
||||||
|
io::display::{DisplayDevice, DisplayHandle},
|
||||||
|
};
|
||||||
|
use std::{
|
||||||
|
os::unix::thread::JoinHandleExt,
|
||||||
|
process::exit,
|
||||||
|
sync::{Arc, atomic::Ordering},
|
||||||
|
thread,
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let args = DsaArgs::parse();
|
||||||
|
|
||||||
|
let mmap = args.get_memory_map();
|
||||||
|
let iomap = args.get_io_map();
|
||||||
|
|
||||||
|
let store = BulkAllocStore::new();
|
||||||
|
let (display, handle) = DisplayDevice::new(80, 25);
|
||||||
|
|
||||||
|
let mut emulator = Emulator::new(store)
|
||||||
|
.with_device(display)
|
||||||
|
.apply_memory_map(mmap)
|
||||||
|
.apply_io_map(iomap)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
if let Some(bin) = args.get_binary() {
|
||||||
|
for (i, ch) in bin.chunks(4).enumerate() {
|
||||||
|
let instruction = Instruction(u32::from_le_bytes(ch.try_into().unwrap()));
|
||||||
|
let hex = ch
|
||||||
|
.iter()
|
||||||
|
.map(|b| format!("{:02X}", b))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(" ");
|
||||||
|
let chars = ch
|
||||||
|
.iter()
|
||||||
|
.map(|b| {
|
||||||
|
if b.is_ascii_graphic() {
|
||||||
|
*b as char
|
||||||
|
} else {
|
||||||
|
'.'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<String>();
|
||||||
|
println!("{:04X}: {:<12} {:4} {:?}", i * 4, hex, chars, instruction);
|
||||||
|
}
|
||||||
|
|
||||||
|
let program: Vec<Page> = Page::paginate(&bin).collect();
|
||||||
|
|
||||||
|
for (i, page) in program.iter().enumerate() {
|
||||||
|
emulator.memory_mut().write_page(i as u32 * 4096, &page);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let state = emulator.state_handle();
|
||||||
|
|
||||||
|
const STACK_SIZE: usize = 1024 * 1024 * 16;
|
||||||
|
let runner = thread::Builder::new()
|
||||||
|
.stack_size(STACK_SIZE)
|
||||||
|
.spawn(move || emulator.run().unwrap())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let observer = thread::spawn(|| observe(state, handle));
|
||||||
|
|
||||||
|
runner.join().unwrap();
|
||||||
|
thread::sleep(Duration::from_millis(100));
|
||||||
|
|
||||||
|
// yeah we don't need this tbh.
|
||||||
|
// observer.join().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// todo: remove this!
|
||||||
|
fn observe(state: Arc<SharedState>, handle: DisplayHandle) {
|
||||||
|
loop {
|
||||||
|
state.update_req.store(true, Ordering::Relaxed);
|
||||||
|
thread::sleep(std::time::Duration::from_millis(20));
|
||||||
|
let state = state.proc.load();
|
||||||
|
// println!(
|
||||||
|
// "clock: {}, GP Registers: {:?}",
|
||||||
|
// state.clock, state.registers
|
||||||
|
// );
|
||||||
|
|
||||||
|
if handle.is_dirty() {
|
||||||
|
for line in handle.read_all().chunks(80) {
|
||||||
|
for (i, byte) in line.iter().enumerate() {
|
||||||
|
print!("{}", *byte as char);
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,21 +5,21 @@ use std::{
|
|||||||
|
|
||||||
use crate::memory::{
|
use crate::memory::{
|
||||||
PhysAddr, RandomAccessMemory,
|
PhysAddr, RandomAccessMemory,
|
||||||
ram::{NUM_PAGES, Page, idx},
|
ram::{Page, idx},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct BulkAllocStore {
|
pub struct BulkAllocStore {
|
||||||
pages: Box<[*mut Page; NUM_PAGES]>,
|
pages: Box<[*mut Page; Page::COUNT]>,
|
||||||
pre_allocated: [*mut Page; Self::PREALLOC],
|
pre_allocated: [*mut Page; Self::ALLOC_AT_ONCE],
|
||||||
idx: u8,
|
idx: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl Send for BulkAllocStore {}
|
unsafe impl Send for BulkAllocStore {}
|
||||||
impl BulkAllocStore {
|
impl BulkAllocStore {
|
||||||
const PREALLOC: usize = u8::MAX as usize;
|
const ALLOC_AT_ONCE: usize = 256 as usize;
|
||||||
|
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let pages = vec![0 as *mut Page; 2 << 20]
|
let pages = vec![0 as *mut Page; Page::COUNT]
|
||||||
.into_boxed_slice()
|
.into_boxed_slice()
|
||||||
.try_into()
|
.try_into()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -31,11 +31,11 @@ impl BulkAllocStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn alloc_set() -> [*mut Page; Self::PREALLOC] {
|
fn alloc_set() -> [*mut Page; Self::ALLOC_AT_ONCE] {
|
||||||
let layout = Layout::from_size_align(4096 * Self::PREALLOC, 4096).unwrap();
|
let layout = Layout::from_size_align(Page::SIZE * Self::ALLOC_AT_ONCE, Page::SIZE).unwrap();
|
||||||
let mut region = unsafe { alloc_zeroed(layout) as *mut Page };
|
let mut region = unsafe { alloc_zeroed(layout) as *mut Page };
|
||||||
let mut set = [0 as *mut Page; Self::PREALLOC];
|
let mut set = [0 as *mut Page; Self::ALLOC_AT_ONCE];
|
||||||
for i in 0..Self::PREALLOC {
|
for i in 0..Self::ALLOC_AT_ONCE {
|
||||||
set[i] = region;
|
set[i] = region;
|
||||||
region = unsafe { region.byte_add(4096) };
|
region = unsafe { region.byte_add(4096) };
|
||||||
}
|
}
|
||||||
@@ -43,7 +43,7 @@ impl BulkAllocStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn alloc(&mut self) -> *mut Page {
|
fn alloc(&mut self) -> *mut Page {
|
||||||
if self.idx < Self::PREALLOC as u8 {
|
if self.idx < Self::ALLOC_AT_ONCE as u8 {
|
||||||
let page = self.pre_allocated[self.idx as usize];
|
let page = self.pre_allocated[self.idx as usize];
|
||||||
self.idx += 1;
|
self.idx += 1;
|
||||||
page
|
page
|
||||||
@@ -83,7 +83,7 @@ impl RandomAccessMemory for BulkAllocStore {
|
|||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn read_page(&self, addr: PhysAddr) -> &Page {
|
fn read_page(&self, addr: PhysAddr) -> &Page {
|
||||||
debug_assert_eq!(addr % 4096, 0);
|
debug_assert_eq!(addr % Page::SIZE as u32, 0);
|
||||||
let (idx, _) = idx(addr);
|
let (idx, _) = idx(addr);
|
||||||
|
|
||||||
if likely(!self.pages[idx].is_null()) {
|
if likely(!self.pages[idx].is_null()) {
|
||||||
@@ -120,7 +120,7 @@ impl RandomAccessMemory for BulkAllocStore {
|
|||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn write_page(&mut self, addr: PhysAddr, value: &Page) {
|
fn write_page(&mut self, addr: PhysAddr, value: &Page) {
|
||||||
debug_assert_eq!(addr % 4096, 0);
|
debug_assert_eq!(addr % Page::SIZE as u32, 0);
|
||||||
let (idx, _) = idx(addr);
|
let (idx, _) = idx(addr);
|
||||||
|
|
||||||
if unlikely(self.pages[idx].is_null()) {
|
if unlikely(self.pages[idx].is_null()) {
|
||||||
@@ -27,14 +27,14 @@ pub mod prealloc;
|
|||||||
#[cfg(feature = "mainstore-prealloc")]
|
#[cfg(feature = "mainstore-prealloc")]
|
||||||
pub use prealloc::PreAllocStore;
|
pub use prealloc::PreAllocStore;
|
||||||
|
|
||||||
const NUM_PAGES: usize = 2 << 20;
|
|
||||||
|
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Page([u8; 4096]);
|
pub struct Page([u8; 4096]);
|
||||||
|
|
||||||
impl Page {
|
impl Page {
|
||||||
pub const SIZE: usize = 4096;
|
pub const SIZE: usize = 4096;
|
||||||
|
pub const COUNT: usize = u32::MAX as usize / Self::SIZE + 1;
|
||||||
|
|
||||||
pub const MASK: u32 = 0xFFF;
|
pub const MASK: u32 = 0xFFF;
|
||||||
pub const ZERO: Self = Page::zeroed();
|
pub const ZERO: Self = Page::zeroed();
|
||||||
|
|
||||||
@@ -45,6 +45,22 @@ impl Page {
|
|||||||
pub const fn from(data: [u8; 4096]) -> Self {
|
pub const fn from(data: [u8; 4096]) -> Self {
|
||||||
Self(data)
|
Self(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn read_word(&self, offset: usize) -> u32 {
|
||||||
|
u32::from_le_bytes(self.0[offset..offset + 4].try_into().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_word(&mut self, offset: usize, value: u32) {
|
||||||
|
self.0[offset..offset + 4].copy_from_slice(&value.to_le_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn paginate(data: &[u8]) -> impl Iterator<Item = Page> + '_ {
|
||||||
|
data.chunks(4096).map(|chunk| {
|
||||||
|
let mut arr = [0u8; 4096];
|
||||||
|
arr[..chunk.len()].copy_from_slice(chunk);
|
||||||
|
Page(arr)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for Page {
|
impl Deref for Page {
|
||||||
@@ -61,6 +77,17 @@ impl DerefMut for Page {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Vec<u8>> for Page {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_from(data: Vec<u8>) -> Result<Self, Self::Error> {
|
||||||
|
let arr: [u8; 4096] = data
|
||||||
|
.try_into()
|
||||||
|
.map_err(|v: Vec<u8>| format!("Expected 4096 bytes, got {}", v.len()))?;
|
||||||
|
Ok(Page(arr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
const fn idx(addr: PhysAddr) -> (usize, usize) {
|
const fn idx(addr: PhysAddr) -> (usize, usize) {
|
||||||
((addr >> 12) as usize, (addr & 0xFFF) as usize)
|
((addr >> 12) as usize, (addr & 0xFFF) as usize)
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum Interrupt {
|
pub enum Interrupt {
|
||||||
// CPU exceptions 0-31
|
// CPU exceptions 0-31
|
||||||
InvalidInterrupt = 0,
|
InvalidInterrupt = 0,
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
mod interrupts;
|
pub mod interrupts;
|
||||||
pub mod processor;
|
pub mod processor;
|
||||||
pub mod state;
|
pub mod state;
|
||||||
@@ -0,0 +1,653 @@
|
|||||||
|
use std::{
|
||||||
|
hint::unlikely,
|
||||||
|
ops::{Add, AddAssign},
|
||||||
|
sync::{
|
||||||
|
Arc,
|
||||||
|
atomic::Ordering,
|
||||||
|
mpsc::{self, TryRecvError},
|
||||||
|
},
|
||||||
|
thread,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
|
||||||
|
use common::{
|
||||||
|
instructions::{Instruction, Opcode},
|
||||||
|
register::Register,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
Page,
|
||||||
|
config::{IoMapping, MemoryMap, RegionType},
|
||||||
|
io::{IoAccess, IoDevice, MappedDevice},
|
||||||
|
memory::{mmu::MMU, ram::RandomAccessMemory},
|
||||||
|
processor::{interrupts::Interrupt, state::SharedState},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Emulator<Mem: RandomAccessMemory> {
|
||||||
|
internal_state: ProcessorSnapshot,
|
||||||
|
shared_state: Arc<SharedState>,
|
||||||
|
|
||||||
|
// Interrupts
|
||||||
|
interrupts: mpsc::Receiver<u8>,
|
||||||
|
pending_fault: Option<Interrupt>,
|
||||||
|
|
||||||
|
// memory
|
||||||
|
mmu: MMU,
|
||||||
|
mainstore: Mem,
|
||||||
|
|
||||||
|
// IO
|
||||||
|
mmio: Vec<MappedDevice>,
|
||||||
|
mmio_region: Option<(u32, u32)>, // start end end of segment
|
||||||
|
|
||||||
|
// optionals - not necessarily set on boot.
|
||||||
|
memory_map: Option<MemoryMap>,
|
||||||
|
|
||||||
|
// Config params
|
||||||
|
__mmap_configured: bool,
|
||||||
|
__io_configured: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<Mem: RandomAccessMemory> Send for Emulator<Mem> {}
|
||||||
|
impl<Mem: RandomAccessMemory> Emulator<Mem> {
|
||||||
|
pub fn new(mem: Mem) -> Self {
|
||||||
|
let (sender, receiver) = mpsc::channel::<u8>();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
interrupts: receiver,
|
||||||
|
pending_fault: None,
|
||||||
|
|
||||||
|
internal_state: ProcessorSnapshot::default(),
|
||||||
|
shared_state: Arc::new(SharedState::new(sender)),
|
||||||
|
|
||||||
|
memory_map: None,
|
||||||
|
mmu: MMU::new(),
|
||||||
|
mainstore: mem,
|
||||||
|
|
||||||
|
// mmio
|
||||||
|
mmio: Vec::new(),
|
||||||
|
mmio_region: None,
|
||||||
|
|
||||||
|
// Config params
|
||||||
|
__io_configured: false,
|
||||||
|
__mmap_configured: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_device(mut self, device: impl IoDevice + 'static) -> Self {
|
||||||
|
self.mmio.push(MappedDevice {
|
||||||
|
base: 0,
|
||||||
|
device: Box::new(device),
|
||||||
|
});
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn state_handle(&self) -> Arc<SharedState> {
|
||||||
|
self.shared_state.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn mmu_mut(&mut self) -> &mut MMU {
|
||||||
|
&mut self.mmu
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn memory_mut(&mut self) -> &mut impl RandomAccessMemory {
|
||||||
|
&mut self.mainstore
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
#[inline]
|
||||||
|
pub fn reg(&self, reg: Register) -> u32 {
|
||||||
|
if reg as u8 == Register::Zero as u8 {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
debug_assert!((reg as usize) < ProcessorSnapshot::REG_COUNT);
|
||||||
|
|
||||||
|
unsafe { *self.internal_state.registers.get_unchecked(reg as usize) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn mut_reg(&mut self, reg: Register) -> &mut u32 {
|
||||||
|
debug_assert!((reg as usize) < ProcessorSnapshot::REG_COUNT);
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
self.internal_state
|
||||||
|
.registers
|
||||||
|
.get_unchecked_mut(reg as usize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cold]
|
||||||
|
pub fn apply_memory_map(mut self, map: MemoryMap) -> Self {
|
||||||
|
self.mmio_region = map
|
||||||
|
.regions
|
||||||
|
.iter()
|
||||||
|
.find(|r| matches!(r.region_type, RegionType::MMIO))
|
||||||
|
.map(|r| (r.base, r.base + r.size));
|
||||||
|
|
||||||
|
map.apply(&mut self);
|
||||||
|
self.memory_map = Some(map);
|
||||||
|
self.__mmap_configured = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply_io_map(mut self, map: Vec<IoMapping>) -> Result<Self, String> {
|
||||||
|
if !self.__mmap_configured {
|
||||||
|
return Err("You must map memory before applying I/O mappings".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
for entry in map {
|
||||||
|
if let Some(idx) = self.mmio.iter().position(|d| d.device.id() == entry.device) {
|
||||||
|
self.mmio[idx].base = entry.base;
|
||||||
|
} else {
|
||||||
|
eprintln!("WARN: no device registgered for {:?}", entry.device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.__io_configured = true;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cold]
|
||||||
|
fn update(&mut self) {
|
||||||
|
self.shared_state
|
||||||
|
.proc
|
||||||
|
.store(Arc::new(self.internal_state.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cold]
|
||||||
|
fn boot(&mut self) -> Result<(), String> {
|
||||||
|
if !(self.__io_configured && self.__mmap_configured) {
|
||||||
|
return Err("Processor not configured".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(map) = &self.memory_map {
|
||||||
|
MemoryMap::identity_map(&mut self.mmu, map.regions.get(0).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.internal_state.running = true;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cold]
|
||||||
|
fn shutdown(&mut self) {
|
||||||
|
self.internal_state.running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cold]
|
||||||
|
pub fn idle_wait(&mut self) {
|
||||||
|
self.internal_state.running = false;
|
||||||
|
|
||||||
|
// Wait for an interrupt or state update to continue.
|
||||||
|
loop {
|
||||||
|
// Check for interrupts.
|
||||||
|
if let Ok(code) = self.interrupts.recv_timeout(Duration::from_millis(100)) {
|
||||||
|
self.interrupt(Interrupt::Software(code));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// // If we've received a request to continue running. DEPRECATED (we can run through interrupts)
|
||||||
|
// if self.shared_state.running.load(Ordering::Relaxed) {
|
||||||
|
// panic!("3");
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// UI requested a state update.
|
||||||
|
if self.shared_state.update_req.load(Ordering::Relaxed) {
|
||||||
|
self.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.internal_state.running = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(&mut self) -> Result<(), String> {
|
||||||
|
self.boot()?;
|
||||||
|
|
||||||
|
let mut time = Instant::now();
|
||||||
|
|
||||||
|
'emu: loop {
|
||||||
|
// Update UI thread (roughly every 512k cycles targeting 60 UPS)
|
||||||
|
if unlikely(self.internal_state.clock & 0x7FFFF == 0) {
|
||||||
|
if self.shared_state.update_req.load(Ordering::Relaxed) {
|
||||||
|
self.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for commands or hardware Interrupts every 32k cycles
|
||||||
|
if self.internal_state.clock % 0x7FFF == 0 {
|
||||||
|
if self.shared_state.running.load(Ordering::Relaxed) == false {
|
||||||
|
self.idle_wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.interrupts.try_recv() {
|
||||||
|
Ok(code) => self.interrupt(Interrupt::Software(code)),
|
||||||
|
Err(TryRecvError::Disconnected) => break 'emu,
|
||||||
|
Err(TryRecvError::Empty) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we got a halt instruction, wait
|
||||||
|
if unlikely(!self.internal_state.running) {
|
||||||
|
let mips = (self.internal_state.clock as u128 / time.elapsed().as_micros());
|
||||||
|
println!(
|
||||||
|
"TIME TAKEN: {:?}, clock: {}, {}MIPS",
|
||||||
|
time.elapsed(),
|
||||||
|
self.internal_state.clock,
|
||||||
|
mips
|
||||||
|
);
|
||||||
|
time = Instant::now();
|
||||||
|
|
||||||
|
// temporary while i figure out a better solution
|
||||||
|
// exit when we hit halt (not useful for a real os ofc)
|
||||||
|
break 'emu;
|
||||||
|
|
||||||
|
self.idle_wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
let pc = self.reg(Register::Pcx);
|
||||||
|
let instruction = self.mem_read_word(pc);
|
||||||
|
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
println!(
|
||||||
|
"Clock: {} Executing {:?} PCX: {}, reg: {:?}",
|
||||||
|
self.internal_state.clock,
|
||||||
|
Instruction(instruction),
|
||||||
|
pc,
|
||||||
|
self.internal_state.registers
|
||||||
|
);
|
||||||
|
thread::sleep(Duration::from_micros(10));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.mut_reg(Register::Pcx).add_assign(4);
|
||||||
|
self.execute(Instruction(instruction));
|
||||||
|
|
||||||
|
// Check if executing the interrupt caused a fault.
|
||||||
|
if let Some(fault) = self.pending_fault {
|
||||||
|
self.pending_fault = None;
|
||||||
|
println!("WARN fault: {:?}", fault);
|
||||||
|
self.interrupt(fault);
|
||||||
|
}
|
||||||
|
|
||||||
|
// always increment clock.
|
||||||
|
self.internal_state.clock += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.shutdown();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn interrupt(&mut self, int: Interrupt) {
|
||||||
|
let idt = self.reg(Register::Idr);
|
||||||
|
|
||||||
|
*self.mut_reg(Register::Spr) -= 4;
|
||||||
|
let spr = self.reg(Register::Spr);
|
||||||
|
let pcx = self.reg(Register::Pcx);
|
||||||
|
self.mem_write_word(spr, pcx);
|
||||||
|
|
||||||
|
*self.mut_reg(Register::Pcx) = self.mem_read_word(idt + int.code() as u32 * 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn execute(&mut self, word: Instruction) {
|
||||||
|
// This needs to be unsafe as we're using word.xxxx_uc() functions for decoding which are unsafe
|
||||||
|
// as they perform unchecked transmute operations (performance critical)
|
||||||
|
unsafe {
|
||||||
|
match Opcode::from_u8(word.opcode()) {
|
||||||
|
// Nothing
|
||||||
|
Some(Opcode::Nop) => {}
|
||||||
|
|
||||||
|
// move
|
||||||
|
Some(Opcode::Mov) => {
|
||||||
|
*self.mut_reg(word.dest_uc()) = self.reg(word.src1_uc());
|
||||||
|
}
|
||||||
|
Some(Opcode::CMov) => {
|
||||||
|
if self.reg(word.misc_uc()) != 0 {
|
||||||
|
*self.mut_reg(word.dest_uc()) = self.reg(word.src1_uc());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// load
|
||||||
|
Some(Opcode::Ldbs) => todo!(),
|
||||||
|
Some(Opcode::Ldhs) => todo!(),
|
||||||
|
Some(Opcode::Ldb) => {
|
||||||
|
*self.mut_reg(word.dest_uc()) = u32::from(
|
||||||
|
self.mem_read_byte(self.reg(word.src1_uc()) + word.imm16() as u32),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Some(Opcode::Ldh) => {
|
||||||
|
*self.mut_reg(word.dest_uc()) = u32::from(
|
||||||
|
self.mem_read_word(self.reg(word.src1_uc()) + word.imm16() as u32) >> 16,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Some(Opcode::Ldw) => {
|
||||||
|
*self.mut_reg(word.dest_uc()) = u32::from(
|
||||||
|
self.mem_read_word(self.reg(word.src1_uc()) + word.imm16() as u32),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// store
|
||||||
|
Some(Opcode::Stb) => {
|
||||||
|
self.mem_write_byte(
|
||||||
|
self.reg(word.dest_uc()) + word.imm16() as u32,
|
||||||
|
self.reg(word.src1_uc()) as u8,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Some(Opcode::Sth) => {
|
||||||
|
self.mem_write_byte(
|
||||||
|
self.reg(word.dest_uc()) + word.imm16() as u32,
|
||||||
|
(self.reg(word.src1_uc()) as u16 >> 8) as u8,
|
||||||
|
);
|
||||||
|
self.mem_write_byte(
|
||||||
|
self.reg(word.dest_uc()) + word.imm16() as u32 + 1,
|
||||||
|
self.reg(word.src1_uc()) as u8,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Some(Opcode::Stw) => {
|
||||||
|
self.mem_write_word(
|
||||||
|
self.reg(word.dest_uc()) + word.imm16() as u32,
|
||||||
|
self.reg(word.src1_uc()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// load immediate
|
||||||
|
Some(Opcode::Lli) => {
|
||||||
|
*self.mut_reg(word.dest_uc()) = word.imm16() as u32;
|
||||||
|
}
|
||||||
|
Some(Opcode::Lui) => {
|
||||||
|
*self.mut_reg(word.dest_uc()) =
|
||||||
|
(word.imm16() as u32) << 16 | self.reg(word.dest_uc());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Comparison
|
||||||
|
Some(Opcode::Ieq) => {
|
||||||
|
*self.mut_reg(word.dest_uc()) =
|
||||||
|
(self.reg(word.src1_uc()) == self.reg(word.src2_uc())) as u32;
|
||||||
|
}
|
||||||
|
Some(Opcode::Ine) => {
|
||||||
|
*self.mut_reg(word.dest_uc()) =
|
||||||
|
(self.reg(word.src1_uc()) != self.reg(word.src2_uc())) as u32;
|
||||||
|
}
|
||||||
|
Some(Opcode::Ilt) => {
|
||||||
|
*self.mut_reg(word.dest_uc()) =
|
||||||
|
(self.reg(word.src1_uc()) < self.reg(word.src2_uc())) as u32;
|
||||||
|
}
|
||||||
|
Some(Opcode::Ile) => {
|
||||||
|
*self.mut_reg(word.dest_uc()) =
|
||||||
|
(self.reg(word.src1_uc()) <= self.reg(word.src2_uc())) as u32;
|
||||||
|
}
|
||||||
|
Some(Opcode::Igt) => {
|
||||||
|
*self.mut_reg(word.dest_uc()) =
|
||||||
|
(self.reg(word.src1_uc()) > self.reg(word.src2_uc())) as u32;
|
||||||
|
}
|
||||||
|
Some(Opcode::Ige) => {
|
||||||
|
*self.mut_reg(word.dest_uc()) =
|
||||||
|
(self.reg(word.src1_uc()) >= self.reg(word.src2_uc())) as u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jump
|
||||||
|
Some(Opcode::Jmp) => {
|
||||||
|
*self.mut_reg(Register::Pcx) = self.reg(word.dest_uc()) + word.imm16() as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Opcode::Jez) => {
|
||||||
|
if self.reg(word.src1_uc()) == 0 {
|
||||||
|
*self.mut_reg(Register::Pcx) =
|
||||||
|
self.reg(word.dest_uc()) + word.imm16() as u32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(Opcode::Jnz) => {
|
||||||
|
if self.reg(word.src1_uc()) != 0 {
|
||||||
|
*self.mut_reg(Register::Pcx) =
|
||||||
|
self.reg(word.dest_uc()) + word.imm16() as u32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(Opcode::Jnc) => todo!(),
|
||||||
|
Some(Opcode::Jic) => todo!(),
|
||||||
|
|
||||||
|
// Bitwise
|
||||||
|
Some(Opcode::And) => {
|
||||||
|
*self.mut_reg(word.dest_uc()) =
|
||||||
|
self.reg(word.src1_uc()) & self.reg(word.src2_uc());
|
||||||
|
}
|
||||||
|
Some(Opcode::Nand) => {
|
||||||
|
*self.mut_reg(word.dest_uc()) =
|
||||||
|
!(self.reg(word.src1_uc()) & self.reg(word.src2_uc()));
|
||||||
|
}
|
||||||
|
Some(Opcode::Or) => {
|
||||||
|
*self.mut_reg(word.dest_uc()) =
|
||||||
|
self.reg(word.src1_uc()) | self.reg(word.src2_uc());
|
||||||
|
}
|
||||||
|
Some(Opcode::Nor) => {
|
||||||
|
*self.mut_reg(word.dest_uc()) =
|
||||||
|
!(self.reg(word.src1_uc()) | self.reg(word.src2_uc()));
|
||||||
|
}
|
||||||
|
Some(Opcode::Xor) => {
|
||||||
|
*self.mut_reg(word.dest_uc()) =
|
||||||
|
self.reg(word.src1_uc()) ^ self.reg(word.src2_uc());
|
||||||
|
}
|
||||||
|
Some(Opcode::Xnor) => {
|
||||||
|
*self.mut_reg(word.dest_uc()) =
|
||||||
|
!(self.reg(word.src1_uc()) ^ self.reg(word.src2_uc()));
|
||||||
|
}
|
||||||
|
Some(Opcode::Not) => {
|
||||||
|
*self.mut_reg(word.dest_uc()) = !self.reg(word.src1_uc());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Arithmetic
|
||||||
|
Some(Opcode::Add) => {
|
||||||
|
*self.mut_reg(word.dest_uc()) =
|
||||||
|
self.reg(word.src1_uc()) + self.reg(word.src2_uc());
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Opcode::Sub) => {
|
||||||
|
*self.mut_reg(word.dest_uc()) =
|
||||||
|
self.reg(word.src1_uc()) - self.reg(word.src2_uc());
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Opcode::Shl) => {
|
||||||
|
*self.mut_reg(word.dest_uc()) = self.reg(word.src1_uc())
|
||||||
|
<< (self.reg(word.src2_uc()) + word.shamt() as u32);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Opcode::Shr) => {
|
||||||
|
*self.mut_reg(word.dest_uc()) = self.reg(word.src1_uc())
|
||||||
|
>> (self.reg(word.src2_uc()) + word.shamt() as u32);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Opcode::Addi) => {
|
||||||
|
*self.mut_reg(word.dest_uc()) = self.reg(word.src1_uc()) + word.imm16() as u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Opcode::Subi) => {
|
||||||
|
*self.mut_reg(word.dest_uc()) = self.reg(word.src1_uc()) - word.imm16() as u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility
|
||||||
|
Some(Opcode::Push) => {
|
||||||
|
self.push(word.src1_uc());
|
||||||
|
}
|
||||||
|
Some(Opcode::Pop) => {
|
||||||
|
self.pop(word.dest_uc());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function
|
||||||
|
Some(Opcode::Call) => {
|
||||||
|
self.push(Register::Pcx);
|
||||||
|
*self.mut_reg(Register::Pcx) = self.reg(word.dest_uc()) + word.imm16() as u32
|
||||||
|
}
|
||||||
|
Some(Opcode::Ret) => {
|
||||||
|
self.pop(Register::Ret);
|
||||||
|
*self.mut_reg(Register::Pcx) = self.reg(Register::Ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Opcode::Int) => {
|
||||||
|
self.interrupt(Interrupt::Software(word.imm16() as u8));
|
||||||
|
}
|
||||||
|
Some(Opcode::Hlt) => self.internal_state.running = false,
|
||||||
|
Some(Opcode::IRet) => todo!(),
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn push(&mut self, reg: Register) {
|
||||||
|
*self.mut_reg(Register::Spr) -= 4;
|
||||||
|
self.mem_write_word(self.reg(Register::Spr), self.reg(reg));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn pop(&mut self, reg: Register) {
|
||||||
|
*self.mut_reg(reg) = self.mem_read_word(self.reg(Register::Spr));
|
||||||
|
*self.mut_reg(Register::Spr) += 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn mem_read_byte(&mut self, addr: u32) -> u8 {
|
||||||
|
if unlikely(self.is_mmio(addr)) {
|
||||||
|
return self.io_read_byte(addr);
|
||||||
|
}
|
||||||
|
self.mainstore.read_byte(addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn mem_write_byte(&mut self, addr: u32, val: u8) {
|
||||||
|
if unlikely(self.is_mmio(addr)) {
|
||||||
|
self.io_write_byte(addr, val);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.mainstore.write_byte(addr, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn mem_read_word(&mut self, addr: u32) -> u32 {
|
||||||
|
if unlikely(self.is_mmio(addr)) {
|
||||||
|
return self.io_read_word(addr);
|
||||||
|
}
|
||||||
|
self.mainstore.read_word(addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn mem_write_word(&mut self, addr: u32, val: u32) {
|
||||||
|
if unlikely(self.is_mmio(addr)) {
|
||||||
|
self.io_write_word(addr, val);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.mainstore.write_word(addr, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn mem_read_page(&mut self, addr: u32) -> &Page {
|
||||||
|
if unlikely(self.is_mmio(addr)) {
|
||||||
|
// pages spanning MMIO don't really make sense
|
||||||
|
// treat as fault
|
||||||
|
self.pending_fault = Some(Interrupt::ProtectionFault);
|
||||||
|
return &Page::ZERO;
|
||||||
|
}
|
||||||
|
self.mainstore.read_page(addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn mem_write_page(&mut self, addr: u32, val: &Page) {
|
||||||
|
if unlikely(self.is_mmio(addr)) {
|
||||||
|
self.pending_fault = Some(Interrupt::ProtectionFault);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.mainstore.write_page(addr, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
// single MMIO check reused by all of the above
|
||||||
|
#[inline]
|
||||||
|
fn is_mmio(&self, addr: u32) -> bool {
|
||||||
|
self.mmio_region
|
||||||
|
.map(|(base, end)| addr >= base && addr < end)
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fault(&mut self, interrupt: Interrupt) {
|
||||||
|
self.pending_fault = Some(interrupt);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn get_device(&mut self, addr: u32) -> Option<(&mut Box<dyn IoDevice>, u32)> {
|
||||||
|
// Find the device that covers the address.
|
||||||
|
self.mmio
|
||||||
|
.iter_mut()
|
||||||
|
.find(|d| d.base <= addr && addr < d.base + d.device.size())
|
||||||
|
.map(|d| (&mut d.device, addr - d.base))
|
||||||
|
}
|
||||||
|
// --- cold IO paths ---
|
||||||
|
|
||||||
|
#[cold]
|
||||||
|
fn io_read_byte(&mut self, addr: u32) -> u8 {
|
||||||
|
if let Some((device, offset)) = self.get_device(addr) {
|
||||||
|
device.read_byte(offset).unwrap_or_else(|fault| {
|
||||||
|
self.fault(fault);
|
||||||
|
0
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
self.fault(Interrupt::UnmappedIo);
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cold]
|
||||||
|
fn io_read_word(&mut self, addr: u32) -> u32 {
|
||||||
|
if let Some((device, offset)) = self.get_device(addr) {
|
||||||
|
device.read_word(offset).unwrap_or_else(|fault| {
|
||||||
|
self.fault(fault);
|
||||||
|
0
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
self.fault(Interrupt::UnmappedIo);
|
||||||
|
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cold]
|
||||||
|
fn io_write_byte(&mut self, addr: u32, val: u8) {
|
||||||
|
if let Some((dev, offset)) = self.get_device(addr) {
|
||||||
|
dev.write_byte(offset, val).unwrap_or_else(|fault| {
|
||||||
|
self.fault(fault);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
self.fault(Interrupt::UnmappedIo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cold]
|
||||||
|
fn io_write_word(&mut self, addr: u32, val: u32) {
|
||||||
|
if let Some((dev, offset)) = self.get_device(addr) {
|
||||||
|
dev.write_word(offset, val).unwrap_or_else(|fault| {
|
||||||
|
self.fault(fault);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
self.fault(Interrupt::UnmappedIo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ProcessorSnapshot {
|
||||||
|
pub running: bool,
|
||||||
|
pub clock: usize,
|
||||||
|
pub registers: [u32; Self::REG_COUNT],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ProcessorSnapshot {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
running: false,
|
||||||
|
clock: 0,
|
||||||
|
registers: [0; Self::REG_COUNT],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProcessorSnapshot {
|
||||||
|
const REG_COUNT: usize = 28;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
[package]
|
||||||
|
name = "linker"
|
||||||
|
edition.workspace = true
|
||||||
|
version.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
pub fn add(left: u64, right: u64) -> u64 {
|
||||||
|
left + right
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_works() {
|
||||||
|
let result = add(2, 2);
|
||||||
|
assert_eq!(result, 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
include print: "./print.dsa"
|
||||||
|
db msg: "hello world"
|
||||||
|
dw stack: 0x1000
|
||||||
|
_init:
|
||||||
|
ldw stack, bpr
|
||||||
|
mov bpr, spr
|
||||||
|
lwi 1000000, rg5
|
||||||
|
|
||||||
|
_loop:
|
||||||
|
call print::reset
|
||||||
|
lwi msg, rg0
|
||||||
|
push rg0
|
||||||
|
call print::print
|
||||||
|
pop zero
|
||||||
|
subi rg5, 1, rg5
|
||||||
|
igt rg5, zero, rg4
|
||||||
|
jnz rg4, _loop
|
||||||
|
|
||||||
|
hlt
|
||||||
@@ -0,0 +1,227 @@
|
|||||||
|
// a simple brainf##k interpreter,
|
||||||
|
// because I already wrote a compiler lol.
|
||||||
|
|
||||||
|
include print "./print.dsa"
|
||||||
|
|
||||||
|
// "print hello world"
|
||||||
|
db program: "++++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
>++++++++++++++++++++++++++++++++
|
||||||
|
>++++++++++++++++
|
||||||
|
>
|
||||||
|
>+
|
||||||
|
<<
|
||||||
|
[
|
||||||
|
>>
|
||||||
|
>
|
||||||
|
>++++++++++
|
||||||
|
<<
|
||||||
|
[->+>-[>+>>]>[+[-<+>]>+>>]<<<<<<]
|
||||||
|
>[<+>-]
|
||||||
|
>[-]
|
||||||
|
>>
|
||||||
|
>++++++++++
|
||||||
|
<
|
||||||
|
[->-[>+>>]>[+[-<+>]>+>>]<<<<<]
|
||||||
|
>[-]
|
||||||
|
>>[++++++++++++++++++++++++++++++++++++++++++++++++.[-]]
|
||||||
|
<[++++++++++++++++++++++++++++++++++++++++++++++++.[-]]
|
||||||
|
<<<++++++++++++++++++++++++++++++++++++++++++++++++.[-]
|
||||||
|
<<<<<<<.>.
|
||||||
|
>>[>>+<<-]
|
||||||
|
>[>+<<+>-]
|
||||||
|
>[<+>-]
|
||||||
|
<<<-
|
||||||
|
]
|
||||||
|
<<++..."
|
||||||
|
|
||||||
|
db error: "Invalid Instruction!"
|
||||||
|
dw stack: 0x10000
|
||||||
|
dw input: 0x30000
|
||||||
|
resb data: 1024
|
||||||
|
|
||||||
|
// set up a stack so we can call functions
|
||||||
|
_init_stack:
|
||||||
|
ldw stack, bpr
|
||||||
|
mov bpr, spr
|
||||||
|
|
||||||
|
start:
|
||||||
|
// load the start of the program into rg0
|
||||||
|
lwi program, rg0
|
||||||
|
lwi data, rg1
|
||||||
|
|
||||||
|
// rg0 is our instruction pointer
|
||||||
|
// rg1 is our data pointer
|
||||||
|
// rg2 is the value at the data pointer
|
||||||
|
// rg3 stores the current instruction
|
||||||
|
// rg4 is the expression nesting level.
|
||||||
|
|
||||||
|
lli 43, rg8 // + = 43 increment
|
||||||
|
lli 45, rg9 // - = 45 decrement
|
||||||
|
lli 62, rga // > = 62 increment pointer
|
||||||
|
lli 60, rgb // < = 60 decrement pointer
|
||||||
|
lli 46, rgc // . = 46 output
|
||||||
|
lli 44, rgd // , = 44 input
|
||||||
|
lli 91, rge // [ = 91 loop start
|
||||||
|
lli 93, rgf // ] = 93 loop end
|
||||||
|
|
||||||
|
loop_start:
|
||||||
|
// load the current instruction into rg3
|
||||||
|
ldb rg0, rg3
|
||||||
|
|
||||||
|
// switch on the instruction
|
||||||
|
ieq rg3, rg8, rg4
|
||||||
|
jnz rg4, increment
|
||||||
|
ieq rg3, rg9, rg4
|
||||||
|
jnz rg4, decrement
|
||||||
|
ieq rg3, rga, rg4
|
||||||
|
jnz rg4, inc_ptr
|
||||||
|
ieq rg3, rgb, rg4
|
||||||
|
jnz rg4, dec_ptr
|
||||||
|
ieq rg3, rgc, rg4
|
||||||
|
jnz rg4, output
|
||||||
|
ieq rg3, rgd, rg4
|
||||||
|
jnz rg4, input
|
||||||
|
ieq rg3, rge, rg4
|
||||||
|
jnz rg4, expr_start
|
||||||
|
ieq rg3, rgf, rg4
|
||||||
|
jnz rg4, expr_end
|
||||||
|
ieq rg3, zero, rg4
|
||||||
|
jnz rg4, end
|
||||||
|
|
||||||
|
// if we get here, we don't know what the instruction is
|
||||||
|
lwi error, rg2
|
||||||
|
push rg0
|
||||||
|
push rg1
|
||||||
|
push rg2
|
||||||
|
call print::print
|
||||||
|
pop zero
|
||||||
|
pop rg1
|
||||||
|
pop rg0
|
||||||
|
|
||||||
|
end:
|
||||||
|
hlt
|
||||||
|
|
||||||
|
loop_end:
|
||||||
|
addi rg0, 1, rg0
|
||||||
|
jmp loop_start
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// increment the current cell
|
||||||
|
increment:
|
||||||
|
addi rg2, 1, rg2
|
||||||
|
jmp loop_end
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// decrement the current cell
|
||||||
|
decrement:
|
||||||
|
subi rg2, 1, rg2
|
||||||
|
jmp loop_end
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// increment the pointer
|
||||||
|
inc_ptr:
|
||||||
|
stw rg2, rg1
|
||||||
|
addi rg1, 4
|
||||||
|
ldw rg1, rg2
|
||||||
|
jmp loop_end
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// decrement the pointer
|
||||||
|
dec_ptr:
|
||||||
|
stw rg2, rg1
|
||||||
|
subi rg1, 4
|
||||||
|
ldw rg1, rg2
|
||||||
|
jmp loop_end
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// print the byte in the current cell
|
||||||
|
output:
|
||||||
|
push rg0
|
||||||
|
push rg1
|
||||||
|
push rg2
|
||||||
|
call print::print_byte
|
||||||
|
pop zero
|
||||||
|
pop rg1
|
||||||
|
pop rg0
|
||||||
|
jmp loop_end
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// read a byte into the current cell
|
||||||
|
input:
|
||||||
|
ldw input, rg2
|
||||||
|
jmp loop_end
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// handle an open bracket instruction
|
||||||
|
expr_start:
|
||||||
|
ieq rg2, zero, rg4
|
||||||
|
jez rg4, loop_end
|
||||||
|
|
||||||
|
_traverse_right_start:
|
||||||
|
// push a register that definitely has a nonzero value
|
||||||
|
// when we pop this value from the stack
|
||||||
|
// we know we've finished traversing.
|
||||||
|
push rg8
|
||||||
|
|
||||||
|
_traverse_right:
|
||||||
|
addi rg0, 1, rg0
|
||||||
|
ldb rg0, rg3
|
||||||
|
|
||||||
|
ieq rg3, rge, rg4
|
||||||
|
jnz rg4, open_right
|
||||||
|
ieq rg3, rgf, rg4
|
||||||
|
jnz rg4, close_right
|
||||||
|
ieq rg3, zero, rg4
|
||||||
|
jnz rg4, end
|
||||||
|
jmp _traverse_right
|
||||||
|
|
||||||
|
open_right:
|
||||||
|
// push zero to the stack
|
||||||
|
push zero
|
||||||
|
jmp _traverse_right
|
||||||
|
|
||||||
|
close_right:
|
||||||
|
// check if we've reached the bottom of the stack
|
||||||
|
pop rg4
|
||||||
|
ieq rg4, zero, rg5
|
||||||
|
jnz rg5, _traverse_right
|
||||||
|
|
||||||
|
// go to next instruction after closing bracket
|
||||||
|
addi rg0, 1, rg0
|
||||||
|
jmp loop_start
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// handle the close bracket instruction
|
||||||
|
expr_end:
|
||||||
|
ieq rg2, zero, rg4
|
||||||
|
jnz rg4, loop_end
|
||||||
|
|
||||||
|
_traverse_left_start:
|
||||||
|
push rg8
|
||||||
|
|
||||||
|
_traverse_left:
|
||||||
|
subi rg0, 1, rg0
|
||||||
|
ldb rg0, rg3
|
||||||
|
|
||||||
|
ieq rg3, rge, rg4
|
||||||
|
jnz rg4, open_left
|
||||||
|
ieq rg3, rgf, rg4
|
||||||
|
jnz rg4, close_left
|
||||||
|
ieq rg3, zero, rg4
|
||||||
|
jnz rg4, end
|
||||||
|
jmp _traverse_left
|
||||||
|
|
||||||
|
open_left:
|
||||||
|
// check if we've reached the bottom of the stack
|
||||||
|
pop rg4
|
||||||
|
ieq rg4, zero, rg5
|
||||||
|
jnz rg5, _traverse_left
|
||||||
|
|
||||||
|
// go to next instruction after open bracket
|
||||||
|
addi rg0, 1, rg0
|
||||||
|
jmp loop_start
|
||||||
|
|
||||||
|
close_left:
|
||||||
|
// push zero to the stack
|
||||||
|
push zero
|
||||||
|
jmp _traverse_left
|
||||||
Binary file not shown.
@@ -0,0 +1,292 @@
|
|||||||
|
// lib:
|
||||||
|
// print.dsa
|
||||||
|
|
||||||
|
// usage:
|
||||||
|
//
|
||||||
|
// include print "<relative path>"
|
||||||
|
//
|
||||||
|
// usage for print:
|
||||||
|
// push (register containing address of string)
|
||||||
|
// call print::print
|
||||||
|
//
|
||||||
|
// usage for reset:
|
||||||
|
// call print::reset
|
||||||
|
//
|
||||||
|
// usage for clear:
|
||||||
|
// call print::clear
|
||||||
|
//
|
||||||
|
// usage for print_byte:
|
||||||
|
// push (register containing byte)
|
||||||
|
// call print::print_byte
|
||||||
|
//
|
||||||
|
// usage for print_word:
|
||||||
|
// push (register containing word)
|
||||||
|
// call print::print_word
|
||||||
|
//
|
||||||
|
// usage for print_num:
|
||||||
|
// push (register containing number to print in decimal)
|
||||||
|
// call print::print_num
|
||||||
|
//
|
||||||
|
|
||||||
|
dw display: 0x20000
|
||||||
|
dw current: 0x20000
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// prints the string at addr(arg[0]) to the screen.
|
||||||
|
print: func
|
||||||
|
|
||||||
|
ldw bpr, rg0, 8
|
||||||
|
ldw current, rg1
|
||||||
|
|
||||||
|
_print_loop:
|
||||||
|
ldb rg0, rg2
|
||||||
|
ieq rg2, zero, rg4
|
||||||
|
jnz rg4, _end
|
||||||
|
stb rg2, rg1
|
||||||
|
|
||||||
|
addi rg0, 1
|
||||||
|
addi rg1, 1
|
||||||
|
|
||||||
|
jmp _print_loop
|
||||||
|
// ------------------------------------------
|
||||||
|
// println:
|
||||||
|
// push bpr
|
||||||
|
// mov spr, bpr
|
||||||
|
|
||||||
|
// ldw bpr, rg0, 8
|
||||||
|
// ldw current, rg1
|
||||||
|
|
||||||
|
// _println_loop:
|
||||||
|
// ldb rg0, acc
|
||||||
|
// ieq acc, zero, rg4
|
||||||
|
// jnz rg4, _println_end
|
||||||
|
// stb acc, rg1
|
||||||
|
|
||||||
|
// addi rg0, 1
|
||||||
|
// addi rg1, 1
|
||||||
|
|
||||||
|
// jmp _println_loop
|
||||||
|
|
||||||
|
// _println_end:
|
||||||
|
// call print_newline
|
||||||
|
// jmp _end
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// prints the value of arg[0] to the screen.
|
||||||
|
// print_word:
|
||||||
|
// push bpr
|
||||||
|
// mov spr, bpr
|
||||||
|
|
||||||
|
// ldw bpr, rg0, 8
|
||||||
|
// ldw current, rg1
|
||||||
|
|
||||||
|
// addi rg1, 3
|
||||||
|
|
||||||
|
// stb rg0, rg1
|
||||||
|
// subi rg1, 1
|
||||||
|
// shr rg0, 8
|
||||||
|
// stb rg0, rg1
|
||||||
|
// subi rg1, 1
|
||||||
|
// shr rg0, 8
|
||||||
|
// stb rg0, rg1
|
||||||
|
// subi rg1, 1
|
||||||
|
// shr rg0, 8
|
||||||
|
// stb rg0, rg1
|
||||||
|
|
||||||
|
// addi rg1, 4
|
||||||
|
// jmp _end
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// prints the last byte of arg[0] to the screen.
|
||||||
|
print_byte: func
|
||||||
|
ldw bpr, rg0, 8
|
||||||
|
ldw current, rg1
|
||||||
|
|
||||||
|
stb rg0, rg1
|
||||||
|
addi rg1, 1
|
||||||
|
jmp _end
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// prints the value of arg[0] to the screen in hex.
|
||||||
|
// print_hex_word:
|
||||||
|
// push bpr
|
||||||
|
// mov spr, bpr
|
||||||
|
|
||||||
|
// ldw current, rg1
|
||||||
|
|
||||||
|
// ldb bpr, rg0, 8
|
||||||
|
// push rg0
|
||||||
|
// call _print_hex_byte
|
||||||
|
// addi spr, 4
|
||||||
|
|
||||||
|
// ldb bpr, rg0, 9
|
||||||
|
// push rg0
|
||||||
|
// call _print_hex_byte
|
||||||
|
// addi spr, 4
|
||||||
|
|
||||||
|
// ldb bpr, rg0, 10
|
||||||
|
// push rg0
|
||||||
|
// call _print_hex_byte
|
||||||
|
// addi spr, 4
|
||||||
|
|
||||||
|
// ldb bpr, rg0, 11
|
||||||
|
// push rg0
|
||||||
|
// call _print_hex_byte
|
||||||
|
// addi spr, 4
|
||||||
|
|
||||||
|
// jmp _end
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// prints the last byte of arg[0] to the screen in hex.
|
||||||
|
print_hex_byte: func
|
||||||
|
ldw bpr, rg0, 8
|
||||||
|
ldw current, rg1
|
||||||
|
|
||||||
|
call _print_hex_byte
|
||||||
|
jmp _end
|
||||||
|
|
||||||
|
// function body
|
||||||
|
_print_hex_byte:
|
||||||
|
lli 0xF, rg2
|
||||||
|
push rg0
|
||||||
|
|
||||||
|
shr rg0, 4
|
||||||
|
and rg0, rg2, rg0
|
||||||
|
call _print_hex_nibble
|
||||||
|
pop rg0
|
||||||
|
|
||||||
|
and rg0, rg2, rg0
|
||||||
|
call _print_hex_nibble
|
||||||
|
ret
|
||||||
|
|
||||||
|
// print a hex digit
|
||||||
|
_print_hex_nibble:
|
||||||
|
lli 10, rg3
|
||||||
|
ilt rg0, rg3, rg4
|
||||||
|
jnz rg4, _print_hex_nibble_number
|
||||||
|
addi rg0, 0x37, rg0
|
||||||
|
stb rg0, rg1
|
||||||
|
addi rg1, 1
|
||||||
|
ret
|
||||||
|
|
||||||
|
// helper function.
|
||||||
|
_print_hex_nibble_number:
|
||||||
|
addi rg0, 0x30, rg0
|
||||||
|
stb rg0, rg1
|
||||||
|
addi rg1, 1
|
||||||
|
ret
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// print whitespace
|
||||||
|
print_whitespace: func
|
||||||
|
ldw current, rg1
|
||||||
|
lli 0x20, rg0
|
||||||
|
stb rg0, rg1
|
||||||
|
addi rg1, 1
|
||||||
|
jmp _end
|
||||||
|
|
||||||
|
// // ------------------------------------------
|
||||||
|
// // print newline
|
||||||
|
// print_newline:
|
||||||
|
// push bpr
|
||||||
|
// mov spr, bpr
|
||||||
|
|
||||||
|
// ldw display, rg0
|
||||||
|
// ldw current, rg1
|
||||||
|
|
||||||
|
// sub rg1, rg0, rg0
|
||||||
|
|
||||||
|
// lwi 80, rg2
|
||||||
|
// push rg0
|
||||||
|
// push rg1
|
||||||
|
// push rg2
|
||||||
|
// push rg0
|
||||||
|
// push rg2
|
||||||
|
// call maths::divmod
|
||||||
|
// pop zero // result
|
||||||
|
// pop rg3 // remainder
|
||||||
|
// pop rg0
|
||||||
|
// pop rg1
|
||||||
|
// pop rg2
|
||||||
|
|
||||||
|
// sub rg1, rg3, rg2
|
||||||
|
// addi rg2, 80, rg1
|
||||||
|
|
||||||
|
// jmp _end
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// prints arg[0] as a decimal number to the screen.
|
||||||
|
// print_num:
|
||||||
|
// push bpr
|
||||||
|
// mov spr, bpr
|
||||||
|
|
||||||
|
// ldw bpr, rg0, 8
|
||||||
|
// lli 0, rg5
|
||||||
|
|
||||||
|
// ieq rg0, zero, rg4
|
||||||
|
// jez rg4, _print_num_extract_digits
|
||||||
|
|
||||||
|
// lli 0x30, rg6
|
||||||
|
// push rg6
|
||||||
|
// lli 1, rg5
|
||||||
|
// jmp _print_num_output
|
||||||
|
|
||||||
|
// _print_num_extract_digits:
|
||||||
|
// ieq rg0, zero, rg4
|
||||||
|
// jnz rg4, _print_num_output
|
||||||
|
|
||||||
|
// push rg0
|
||||||
|
// lli 10, rg1
|
||||||
|
// push rg1
|
||||||
|
// call maths::divmod
|
||||||
|
// pop rg0 // quotient
|
||||||
|
// pop rg1 // remainder
|
||||||
|
|
||||||
|
// addi rg1, 0x30, rg6
|
||||||
|
// push rg6
|
||||||
|
// inc rg5
|
||||||
|
|
||||||
|
// jmp _print_num_extract_digits
|
||||||
|
|
||||||
|
// _print_num_output:
|
||||||
|
// ldw current, rg1
|
||||||
|
|
||||||
|
// _print_num_output_loop:
|
||||||
|
// ieq rg5, zero, rg4
|
||||||
|
// jnz rg4, _print_num_done
|
||||||
|
|
||||||
|
// pop rg6
|
||||||
|
// stb rg6, rg1
|
||||||
|
// addi rg1, 1
|
||||||
|
// dec rg5
|
||||||
|
|
||||||
|
// jmp _print_num_output_loop
|
||||||
|
|
||||||
|
// _print_num_done:
|
||||||
|
// jmp _end
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// resets the cursor position to 0x20000 (0,0)
|
||||||
|
reset: func
|
||||||
|
ldw display, rg1
|
||||||
|
jmp _end
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// clears the screen
|
||||||
|
clear: func
|
||||||
|
lli 500, rg0
|
||||||
|
ldw display, rg1
|
||||||
|
|
||||||
|
_clear_loop:
|
||||||
|
subi rg0, 1, rg0
|
||||||
|
stw zero, rg1
|
||||||
|
addi rg1, 4
|
||||||
|
igt rg0, zero, rg4
|
||||||
|
jnz rg4, _clear_loop
|
||||||
|
jmp _end
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// return — saves current, restores frame, returns
|
||||||
|
_end:
|
||||||
|
stw rg1, current
|
||||||
|
return
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
DSX is a collection of higher‑level tools that orchestrate the DSA ecosystem.
|
||||||
|
While **DSA** supplies the core emulator, compiler, assembler and runtime, DSX provides:
|
||||||
|
|
||||||
|
* **dsx-build** – A build system and package manager that compiles DSA projects, resolves dependencies, and produces deployable artifacts.
|
||||||
|
* **dsx-server** – A lightweight package repository server that hosts DSX packages, enabling sharing and version control across teams, with CI/CD configured out‑of‑the‑box.
|
||||||
|
|
||||||
|
Together, these tools streamline development workflows, automate builds, and manage the distribution of DSA components within an organization.
|
||||||
Reference in New Issue
Block a user