diff --git a/dsa-emu/.gitignore b/.gitignore similarity index 100% rename from dsa-emu/.gitignore rename to .gitignore diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..7115af1 --- /dev/null +++ b/Cargo.toml @@ -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"] diff --git a/dsa-emu/config/dsa.boot.ron b/config/dsa.boot.ron similarity index 100% rename from dsa-emu/config/dsa.boot.ron rename to config/dsa.boot.ron diff --git a/dsa-emu/config/dsa.iomap.ron b/config/dsa.iomap.ron similarity index 100% rename from dsa-emu/config/dsa.iomap.ron rename to config/dsa.iomap.ron diff --git a/dsa-emu/config/dsa.mmap.ron b/config/dsa.mmap.ron similarity index 100% rename from dsa-emu/config/dsa.mmap.ron rename to config/dsa.mmap.ron diff --git a/doc/ISA.md b/doc/ISA.md index 164fe72..93fb8c7 100644 --- a/doc/ISA.md +++ b/doc/ISA.md @@ -73,7 +73,8 @@ subi // Misc nop (zero/do nothing) -int +int +irt hlt // Atomic diff --git a/dsa-emu/Cargo.lock b/dsa-emu/Cargo.lock deleted file mode 100644 index 3f728be..0000000 --- a/dsa-emu/Cargo.lock +++ /dev/null @@ -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" diff --git a/dsa-emu/src/common/instructions.rs b/dsa-emu/src/common/instructions.rs deleted file mode 100644 index bd7a974..0000000 --- a/dsa-emu/src/common/instructions.rs +++ /dev/null @@ -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 { - 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 { - if idx > 28 { - return Err(()); - } - Ok(Self::from_u8_unchecked(idx)) - } -} diff --git a/dsa-emu/src/common/mod.rs b/dsa-emu/src/common/mod.rs deleted file mode 100644 index 4f17719..0000000 --- a/dsa-emu/src/common/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod instructions; diff --git a/dsa-emu/src/main.rs b/dsa-emu/src/main.rs deleted file mode 100644 index 53ce738..0000000 --- a/dsa-emu/src/main.rs +++ /dev/null @@ -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, 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!(); - } - } - } -} diff --git a/dsa-emu/src/processor/processor.rs b/dsa-emu/src/processor/processor.rs deleted file mode 100644 index df7c7f4..0000000 --- a/dsa-emu/src/processor/processor.rs +++ /dev/null @@ -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 { - internal_state: ProcessorSnapshot, - shared_state: Arc, - - // Interrupts - interrupts: mpsc::Receiver, - pending_fault: Option, - - // memory - mmu: MMU, - mainstore: Mem, - - // IO - mmio: Vec, - mmio_region: Option<(u32, u32)>, // start end end of segment - - // optionals - not necessarily set on boot. - memory_map: Option, - - // Config params - __mmap_configured: bool, - __io_configured: bool, -} - -unsafe impl Send for Emulator {} -impl Emulator { - pub fn new(mem: Mem) -> Self { - let (sender, receiver) = mpsc::channel::(); - - 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 { - 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) -> Result { - 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; -} diff --git a/dsa/README.md b/dsa/README.md new file mode 100644 index 0000000..db23fc4 --- /dev/null +++ b/dsa/README.md @@ -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. diff --git a/dsa/assembler_old/Cargo.toml b/dsa/assembler_old/Cargo.toml new file mode 100644 index 0000000..6519294 --- /dev/null +++ b/dsa/assembler_old/Cargo.toml @@ -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" diff --git a/dsa/assembler_old/src/assembler/codegen.rs b/dsa/assembler_old/src/assembler/codegen.rs new file mode 100644 index 0000000..78ddadf --- /dev/null +++ b/dsa/assembler_old/src/assembler/codegen.rs @@ -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) -> Result, 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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), + )) +} diff --git a/dsa/assembler_old/src/assembler/expand.rs b/dsa/assembler_old/src/assembler/expand.rs new file mode 100644 index 0000000..12f9086 --- /dev/null +++ b/dsa/assembler_old/src/assembler/expand.rs @@ -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, module: u64) -> Result, AssembleError> { + let mut result = Vec::::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, _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) { + 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) { + 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) -> Result<(), AssembleError> { + let opcode = current.opcode(); + let args: Vec = 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) -> Result<(), AssembleError> { + let opcode = current.opcode(); + + let args: Vec = 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) -> 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) -> 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) -> 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, size: usize) -> Result, AssembleError> { + assert!(matches!(size, 1 | 2 | 4)); + + let mut buffer = Vec::::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) +} diff --git a/dsa/assembler_old/src/assembler/lexer.rs b/dsa/assembler_old/src/assembler/lexer.rs new file mode 100644 index 0000000..1a1947b --- /dev/null +++ b/dsa/assembler_old/src/assembler/lexer.rs @@ -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, 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, AssembleError> { + Ok(Register::from_str(token).map(Token::Register).ok()) +} + +pub fn parse_opcode(token: &str) -> Result, AssembleError> { + Ok(Opcode::from_str(token).ok().map(Token::Opcode)) +} + +pub fn parse_hex(token: &str) -> Result, 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, 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, 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, AssembleError> { + let Ok(tok) = token.parse::() else { + return Ok(None); + }; + + Ok(Some(Token::Immediate(tok))) +} + +pub fn parse_label(token: &str, module: u64) -> Result, 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, 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), + }))) + } +} diff --git a/dsa/assembler_old/src/assembler/macros.rs b/dsa/assembler_old/src/assembler/macros.rs new file mode 100644 index 0000000..e84c85c --- /dev/null +++ b/dsa/assembler_old/src/assembler/macros.rs @@ -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 }; +} diff --git a/dsa/assembler_old/src/assembler/mod.rs b/dsa/assembler_old/src/assembler/mod.rs new file mode 100644 index 0000000..69f1e5d --- /dev/null +++ b/dsa/assembler_old/src/assembler/mod.rs @@ -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, AssembleError>>, + result_rx: Option, AssembleError>>>, + is_running: bool, +} + +impl Assembler { + #[must_use] + pub fn new(src_path: impl Into) -> 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 = 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, 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, 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, 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, + 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() +} diff --git a/dsa/assembler_old/src/assembler/model.rs b/dsa/assembler_old/src/assembler/model.rs new file mode 100644 index 0000000..f04864d --- /dev/null +++ b/dsa/assembler_old/src/assembler/model.rs @@ -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, + pub opcode: Opcode, + pub tokens: Vec, +} + +impl Node { + #[must_use] + pub const fn new(symbol: Option, opcode: Opcode, tokens: Vec) -> Self { + Self { + symbol, + opcode, + tokens, + } + } + + #[must_use] + pub fn label(&self) -> Option { + self.symbol.clone() + } + + #[must_use] + pub const fn opcode(&self) -> Opcode { + self.opcode + } + + #[must_use] + pub fn args(&self) -> Vec { + self.tokens.clone() + } + + pub fn arg(&self, index: usize) -> Result { + 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::>() + .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(&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 { + 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 { + 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 + ) + } +} diff --git a/dsa/assembler_old/src/assembler/parser.rs b/dsa/assembler_old/src/assembler/parser.rs new file mode 100644 index 0000000..31abfe3 --- /dev/null +++ b/dsa/assembler_old/src/assembler/parser.rs @@ -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, + nodes: Vec, +} + +#[derive(Debug)] +pub struct Program { + pub nodes: Vec, +} + +impl Program { + #[must_use] + pub const fn new() -> Self { + Self { nodes: vec![] } + } + + pub fn add_module(&mut self, module: Vec) { + 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) -> Result, 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, + source_path: &Path, + ) -> Result, 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 { + 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; + + #[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, 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 { + 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 { + if self.tokens.is_empty() { + Err(AssembleError::UnexpectedEof) + } else { + Ok(self + .tokens + .last() + .expect("peek_next called on empty tokens vector") + .clone()) + } + } +} diff --git a/dsa/assembler_old/src/assembler/resolver.rs b/dsa/assembler_old/src/assembler/resolver.rs new file mode 100644 index 0000000..2487d1a --- /dev/null +++ b/dsa/assembler_old/src/assembler/resolver.rs @@ -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 { + 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, + base_dir: &Path, +) -> Result, 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) -> Result<(), AssembleError> { + let mut res = Vec::::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(()) +} diff --git a/dsa/assembler_old/src/lib.rs b/dsa/assembler_old/src/lib.rs new file mode 100644 index 0000000..090065d --- /dev/null +++ b/dsa/assembler_old/src/lib.rs @@ -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; +} diff --git a/dsa/assembler_old/src/main.rs b/dsa/assembler_old/src/main.rs new file mode 100644 index 0000000..2ab7227 --- /dev/null +++ b/dsa/assembler_old/src/main.rs @@ -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 = 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); + } +} diff --git a/dsa/common/Cargo.toml b/dsa/common/Cargo.toml new file mode 100644 index 0000000..d8a1436 --- /dev/null +++ b/dsa/common/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "common" +version = "0.1.0" +edition = "2024" + +[lib] +path = "src/lib.rs" + +[dependencies] diff --git a/dsa/common/src/instructions/decode.rs b/dsa/common/src/instructions/decode.rs new file mode 100644 index 0000000..cfabe5d --- /dev/null +++ b/dsa/common/src/instructions/decode.rs @@ -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 + } +} diff --git a/dsa/common/src/instructions/encode.rs b/dsa/common/src/instructions/encode.rs new file mode 100644 index 0000000..fd79163 --- /dev/null +++ b/dsa/common/src/instructions/encode.rs @@ -0,0 +1,334 @@ +use std::ops::{Deref, DerefMut}; + +use crate::{ + instructions::{Instruction, Opcode}, + register::Register, +}; + +impl From 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) + } +} diff --git a/dsa/common/src/instructions/mod.rs b/dsa/common/src/instructions/mod.rs new file mode 100644 index 0000000..8568d83 --- /dev/null +++ b/dsa/common/src/instructions/mod.rs @@ -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 { + 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() + ) + } + } + } +} diff --git a/dsa/common/src/lib.rs b/dsa/common/src/lib.rs new file mode 100644 index 0000000..13ccda5 --- /dev/null +++ b/dsa/common/src/lib.rs @@ -0,0 +1,7 @@ +pub mod instructions; +pub mod register; + +pub mod prelude { + pub use crate::instructions::Instruction; + pub use crate::register::Register; +} diff --git a/dsa/common/src/register.rs b/dsa/common/src/register.rs new file mode 100644 index 0000000..c438b3d --- /dev/null +++ b/dsa/common/src/register.rs @@ -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 { + 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 { + 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(()), + } + } +} diff --git a/dsa/compiler/Cargo.toml b/dsa/compiler/Cargo.toml new file mode 100644 index 0000000..1a32c86 --- /dev/null +++ b/dsa/compiler/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "compiler" +edition.workspace = true +version.workspace = true +authors.workspace = true + +[dependencies] diff --git a/dsa/compiler/src/lib.rs b/dsa/compiler/src/lib.rs new file mode 100644 index 0000000..b93cf3f --- /dev/null +++ b/dsa/compiler/src/lib.rs @@ -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); + } +} diff --git a/dsa-emu/Cargo.toml b/dsa/emulator/Cargo.toml similarity index 85% rename from dsa-emu/Cargo.toml rename to dsa/emulator/Cargo.toml index f879708..22c947b 100644 --- a/dsa-emu/Cargo.toml +++ b/dsa/emulator/Cargo.toml @@ -1,8 +1,12 @@ [package] -name = "dsa-emu" +name = "emulator" version = "0.1.0" edition = "2024" +[[bin]] +name = "dsa" +path = "src/main.rs" + [lib] name = "dsa" path = "src/lib.rs" @@ -17,6 +21,10 @@ fxhash = "0.2.1" ron = "0.12.0" serde = { version = "1.0.228", features = ["derive"] } +common = { path = "../common" } +#assembler = { path = "../assembler" } + + [[bench]] name = "bench_mainstore" harness = false diff --git a/dsa-emu/benches/bench_mainstore.rs b/dsa/emulator/benches/bench_mainstore.rs similarity index 100% rename from dsa-emu/benches/bench_mainstore.rs rename to dsa/emulator/benches/bench_mainstore.rs diff --git a/dsa-emu/src/args.rs b/dsa/emulator/src/args.rs similarity index 89% rename from dsa-emu/src/args.rs rename to dsa/emulator/src/args.rs index 5b24a1a..b59e863 100644 --- a/dsa-emu/src/args.rs +++ b/dsa/emulator/src/args.rs @@ -13,6 +13,9 @@ pub struct DsaArgs { #[arg(long = "mmap")] memory_map: Option, + #[arg(long = "bin")] + binary: Option, + #[arg(long = "iomap")] io_map: Option, @@ -34,6 +37,12 @@ impl DsaArgs { }) } + pub fn get_binary(&self) -> Option> { + self.binary + .clone() + .and_then(|path| Some(fs::read(path).unwrap())) + } + pub fn get_io_map(&self) -> Vec { self.io_map .as_ref() diff --git a/dsa/emulator/src/bin/bench_emu.rs b/dsa/emulator/src/bin/bench_emu.rs new file mode 100644 index 0000000..fd2e29e --- /dev/null +++ b/dsa/emulator/src/bin/bench_emu.rs @@ -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 { + 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, +// 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 { + 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 { + 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 { + // 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 = 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 { + 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) -> BenchResult { + let mut emu = make_emulator(); + let handle = emu.state_handle(); + + let pages: Vec = 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)] = &[ + ("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::() / results.len() as f64; + println!("{}", "-".repeat(72)); + println!("{:<35} {:>35.1} MIPS avg", "Overall", avg_mips); +} diff --git a/dsa-emu/src/config/default/dsa.iomap.ron b/dsa/emulator/src/config/default/dsa.iomap.ron similarity index 78% rename from dsa-emu/src/config/default/dsa.iomap.ron rename to dsa/emulator/src/config/default/dsa.iomap.ron index 28da39d..6575514 100644 --- a/dsa-emu/src/config/default/dsa.iomap.ron +++ b/dsa/emulator/src/config/default/dsa.iomap.ron @@ -1,7 +1,7 @@ [ IoMapping( device: Display, - base: 0x0000_0000, + base: 0x0002_0000, size: 0x0000_07D0, // 2000 bytes (80x25) ), ] diff --git a/dsa-emu/src/config/default/dsa.mmap.ron b/dsa/emulator/src/config/default/dsa.mmap.ron similarity index 68% rename from dsa-emu/src/config/default/dsa.mmap.ron rename to dsa/emulator/src/config/default/dsa.mmap.ron index a589872..defd625 100644 --- a/dsa-emu/src/config/default/dsa.mmap.ron +++ b/dsa/emulator/src/config/default/dsa.mmap.ron @@ -2,20 +2,20 @@ MemoryMap( table_addr: 0x1000, regions: [ // 0000-1000 = reserved - Region( - base: 0x0, - size: 0x1000, - region_type: Reserved - ), - // 1000-4000 = bootloader - Region( - base: 0x1000, - size: 0x3000, - region_type: Bootloader - ), + // Region( + // base: 0x0, + // size: 0x1000, + // region_type: Reserved + // ), + // // 1000-4000 = bootloader + // Region( + // base: 0x1000, + // size: 0x3000, + // region_type: Bootloader + // ), // 5000-0FFFFFFF = MMIO Region( - base: 0x5000, + base: 0x20000, size: 0x0FFF_FFFF, region_type: MMIO ), diff --git a/dsa-emu/src/config/mod.rs b/dsa/emulator/src/config/mod.rs similarity index 96% rename from dsa-emu/src/config/mod.rs rename to dsa/emulator/src/config/mod.rs index 5aab930..56fabb5 100644 --- a/dsa-emu/src/config/mod.rs +++ b/dsa/emulator/src/config/mod.rs @@ -25,12 +25,12 @@ pub struct Region { // flags for the emulator #[serde(default)] - identity_map_on_boot: bool, + pub identity_map_on_boot: bool, } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct MemoryMap { - table_addr: PhysAddr, + pub table_addr: PhysAddr, pub regions: Vec, } diff --git a/dsa-emu/src/io/display.rs b/dsa/emulator/src/io/display.rs similarity index 85% rename from dsa-emu/src/io/display.rs rename to dsa/emulator/src/io/display.rs index 0afd685..25906d0 100644 --- a/dsa-emu/src/io/display.rs +++ b/dsa/emulator/src/io/display.rs @@ -3,7 +3,10 @@ use std::sync::{ atomic::{AtomicBool, AtomicU8, Ordering}, }; -use crate::io::{IoAccess, IoDevice}; +use crate::{ + io::{IoAccess, IoDevice}, + processor::interrupts::Interrupt, +}; pub struct DisplayDevice { buffer: Arc>, @@ -34,27 +37,27 @@ impl IoDevice for DisplayDevice { (self.buffer.len()) as u32 } fn access(&self) -> IoAccess { - IoAccess::WriteOnly + IoAccess::WRITE } fn id(&self) -> super::DeviceId { 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(); 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 + 2].store(bytes[2], Ordering::Relaxed); self.buffer[offset as usize + 3].store(bytes[3], 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.dirty.store(true, Ordering::Relaxed); - true + Ok(()) } } diff --git a/dsa-emu/src/io/mod.rs b/dsa/emulator/src/io/mod.rs similarity index 50% rename from dsa-emu/src/io/mod.rs rename to dsa/emulator/src/io/mod.rs index 46fa1fe..adaf491 100644 --- a/dsa-emu/src/io/mod.rs +++ b/dsa/emulator/src/io/mod.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use crate::RandomAccessMemory; +use crate::{Emulator, RandomAccessMemory, processor::interrupts::Interrupt}; pub mod display; @@ -9,10 +9,28 @@ pub struct MappedDevice { pub device: Box, } -pub enum IoAccess { - ReadOnly, - WriteOnly, - ReadWrite, +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct IoAccess(u8); + +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)] @@ -42,10 +60,11 @@ pub trait IoDevice: Send { /// By default, write‑only devices return `None`. All other devices /// return `Some(0)` as placeholder data. Implementations should override /// this method to provide actual read logic. - fn read_byte(&self, offset: u32) -> Option { - match self.access() { - IoAccess::WriteOnly => None, - _ => Some(0), + fn read_byte(&self, offset: u32) -> Result { + if self.access().contains(IoAccess::READ) { + Ok(0) + } else { + Err(Interrupt::ReadFromWriteOnly) } } @@ -56,10 +75,11 @@ pub trait IoDevice: Send { /// All other devices return `true`. Implementations should override /// this method to perform real write operations and return whether /// the operation succeeded. - fn write_byte(&mut self, offset: u32, val: u8) -> bool { - match self.access() { - IoAccess::ReadOnly => false, - _ => true, + fn write_byte(&mut self, offset: u32, val: u8) -> Result<(), Interrupt> { + if self.access().contains(IoAccess::WRITE) { + Ok(()) + } else { + Err(Interrupt::WriteToReadOnly) } } @@ -70,18 +90,17 @@ pub trait IoDevice: Send { /// Write‑only devices return `None` immediately. Override this method for /// more efficient word reads. #[inline] - fn read_word(&self, offset: u32) -> Option { - match self.access() { - IoAccess::WriteOnly => None, - _ => { - // default: compose from bytes - let b0 = self.read_byte(offset)? as u32; - let b1 = self.read_byte(offset + 1)? as u32; - let b2 = self.read_byte(offset + 2)? as u32; - let b3 = self.read_byte(offset + 3)? as u32; - Some(b0 | b1 << 8 | b2 << 16 | b3 << 24) - } + fn read_word(&self, offset: u32) -> Result { + if self.access().contains(IoAccess::READ) { + // default: compose from bytes + let b0 = self.read_byte(offset)? as u32; + let b1 = self.read_byte(offset + 1)? as u32; + let b2 = self.read_byte(offset + 2)? as u32; + let b3 = self.read_byte(offset + 3)? as u32; + 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. @@ -90,17 +109,16 @@ pub trait IoDevice: Send { /// using `write_byte`. Read‑only devices return `false`; otherwise returns /// `true` after attempting all byte writes. Override for more efficient /// word writes or to handle partial failures. - fn write_word(&mut self, offset: u32, val: u32) -> bool { - match self.access() { - IoAccess::ReadOnly => false, - _ => { - let bytes = val.to_le_bytes(); - self.write_byte(offset, bytes[0]); - self.write_byte(offset + 1, bytes[1]); - self.write_byte(offset + 2, bytes[2]); - self.write_byte(offset + 3, bytes[3]); - true - } + fn write_word(&mut self, offset: u32, val: u32) -> Result<(), Interrupt> { + if self.access().contains(IoAccess::WRITE) { + let bytes = val.to_le_bytes(); + self.write_byte(offset, bytes[0]); + self.write_byte(offset + 1, bytes[1]); + self.write_byte(offset + 2, bytes[2]); + self.write_byte(offset + 3, bytes[3]); + return Ok(()); } + + Err(Interrupt::WriteToReadOnly) } } diff --git a/dsa-emu/src/lib.rs b/dsa/emulator/src/lib.rs similarity index 94% rename from dsa-emu/src/lib.rs rename to dsa/emulator/src/lib.rs index 90f7e75..9e368f0 100644 --- a/dsa-emu/src/lib.rs +++ b/dsa/emulator/src/lib.rs @@ -1,7 +1,6 @@ #![feature(likely_unlikely)] pub mod args; -mod common; pub mod config; pub mod io; mod memory; diff --git a/dsa/emulator/src/main.rs b/dsa/emulator/src/main.rs new file mode 100644 index 0000000..2842702 --- /dev/null +++ b/dsa/emulator/src/main.rs @@ -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::>() + .join(" "); + let chars = ch + .iter() + .map(|b| { + if b.is_ascii_graphic() { + *b as char + } else { + '.' + } + }) + .collect::(); + println!("{:04X}: {:<12} {:4} {:?}", i * 4, hex, chars, instruction); + } + + let program: Vec = 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, 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!(); + } + } + } +} diff --git a/dsa-emu/src/memory/cache.rs b/dsa/emulator/src/memory/cache.rs similarity index 100% rename from dsa-emu/src/memory/cache.rs rename to dsa/emulator/src/memory/cache.rs diff --git a/dsa-emu/src/memory/mmu.rs b/dsa/emulator/src/memory/mmu.rs similarity index 100% rename from dsa-emu/src/memory/mmu.rs rename to dsa/emulator/src/memory/mmu.rs diff --git a/dsa-emu/src/memory/mod.rs b/dsa/emulator/src/memory/mod.rs similarity index 100% rename from dsa-emu/src/memory/mod.rs rename to dsa/emulator/src/memory/mod.rs diff --git a/dsa-emu/src/memory/ram/arraymap.rs b/dsa/emulator/src/memory/ram/arraymap.rs similarity index 100% rename from dsa-emu/src/memory/ram/arraymap.rs rename to dsa/emulator/src/memory/ram/arraymap.rs diff --git a/dsa-emu/src/memory/ram/bulkalloc.rs b/dsa/emulator/src/memory/ram/bulkalloc.rs similarity index 82% rename from dsa-emu/src/memory/ram/bulkalloc.rs rename to dsa/emulator/src/memory/ram/bulkalloc.rs index eb32249..278b457 100644 --- a/dsa-emu/src/memory/ram/bulkalloc.rs +++ b/dsa/emulator/src/memory/ram/bulkalloc.rs @@ -5,21 +5,21 @@ use std::{ use crate::memory::{ PhysAddr, RandomAccessMemory, - ram::{NUM_PAGES, Page, idx}, + ram::{Page, idx}, }; pub struct BulkAllocStore { - pages: Box<[*mut Page; NUM_PAGES]>, - pre_allocated: [*mut Page; Self::PREALLOC], + pages: Box<[*mut Page; Page::COUNT]>, + pre_allocated: [*mut Page; Self::ALLOC_AT_ONCE], idx: u8, } unsafe impl Send for BulkAllocStore {} impl BulkAllocStore { - const PREALLOC: usize = u8::MAX as usize; + const ALLOC_AT_ONCE: usize = 256 as usize; 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() .try_into() .unwrap(); @@ -31,11 +31,11 @@ impl BulkAllocStore { } } - fn alloc_set() -> [*mut Page; Self::PREALLOC] { - let layout = Layout::from_size_align(4096 * Self::PREALLOC, 4096).unwrap(); + fn alloc_set() -> [*mut Page; Self::ALLOC_AT_ONCE] { + 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 set = [0 as *mut Page; Self::PREALLOC]; - for i in 0..Self::PREALLOC { + let mut set = [0 as *mut Page; Self::ALLOC_AT_ONCE]; + for i in 0..Self::ALLOC_AT_ONCE { set[i] = region; region = unsafe { region.byte_add(4096) }; } @@ -43,7 +43,7 @@ impl BulkAllocStore { } 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]; self.idx += 1; page @@ -83,7 +83,7 @@ impl RandomAccessMemory for BulkAllocStore { #[inline(always)] 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); if likely(!self.pages[idx].is_null()) { @@ -120,7 +120,7 @@ impl RandomAccessMemory for BulkAllocStore { #[inline(always)] 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); if unlikely(self.pages[idx].is_null()) { diff --git a/dsa-emu/src/memory/ram/hashmap.rs b/dsa/emulator/src/memory/ram/hashmap.rs similarity index 100% rename from dsa-emu/src/memory/ram/hashmap.rs rename to dsa/emulator/src/memory/ram/hashmap.rs diff --git a/dsa-emu/src/memory/ram/mod.rs b/dsa/emulator/src/memory/ram/mod.rs similarity index 66% rename from dsa-emu/src/memory/ram/mod.rs rename to dsa/emulator/src/memory/ram/mod.rs index c0b212c..97f4578 100644 --- a/dsa-emu/src/memory/ram/mod.rs +++ b/dsa/emulator/src/memory/ram/mod.rs @@ -27,14 +27,14 @@ pub mod prealloc; #[cfg(feature = "mainstore-prealloc")] pub use prealloc::PreAllocStore; -const NUM_PAGES: usize = 2 << 20; - #[repr(transparent)] #[derive(Clone)] pub struct Page([u8; 4096]); impl Page { pub const SIZE: usize = 4096; + pub const COUNT: usize = u32::MAX as usize / Self::SIZE + 1; + pub const MASK: u32 = 0xFFF; pub const ZERO: Self = Page::zeroed(); @@ -45,6 +45,22 @@ impl Page { pub const fn from(data: [u8; 4096]) -> Self { 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 + '_ { + data.chunks(4096).map(|chunk| { + let mut arr = [0u8; 4096]; + arr[..chunk.len()].copy_from_slice(chunk); + Page(arr) + }) + } } impl Deref for Page { @@ -61,6 +77,17 @@ impl DerefMut for Page { } } +impl TryFrom> for Page { + type Error = String; + + fn try_from(data: Vec) -> Result { + let arr: [u8; 4096] = data + .try_into() + .map_err(|v: Vec| format!("Expected 4096 bytes, got {}", v.len()))?; + Ok(Page(arr)) + } +} + #[inline(always)] const fn idx(addr: PhysAddr) -> (usize, usize) { ((addr >> 12) as usize, (addr & 0xFFF) as usize) diff --git a/dsa-emu/src/memory/ram/prealloc.rs b/dsa/emulator/src/memory/ram/prealloc.rs similarity index 100% rename from dsa-emu/src/memory/ram/prealloc.rs rename to dsa/emulator/src/memory/ram/prealloc.rs diff --git a/dsa-emu/src/memory/ram/stackarray.rs b/dsa/emulator/src/memory/ram/stackarray.rs similarity index 100% rename from dsa-emu/src/memory/ram/stackarray.rs rename to dsa/emulator/src/memory/ram/stackarray.rs diff --git a/dsa-emu/src/memory/tlb.rs b/dsa/emulator/src/memory/tlb.rs similarity index 100% rename from dsa-emu/src/memory/tlb.rs rename to dsa/emulator/src/memory/tlb.rs diff --git a/dsa-emu/src/processor/interrupts.rs b/dsa/emulator/src/processor/interrupts.rs similarity index 97% rename from dsa-emu/src/processor/interrupts.rs rename to dsa/emulator/src/processor/interrupts.rs index 1344e96..268be36 100644 --- a/dsa-emu/src/processor/interrupts.rs +++ b/dsa/emulator/src/processor/interrupts.rs @@ -1,5 +1,5 @@ #[repr(u8)] -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] pub enum Interrupt { // CPU exceptions 0-31 InvalidInterrupt = 0, diff --git a/dsa-emu/src/processor/mod.rs b/dsa/emulator/src/processor/mod.rs similarity index 62% rename from dsa-emu/src/processor/mod.rs rename to dsa/emulator/src/processor/mod.rs index 8f75033..4a4f492 100644 --- a/dsa-emu/src/processor/mod.rs +++ b/dsa/emulator/src/processor/mod.rs @@ -1,3 +1,3 @@ -mod interrupts; +pub mod interrupts; pub mod processor; pub mod state; diff --git a/dsa/emulator/src/processor/processor.rs b/dsa/emulator/src/processor/processor.rs new file mode 100644 index 0000000..93f402c --- /dev/null +++ b/dsa/emulator/src/processor/processor.rs @@ -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 { + internal_state: ProcessorSnapshot, + shared_state: Arc, + + // Interrupts + interrupts: mpsc::Receiver, + pending_fault: Option, + + // memory + mmu: MMU, + mainstore: Mem, + + // IO + mmio: Vec, + mmio_region: Option<(u32, u32)>, // start end end of segment + + // optionals - not necessarily set on boot. + memory_map: Option, + + // Config params + __mmap_configured: bool, + __io_configured: bool, +} + +unsafe impl Send for Emulator {} +impl Emulator { + pub fn new(mem: Mem) -> Self { + let (sender, receiver) = mpsc::channel::(); + + 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 { + 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) -> Result { + 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, 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; +} diff --git a/dsa-emu/src/processor/state.rs b/dsa/emulator/src/processor/state.rs similarity index 100% rename from dsa-emu/src/processor/state.rs rename to dsa/emulator/src/processor/state.rs diff --git a/dsa/linker/Cargo.toml b/dsa/linker/Cargo.toml new file mode 100644 index 0000000..06626eb --- /dev/null +++ b/dsa/linker/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "linker" +edition.workspace = true +version.workspace = true +authors.workspace = true + +[dependencies] diff --git a/dsa/linker/src/lib.rs b/dsa/linker/src/lib.rs new file mode 100644 index 0000000..b93cf3f --- /dev/null +++ b/dsa/linker/src/lib.rs @@ -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); + } +} diff --git a/dsa_resources/bench.dsa b/dsa_resources/bench.dsa new file mode 100644 index 0000000..20d8dcd --- /dev/null +++ b/dsa_resources/bench.dsa @@ -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 diff --git a/dsa_resources/bf.dsa b/dsa_resources/bf.dsa new file mode 100644 index 0000000..598c6d2 --- /dev/null +++ b/dsa_resources/bf.dsa @@ -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 diff --git a/dsa_resources/out.dsb b/dsa_resources/out.dsb new file mode 100644 index 0000000..5128996 Binary files /dev/null and b/dsa_resources/out.dsb differ diff --git a/dsa_resources/print.dsa b/dsa_resources/print.dsa new file mode 100644 index 0000000..5e6816a --- /dev/null +++ b/dsa_resources/print.dsa @@ -0,0 +1,292 @@ +// lib: +// print.dsa + +// usage: +// +// include print "" +// +// 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 diff --git a/dsx/README.md b/dsx/README.md new file mode 100644 index 0000000..1bedcbb --- /dev/null +++ b/dsx/README.md @@ -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.