Compare commits
68 Commits
5c132b55e9
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 6b6b778ab0 | |||
| bbfa20befe | |||
| 36e6ec1006 | |||
| d3e8af85a6 | |||
| 3451a8227c | |||
| d1b4cc7b56 | |||
| 9861187fa6 | |||
| 07e755340e | |||
| ceb7f2f172 | |||
| 21fb6cbc8b | |||
| bbc9e0b9ff | |||
| 7fcfc031ef | |||
| 21a8479ce9 | |||
| 09fe12f516 | |||
| 0f0e844223 | |||
| 5a383956c9 | |||
| 6a0e5e63c1 | |||
| e5c5312566 | |||
| 9a9bef7dd7 | |||
| 8024af6b13 | |||
| 5c008bfc04 | |||
| b5d36b7969 | |||
| 970c1adcb0 | |||
| 6a3920895b | |||
| 67406a9c48 | |||
| 9f8e9ec380 | |||
| a64fcaa3b5 | |||
| 34034dd5db | |||
| 75e843f5f9 | |||
| 528b519ce9 | |||
| 6d9efb7eb8 | |||
| 44394b3d19 | |||
| 66c63ab63c | |||
| 09d9064372 | |||
| 3f789442c0 | |||
| 96c2cbf7ae | |||
| 8ed4845d58 | |||
| 36faa1e39c | |||
| 43bae12ea0 | |||
| 0c6a540a85 | |||
| 23392a55df | |||
| f38114dbd7 | |||
| c6da147d50 | |||
| 643a39c24a | |||
| 1b409cd14e | |||
| 976bd688b0 | |||
| 0ac363e203 | |||
| 7a22570a0f | |||
| 2b5eb96187 | |||
| be1b1b9fe6 | |||
| 5cbaf2dc66 | |||
| ae57cdc691 | |||
| bac68d7118 | |||
| 8cce960b29 | |||
| cb100e92ac | |||
| d0d3775b88 | |||
| 1ddda6614a | |||
| ff161a69e6 | |||
| e00103375d | |||
| 7177633477 | |||
| 48477bd8b1 | |||
| 24dcf5d5bd | |||
| 209e44ae64 | |||
| 5b2d6a1af0 | |||
| a2d4dec417 | |||
| 6c39a5eef2 | |||
| 944ed573c6 | |||
| 2e1c0a7dce |
2
.rust-analyzer.toml
Normal file
2
.rust-analyzer.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[diagnostics]
|
||||
disabled = ["inactive-code"]
|
||||
354
Cargo.lock
generated
354
Cargo.lock
generated
@@ -2,12 +2,229 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[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 = "anyhow"
|
||||
version = "1.0.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "cfg_aliases"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"crossbeam-deque",
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-queue",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[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-queue"
|
||||
version = "0.3.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115"
|
||||
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 = "goblin"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4db6758c546e6f81f265638c980e5e84dfbda80cfd8e89e02f83454c8e8124bd"
|
||||
dependencies = [
|
||||
"log",
|
||||
"plain",
|
||||
"scroll",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "int-enum"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e366a1634cccc76b4cfd3e7580de9b605e4d93f1edac48d786c1f867c0def495"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"proc-macro2-diagnostics",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.176"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
|
||||
[[package]]
|
||||
name = "memmap2"
|
||||
version = "0.9.8"
|
||||
@@ -17,9 +234,146 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.30.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||
|
||||
[[package]]
|
||||
name = "oneshot"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4ce411919553d3f9fa53a0880544cda985a112117a0444d5ff1e870a893d6ea"
|
||||
|
||||
[[package]]
|
||||
name = "plain"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2-diagnostics"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scroll"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1257cd4248b4132760d6524d6dda4e053bc648c9070b960929bf50cfb1e7add"
|
||||
dependencies = [
|
||||
"scroll_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scroll_derive"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed76efe62313ab6610570951494bdaa81568026e0318eaa55f167de70eeea67d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[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.111"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "trve"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"crossbeam",
|
||||
"goblin",
|
||||
"int-enum",
|
||||
"memmap2",
|
||||
"nix",
|
||||
"oneshot",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[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",
|
||||
]
|
||||
|
||||
@@ -4,4 +4,11 @@ version = "0.0.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.100"
|
||||
clap = { version = "4.5.53", features = ["derive"] }
|
||||
crossbeam = { version = "0.8.4", features = ["crossbeam-channel"] }
|
||||
goblin = "0.10.4"
|
||||
int-enum = "1.2.0"
|
||||
memmap2 = "0.9.8"
|
||||
nix = { version = "0.30.1", features = ["fs"] }
|
||||
oneshot = "0.1.11"
|
||||
|
||||
19
LICENSE
19
LICENSE
@@ -1,18 +1,9 @@
|
||||
MIT License
|
||||
Copyright 2025-2026 taitep
|
||||
|
||||
Copyright (c) 2025 taitep
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
associated documentation files (the "Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
|
||||
following conditions:
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial
|
||||
portions of the Software.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
||||
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
|
||||
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
18
README.md
18
README.md
@@ -4,21 +4,31 @@ taitep's RISC-V Emulator.
|
||||
The goal is to support at least RV64GC and be able to run Linux,
|
||||
potentially more. No plans for RV32I or RV32/64E.
|
||||
|
||||
Currently implemented RISC-V ISA: `RV64IM-Zalrsc`
|
||||
|
||||
## Current Use
|
||||
Currently, the emulator is nowhere near complete,
|
||||
its not even at rv64i, but it does work for a subset of it.
|
||||
|
||||
The emulator will load a raw binary image from a file specified as a CLI argument into RAM,
|
||||
The emulator will load a raw binary image or static ELF executable from a file specified as a CLI argument into RAM,
|
||||
which starts at 0x80000000 and is currently 16MiB,
|
||||
and start execution at the start of the image/ram.
|
||||
and start execution at the start of the image/ram or the ELF entry point.
|
||||
|
||||
There is also a debug out page starting at `0x00000000`-`0x00001000`.
|
||||
It also starts a gdb stub/server listening on localhost:1234. By giving the command line argument
|
||||
`--wait`, you can make execution wait until GDB is connected, allowing you to follow execution
|
||||
from the start.
|
||||
|
||||
There is also a debug out section at `0x00000000`-`0x00010000`.
|
||||
Anything written to it will be logged out in hex.
|
||||
|
||||
There is also a UART at `0x00001000`-`0x00002000`, the interface is quite simple:
|
||||
There is also a UART at `0x00010000`-`0x00010002`, the interface is quite simple:
|
||||
- byte `0`: Data. When written, writes out the character
|
||||
When read, reads a character from the buffer, or 0 if empty.
|
||||
- byte `1`: Status. Read-only. Least significant bit is `TX_READY` and indicates whether
|
||||
the UART is ready to be written to. Currently always 1.
|
||||
Next least significant is `RX_READY`, indicates whether the read buffer
|
||||
has any data to read.
|
||||
|
||||
## Licensing
|
||||
This project is licensed under the BSD 2-Clause license.
|
||||
See the LICENSE file in the project root
|
||||
|
||||
18
echo.S
18
echo.S
@@ -1,18 +0,0 @@
|
||||
.section .text
|
||||
.globl _start
|
||||
|
||||
.equ UART_DATA, 0
|
||||
.equ UART_STATUS, 1
|
||||
.equ UART_RX_READY, 0b10
|
||||
.equ UART_TX_READY, 0b01
|
||||
|
||||
_start:
|
||||
li a0, 0x1000
|
||||
|
||||
loop:
|
||||
lbu t0, UART_STATUS(a0)
|
||||
andi t0, t0, UART_RX_READY
|
||||
beqz t0, loop
|
||||
lbu t0, UART_DATA(a0)
|
||||
sb t0, UART_DATA(a0)
|
||||
j loop
|
||||
36
link.ld
36
link.ld
@@ -1,22 +1,46 @@
|
||||
ENTRY(_start)
|
||||
|
||||
SECTIONS {
|
||||
. = 0x80000000;
|
||||
MEMORY {
|
||||
RAM (rwx) : ORIGIN = 0x80000000, LENGTH = 16M
|
||||
}
|
||||
|
||||
SECTIONS {
|
||||
.text : ALIGN(4) {
|
||||
*(.text*)
|
||||
}
|
||||
} > RAM
|
||||
|
||||
.rodata : ALIGN(8) {
|
||||
*(.rodata*)
|
||||
}
|
||||
} > RAM
|
||||
|
||||
.data : ALIGN(8) {
|
||||
_data_start = .;
|
||||
*(.data*)
|
||||
}
|
||||
_data_end = .;
|
||||
} > RAM
|
||||
|
||||
.bss : ALIGN(8) {
|
||||
_bss_start = .;
|
||||
*(.bss*)
|
||||
*(COMMON)
|
||||
}
|
||||
_bss_end = .;
|
||||
} > RAM
|
||||
|
||||
.sdata : ALIGN(8) {
|
||||
_sdata_start = .;
|
||||
*(.sdata*)
|
||||
_sdata_end = .;
|
||||
} > RAM
|
||||
|
||||
.sbss : ALIGN(8) {
|
||||
_sbss_start = .;
|
||||
*(.sbss*)
|
||||
_sbss_end = .;
|
||||
} > RAM
|
||||
|
||||
__global_pointer$ = _sdata_start + ((_sdata_end - _sdata_start + _sbss_end - _sbss_start) / 2);
|
||||
|
||||
_heap_start = ALIGN(8);
|
||||
|
||||
_stack_top = ORIGIN(RAM) + LENGTH(RAM);
|
||||
}
|
||||
|
||||
12
run-riscv-tests.sh
Executable file
12
run-riscv-tests.sh
Executable file
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
|
||||
for f in $(cat torun.txt); do
|
||||
result=$(cargo run $f 2>&1 | tail -n 1 | awk '{print $NF}')
|
||||
if [[ $result != 0 ]]; then
|
||||
testnum=$(( result >> 1 ))
|
||||
echo $f: test $testnum failed
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
echo all tests passed
|
||||
@@ -1,153 +0,0 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::io::{Read, stdin};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use trve::consts::{Byte, DWord, HWord, Word};
|
||||
use trve::mem::{MemAccessFault, MemDeviceInterface, PageNum};
|
||||
|
||||
/// byte 0: rx/tx
|
||||
/// byte 1: status (------rt, r=rxready, t=txready)/none
|
||||
pub struct BasicUart {
|
||||
buffers: Mutex<UartBuffers>,
|
||||
}
|
||||
|
||||
struct UartBuffers {
|
||||
rx: VecDeque<u8>,
|
||||
tx: VecDeque<u8>,
|
||||
}
|
||||
|
||||
impl BasicUart {
|
||||
pub fn new() -> Self {
|
||||
BasicUart {
|
||||
buffers: Mutex::new(UartBuffers {
|
||||
rx: VecDeque::new(),
|
||||
tx: VecDeque::new(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spawn_poller(self, poll_interval: Duration) -> Arc<Self> {
|
||||
let shared = Arc::new(self);
|
||||
|
||||
let uart_clone = shared.clone();
|
||||
|
||||
thread::spawn(move || {
|
||||
loop {
|
||||
uart_clone.poll();
|
||||
thread::sleep(poll_interval);
|
||||
}
|
||||
});
|
||||
|
||||
shared
|
||||
}
|
||||
|
||||
fn write(&self, byte: u8) {
|
||||
let mut bufs = self.buffers.lock().unwrap();
|
||||
bufs.tx.push_back(byte);
|
||||
}
|
||||
|
||||
fn read(&self) -> u8 {
|
||||
let mut bufs = self.buffers.lock().unwrap();
|
||||
bufs.rx.pop_front().unwrap_or(0)
|
||||
}
|
||||
|
||||
fn can_read(&self) -> bool {
|
||||
let bufs = self.buffers.lock().unwrap();
|
||||
!bufs.rx.is_empty()
|
||||
}
|
||||
|
||||
pub fn poll(&self) {
|
||||
let mut bufs = self.buffers.lock().unwrap();
|
||||
|
||||
while let Some(byte) = bufs.tx.pop_front() {
|
||||
print!("{}", byte as char);
|
||||
}
|
||||
|
||||
let mut buffer = [0u8; 1];
|
||||
if let Ok(n) = stdin().read(&mut buffer) {
|
||||
if n > 0 {
|
||||
bufs.rx.push_back(buffer[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MemDeviceInterface for BasicUart {
|
||||
fn write_dword(
|
||||
&self,
|
||||
_page: PageNum,
|
||||
_offset: u16,
|
||||
_value: DWord,
|
||||
) -> Result<(), MemAccessFault> {
|
||||
Err(MemAccessFault)
|
||||
}
|
||||
|
||||
fn write_word(&self, _page: PageNum, _offset: u16, _value: Word) -> Result<(), MemAccessFault> {
|
||||
Err(MemAccessFault)
|
||||
}
|
||||
|
||||
fn write_hword(
|
||||
&self,
|
||||
_page: PageNum,
|
||||
_offset: u16,
|
||||
_value: HWord,
|
||||
) -> Result<(), MemAccessFault> {
|
||||
Err(MemAccessFault)
|
||||
}
|
||||
|
||||
fn write_byte(&self, page: PageNum, offset: u16, value: Byte) -> Result<(), MemAccessFault> {
|
||||
if page > 0 {
|
||||
return Err(MemAccessFault);
|
||||
}
|
||||
|
||||
match offset {
|
||||
0 => {
|
||||
self.write(value);
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(MemAccessFault),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_dword(&self, _page: PageNum, _offset: u16) -> Result<DWord, MemAccessFault> {
|
||||
Err(MemAccessFault)
|
||||
}
|
||||
|
||||
fn read_word(&self, _page: PageNum, _offset: u16) -> Result<Word, MemAccessFault> {
|
||||
Err(MemAccessFault)
|
||||
}
|
||||
|
||||
fn read_hword(&self, _page: PageNum, _offset: u16) -> Result<HWord, MemAccessFault> {
|
||||
Err(MemAccessFault)
|
||||
}
|
||||
|
||||
fn read_byte(&self, page: PageNum, offset: u16) -> Result<Byte, MemAccessFault> {
|
||||
if page > 0 {
|
||||
return Err(MemAccessFault);
|
||||
}
|
||||
|
||||
match offset {
|
||||
0 => Ok(self.read()),
|
||||
1 => Ok(1 | (self.can_read() as u8) << 1),
|
||||
_ => Err(MemAccessFault),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_atomic_word(
|
||||
&self,
|
||||
_page: PageNum,
|
||||
_offset: u16,
|
||||
) -> Result<&std::sync::atomic::AtomicU32, MemAccessFault> {
|
||||
Err(MemAccessFault)
|
||||
}
|
||||
|
||||
fn get_atomic_dword(
|
||||
&self,
|
||||
_page: PageNum,
|
||||
_offset: u16,
|
||||
) -> Result<&std::sync::atomic::AtomicU64, MemAccessFault> {
|
||||
Err(MemAccessFault)
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
pub type Byte = u8;
|
||||
pub type HWord = u16;
|
||||
pub type Word = u32;
|
||||
pub type DWord = u64;
|
||||
|
||||
pub type RegValue = DWord;
|
||||
pub type Addr = DWord;
|
||||
|
||||
pub type RegId = u8;
|
||||
191
src/core.rs
191
src/core.rs
@@ -1,92 +1,197 @@
|
||||
// Copyright (c) 2025 taitep
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2025-2026 taitep
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
//
|
||||
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
||||
// See LICENSE file in the project root for full license text.
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::{
|
||||
consts::{Addr, RegId, RegValue},
|
||||
core::commands::CoreCmd,
|
||||
decode::Instruction,
|
||||
exceptions::{Exception, ExceptionType, MemoryException},
|
||||
gdb::{self, DebugCommand, DebugStream, StopReason},
|
||||
instructions::find_and_exec,
|
||||
mem::MemConfig,
|
||||
};
|
||||
|
||||
// placeholder - change when exception system is in place
|
||||
pub(crate) type Exception = ();
|
||||
|
||||
pub(crate) enum InstructionResult {
|
||||
Normal,
|
||||
Exception(Exception),
|
||||
Pause,
|
||||
}
|
||||
|
||||
pub struct Core {
|
||||
pub(crate) x_regs: [RegValue; 32],
|
||||
pub(crate) pc: Addr,
|
||||
pub(crate) x_regs: [u64; 32],
|
||||
pub(crate) pc: u64,
|
||||
pub(crate) mem: MemConfig,
|
||||
command_stream: crossbeam::channel::Receiver<CoreCmd>,
|
||||
// LR/SC reservation set. Pair of the RAM version block index and expected version.
|
||||
pub(crate) reservation: Option<(usize, u32)>,
|
||||
}
|
||||
|
||||
pub mod commands;
|
||||
|
||||
impl Core {
|
||||
pub fn new(mem: MemConfig) -> Self {
|
||||
pub fn new(mem: MemConfig, command_stream: crossbeam::channel::Receiver<CoreCmd>) -> Self {
|
||||
Self {
|
||||
x_regs: [0; 32],
|
||||
pc: 0,
|
||||
mem,
|
||||
command_stream,
|
||||
reservation: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(&mut self) {
|
||||
loop {
|
||||
let page = (self.pc / 4096) as usize;
|
||||
let offset = (self.pc / 4) as u16;
|
||||
if !self.pc.is_multiple_of(4) {
|
||||
//replace eprint with logging, replace break with exception
|
||||
eprintln!("PC not aligned");
|
||||
break;
|
||||
if let Ok(cmd) = self.command_stream.try_recv() {
|
||||
self.handle_command(cmd);
|
||||
}
|
||||
|
||||
let instr = match self.mem.read_word(page, offset) {
|
||||
Ok(i) => i,
|
||||
Err(_) => {
|
||||
eprintln!("Memory access fault while fetching instruction");
|
||||
if let Err(e) = self.step() {
|
||||
self.throw_exception(e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn step(&mut self) -> Result<(), Exception> {
|
||||
if !self.pc.is_multiple_of(4) {
|
||||
self.throw_exception(Exception {
|
||||
type_: ExceptionType::InstructionAddressMisaligned,
|
||||
value: self.pc,
|
||||
});
|
||||
}
|
||||
|
||||
let instr = match self.mem.read_word(self.pc) {
|
||||
Ok(i) => i,
|
||||
Err(e) => {
|
||||
return Err(e.into_exception_instr());
|
||||
}
|
||||
};
|
||||
|
||||
assert_eq!(instr & 3, 3, "Compressed instructions not supported");
|
||||
if instr & 3 != 3 {
|
||||
// Compressed instruction - (currently) unsupported
|
||||
// Could also be zero instruction, that also matches this
|
||||
return Err(Exception {
|
||||
type_: ExceptionType::IllegalInstruction,
|
||||
value: instr as u64,
|
||||
});
|
||||
}
|
||||
|
||||
let instr = Instruction(instr);
|
||||
|
||||
let res = find_and_exec(instr, self);
|
||||
if let Err(e) = find_and_exec(instr, self) {
|
||||
dbg!(instr);
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
if let Some(res) = res {
|
||||
match res {
|
||||
InstructionResult::Normal => {}
|
||||
InstructionResult::Exception(_e) => {
|
||||
eprintln!("Exception from instruction");
|
||||
break;
|
||||
}
|
||||
InstructionResult::Pause => {
|
||||
eprintln!("Instruction asked for pause");
|
||||
break;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_waiting_for_cmd(&mut self) {
|
||||
eprintln!("Waiting for any core command...");
|
||||
if let Ok(cmd) = self.command_stream.recv() {
|
||||
eprintln!("Recieved a command");
|
||||
self.handle_command(cmd);
|
||||
} else {
|
||||
eprintln!("Invalid Instruction 0x{:08x} 0b{:032b}", instr.0, instr.0);
|
||||
break;
|
||||
eprintln!("Error recieving command, starting anyway");
|
||||
}
|
||||
|
||||
eprintln!("Command processed");
|
||||
|
||||
self.run();
|
||||
}
|
||||
|
||||
fn handle_command(&mut self, cmd: CoreCmd) {
|
||||
match cmd {
|
||||
CoreCmd::EnterDbgMode(DebugStream(dbg_stream)) => {
|
||||
let _ = self.debug_loop(dbg_stream);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn debug_loop(
|
||||
&mut self,
|
||||
dbg_stream: crossbeam::channel::Receiver<gdb::DebugCommand>,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut breakpoints = HashSet::new();
|
||||
|
||||
loop {
|
||||
match dbg_stream.recv()? {
|
||||
DebugCommand::GetRegs(sender) => sender.send(gdb::RegsResponse {
|
||||
x_regs: self.x_regs,
|
||||
pc: self.pc,
|
||||
})?,
|
||||
DebugCommand::ReadMem {
|
||||
addr,
|
||||
len,
|
||||
responder,
|
||||
} => {
|
||||
let data = (0..len)
|
||||
.map(|offset| self.mem.read_byte(addr + offset))
|
||||
.collect::<Result<_, MemoryException>>()
|
||||
.map_err(Into::into);
|
||||
|
||||
responder.send(data)?;
|
||||
}
|
||||
DebugCommand::SetBreakpoint(addr) => {
|
||||
breakpoints.insert(addr);
|
||||
}
|
||||
DebugCommand::RemoveBreakpoint(addr) => {
|
||||
breakpoints.remove(&addr);
|
||||
}
|
||||
DebugCommand::Step(responder) => {
|
||||
responder.send(match self.step() {
|
||||
Ok(_) => gdb::StopReason::Step,
|
||||
Err(e) => {
|
||||
self.throw_exception(e);
|
||||
gdb::StopReason::Exception(e.into())
|
||||
}
|
||||
})?;
|
||||
}
|
||||
DebugCommand::Continue(responder, stopper) => {
|
||||
responder.send(self.continue_loop(&breakpoints, stopper))?;
|
||||
}
|
||||
DebugCommand::ExitDebugMode => {
|
||||
eprintln!("exitdbgmode");
|
||||
break Ok(());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn continue_loop(
|
||||
&mut self,
|
||||
breakpoints: &HashSet<u64>,
|
||||
stopper: oneshot::Receiver<()>,
|
||||
) -> StopReason {
|
||||
loop {
|
||||
if breakpoints.contains(&self.pc) {
|
||||
return StopReason::Exception(ExceptionType::Breakpoint);
|
||||
}
|
||||
|
||||
if stopper.try_recv().is_ok() {
|
||||
return StopReason::Interrupted;
|
||||
}
|
||||
|
||||
if let Err(e) = self.step() {
|
||||
self.throw_exception(e);
|
||||
return StopReason::Exception(e.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(&mut self, pc: Addr) {
|
||||
fn throw_exception(&mut self, exception: Exception) {
|
||||
eprintln!("Exception: {exception:?}");
|
||||
dbg!(self.pc, self.x_regs);
|
||||
dbg!(self.x_regs[10]);
|
||||
}
|
||||
|
||||
pub fn reset(&mut self, pc: u64) {
|
||||
self.pc = pc;
|
||||
}
|
||||
|
||||
pub(crate) fn reg_read(&self, id: RegId) -> RegValue {
|
||||
pub(crate) fn reg_read(&self, id: u8) -> u64 {
|
||||
self.x_regs[id as usize]
|
||||
}
|
||||
|
||||
pub(crate) fn reg_write(&mut self, id: RegId, value: RegValue) {
|
||||
pub(crate) fn reg_write(&mut self, id: u8, value: u64) {
|
||||
if id == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
11
src/core/commands.rs
Normal file
11
src/core/commands.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
// Copyright (c) 2025 taitep
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
//
|
||||
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
||||
// See LICENSE file in the project root for full license text.
|
||||
|
||||
use crate::gdb;
|
||||
|
||||
pub enum CoreCmd {
|
||||
EnterDbgMode(gdb::DebugStream),
|
||||
}
|
||||
@@ -1,15 +1,21 @@
|
||||
// Copyright (c) 2025 taitep
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
//
|
||||
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
||||
// See LICENSE file in the project root for full license text.
|
||||
|
||||
use crate::consts::{DWord, RegId, Word};
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
const MASK_REGISTER: Word = 0x1f;
|
||||
const MASK_REGISTER: u32 = 0x1f;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Instruction(pub Word);
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Instruction(pub u32);
|
||||
|
||||
impl std::fmt::Debug for Instruction {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_fmt(format_args!("{:08x}", self.0))
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl Instruction {
|
||||
@@ -26,8 +32,8 @@ impl Instruction {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn rd(self) -> RegId {
|
||||
(self.0 >> 7 & MASK_REGISTER) as RegId
|
||||
pub fn rd(self) -> u8 {
|
||||
(self.0 >> 7 & MASK_REGISTER) as u8
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -36,64 +42,86 @@ impl Instruction {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn rs1(self) -> RegId {
|
||||
(self.0 >> 15 & MASK_REGISTER) as RegId
|
||||
pub fn rs1(self) -> u8 {
|
||||
(self.0 >> 15 & MASK_REGISTER) as u8
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn rs2(self) -> RegId {
|
||||
(self.0 >> 20 & MASK_REGISTER) as RegId
|
||||
pub fn rs2(self) -> u8 {
|
||||
(self.0 >> 20 & MASK_REGISTER) as u8
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn funct7(self) -> u8 {
|
||||
(self.0 >> 25 & 0x7f) as u8
|
||||
(self.0 >> 25) as u8
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn imm_i(self) -> DWord {
|
||||
(self.0 as i32 as i64 >> 20) as DWord
|
||||
pub fn funct5(self) -> u8 {
|
||||
(self.0 >> 27) as u8
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn imm_s(self) -> DWord {
|
||||
(self.0 as i32 as i64 >> (25 - 5) & (0x7f << 5)) as DWord | (self.0 >> 7 & 0b1111) as DWord
|
||||
pub fn imm_i(self) -> u64 {
|
||||
(self.0 as i32 as i64 >> 20) as u64
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn imm_b(self) -> DWord {
|
||||
let imm_12 = ((self.0 & 0x8000_0000) as i32 as i64 >> (31 - 12)) as DWord;
|
||||
let imm_10_5 = ((self.0 >> 25 & 0x3f) << 5) as DWord;
|
||||
let imm_4_1 = ((self.0 >> 8 & 0xf) << 1) as DWord;
|
||||
let imm_11 = ((self.0 >> 7 & 1) << 11) as DWord;
|
||||
pub fn imm_s(self) -> u64 {
|
||||
let imm_11_5 = (self.0 as i32 as i64 >> 25 << 5) as u64;
|
||||
let imm_4_0 = (self.0 >> 7 & 0x1f) as u64;
|
||||
imm_11_5 | imm_4_0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn imm_b(self) -> u64 {
|
||||
let imm_12 = ((self.0 & 0x8000_0000) as i32 as i64 >> (31 - 12)) as u64;
|
||||
let imm_10_5 = ((self.0 >> 25 & 0x3f) << 5) as u64;
|
||||
let imm_4_1 = ((self.0 >> 8 & 0xf) << 1) as u64;
|
||||
let imm_11 = ((self.0 >> 7 & 1) << 11) as u64;
|
||||
|
||||
imm_12 | imm_10_5 | imm_4_1 | imm_11
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn imm_u(self) -> DWord {
|
||||
(self.0 & 0xffff_f000) as i32 as i64 as DWord
|
||||
pub fn imm_u(self) -> u64 {
|
||||
(self.0 & 0xffff_f000) as i32 as i64 as u64
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn imm_j(self) -> DWord {
|
||||
let imm_20 = ((self.0 & 0x8000_0000) as i32 as i64 >> (31 - 20)) as DWord;
|
||||
let imm_10_1 = ((self.0 >> 21 & 0x3ff) << 1) as DWord;
|
||||
let imm_11 = ((self.0 >> 20 & 1) << 11) as DWord;
|
||||
let imm_19_12 = ((self.0 >> 12 & 0xff) << 12) as DWord;
|
||||
pub fn imm_j(self) -> u64 {
|
||||
let imm_20 = ((self.0 & 0x8000_0000) as i32 as i64 >> (31 - 20)) as u64;
|
||||
let imm_10_1 = ((self.0 >> 21 & 0x3ff) << 1) as u64;
|
||||
let imm_11 = ((self.0 >> 20 & 1) << 11) as u64;
|
||||
let imm_19_12 = ((self.0 >> 12 & 0xff) << 12) as u64;
|
||||
|
||||
imm_20 | imm_10_1 | imm_11 | imm_19_12
|
||||
}
|
||||
|
||||
// The following are AFAIK only used for shift by immediate operations
|
||||
|
||||
/// Technically part of immediate. Only used to determine shift type for immediate shifts afaik
|
||||
/// 32bit ones use funct7 in this way
|
||||
#[inline]
|
||||
pub fn funct6(self) -> u8 {
|
||||
(self.0 >> 26 & 0x3f) as u8
|
||||
(self.0 >> 26) as u8
|
||||
}
|
||||
|
||||
/// Mostly/only used for the SYSTEM opcode
|
||||
#[inline]
|
||||
pub fn imm_shamt(self) -> usize {
|
||||
(self.0 >> 20 & 0x3f) as usize
|
||||
pub fn funct12(self) -> u16 {
|
||||
(self.0 >> 20) as u16
|
||||
}
|
||||
|
||||
/// Looks at the aq/rl bits of atomic instructions and converts to an Ordering
|
||||
#[inline]
|
||||
pub fn amo_ordering(self) -> Ordering {
|
||||
let aq = self.0 >> 26 & 1 != 0;
|
||||
let rl = self.0 >> 25 & 1 != 0;
|
||||
|
||||
match (aq, rl) {
|
||||
(false, false) => Ordering::Relaxed,
|
||||
(false, true) => Ordering::Release,
|
||||
(true, false) => Ordering::Acquire,
|
||||
(true, true) => Ordering::AcqRel,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1
src/devices.rs
Normal file
1
src/devices.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod serial;
|
||||
140
src/devices/serial.rs
Normal file
140
src/devices/serial.rs
Normal file
@@ -0,0 +1,140 @@
|
||||
// Copyright (c) 2026 taitep
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
//
|
||||
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
||||
// See LICENSE file in the project root for full license text.
|
||||
|
||||
use std::{
|
||||
io::{Read, Write},
|
||||
sync::{Arc, Mutex},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
mod fifo;
|
||||
use fifo::UartFifo;
|
||||
|
||||
use crate::{exceptions::MemoryExceptionType, mem::MemDeviceInterface};
|
||||
|
||||
pub struct SifiveUart {
|
||||
rx: Mutex<(UartFifo<2048>, bool)>,
|
||||
tx: Mutex<(UartFifo<2048>, bool)>,
|
||||
}
|
||||
|
||||
impl SifiveUart {
|
||||
pub fn new_arc() -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
rx: Mutex::new((UartFifo::default(), false)),
|
||||
tx: Mutex::new((UartFifo::default(), false)),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn spawn_io_thread<R: Read + Send + 'static, T: Write + Send + Sync + 'static>(
|
||||
self: Arc<Self>,
|
||||
mut rx_backend: R,
|
||||
mut tx_backend: T,
|
||||
interval: Duration,
|
||||
) {
|
||||
std::thread::spawn(move || {
|
||||
loop {
|
||||
{
|
||||
// Read data
|
||||
let mut rx_guard = self.rx.lock().expect("could not lock uart RX half");
|
||||
let (rx_buf, rx_en) = &mut *rx_guard;
|
||||
|
||||
if *rx_en {
|
||||
let _ = rx_buf.read_from(&mut rx_backend);
|
||||
}
|
||||
}
|
||||
{
|
||||
// Write data
|
||||
let mut tx_guard = self.tx.lock().expect("could not lock uart RX half");
|
||||
let (tx_buf, tx_en) = &mut *tx_guard;
|
||||
|
||||
if *tx_en {
|
||||
let _ = tx_buf.write_to(&mut tx_backend);
|
||||
let _ = tx_backend.flush();
|
||||
}
|
||||
}
|
||||
|
||||
std::thread::sleep(interval);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl MemDeviceInterface for SifiveUart {
|
||||
fn write_word(&self, addr: u64, value: u32) -> Result<(), MemoryExceptionType> {
|
||||
// dbg!(addr, value);
|
||||
match addr {
|
||||
0x00 => {
|
||||
// TXDATA
|
||||
let (ref mut tx_buf, _) = *self.tx.lock().expect("could not lock uart TX half");
|
||||
tx_buf.push_single_byte(value as u8);
|
||||
Ok(())
|
||||
}
|
||||
0x08 => {
|
||||
// TXCTRL
|
||||
let (_, ref mut tx_en) = *self.tx.lock().expect("could not lock uart TX half");
|
||||
*tx_en = value & 1 != 0;
|
||||
Ok(())
|
||||
}
|
||||
0x04 => Ok(()), // RXDATA
|
||||
0x0c => {
|
||||
// RXCTRL
|
||||
let (_, ref mut rx_en) = *self.rx.lock().expect("could not lock uart RX half");
|
||||
*rx_en = value & 1 != 0;
|
||||
Ok(())
|
||||
}
|
||||
0x10 => Ok(()), // IE
|
||||
0x14 => Ok(()), // IP
|
||||
0x18 => Ok(()), // DIV
|
||||
_ => {
|
||||
if addr < 0x1c {
|
||||
Err(MemoryExceptionType::AddressMisaligned)
|
||||
} else {
|
||||
Err(MemoryExceptionType::AccessFault)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read_word(&self, addr: u64) -> Result<u32, MemoryExceptionType> {
|
||||
// dbg!(addr);
|
||||
match addr {
|
||||
0x00 => {
|
||||
// TXDATA
|
||||
let (ref tx_buf, _) = *self.tx.lock().expect("could not lock uart TX half");
|
||||
Ok(if tx_buf.is_full() { 0x80000000 } else { 0 })
|
||||
}
|
||||
0x08 => {
|
||||
// TXCTRL
|
||||
let (_, tx_en) = *self.tx.lock().expect("could not lock uart TX half");
|
||||
Ok(if tx_en { 1 } else { 0 })
|
||||
}
|
||||
0x04 => {
|
||||
// RXDATA
|
||||
let (ref mut rx_buf, _) = *self.rx.lock().expect("could not lock uart RX half");
|
||||
Ok(match rx_buf.pop_single_byte() {
|
||||
None => 0x80000000,
|
||||
Some(b) => b as u32,
|
||||
})
|
||||
}
|
||||
0x0c => {
|
||||
// RXCTRL
|
||||
let (_, rx_en) = *self.rx.lock().expect("could not lock uart RX half");
|
||||
Ok(if rx_en { 1 } else { 0 })
|
||||
}
|
||||
0x10 => Ok(0), // IE
|
||||
0x14 => Ok(0), // IP
|
||||
0x18 => Ok(1), // DIV
|
||||
|
||||
_ => {
|
||||
if addr < 0x1c {
|
||||
Err(MemoryExceptionType::AddressMisaligned)
|
||||
} else {
|
||||
Err(MemoryExceptionType::AccessFault)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
113
src/devices/serial/fifo.rs
Normal file
113
src/devices/serial/fifo.rs
Normal file
@@ -0,0 +1,113 @@
|
||||
// Copyright (c) 2026 taitep
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
//
|
||||
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
||||
// See LICENSE file in the project root for full license text.
|
||||
|
||||
use std::io::{self, Read, Write};
|
||||
|
||||
pub struct UartFifo<const CAP: usize> {
|
||||
buf: [u8; CAP],
|
||||
head: usize,
|
||||
tail: usize,
|
||||
len: usize,
|
||||
}
|
||||
|
||||
impl<const CAP: usize> UartFifo<CAP> {
|
||||
pub fn pop_single_byte(&mut self) -> Option<u8> {
|
||||
if self.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let value = self.buf[self.tail];
|
||||
self.advance_read(1);
|
||||
Some(value)
|
||||
}
|
||||
|
||||
pub fn push_single_byte(&mut self, value: u8) -> bool {
|
||||
if self.is_full() {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.buf[self.head] = value;
|
||||
self.advance_write(1);
|
||||
true
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len == 0
|
||||
}
|
||||
|
||||
pub fn is_full(&self) -> bool {
|
||||
self.len == CAP
|
||||
}
|
||||
|
||||
fn write_slice(&mut self) -> &mut [u8] {
|
||||
if self.is_full() {
|
||||
return &mut [];
|
||||
}
|
||||
|
||||
if self.head >= self.tail {
|
||||
&mut self.buf[self.head..]
|
||||
} else {
|
||||
&mut self.buf[self.head..self.tail]
|
||||
}
|
||||
}
|
||||
|
||||
fn advance_write(&mut self, n: usize) {
|
||||
debug_assert!(n <= CAP - self.len);
|
||||
self.head = (self.head + n) % CAP;
|
||||
self.len += n;
|
||||
}
|
||||
|
||||
fn read_slice(&self) -> &[u8] {
|
||||
if self.is_empty() {
|
||||
return &[];
|
||||
}
|
||||
|
||||
if self.tail < self.head {
|
||||
&self.buf[self.tail..self.head]
|
||||
} else {
|
||||
&self.buf[self.tail..]
|
||||
}
|
||||
}
|
||||
|
||||
fn advance_read(&mut self, n: usize) {
|
||||
debug_assert!(n <= self.len);
|
||||
self.tail = (self.tail + n) % CAP;
|
||||
self.len -= n;
|
||||
}
|
||||
|
||||
pub fn read_from<R: Read>(&mut self, reader: &mut R) -> io::Result<usize> {
|
||||
let slice = self.write_slice();
|
||||
if slice.is_empty() {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let n = reader.read(slice)?;
|
||||
self.advance_write(n);
|
||||
Ok(n)
|
||||
}
|
||||
|
||||
pub fn write_to<W: Write>(&mut self, writer: &mut W) -> io::Result<usize> {
|
||||
let slice = self.read_slice();
|
||||
if slice.is_empty() {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let n = writer.write(slice)?;
|
||||
self.advance_read(n);
|
||||
Ok(n)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const SIZE: usize> Default for UartFifo<SIZE> {
|
||||
fn default() -> Self {
|
||||
UartFifo {
|
||||
buf: [0; SIZE],
|
||||
head: 0,
|
||||
tail: 0,
|
||||
len: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
111
src/exceptions.rs
Normal file
111
src/exceptions.rs
Normal file
@@ -0,0 +1,111 @@
|
||||
// Copyright (c) 2025 taitep
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
//
|
||||
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
||||
// See LICENSE file in the project root for full license text.
|
||||
|
||||
use int_enum::IntEnum;
|
||||
|
||||
#[repr(u8)]
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, IntEnum)]
|
||||
pub enum ExceptionType {
|
||||
InstructionAddressMisaligned = 0,
|
||||
InstructionAccessFault = 1,
|
||||
IllegalInstruction = 2,
|
||||
Breakpoint = 3,
|
||||
LoadAddressMisaligned = 4,
|
||||
LoadAccessFault = 5,
|
||||
StoreAmoAddressMisaligned = 6,
|
||||
StoreAmoAccessFault = 7,
|
||||
EnvironmentCallFromUMode = 8,
|
||||
EnvironmentCallFromSMode = 9,
|
||||
EnvironmentCallFromMMode = 11,
|
||||
InstructionPageFault = 12,
|
||||
LoadPageFault = 13,
|
||||
StoreAmoPageFault = 15,
|
||||
DoubleTrap = 16,
|
||||
SoftwareCheck = 18,
|
||||
HardwareError = 19,
|
||||
}
|
||||
|
||||
impl ExceptionType {
|
||||
pub fn with_no_value(self) -> Exception {
|
||||
Exception {
|
||||
type_: self,
|
||||
value: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Exception {
|
||||
pub type_: ExceptionType,
|
||||
pub value: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum MemoryExceptionType {
|
||||
AddressMisaligned,
|
||||
AccessFault,
|
||||
PageFault,
|
||||
}
|
||||
|
||||
impl MemoryExceptionType {
|
||||
pub(crate) fn with_addr(self, addr: u64) -> MemoryException {
|
||||
MemoryException { type_: self, addr }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct MemoryException {
|
||||
pub type_: MemoryExceptionType,
|
||||
pub addr: u64,
|
||||
}
|
||||
|
||||
impl MemoryException {
|
||||
pub(crate) fn into_exception_store(self) -> Exception {
|
||||
Exception {
|
||||
type_: match self.type_ {
|
||||
MemoryExceptionType::AddressMisaligned => ExceptionType::StoreAmoAddressMisaligned,
|
||||
MemoryExceptionType::AccessFault => ExceptionType::StoreAmoAccessFault,
|
||||
MemoryExceptionType::PageFault => ExceptionType::StoreAmoPageFault,
|
||||
},
|
||||
value: self.addr,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn into_exception_instr(self) -> Exception {
|
||||
Exception {
|
||||
type_: match self.type_ {
|
||||
MemoryExceptionType::AddressMisaligned => ExceptionType::InstructionAccessFault,
|
||||
MemoryExceptionType::AccessFault => ExceptionType::InstructionAccessFault,
|
||||
MemoryExceptionType::PageFault => ExceptionType::InstructionPageFault,
|
||||
},
|
||||
value: self.addr,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn into_exception_load(self) -> Exception {
|
||||
Exception {
|
||||
type_: match self.type_ {
|
||||
MemoryExceptionType::AddressMisaligned => ExceptionType::LoadAddressMisaligned,
|
||||
MemoryExceptionType::AccessFault => ExceptionType::LoadAccessFault,
|
||||
MemoryExceptionType::PageFault => ExceptionType::LoadPageFault,
|
||||
},
|
||||
value: self.addr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MemoryException> for MemoryExceptionType {
|
||||
fn from(val: MemoryException) -> Self {
|
||||
val.type_
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Exception> for ExceptionType {
|
||||
fn from(val: Exception) -> Self {
|
||||
val.type_
|
||||
}
|
||||
}
|
||||
67
src/execload.rs
Normal file
67
src/execload.rs
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright (c) 2025 taitep
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
//
|
||||
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
||||
// See LICENSE file in the project root for full license text.
|
||||
|
||||
use std::{fs, path::Path};
|
||||
|
||||
use anyhow::{Result, bail};
|
||||
use goblin::{
|
||||
Object,
|
||||
elf::{
|
||||
header::{EM_RISCV, ET_EXEC},
|
||||
program_header::PT_LOAD,
|
||||
},
|
||||
};
|
||||
use trve::mem::RAM_START;
|
||||
|
||||
pub fn load<P: AsRef<Path>>(path: P, ram: &mut [u8]) -> Result<u64> {
|
||||
let buf = fs::read(path)?;
|
||||
|
||||
match Object::parse(&buf)? {
|
||||
Object::Elf(elf) => {
|
||||
if elf.header.e_type != ET_EXEC {
|
||||
bail!("Executable type incorrect, may not be an executable or may be dynamic");
|
||||
}
|
||||
if elf.header.e_machine != EM_RISCV {
|
||||
bail!("Executable architecture is not RISC-V");
|
||||
}
|
||||
if !elf.is_64 {
|
||||
bail!("Executable is not 64-bit");
|
||||
}
|
||||
if !elf.little_endian {
|
||||
bail!("Executable is not little-endian");
|
||||
}
|
||||
|
||||
for ph in elf.program_headers {
|
||||
if ph.p_type == PT_LOAD {
|
||||
let start = (ph.p_vaddr - RAM_START) as usize;
|
||||
let end = start + ph.p_memsz as usize;
|
||||
let file_end = start + ph.p_filesz as usize;
|
||||
|
||||
if end > RAM_START as usize + ram.len() {
|
||||
bail!("Segment at 0x{:x} does not fit in RAM", ph.p_vaddr);
|
||||
}
|
||||
|
||||
ram[start..file_end].copy_from_slice(
|
||||
&buf[ph.p_offset as usize..(ph.p_offset + ph.p_filesz) as usize],
|
||||
);
|
||||
|
||||
ram[file_end..end].fill(0);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(elf.entry)
|
||||
}
|
||||
Object::Unknown(_) => {
|
||||
eprintln!("Unrecognized executable format identifier, assuming raw binary");
|
||||
if buf.len() > ram.len() {
|
||||
bail!("Program too large for RAM");
|
||||
}
|
||||
ram[..buf.len()].copy_from_slice(&buf);
|
||||
Ok(RAM_START)
|
||||
}
|
||||
_ => bail!("Unsupported executable format"),
|
||||
}
|
||||
}
|
||||
320
src/gdb.rs
Normal file
320
src/gdb.rs
Normal file
@@ -0,0 +1,320 @@
|
||||
// Copyright (c) 2025-2026 taitep
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
//
|
||||
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
||||
// See LICENSE file in the project root for full license text.
|
||||
|
||||
use std::{
|
||||
io::{self, BufRead, ErrorKind, Write},
|
||||
net::{TcpListener, TcpStream},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
core::commands::CoreCmd,
|
||||
exceptions::{ExceptionType, MemoryExceptionType},
|
||||
};
|
||||
|
||||
pub(crate) enum DebugCommand {
|
||||
GetRegs(oneshot::Sender<RegsResponse>),
|
||||
ReadMem {
|
||||
addr: u64,
|
||||
len: u64,
|
||||
responder: oneshot::Sender<Result<Vec<u8>, MemoryExceptionType>>,
|
||||
},
|
||||
Step(oneshot::Sender<StopReason>),
|
||||
Continue(oneshot::Sender<StopReason>, oneshot::Receiver<()>),
|
||||
SetBreakpoint(u64),
|
||||
RemoveBreakpoint(u64),
|
||||
ExitDebugMode,
|
||||
}
|
||||
|
||||
pub struct DebugStream(pub(crate) crossbeam::channel::Receiver<DebugCommand>);
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(crate) enum StopReason {
|
||||
Exception(ExceptionType),
|
||||
Step,
|
||||
Interrupted,
|
||||
}
|
||||
|
||||
impl StopReason {
|
||||
fn to_rsp(self) -> &'static str {
|
||||
match self {
|
||||
StopReason::Step => "S05",
|
||||
StopReason::Exception(e) => {
|
||||
use ExceptionType::*;
|
||||
match e {
|
||||
IllegalInstruction => "S04",
|
||||
InstructionAddressMisaligned
|
||||
| InstructionAccessFault
|
||||
| InstructionPageFault
|
||||
| LoadAddressMisaligned
|
||||
| LoadAccessFault
|
||||
| LoadPageFault
|
||||
| StoreAmoAddressMisaligned
|
||||
| StoreAmoAccessFault
|
||||
| StoreAmoPageFault => "S0b",
|
||||
_ => "S05",
|
||||
}
|
||||
}
|
||||
StopReason::Interrupted => "S02",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct RegsResponse {
|
||||
pub x_regs: [u64; 32],
|
||||
pub pc: u64,
|
||||
}
|
||||
|
||||
pub fn run_stub(cmd_sender: crossbeam::channel::Sender<CoreCmd>) {
|
||||
std::thread::spawn(move || {
|
||||
let listener = TcpListener::bind("127.0.0.1:1234").expect("couldnt start tcp listener");
|
||||
|
||||
for stream in listener.incoming().flatten() {
|
||||
let (dbg_tx, dbg_rx) = crossbeam::channel::bounded(16);
|
||||
|
||||
stream
|
||||
.set_nonblocking(true)
|
||||
.expect("Couldnt set TCP stream to nonblocking");
|
||||
|
||||
cmd_sender
|
||||
.send(CoreCmd::EnterDbgMode(DebugStream(dbg_rx)))
|
||||
.expect("couldnt ask core to enter debug mode");
|
||||
|
||||
handle_gdb_connection(stream, dbg_tx).expect("failure during connection");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn handle_gdb_connection(
|
||||
gdb_stream: TcpStream,
|
||||
dbg_tx: crossbeam::channel::Sender<DebugCommand>,
|
||||
) -> io::Result<()> {
|
||||
eprintln!("gdb connected");
|
||||
let mut reader = io::BufReader::new(gdb_stream.try_clone()?);
|
||||
let mut writer = gdb_stream;
|
||||
|
||||
loop {
|
||||
match read_rsp_packet(&mut reader) {
|
||||
Ok(packet) => {
|
||||
if handle_packet(
|
||||
&packet[..packet.len() - 1],
|
||||
&mut writer,
|
||||
&dbg_tx,
|
||||
&mut reader,
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
let _ = dbg_tx.send(DebugCommand::ExitDebugMode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(ref e) if e.kind() == ErrorKind::WouldBlock => {
|
||||
std::thread::yield_now();
|
||||
}
|
||||
Err(_) => {
|
||||
let _ = dbg_tx.send(DebugCommand::ExitDebugMode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_rsp_packet<R: BufRead>(reader: &mut R) -> io::Result<String> {
|
||||
let mut buf = Vec::new();
|
||||
|
||||
// Wait for leading '$'
|
||||
loop {
|
||||
let mut byte = [0u8];
|
||||
let n = reader.read(&mut byte)?;
|
||||
if n == 0 {
|
||||
return Err(io::Error::new(ErrorKind::UnexpectedEof, "Disconnected"));
|
||||
}
|
||||
if byte[0] == b'$' {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Read until '#'
|
||||
reader.read_until(b'#', &mut buf)?;
|
||||
|
||||
let mut checksum = [0u8; 2];
|
||||
reader.read_exact(&mut checksum)?;
|
||||
|
||||
String::from_utf8(buf).map_err(|e| io::Error::new(ErrorKind::InvalidData, e))
|
||||
}
|
||||
|
||||
fn handle_packet<W: Write, R: BufRead>(
|
||||
packet: &str,
|
||||
writer: &mut W,
|
||||
dbg_tx: &crossbeam::channel::Sender<DebugCommand>,
|
||||
reader: &mut R,
|
||||
) -> io::Result<()> {
|
||||
writer.write_all(b"+")?;
|
||||
if packet.is_empty() {
|
||||
send_packet("", writer)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
match &packet[0..1] {
|
||||
"?" => {
|
||||
send_packet("S05", writer)?;
|
||||
}
|
||||
|
||||
"g" => {
|
||||
let (regs_tx, regs_rx) = oneshot::channel();
|
||||
dbg_tx.send(DebugCommand::GetRegs(regs_tx)).unwrap();
|
||||
let regs = regs_rx.recv().unwrap();
|
||||
let mut hex = String::with_capacity(32 * 8 * 2 + 8 * 2);
|
||||
for ® in ®s.x_regs {
|
||||
hex.push_str(
|
||||
®.to_le_bytes()
|
||||
.iter()
|
||||
.map(|b| format!("{b:02x}"))
|
||||
.collect::<String>(),
|
||||
);
|
||||
}
|
||||
hex.push_str(
|
||||
®s
|
||||
.pc
|
||||
.to_le_bytes()
|
||||
.iter()
|
||||
.map(|b| format!("{b:02x}"))
|
||||
.collect::<String>(),
|
||||
);
|
||||
send_packet(&hex, writer)?;
|
||||
}
|
||||
|
||||
"m" => {
|
||||
if let Some((addr_str, len_str)) = packet[1..].split_once(',') {
|
||||
if let (Ok(addr), Ok(len)) = (
|
||||
u64::from_str_radix(addr_str, 16),
|
||||
u64::from_str_radix(len_str, 16),
|
||||
) {
|
||||
let (responder, response) = oneshot::channel();
|
||||
dbg_tx
|
||||
.send(DebugCommand::ReadMem {
|
||||
addr,
|
||||
len,
|
||||
responder,
|
||||
})
|
||||
.unwrap();
|
||||
let response = response.recv().unwrap();
|
||||
|
||||
match response {
|
||||
Ok(data) => {
|
||||
let hex: String = data.iter().map(|b| format!("{b:02x}")).collect();
|
||||
send_packet(&hex, writer)?;
|
||||
}
|
||||
Err(e) => send_packet(&format!("E.{e:?}"), writer)?,
|
||||
};
|
||||
} else {
|
||||
send_packet("", writer)?;
|
||||
}
|
||||
} else {
|
||||
send_packet("", writer)?;
|
||||
}
|
||||
}
|
||||
|
||||
"s" => {
|
||||
let (responder, stop_reason_rx) = oneshot::channel();
|
||||
dbg_tx.send(DebugCommand::Step(responder)).unwrap();
|
||||
let stop_reason = stop_reason_rx.recv().unwrap();
|
||||
send_packet(stop_reason.to_rsp(), writer)?;
|
||||
}
|
||||
|
||||
"c" => {
|
||||
let (responder, stop_reason_rx) = oneshot::channel();
|
||||
let (stopper, stop_listener) = oneshot::channel();
|
||||
dbg_tx
|
||||
.send(DebugCommand::Continue(responder, stop_listener))
|
||||
.unwrap();
|
||||
|
||||
loop {
|
||||
let mut byte = [0u8];
|
||||
match reader.read(&mut byte) {
|
||||
Ok(0) => {
|
||||
stopper.send(()).unwrap();
|
||||
break;
|
||||
}
|
||||
Ok(1) if byte[0] == 0x03 => {
|
||||
stopper.send(()).unwrap();
|
||||
break;
|
||||
}
|
||||
Ok(_) => {}
|
||||
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {}
|
||||
Err(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(stop_reason) = stop_reason_rx.try_recv() {
|
||||
send_packet(stop_reason.to_rsp(), writer)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
std::thread::yield_now();
|
||||
}
|
||||
|
||||
let stop_reason = stop_reason_rx.recv().unwrap();
|
||||
send_packet(stop_reason.to_rsp(), writer)?;
|
||||
}
|
||||
|
||||
"Z" if packet.chars().nth(1) == Some('0') => {
|
||||
if let Some((addr_str, size_str)) = packet[3..].split_once(',')
|
||||
&& let (Ok(addr), Ok(size)) = (
|
||||
u64::from_str_radix(addr_str, 16),
|
||||
u64::from_str_radix(size_str, 16),
|
||||
)
|
||||
{
|
||||
if size != 4 {
|
||||
send_packet("", writer)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
dbg_tx.send(DebugCommand::SetBreakpoint(addr)).unwrap();
|
||||
send_packet("OK", writer)?;
|
||||
return Ok(());
|
||||
}
|
||||
send_packet("", writer)?;
|
||||
}
|
||||
|
||||
"z" if packet.chars().nth(1) == Some('0') => {
|
||||
if let Some((addr_str, size_str)) = packet[3..].split_once(',')
|
||||
&& let (Ok(addr), Ok(size)) = (
|
||||
u64::from_str_radix(addr_str, 16),
|
||||
u64::from_str_radix(size_str, 16),
|
||||
)
|
||||
{
|
||||
if size != 4 {
|
||||
send_packet("", writer)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
dbg_tx.send(DebugCommand::RemoveBreakpoint(addr)).unwrap();
|
||||
send_packet("OK", writer)?;
|
||||
return Ok(());
|
||||
}
|
||||
send_packet("", writer)?;
|
||||
}
|
||||
|
||||
_ => {
|
||||
send_packet("", writer)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn send_packet<W: Write>(packet: &str, writer: &mut W) -> io::Result<()> {
|
||||
writer.write_all(b"$")?;
|
||||
writer.write_all(packet.as_bytes())?;
|
||||
writer.write_all(b"#")?;
|
||||
let checksum = packet.bytes().fold(0u8, |acc, b| acc.wrapping_add(b));
|
||||
write!(writer, "{checksum:02x}")?;
|
||||
// eprintln!("successfully sent packet {packet:?}");
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,53 +1,173 @@
|
||||
// Copyright (c) 2025 taitep
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
//
|
||||
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
||||
// See LICENSE file in the project root for full license text.
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
mod rva;
|
||||
mod rvi;
|
||||
mod rvm;
|
||||
|
||||
use crate::{
|
||||
core::{Core, InstructionResult},
|
||||
core::Core,
|
||||
decode::Instruction,
|
||||
exceptions::{
|
||||
Exception,
|
||||
ExceptionType::{self, IllegalInstruction},
|
||||
},
|
||||
};
|
||||
|
||||
pub(crate) fn find_and_exec(instr: Instruction, core: &mut Core) -> Option<InstructionResult> {
|
||||
fn illegal(instr: Instruction) -> Result<(), Exception> {
|
||||
Err(Exception {
|
||||
type_: IllegalInstruction,
|
||||
value: instr.0 as u64,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn find_and_exec(instr: Instruction, core: &mut Core) -> Result<(), Exception> {
|
||||
match instr.opcode_noncompressed() {
|
||||
0b01100 => match (instr.funct3(), instr.funct7()) {
|
||||
// OP
|
||||
(0b000, 0b0000000) => rvi::add(core, instr),
|
||||
(0b000, 0b0100000) => rvi::sub(core, instr),
|
||||
(0b010, 0b0000000) => rvi::slt(core, instr),
|
||||
(0b011, 0b0000000) => rvi::sltu(core, instr),
|
||||
(0b001, 0b0000000) => rvi::sll(core, instr),
|
||||
(0b101, 0b0000000) => rvi::srl(core, instr),
|
||||
(0b101, 0b0100000) => rvi::sra(core, instr),
|
||||
(0b111, 0b0000000) => rvi::and(core, instr),
|
||||
(0b100, 0b0000000) => rvi::xor(core, instr),
|
||||
(0b110, 0b0000000) => rvi::or(core, instr),
|
||||
// rvm
|
||||
(0b000, 0b0000001) => rvm::mul(core, instr),
|
||||
(0b001, 0b0000001) => rvm::mulh(core, instr),
|
||||
(0b010, 0b0000001) => rvm::mulhsu(core, instr),
|
||||
(0b011, 0b0000001) => rvm::mulhu(core, instr),
|
||||
(0b100, 0b0000001) => rvm::div(core, instr),
|
||||
(0b101, 0b0000001) => rvm::divu(core, instr),
|
||||
(0b110, 0b0000001) => rvm::rem(core, instr),
|
||||
(0b111, 0b0000001) => rvm::remu(core, instr),
|
||||
_ => illegal(instr),
|
||||
},
|
||||
0b01110 => match (instr.funct3(), instr.funct7()) {
|
||||
// OP_32
|
||||
(0b000, 0b0000000) => rvi::addw(core, instr),
|
||||
(0b000, 0b0100000) => rvi::subw(core, instr),
|
||||
(0b001, 0b0000000) => rvi::sllw(core, instr),
|
||||
(0b101, 0b0000000) => rvi::srlw(core, instr),
|
||||
(0b101, 0b0100000) => rvi::sraw(core, instr),
|
||||
// rvm
|
||||
(0b000, 0b0000001) => rvm::mulw(core, instr),
|
||||
(0b100, 0b0000001) => rvm::divw(core, instr),
|
||||
(0b101, 0b0000001) => rvm::divuw(core, instr),
|
||||
(0b110, 0b0000001) => rvm::remw(core, instr),
|
||||
(0b111, 0b0000001) => rvm::remuw(core, instr),
|
||||
_ => illegal(instr),
|
||||
},
|
||||
0b00100 => match instr.funct3() {
|
||||
// OP_IMM
|
||||
0b000 => Some(rvi::addi(core, instr)),
|
||||
0b000 => rvi::addi(core, instr),
|
||||
0b010 => rvi::slti(core, instr),
|
||||
0b011 => rvi::sltiu(core, instr),
|
||||
0b001 => match instr.funct6() {
|
||||
// left-shift immediate
|
||||
0b000000 => Some(rvi::slli(core, instr)),
|
||||
_ => None,
|
||||
0 => rvi::slli(core, instr),
|
||||
_ => illegal(instr),
|
||||
},
|
||||
0b111 => Some(rvi::andi(core, instr)),
|
||||
_ => None,
|
||||
0b101 => match instr.funct6() {
|
||||
0b000000 => rvi::srli(core, instr),
|
||||
0b010000 => rvi::srai(core, instr),
|
||||
_ => illegal(instr),
|
||||
},
|
||||
0b100 => rvi::xori(core, instr),
|
||||
0b110 => rvi::ori(core, instr),
|
||||
0b111 => rvi::andi(core, instr),
|
||||
_ => illegal(instr),
|
||||
},
|
||||
0b00110 => match instr.funct3() {
|
||||
// OP_IMM_32
|
||||
0b000 => Some(rvi::addiw(core, instr)),
|
||||
_ => None,
|
||||
0b000 => rvi::addiw(core, instr),
|
||||
0b001 => match instr.funct7() {
|
||||
0 => rvi::slliw(core, instr),
|
||||
_ => illegal(instr),
|
||||
},
|
||||
0b101 => match instr.funct7() {
|
||||
0b0000000 => rvi::srliw(core, instr),
|
||||
0b0100000 => rvi::sraiw(core, instr),
|
||||
_ => illegal(instr),
|
||||
},
|
||||
_ => illegal(instr),
|
||||
},
|
||||
0b01000 => match instr.funct3() {
|
||||
// STORE
|
||||
0b000 => Some(rvi::sb(core, instr)),
|
||||
0b011 => Some(rvi::sd(core, instr)),
|
||||
_ => None,
|
||||
0b000 => rvi::sb(core, instr),
|
||||
0b001 => rvi::sh(core, instr),
|
||||
0b010 => rvi::sw(core, instr),
|
||||
0b011 => rvi::sd(core, instr),
|
||||
_ => illegal(instr),
|
||||
},
|
||||
0b00000 => match instr.funct3() {
|
||||
// LOAD
|
||||
0b000 => Some(rvi::lb(core, instr)),
|
||||
0b100 => Some(rvi::lbu(core, instr)),
|
||||
_ => None,
|
||||
0b000 => rvi::lb(core, instr),
|
||||
0b100 => rvi::lbu(core, instr),
|
||||
0b001 => rvi::lh(core, instr),
|
||||
0b101 => rvi::lhu(core, instr),
|
||||
0b010 => rvi::lw(core, instr),
|
||||
0b110 => rvi::lwu(core, instr),
|
||||
0b011 => rvi::ld(core, instr),
|
||||
_ => illegal(instr),
|
||||
},
|
||||
0b11000 => match instr.funct3() {
|
||||
// BRANCH
|
||||
0b000 => Some(rvi::beq(core, instr)),
|
||||
_ => None,
|
||||
0b000 => rvi::beq(core, instr),
|
||||
0b001 => rvi::bne(core, instr),
|
||||
0b100 => rvi::blt(core, instr),
|
||||
0b101 => rvi::bge(core, instr),
|
||||
0b110 => rvi::bltu(core, instr),
|
||||
0b111 => rvi::bgeu(core, instr),
|
||||
_ => illegal(instr),
|
||||
},
|
||||
0b01101 => Some(rvi::lui(core, instr)),
|
||||
0b11011 => Some(rvi::jal(core, instr)),
|
||||
_ => None,
|
||||
0b01101 => rvi::lui(core, instr),
|
||||
0b00101 => rvi::auipc(core, instr),
|
||||
0b11011 => rvi::jal(core, instr),
|
||||
0b11001 => {
|
||||
if instr.funct3() == 0 {
|
||||
rvi::jalr(core, instr)
|
||||
} else {
|
||||
illegal(instr)
|
||||
}
|
||||
}
|
||||
0b00011 => match instr.funct3() {
|
||||
// MISC_MEM
|
||||
0b000 => {
|
||||
// FENCE is just implemented as a SeqCst fence always here
|
||||
// I dont yet care about the potential performance issue this may bring
|
||||
std::sync::atomic::fence(std::sync::atomic::Ordering::SeqCst);
|
||||
core.advance_pc();
|
||||
Ok(())
|
||||
}
|
||||
_ => illegal(instr),
|
||||
},
|
||||
0b11100 => match (instr.funct3(), instr.funct12(), instr.rs1(), instr.rd()) {
|
||||
(0b000, 0b000000000000, 0, 0) => {
|
||||
// TODO: When privilege modes are added, make the exception raised by ecall
|
||||
// depend on privilege mode
|
||||
Err(ExceptionType::EnvironmentCallFromMMode.with_no_value())
|
||||
}
|
||||
(0b000, 0b000000000001, 0, 0) => Err(ExceptionType::Breakpoint.with_no_value()),
|
||||
_ => {
|
||||
// Temporarily allowing unrecognized instructions here to be able to run
|
||||
// the official ISA tests, which perform CSR operations but work just fine
|
||||
// without them
|
||||
eprintln!("Unrecognized instruction within SYSTEM opcode");
|
||||
dbg!(instr);
|
||||
core.advance_pc();
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
0b01011 => rva::find_and_exec(instr, core),
|
||||
_ => illegal(instr),
|
||||
}
|
||||
}
|
||||
|
||||
64
src/instructions/macros.rs
Normal file
64
src/instructions/macros.rs
Normal file
@@ -0,0 +1,64 @@
|
||||
// Copyright (c) 2025 taitep
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
//
|
||||
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
||||
// See LICENSE file in the project root for full license text.
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! instr_branch {
|
||||
($name:ident, $cond:expr) => {
|
||||
pub fn $name(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
||||
let a = core.reg_read(instr.rs1());
|
||||
let b = core.reg_read(instr.rs2());
|
||||
if $cond(a, b) {
|
||||
core.pc = core.pc.wrapping_add(instr.imm_b());
|
||||
} else {
|
||||
core.advance_pc();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! instr_branch_signed {
|
||||
($name:ident, $cond:expr) => {
|
||||
instr_branch!($name, |a, b| $cond((a as i64), (b as i64)));
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! instr_op_r {
|
||||
($name:ident, $op:expr) => {
|
||||
pub fn $name(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
||||
let a = core.reg_read(instr.rs1());
|
||||
let b = core.reg_read(instr.rs2());
|
||||
let res = $op(a, b);
|
||||
core.reg_write(instr.rd(), res);
|
||||
core.advance_pc();
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! instr_op_i {
|
||||
($name:ident, $op:expr) => {
|
||||
pub fn $name(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
||||
let a = core.reg_read(instr.rs1());
|
||||
let b = instr.imm_i();
|
||||
let res = $op(a, b);
|
||||
core.reg_write(instr.rd(), res);
|
||||
core.advance_pc();
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! instr_op {
|
||||
($name:ident, $name_imm:ident, $op:expr) => {
|
||||
instr_op_r!($name, $op);
|
||||
instr_op_i!($name_imm, $op);
|
||||
};
|
||||
}
|
||||
247
src/instructions/rva.rs
Normal file
247
src/instructions/rva.rs
Normal file
@@ -0,0 +1,247 @@
|
||||
// Copyright (c) 2026 taitep
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
//
|
||||
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
||||
// See LICENSE file in the project root for full license text.
|
||||
|
||||
use std::sync::atomic::{AtomicU32, AtomicU64};
|
||||
|
||||
use super::illegal;
|
||||
use crate::{
|
||||
core::Core,
|
||||
decode::Instruction,
|
||||
exceptions::{Exception, ExceptionType},
|
||||
mem::{RAM_START, Ram},
|
||||
};
|
||||
|
||||
pub(super) fn find_and_exec(instr: Instruction, core: &mut Core) -> Result<(), Exception> {
|
||||
match (instr.funct5(), instr.funct3()) {
|
||||
(0b00010, 0b010) if instr.rs2() == 0 => lr_w(core, instr),
|
||||
(0b00010, 0b011) if instr.rs2() == 0 => lr_d(core, instr),
|
||||
(0b00011, 0b010) => sc_w(core, instr),
|
||||
(0b00011, 0b011) => sc_d(core, instr),
|
||||
_ => illegal(instr),
|
||||
}
|
||||
}
|
||||
|
||||
fn lr_d(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
||||
core.reservation = None;
|
||||
|
||||
let addr = core.reg_read(instr.rs1());
|
||||
if !addr.is_multiple_of(8) {
|
||||
return Err(Exception {
|
||||
type_: ExceptionType::LoadAddressMisaligned,
|
||||
value: addr,
|
||||
});
|
||||
}
|
||||
if addr < RAM_START {
|
||||
return Err(Exception {
|
||||
type_: ExceptionType::LoadAccessFault,
|
||||
value: addr,
|
||||
});
|
||||
}
|
||||
let ram_addr = addr - RAM_START;
|
||||
|
||||
let reservation_data = core
|
||||
.mem
|
||||
.ram
|
||||
.wait_for_even_version(ram_addr)
|
||||
.ok_or_else(|| Exception {
|
||||
type_: ExceptionType::LoadAccessFault,
|
||||
value: addr,
|
||||
})?;
|
||||
|
||||
core.reg_write(instr.rd(), unsafe {
|
||||
let index = ram_addr as usize / 8;
|
||||
core.mem
|
||||
.ram
|
||||
.buf_transmuted::<AtomicU64>()
|
||||
.get(index)
|
||||
.ok_or_else(|| Exception {
|
||||
type_: ExceptionType::LoadAccessFault,
|
||||
value: addr,
|
||||
})?
|
||||
.load(instr.amo_ordering())
|
||||
});
|
||||
|
||||
core.reservation = Some(reservation_data);
|
||||
core.advance_pc();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
fn sc_d(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
||||
let res = if let Some((reserved_chunk_id, reserved_version)) = core.reservation {
|
||||
let addr = core.reg_read(instr.rs1());
|
||||
if !addr.is_multiple_of(8) {
|
||||
return Err(Exception {
|
||||
type_: ExceptionType::StoreAmoAddressMisaligned,
|
||||
value: addr,
|
||||
});
|
||||
}
|
||||
if addr < RAM_START {
|
||||
return Err(Exception {
|
||||
type_: ExceptionType::StoreAmoAccessFault,
|
||||
value: addr,
|
||||
});
|
||||
}
|
||||
let ram_addr = addr - RAM_START;
|
||||
let chunk_id = ram_addr as usize / Ram::VERSION_CHUNK_SIZE;
|
||||
|
||||
if chunk_id != reserved_chunk_id {
|
||||
// Mismatched reservation location and address
|
||||
core.reg_write(instr.rd(), 1);
|
||||
core.reservation = None;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let claim_res = core
|
||||
.mem
|
||||
.ram
|
||||
.claim_expected(chunk_id, reserved_version)
|
||||
.ok_or_else(|| Exception {
|
||||
type_: ExceptionType::StoreAmoAccessFault,
|
||||
value: addr,
|
||||
})?;
|
||||
|
||||
if claim_res.is_some() {
|
||||
core.reservation = None;
|
||||
let value = core.reg_read(instr.rs2());
|
||||
unsafe {
|
||||
let index = ram_addr as usize / 8;
|
||||
core.mem
|
||||
.ram
|
||||
.buf_transmuted::<AtomicU64>()
|
||||
.get(index)
|
||||
.ok_or_else(|| Exception {
|
||||
type_: ExceptionType::StoreAmoAccessFault,
|
||||
value: addr,
|
||||
})?
|
||||
.store(value, instr.amo_ordering());
|
||||
|
||||
Ok(0)
|
||||
}
|
||||
} else {
|
||||
core.reservation = None;
|
||||
Ok(1)
|
||||
}
|
||||
} else {
|
||||
core.reservation = None;
|
||||
Ok(1)
|
||||
}
|
||||
.map(|s| core.reg_write(instr.rd(), s));
|
||||
|
||||
core.advance_pc();
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
fn lr_w(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
||||
core.reservation = None;
|
||||
|
||||
let addr = core.reg_read(instr.rs1());
|
||||
if !addr.is_multiple_of(4) {
|
||||
return Err(Exception {
|
||||
type_: ExceptionType::LoadAddressMisaligned,
|
||||
value: addr,
|
||||
});
|
||||
}
|
||||
if addr < RAM_START {
|
||||
return Err(Exception {
|
||||
type_: ExceptionType::LoadAccessFault,
|
||||
value: addr,
|
||||
});
|
||||
}
|
||||
let ram_addr = addr - RAM_START;
|
||||
|
||||
let reservation_data = core
|
||||
.mem
|
||||
.ram
|
||||
.wait_for_even_version(ram_addr)
|
||||
.ok_or_else(|| Exception {
|
||||
type_: ExceptionType::LoadAccessFault,
|
||||
value: addr,
|
||||
})?;
|
||||
|
||||
core.reg_write(instr.rd(), unsafe {
|
||||
let index = ram_addr as usize / 4;
|
||||
core.mem
|
||||
.ram
|
||||
.buf_transmuted::<AtomicU32>()
|
||||
.get(index)
|
||||
.ok_or_else(|| Exception {
|
||||
type_: ExceptionType::LoadAccessFault,
|
||||
value: addr,
|
||||
})?
|
||||
.load(instr.amo_ordering())
|
||||
} as i32 as i64 as u64);
|
||||
|
||||
core.reservation = Some(reservation_data);
|
||||
core.advance_pc();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
fn sc_w(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
||||
let res = if let Some((reserved_chunk_id, reserved_version)) = core.reservation {
|
||||
let addr = core.reg_read(instr.rs1());
|
||||
if !addr.is_multiple_of(4) {
|
||||
return Err(Exception {
|
||||
type_: ExceptionType::StoreAmoAddressMisaligned,
|
||||
value: addr,
|
||||
});
|
||||
}
|
||||
if addr < RAM_START {
|
||||
return Err(Exception {
|
||||
type_: ExceptionType::StoreAmoAccessFault,
|
||||
value: addr,
|
||||
});
|
||||
}
|
||||
let ram_addr = addr - RAM_START;
|
||||
let chunk_id = ram_addr as usize / Ram::VERSION_CHUNK_SIZE;
|
||||
|
||||
if chunk_id != reserved_chunk_id {
|
||||
// Mismatched reservation location and address
|
||||
core.reg_write(instr.rd(), 1);
|
||||
core.reservation = None;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let claim_res = core
|
||||
.mem
|
||||
.ram
|
||||
.claim_expected(chunk_id, reserved_version)
|
||||
.ok_or_else(|| Exception {
|
||||
type_: ExceptionType::StoreAmoAccessFault,
|
||||
value: addr,
|
||||
})?;
|
||||
|
||||
if claim_res.is_some() {
|
||||
core.reservation = None;
|
||||
let value = core.reg_read(instr.rs2());
|
||||
unsafe {
|
||||
let index = ram_addr as usize / 4;
|
||||
core.mem
|
||||
.ram
|
||||
.buf_transmuted::<AtomicU32>()
|
||||
.get(index)
|
||||
.ok_or_else(|| Exception {
|
||||
type_: ExceptionType::StoreAmoAccessFault,
|
||||
value: addr,
|
||||
})?
|
||||
.store(value as u32, instr.amo_ordering());
|
||||
|
||||
Ok(0)
|
||||
}
|
||||
} else {
|
||||
core.reservation = None;
|
||||
Ok(1)
|
||||
}
|
||||
} else {
|
||||
core.reservation = None;
|
||||
Ok(1)
|
||||
}
|
||||
.map(|s| core.reg_write(instr.rd(), s));
|
||||
|
||||
core.advance_pc();
|
||||
|
||||
res
|
||||
}
|
||||
@@ -1,142 +1,78 @@
|
||||
// Copyright (c) 2025 taitep
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
//
|
||||
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
||||
// See LICENSE file in the project root for full license text.
|
||||
|
||||
use crate::{
|
||||
consts::{Addr, DWord},
|
||||
core::{Core, InstructionResult},
|
||||
decode::Instruction,
|
||||
mem::PageNum,
|
||||
};
|
||||
use crate::{core::Core, decode::Instruction, exceptions::Exception};
|
||||
|
||||
pub fn addi(core: &mut Core, instr: Instruction) -> InstructionResult {
|
||||
core.reg_write(
|
||||
instr.rd(),
|
||||
core.reg_read(instr.rs1()).wrapping_add(instr.imm_i()),
|
||||
use std::ops::{BitAnd, BitOr, BitXor};
|
||||
|
||||
mod mem;
|
||||
|
||||
pub use mem::*;
|
||||
|
||||
instr_op!(add, addi, u64::wrapping_add);
|
||||
instr_op!(addw, addiw, |a, b| u64::wrapping_add(a, b) as i32 as i64
|
||||
as u64);
|
||||
instr_op_r!(sub, u64::wrapping_sub);
|
||||
instr_op_r!(subw, |a, b| u64::wrapping_sub(a, b) as i32 as i64 as u64);
|
||||
|
||||
instr_op!(and, andi, u64::bitand);
|
||||
instr_op!(or, ori, u64::bitor);
|
||||
instr_op!(xor, xori, u64::bitxor);
|
||||
|
||||
instr_op!(sll, slli, |x, shamt| x << (shamt & 0b111111));
|
||||
instr_op!(
|
||||
sllw,
|
||||
slliw,
|
||||
|x, shamt| (x << (shamt & 0b11111)) as i32 as i64 as u64
|
||||
);
|
||||
instr_op!(srl, srli, |x, shamt| x >> (shamt & 0b111111));
|
||||
instr_op!(
|
||||
srlw,
|
||||
srliw,
|
||||
|x, shamt| (x as u32 >> (shamt & 0b11111)) as i32 as i64 as u64
|
||||
);
|
||||
instr_op!(sra, srai, |x, shamt| (x as i64 >> (shamt & 0b111111))
|
||||
as u64);
|
||||
instr_op!(
|
||||
sraw,
|
||||
sraiw,
|
||||
|x, shamt| (x as i32 >> (shamt & 0b11111)) as i64 as u64
|
||||
);
|
||||
|
||||
core.advance_pc();
|
||||
instr_op!(sltu, sltiu, |a, b| (a < b) as u64);
|
||||
instr_op!(slt, slti, |a, b| ((a as i64) < (b as i64)) as u64);
|
||||
|
||||
InstructionResult::Normal
|
||||
}
|
||||
|
||||
pub fn addiw(core: &mut Core, instr: Instruction) -> InstructionResult {
|
||||
let res = core.reg_read(instr.rs1()).wrapping_add(instr.imm_i()) as i32;
|
||||
|
||||
core.reg_write(instr.rd(), res as i64 as u64);
|
||||
|
||||
core.advance_pc();
|
||||
|
||||
InstructionResult::Normal
|
||||
}
|
||||
|
||||
pub fn andi(core: &mut Core, instr: Instruction) -> InstructionResult {
|
||||
core.reg_write(instr.rd(), core.reg_read(instr.rs1()) & instr.imm_i());
|
||||
|
||||
core.advance_pc();
|
||||
|
||||
InstructionResult::Normal
|
||||
}
|
||||
|
||||
// TODO: Support misaligned memory access
|
||||
pub fn sd(core: &mut Core, instr: Instruction) -> InstructionResult {
|
||||
let addr = core.reg_read(instr.rs1()).wrapping_add(instr.imm_s());
|
||||
|
||||
if !addr.is_multiple_of(std::mem::size_of::<DWord>() as Addr) {
|
||||
return InstructionResult::Exception(());
|
||||
}
|
||||
|
||||
let page = (addr / 4096) as PageNum;
|
||||
let offset = (addr / 8 & ((4096 / 8 as Addr) - 1)) as u16;
|
||||
let value = core.reg_read(instr.rs2());
|
||||
|
||||
match core.mem.write_dword(page, offset, value) {
|
||||
Ok(_) => {
|
||||
core.advance_pc();
|
||||
InstructionResult::Normal
|
||||
}
|
||||
Err(_) => InstructionResult::Exception(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sb(core: &mut Core, instr: Instruction) -> InstructionResult {
|
||||
let addr = core.reg_read(instr.rs1()).wrapping_add(instr.imm_s());
|
||||
|
||||
let page = (addr / 4096) as PageNum;
|
||||
let offset = (addr & (4096 as Addr - 1)) as u16;
|
||||
let value = core.reg_read(instr.rs2()) as u8;
|
||||
|
||||
match core.mem.write_byte(page, offset, value) {
|
||||
Ok(_) => {
|
||||
core.advance_pc();
|
||||
InstructionResult::Normal
|
||||
}
|
||||
Err(_) => InstructionResult::Exception(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lb(core: &mut Core, instr: Instruction) -> InstructionResult {
|
||||
let addr = core.reg_read(instr.rs1()).wrapping_add(instr.imm_i());
|
||||
|
||||
let page = (addr / 4096) as PageNum;
|
||||
let offset = (addr & (4096 as Addr - 1)) as u16;
|
||||
|
||||
match core.mem.read_byte(page, offset) {
|
||||
Ok(x) => {
|
||||
let x = x as i8 as i64 as DWord;
|
||||
core.reg_write(instr.rd(), x);
|
||||
core.advance_pc();
|
||||
InstructionResult::Normal
|
||||
}
|
||||
Err(_) => InstructionResult::Exception(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lbu(core: &mut Core, instr: Instruction) -> InstructionResult {
|
||||
let addr = core.reg_read(instr.rs1()).wrapping_add(instr.imm_i());
|
||||
|
||||
let page = (addr / 4096) as PageNum;
|
||||
let offset = (addr & (4096 as Addr - 1)) as u16;
|
||||
|
||||
match core.mem.read_byte(page, offset) {
|
||||
Ok(x) => {
|
||||
let x = x as DWord;
|
||||
core.reg_write(instr.rd(), x);
|
||||
core.advance_pc();
|
||||
InstructionResult::Normal
|
||||
}
|
||||
Err(_) => InstructionResult::Exception(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lui(core: &mut Core, instr: Instruction) -> InstructionResult {
|
||||
pub fn lui(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
||||
core.reg_write(instr.rd(), instr.imm_u());
|
||||
core.advance_pc();
|
||||
InstructionResult::Normal
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn jal(core: &mut Core, instr: Instruction) -> InstructionResult {
|
||||
core.reg_write(instr.rd(), core.pc);
|
||||
pub fn auipc(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
||||
core.reg_write(instr.rd(), core.pc.wrapping_add(instr.imm_u()));
|
||||
core.advance_pc();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn jal(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
||||
core.reg_write(instr.rd(), core.pc.wrapping_add(4));
|
||||
core.pc = core.pc.wrapping_add(instr.imm_j());
|
||||
InstructionResult::Normal
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn beq(core: &mut Core, instr: Instruction) -> InstructionResult {
|
||||
if core.reg_read(instr.rs1()) == core.reg_read(instr.rs2()) {
|
||||
core.pc = core.pc.wrapping_add(instr.imm_b());
|
||||
} else {
|
||||
core.advance_pc();
|
||||
pub fn jalr(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
||||
let target = core.reg_read(instr.rs1()).wrapping_add(instr.imm_i()) & !1;
|
||||
core.reg_write(instr.rd(), core.pc.wrapping_add(4));
|
||||
core.pc = target;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
InstructionResult::Normal
|
||||
}
|
||||
|
||||
pub fn slli(core: &mut Core, instr: Instruction) -> InstructionResult {
|
||||
core.reg_write(instr.rd(), core.reg_read(instr.rs1()) << instr.imm_shamt());
|
||||
|
||||
core.advance_pc();
|
||||
|
||||
InstructionResult::Normal
|
||||
}
|
||||
instr_branch!(beq, |a, b| a == b);
|
||||
instr_branch!(bne, |a, b| a != b);
|
||||
instr_branch!(bltu, |a, b| a < b);
|
||||
instr_branch!(bgeu, |a, b| a >= b);
|
||||
instr_branch_signed!(blt, |a, b| a < b);
|
||||
instr_branch_signed!(bge, |a, b| a >= b);
|
||||
|
||||
131
src/instructions/rvi/mem.rs
Normal file
131
src/instructions/rvi/mem.rs
Normal file
@@ -0,0 +1,131 @@
|
||||
// Copyright (c) 2025 taitep
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
//
|
||||
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
||||
// See LICENSE file in the project root for full license text.
|
||||
|
||||
use crate::{core::Core, exceptions::Exception, instructions::Instruction};
|
||||
|
||||
pub fn sd(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
||||
let addr = core.reg_read(instr.rs1()).wrapping_add(instr.imm_s());
|
||||
let value = core.reg_read(instr.rs2());
|
||||
core.mem
|
||||
.write_dword(addr, value)
|
||||
.map_err(|e| e.into_exception_store())?;
|
||||
core.advance_pc();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn ld(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
||||
let addr = core.reg_read(instr.rs1()).wrapping_add(instr.imm_i());
|
||||
core.reg_write(
|
||||
instr.rd(),
|
||||
core.mem
|
||||
.read_dword(addr)
|
||||
.map_err(|e| e.into_exception_load())?,
|
||||
);
|
||||
core.advance_pc();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn sw(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
||||
let addr = core.reg_read(instr.rs1()).wrapping_add(instr.imm_s());
|
||||
let value = core.reg_read(instr.rs2()) as u32;
|
||||
core.mem
|
||||
.write_word(addr, value)
|
||||
.map_err(|e| e.into_exception_store())?;
|
||||
core.advance_pc();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn lw(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
||||
let addr = core.reg_read(instr.rs1()).wrapping_add(instr.imm_i());
|
||||
core.reg_write(
|
||||
instr.rd(),
|
||||
core.mem
|
||||
.read_word(addr)
|
||||
.map_err(|e| e.into_exception_load())? as i32 as i64 as u64,
|
||||
);
|
||||
core.advance_pc();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn lwu(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
||||
let addr = core.reg_read(instr.rs1()).wrapping_add(instr.imm_i());
|
||||
core.reg_write(
|
||||
instr.rd(),
|
||||
core.mem
|
||||
.read_word(addr)
|
||||
.map_err(|e| e.into_exception_load())? as u64,
|
||||
);
|
||||
core.advance_pc();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn sh(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
||||
let addr = core.reg_read(instr.rs1()).wrapping_add(instr.imm_s());
|
||||
let value = core.reg_read(instr.rs2()) as u16;
|
||||
core.mem
|
||||
.write_hword(addr, value)
|
||||
.map_err(|e| e.into_exception_store())?;
|
||||
core.advance_pc();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn lh(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
||||
let addr = core.reg_read(instr.rs1()).wrapping_add(instr.imm_i());
|
||||
core.reg_write(
|
||||
instr.rd(),
|
||||
core.mem
|
||||
.read_hword(addr)
|
||||
.map_err(|e| e.into_exception_load())? as i16 as i64 as u64,
|
||||
);
|
||||
core.advance_pc();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn lhu(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
||||
let addr = core.reg_read(instr.rs1()).wrapping_add(instr.imm_i());
|
||||
core.reg_write(
|
||||
instr.rd(),
|
||||
core.mem
|
||||
.read_hword(addr)
|
||||
.map_err(|e| e.into_exception_load())? as u64,
|
||||
);
|
||||
core.advance_pc();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn sb(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
||||
let addr = core.reg_read(instr.rs1()).wrapping_add(instr.imm_s());
|
||||
let value = core.reg_read(instr.rs2()) as u8;
|
||||
core.mem
|
||||
.write_byte(addr, value)
|
||||
.map_err(|e| e.into_exception_store())?;
|
||||
core.advance_pc();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn lb(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
||||
let addr = core.reg_read(instr.rs1()).wrapping_add(instr.imm_i());
|
||||
core.reg_write(
|
||||
instr.rd(),
|
||||
core.mem
|
||||
.read_byte(addr)
|
||||
.map_err(|e| e.into_exception_load())? as i8 as i64 as u64,
|
||||
);
|
||||
core.advance_pc();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn lbu(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
||||
let addr = core.reg_read(instr.rs1()).wrapping_add(instr.imm_i());
|
||||
core.reg_write(
|
||||
instr.rd(),
|
||||
core.mem
|
||||
.read_byte(addr)
|
||||
.map_err(|e| e.into_exception_load())? as u64,
|
||||
);
|
||||
core.advance_pc();
|
||||
Ok(())
|
||||
}
|
||||
52
src/instructions/rvm.rs
Normal file
52
src/instructions/rvm.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright (c) 2025 taitep
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
//
|
||||
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
||||
// See LICENSE file in the project root for full license text.
|
||||
//
|
||||
use crate::{core::Core, decode::Instruction, exceptions::Exception};
|
||||
|
||||
// multiplication
|
||||
instr_op_r!(mul, u64::wrapping_mul);
|
||||
instr_op_r!(mulw, |a, b| u32::wrapping_mul(a as u32, b as u32) as u64);
|
||||
|
||||
instr_op_r!(mulh, |a, b| ((a as i64 as i128 * b as i64 as i128) >> 64)
|
||||
as u64);
|
||||
instr_op_r!(mulhsu, |a, b| ((a as i64 as i128 * b as i128) >> 64) as u64);
|
||||
instr_op_r!(mulhu, |a, b| ((a as u128 * b as u128) >> 64) as u64);
|
||||
|
||||
// division
|
||||
instr_op_r!(div, |a, b| match b {
|
||||
0 => -1,
|
||||
_ => i64::wrapping_div(a as i64, b as i64),
|
||||
} as u64);
|
||||
instr_op_r!(divu, |a, b| match b {
|
||||
0 => u64::MAX,
|
||||
_ => a / b,
|
||||
});
|
||||
instr_op_r!(divw, |a, b| match b {
|
||||
0 => -1,
|
||||
_ => i32::wrapping_div(a as i32, b as i32),
|
||||
} as i64 as u64);
|
||||
instr_op_r!(divuw, |a, b| match b {
|
||||
0 => u32::MAX,
|
||||
_ => a as u32 / b as u32,
|
||||
} as i32 as i64 as u64);
|
||||
|
||||
// remainder
|
||||
instr_op_r!(rem, |a, b| match b {
|
||||
0 => a,
|
||||
_ => i64::wrapping_rem(a as i64, b as i64) as u64,
|
||||
});
|
||||
instr_op_r!(remu, |a, b| match b {
|
||||
0 => a,
|
||||
_ => a % b,
|
||||
});
|
||||
instr_op_r!(remw, |a, b| match b {
|
||||
0 => a as i32,
|
||||
_ => i32::wrapping_rem(a as i32, b as i32),
|
||||
} as i64 as u64);
|
||||
instr_op_r!(remuw, |a, b| match b {
|
||||
0 => a as u32,
|
||||
_ => a as u32 % b as u32,
|
||||
} as i32 as i64 as u64);
|
||||
@@ -1,5 +1,7 @@
|
||||
pub mod consts;
|
||||
pub mod core;
|
||||
mod decode;
|
||||
pub mod devices;
|
||||
pub mod exceptions;
|
||||
pub mod gdb;
|
||||
mod instructions;
|
||||
pub mod mem;
|
||||
|
||||
177
src/main.rs
177
src/main.rs
@@ -1,168 +1,97 @@
|
||||
// Copyright (c) 2025 taitep
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2025-2026 taitep
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
//
|
||||
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
||||
// See LICENSE file in the project root for full license text.
|
||||
|
||||
use std::{
|
||||
env,
|
||||
error::Error,
|
||||
fs::File,
|
||||
io::{self, Read},
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
use std::{io, path::PathBuf, sync::Arc, time::Duration};
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
use nix::fcntl::{FcntlArg, OFlag, fcntl};
|
||||
use trve::{
|
||||
consts::{Byte, DWord, HWord, Word},
|
||||
core::Core,
|
||||
mem::{DeviceEntry, MemAccessFault, MemConfig, MemDeviceInterface, PageNum, Ram},
|
||||
devices,
|
||||
exceptions::MemoryExceptionType,
|
||||
gdb,
|
||||
mem::{MemConfig, MemDeviceInterface, MmioRoot, Ram},
|
||||
};
|
||||
|
||||
use crate::basic_uart::BasicUart;
|
||||
use anyhow::Result;
|
||||
|
||||
fn read_file_to_buffer(path: &str, buffer: &mut [u8]) -> io::Result<usize> {
|
||||
let mut file = File::open(path)?;
|
||||
let mut total_read = 0;
|
||||
mod execload;
|
||||
|
||||
while total_read < buffer.len() {
|
||||
let n = file.read(&mut buffer[total_read..])?;
|
||||
if n == 0 {
|
||||
return Ok(total_read);
|
||||
}
|
||||
total_read += n;
|
||||
/// Taitep's RISC-V Emulator
|
||||
#[derive(Parser)]
|
||||
struct Args {
|
||||
/// Path to ELF or raw binary executable to load
|
||||
executable: PathBuf,
|
||||
/// Make CPU wait for a GDB connection before starting execution
|
||||
#[arg(long)]
|
||||
wait: bool,
|
||||
}
|
||||
|
||||
let mut tmp = [0u8; 1];
|
||||
if file.read(&mut tmp)? != 0 {
|
||||
return Err(io::Error::other("RAM too small for file"));
|
||||
}
|
||||
fn main() -> Result<()> {
|
||||
let args = Args::parse();
|
||||
|
||||
Ok(total_read)
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let mut ram = Ram::try_new(16 * 1024 * 1024 / 4096)?;
|
||||
let mut ram = Ram::try_new(16 * 1024 * 1024)?;
|
||||
let buf = ram.buf_mut();
|
||||
|
||||
let args: Vec<String> = env::args().collect();
|
||||
let entry_point = execload::load(args.executable, buf)?;
|
||||
|
||||
if args.len() != 2 {
|
||||
eprintln!("USAGE: trve <ram_image>")
|
||||
let mut mmio_root = MmioRoot::default();
|
||||
mmio_root.insert(0, Arc::new(DbgOut));
|
||||
|
||||
if let Err(e) = fcntl(io::stdin(), FcntlArg::F_SETFL(OFlag::O_NONBLOCK)) {
|
||||
eprintln!("Could not make stdout nonblocking, skipping. Error: {e}");
|
||||
}
|
||||
|
||||
read_file_to_buffer(&args[1], buf)?;
|
||||
|
||||
let uart = BasicUart::new();
|
||||
let uart = uart.spawn_poller(Duration::from_millis(10));
|
||||
let uart = devices::serial::SifiveUart::new_arc();
|
||||
uart.clone()
|
||||
.spawn_io_thread(io::stdin(), io::stdout(), Duration::from_millis(10));
|
||||
mmio_root.insert(0x10000, uart);
|
||||
|
||||
let mem_cfg = MemConfig {
|
||||
ram: Arc::new(ram),
|
||||
ram_start: 0x8000_0000 / 4096,
|
||||
devices: Box::new([
|
||||
DeviceEntry {
|
||||
base: 0,
|
||||
size: 1,
|
||||
interface: Arc::new(DbgOut),
|
||||
},
|
||||
DeviceEntry {
|
||||
base: 1,
|
||||
size: 1,
|
||||
interface: uart,
|
||||
},
|
||||
]),
|
||||
mmio_root,
|
||||
};
|
||||
|
||||
let mut core = Core::new(mem_cfg);
|
||||
core.reset(0x8000_0000);
|
||||
let (cmd_sender, cmd_reciever) = crossbeam::channel::bounded(16);
|
||||
|
||||
gdb::run_stub(cmd_sender.clone());
|
||||
|
||||
let mut core = Core::new(mem_cfg, cmd_reciever);
|
||||
core.reset(entry_point);
|
||||
|
||||
if args.wait {
|
||||
core.run_waiting_for_cmd();
|
||||
} else {
|
||||
core.run();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
mod basic_uart;
|
||||
|
||||
struct DbgOut;
|
||||
|
||||
impl MemDeviceInterface for DbgOut {
|
||||
fn write_dword(
|
||||
&self,
|
||||
page: PageNum,
|
||||
offset: u16,
|
||||
value: DWord,
|
||||
) -> Result<(), trve::mem::MemAccessFault> {
|
||||
eprintln!(
|
||||
"Wrote DWord {value:016x} to Debug-Out page {page}, offset {offset} (byte {})",
|
||||
offset * 8
|
||||
);
|
||||
fn write_dword(&self, addr: u64, value: u64) -> Result<(), MemoryExceptionType> {
|
||||
eprintln!("Wrote DWord {value:016x} to Debug-Out address {addr:x}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_word(
|
||||
&self,
|
||||
page: PageNum,
|
||||
offset: u16,
|
||||
value: Word,
|
||||
) -> Result<(), trve::mem::MemAccessFault> {
|
||||
eprintln!(
|
||||
"Wrote Word {value:08x} to Debug-Out page {page}, offset {offset} (byte {})",
|
||||
offset * 4
|
||||
);
|
||||
fn write_word(&self, addr: u64, value: u32) -> Result<(), MemoryExceptionType> {
|
||||
eprintln!("Wrote Word {value:08x} to Debug-Out address {addr:x}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_hword(
|
||||
&self,
|
||||
page: PageNum,
|
||||
offset: u16,
|
||||
value: HWord,
|
||||
) -> Result<(), trve::mem::MemAccessFault> {
|
||||
eprintln!(
|
||||
"Wrote HWord {value:04x} to Debug-Out page {page}, offset {offset} (byte {})",
|
||||
offset * 2
|
||||
);
|
||||
fn write_hword(&self, addr: u64, value: u16) -> Result<(), MemoryExceptionType> {
|
||||
eprintln!("Wrote HWord {value:04x} to Debug-Out address {addr:x}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_byte(
|
||||
&self,
|
||||
page: PageNum,
|
||||
offset: u16,
|
||||
value: Byte,
|
||||
) -> Result<(), trve::mem::MemAccessFault> {
|
||||
eprintln!("Wrote Byte {value:02x} to Debug-Out page {page}, offset {offset}");
|
||||
fn write_byte(&self, addr: u64, value: u8) -> Result<(), MemoryExceptionType> {
|
||||
eprintln!("Wrote Byte {value:02x} to Debug-Out address {addr:x}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_dword(&self, _page: PageNum, _offset: u16) -> Result<DWord, trve::mem::MemAccessFault> {
|
||||
Err(MemAccessFault)
|
||||
}
|
||||
|
||||
fn read_word(&self, _page: PageNum, _offset: u16) -> Result<Word, trve::mem::MemAccessFault> {
|
||||
Err(MemAccessFault)
|
||||
}
|
||||
|
||||
fn read_hword(&self, _page: PageNum, _offset: u16) -> Result<HWord, trve::mem::MemAccessFault> {
|
||||
Err(MemAccessFault)
|
||||
}
|
||||
|
||||
fn read_byte(&self, _page: PageNum, _offset: u16) -> Result<Byte, trve::mem::MemAccessFault> {
|
||||
Err(MemAccessFault)
|
||||
}
|
||||
|
||||
fn get_atomic_word(
|
||||
&self,
|
||||
_page: PageNum,
|
||||
_offset: u16,
|
||||
) -> Result<&std::sync::atomic::AtomicU32, trve::mem::MemAccessFault> {
|
||||
Err(MemAccessFault)
|
||||
}
|
||||
|
||||
fn get_atomic_dword(
|
||||
&self,
|
||||
_page: PageNum,
|
||||
_offset: u16,
|
||||
) -> Result<&std::sync::atomic::AtomicU64, trve::mem::MemAccessFault> {
|
||||
Err(MemAccessFault)
|
||||
}
|
||||
}
|
||||
|
||||
688
src/mem.rs
688
src/mem.rs
@@ -1,186 +1,177 @@
|
||||
// Copyright (c) 2025 taitep
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2025-2026 taitep
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
//
|
||||
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
||||
// See LICENSE file in the project root for full license text.
|
||||
|
||||
use std::sync::{
|
||||
Arc,
|
||||
atomic::{AtomicU8, AtomicU16, AtomicU32, AtomicU64, Ordering::Relaxed},
|
||||
atomic::{
|
||||
AtomicU8, AtomicU16, AtomicU32, AtomicU64,
|
||||
Ordering::{self, Relaxed},
|
||||
},
|
||||
};
|
||||
|
||||
use memmap2::MmapMut;
|
||||
|
||||
use crate::consts::{Byte, DWord, HWord, Word};
|
||||
use crate::exceptions::{MemoryException, MemoryExceptionType};
|
||||
|
||||
pub type PageNum = usize;
|
||||
|
||||
const PAGE_SIZE: usize = 4096;
|
||||
pub const RAM_START: u64 = 0x8000_0000;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MemConfig {
|
||||
pub ram: Arc<Ram>,
|
||||
pub ram_start: PageNum,
|
||||
pub devices: Box<[DeviceEntry]>,
|
||||
pub mmio_root: MmioRoot,
|
||||
}
|
||||
|
||||
impl MemConfig {
|
||||
#[allow(clippy::needless_borrow)]
|
||||
pub fn find_device_by_page(&self, page: PageNum) -> Option<&DeviceEntry> {
|
||||
for entry in self.devices.iter() {
|
||||
if page_in_range(page, entry.base, entry.size) {
|
||||
return Some(&entry);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn memory_mapping_type(&self, page: PageNum) -> Option<MemoryMappingType> {
|
||||
if page_in_range(page, self.ram_start, self.ram.pages) {
|
||||
pub fn memory_mapping_type(&self, addr: u64) -> Option<MemoryMappingType> {
|
||||
if addr >= RAM_START {
|
||||
Some(MemoryMappingType::RAM)
|
||||
} else {
|
||||
self.find_device_by_page(page)
|
||||
.map(|_x| MemoryMappingType::MMIO)
|
||||
self.mmio_root
|
||||
.get_device(addr)
|
||||
.map(|_| MemoryMappingType::MMIO)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_dword(&self, page: PageNum, offset: u16) -> Result<DWord, MemAccessFault> {
|
||||
if page_in_range(page, self.ram_start, self.ram.pages) {
|
||||
self.ram.read_dword(page - self.ram_start, offset)
|
||||
pub fn read_dword(&self, addr: u64) -> Result<u64, MemoryException> {
|
||||
if addr >= RAM_START {
|
||||
self.ram.read_dword(addr - RAM_START)
|
||||
} else {
|
||||
let entry = self.find_device_by_page(page).ok_or(MemAccessFault)?;
|
||||
if !addr.is_multiple_of(8) && self.mmio_root.crosses_boundary(addr, 8) {
|
||||
return Err(MemoryException {
|
||||
type_: MemoryExceptionType::AddressMisaligned,
|
||||
addr,
|
||||
});
|
||||
}
|
||||
let (interface, addr) = self.mmio_root.get_device(addr).ok_or(MemoryException {
|
||||
type_: MemoryExceptionType::AccessFault,
|
||||
addr,
|
||||
})?;
|
||||
|
||||
entry.interface.read_dword(page - entry.base, offset)
|
||||
interface.read_dword(addr).map_err(|e| e.with_addr(addr))
|
||||
}
|
||||
}
|
||||
pub fn read_word(&self, page: PageNum, offset: u16) -> Result<Word, MemAccessFault> {
|
||||
if page_in_range(page, self.ram_start, self.ram.pages) {
|
||||
self.ram.read_word(page - self.ram_start, offset)
|
||||
pub fn read_word(&self, addr: u64) -> Result<u32, MemoryException> {
|
||||
if addr >= RAM_START {
|
||||
self.ram.read_word(addr - RAM_START)
|
||||
} else {
|
||||
let entry = self.find_device_by_page(page).ok_or(MemAccessFault)?;
|
||||
if !addr.is_multiple_of(4) && self.mmio_root.crosses_boundary(addr, 4) {
|
||||
return Err(MemoryException {
|
||||
type_: MemoryExceptionType::AddressMisaligned,
|
||||
addr,
|
||||
});
|
||||
}
|
||||
let (interface, addr) = self.mmio_root.get_device(addr).ok_or(MemoryException {
|
||||
type_: MemoryExceptionType::AccessFault,
|
||||
addr,
|
||||
})?;
|
||||
|
||||
entry.interface.read_word(page - entry.base, offset)
|
||||
interface.read_word(addr).map_err(|e| e.with_addr(addr))
|
||||
}
|
||||
}
|
||||
pub fn read_hword(&self, page: PageNum, offset: u16) -> Result<HWord, MemAccessFault> {
|
||||
if page_in_range(page, self.ram_start, self.ram.pages) {
|
||||
self.ram.read_hword(page - self.ram_start, offset)
|
||||
pub fn read_hword(&self, addr: u64) -> Result<u16, MemoryException> {
|
||||
if addr >= RAM_START {
|
||||
self.ram.read_hword(addr - RAM_START)
|
||||
} else {
|
||||
let entry = self.find_device_by_page(page).ok_or(MemAccessFault)?;
|
||||
|
||||
entry.interface.read_hword(page - entry.base, offset)
|
||||
if !addr.is_multiple_of(2) && self.mmio_root.crosses_boundary(addr, 2) {
|
||||
return Err(MemoryException {
|
||||
type_: MemoryExceptionType::AddressMisaligned,
|
||||
addr,
|
||||
});
|
||||
}
|
||||
let (interface, addr) = self.mmio_root.get_device(addr).ok_or(MemoryException {
|
||||
type_: MemoryExceptionType::AccessFault,
|
||||
addr,
|
||||
})?;
|
||||
interface.read_hword(addr).map_err(|e| e.with_addr(addr))
|
||||
}
|
||||
}
|
||||
pub fn read_byte(&self, page: PageNum, offset: u16) -> Result<Byte, MemAccessFault> {
|
||||
if page_in_range(page, self.ram_start, self.ram.pages) {
|
||||
self.ram.read_byte(page - self.ram_start, offset)
|
||||
pub fn read_byte(&self, addr: u64) -> Result<u8, MemoryException> {
|
||||
if addr >= RAM_START {
|
||||
self.ram.read_byte(addr - RAM_START)
|
||||
} else {
|
||||
let entry = self.find_device_by_page(page).ok_or(MemAccessFault)?;
|
||||
|
||||
entry.interface.read_byte(page - entry.base, offset)
|
||||
let (interface, addr) = self.mmio_root.get_device(addr).ok_or(MemoryException {
|
||||
type_: MemoryExceptionType::AccessFault,
|
||||
addr,
|
||||
})?;
|
||||
interface.read_byte(addr).map_err(|e| e.with_addr(addr))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_dword(
|
||||
&self,
|
||||
page: PageNum,
|
||||
offset: u16,
|
||||
value: DWord,
|
||||
) -> Result<(), MemAccessFault> {
|
||||
if page_in_range(page, self.ram_start, self.ram.pages) {
|
||||
self.ram.write_dword(page - self.ram_start, offset, value)
|
||||
pub fn write_dword(&self, addr: u64, value: u64) -> Result<(), MemoryException> {
|
||||
if addr >= RAM_START {
|
||||
self.ram.write_dword(addr - RAM_START, value)
|
||||
} else {
|
||||
let entry = self.find_device_by_page(page).ok_or(MemAccessFault)?;
|
||||
entry
|
||||
.interface
|
||||
.write_dword(page - entry.base, offset, value)
|
||||
if !addr.is_multiple_of(8) && self.mmio_root.crosses_boundary(addr, 8) {
|
||||
return Err(MemoryException {
|
||||
type_: MemoryExceptionType::AddressMisaligned,
|
||||
addr,
|
||||
});
|
||||
}
|
||||
let (interface, addr) = self.mmio_root.get_device(addr).ok_or(MemoryException {
|
||||
type_: MemoryExceptionType::AccessFault,
|
||||
addr,
|
||||
})?;
|
||||
interface
|
||||
.write_dword(addr, value)
|
||||
.map_err(|e| e.with_addr(addr))
|
||||
}
|
||||
}
|
||||
pub fn write_word(
|
||||
&self,
|
||||
page: PageNum,
|
||||
offset: u16,
|
||||
value: Word,
|
||||
) -> Result<(), MemAccessFault> {
|
||||
if page_in_range(page, self.ram_start, self.ram.pages) {
|
||||
self.ram.write_word(page - self.ram_start, offset, value)
|
||||
pub fn write_word(&self, addr: u64, value: u32) -> Result<(), MemoryException> {
|
||||
if addr >= RAM_START {
|
||||
self.ram.write_word(addr - RAM_START, value)
|
||||
} else {
|
||||
let entry = self.find_device_by_page(page).ok_or(MemAccessFault)?;
|
||||
entry.interface.write_word(page - entry.base, offset, value)
|
||||
if !addr.is_multiple_of(4) && self.mmio_root.crosses_boundary(addr, 4) {
|
||||
return Err(MemoryException {
|
||||
type_: MemoryExceptionType::AddressMisaligned,
|
||||
addr,
|
||||
});
|
||||
}
|
||||
let (interface, addr) = self.mmio_root.get_device(addr).ok_or(MemoryException {
|
||||
type_: MemoryExceptionType::AccessFault,
|
||||
addr,
|
||||
})?;
|
||||
interface
|
||||
.write_word(addr, value)
|
||||
.map_err(|e| e.with_addr(addr))
|
||||
}
|
||||
}
|
||||
pub fn write_hword(
|
||||
&self,
|
||||
page: PageNum,
|
||||
offset: u16,
|
||||
value: HWord,
|
||||
) -> Result<(), MemAccessFault> {
|
||||
if page_in_range(page, self.ram_start, self.ram.pages) {
|
||||
self.ram.write_hword(page - self.ram_start, offset, value)
|
||||
pub fn write_hword(&self, addr: u64, value: u16) -> Result<(), MemoryException> {
|
||||
if addr >= RAM_START {
|
||||
self.ram.write_hword(addr - RAM_START, value)
|
||||
} else {
|
||||
let entry = self.find_device_by_page(page).ok_or(MemAccessFault)?;
|
||||
entry
|
||||
.interface
|
||||
.write_hword(page - entry.base, offset, value)
|
||||
if !addr.is_multiple_of(2) && self.mmio_root.crosses_boundary(addr, 2) {
|
||||
return Err(MemoryException {
|
||||
type_: MemoryExceptionType::AddressMisaligned,
|
||||
addr,
|
||||
});
|
||||
}
|
||||
let (interface, addr) = self.mmio_root.get_device(addr).ok_or(MemoryException {
|
||||
type_: MemoryExceptionType::AccessFault,
|
||||
addr,
|
||||
})?;
|
||||
interface
|
||||
.write_hword(addr, value)
|
||||
.map_err(|e| e.with_addr(addr))
|
||||
}
|
||||
}
|
||||
pub fn write_byte(
|
||||
&self,
|
||||
page: PageNum,
|
||||
offset: u16,
|
||||
value: Byte,
|
||||
) -> Result<(), MemAccessFault> {
|
||||
if page_in_range(page, self.ram_start, self.ram.pages) {
|
||||
self.ram.write_byte(page - self.ram_start, offset, value)
|
||||
pub fn write_byte(&self, addr: u64, value: u8) -> Result<(), MemoryException> {
|
||||
if addr >= RAM_START {
|
||||
self.ram.write_byte(addr - RAM_START, value)
|
||||
} else {
|
||||
let entry = self.find_device_by_page(page).ok_or(MemAccessFault)?;
|
||||
entry.interface.write_byte(page - entry.base, offset, value)
|
||||
let (interface, addr) = self.mmio_root.get_device(addr).ok_or(MemoryException {
|
||||
type_: MemoryExceptionType::AccessFault,
|
||||
addr,
|
||||
})?;
|
||||
interface
|
||||
.write_byte(addr, value)
|
||||
.map_err(|e| e.with_addr(addr))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_atomic_dword(
|
||||
&self,
|
||||
page: PageNum,
|
||||
offset: u16,
|
||||
) -> Result<&AtomicU64, MemAccessFault> {
|
||||
if page_in_range(page, self.ram_start, self.ram.pages) {
|
||||
debug_assert!(((offset * 8) as usize) < PAGE_SIZE);
|
||||
let index = page * (PAGE_SIZE / 8) + (offset as usize);
|
||||
unsafe {
|
||||
self.ram
|
||||
.buf_transmuted::<AtomicU64>()
|
||||
.get(index)
|
||||
.ok_or(MemAccessFault)
|
||||
}
|
||||
} else {
|
||||
let entry = self.find_device_by_page(page).ok_or(MemAccessFault)?;
|
||||
entry.interface.get_atomic_dword(page - entry.base, offset)
|
||||
}
|
||||
}
|
||||
pub fn get_atomic_word(
|
||||
&self,
|
||||
page: PageNum,
|
||||
offset: u16,
|
||||
) -> Result<&AtomicU32, MemAccessFault> {
|
||||
if page_in_range(page, self.ram_start, self.ram.pages) {
|
||||
debug_assert!(((offset * 4) as usize) < PAGE_SIZE);
|
||||
let index = page * (PAGE_SIZE / 4) + (offset as usize);
|
||||
unsafe {
|
||||
self.ram
|
||||
.buf_transmuted::<AtomicU32>()
|
||||
.get(index)
|
||||
.ok_or(MemAccessFault)
|
||||
}
|
||||
} else {
|
||||
let entry = self.find_device_by_page(page).ok_or(MemAccessFault)?;
|
||||
entry.interface.get_atomic_word(page - entry.base, offset)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn page_in_range(page: PageNum, start: PageNum, pages: PageNum) -> bool {
|
||||
page >= start && page - start < pages
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@@ -191,32 +182,41 @@ pub enum MemoryMappingType {
|
||||
|
||||
pub struct Ram {
|
||||
buf: MmapMut,
|
||||
pages: PageNum,
|
||||
version_counters: Arc<[AtomicU32]>,
|
||||
}
|
||||
|
||||
#[cfg(target_endian = "big")]
|
||||
compile_error!("Current RAM implementation requires a little-endian host.");
|
||||
|
||||
impl Ram {
|
||||
pub fn try_new(pages: PageNum) -> Result<Self, std::io::Error> {
|
||||
pub fn try_new(size: usize) -> Result<Self, std::io::Error> {
|
||||
if !size.is_multiple_of(8) {
|
||||
return Err(std::io::Error::other("ram size must be a multiple of 8"));
|
||||
}
|
||||
Ok(Self {
|
||||
buf: MmapMut::map_anon(pages * PAGE_SIZE)?,
|
||||
pages,
|
||||
buf: MmapMut::map_anon(size)?,
|
||||
// SAFETY: We do not care about the initial version counts. Wrapping is fine. Only
|
||||
// equality is ever checked for, not magnitude.
|
||||
version_counters: unsafe {
|
||||
Arc::new_uninit_slice(size.div_ceil(Self::VERSION_CHUNK_SIZE)).assume_init()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
pub const VERSION_CHUNK_SIZE: usize = 64;
|
||||
|
||||
pub fn buf_mut(&mut self) -> &mut [u8] {
|
||||
self.buf.as_mut()
|
||||
}
|
||||
|
||||
pub fn pages(&self) -> PageNum {
|
||||
self.pages
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// Safe if T has a size divisible by page size (4kb) (or is known to have a size divisible by the full ram size) and you know that the RAM is made up of valid naturally aligned values of T
|
||||
/// Safe if the size of the memory in bytes is divisible by the size of T
|
||||
/// Assuming try_new is used, RAM size is guaranteed to be a multiple of 8
|
||||
/// meaning anything with size 1, 2, 4, or 8 bytes is valid.
|
||||
/// It must also be known that the contents of RAM are made up of naturally
|
||||
/// aligned valid instances of T.
|
||||
#[inline]
|
||||
pub unsafe fn buf_transmuted<T>(&self) -> &[T] {
|
||||
pub(crate) unsafe fn buf_transmuted<T>(&self) -> &[T] {
|
||||
debug_assert!(self.buf.len().is_multiple_of(std::mem::size_of::<T>()));
|
||||
unsafe {
|
||||
std::slice::from_raw_parts(
|
||||
@@ -232,140 +232,398 @@ impl Ram {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn read_dword(&self, page: PageNum, offset: u16) -> Result<DWord, MemAccessFault> {
|
||||
debug_assert!(((offset * 8) as usize) < PAGE_SIZE);
|
||||
let index = page * (PAGE_SIZE / 8) + (offset as usize);
|
||||
pub fn read_dword(&self, addr: u64) -> Result<u64, MemoryException> {
|
||||
if !addr.is_multiple_of(8) {
|
||||
let high_word_addr = addr.wrapping_add(4);
|
||||
|
||||
let low_word = self.read_word(addr)?;
|
||||
let high_word = self.read_word(high_word_addr)?;
|
||||
|
||||
return Ok((low_word as u64) | (high_word as u64) << 32);
|
||||
}
|
||||
|
||||
let index = (addr / 8) as usize;
|
||||
Ok(unsafe {
|
||||
self.buf_transmuted::<AtomicU64>()
|
||||
.get(index)
|
||||
.ok_or(MemAccessFault)
|
||||
.ok_or(MemoryException {
|
||||
type_: MemoryExceptionType::AccessFault,
|
||||
addr,
|
||||
})
|
||||
}?
|
||||
.load(Relaxed))
|
||||
}
|
||||
#[inline]
|
||||
pub fn read_word(&self, page: PageNum, offset: u16) -> Result<Word, MemAccessFault> {
|
||||
debug_assert!(((offset * 4) as usize) < PAGE_SIZE);
|
||||
let index = page * (PAGE_SIZE / 4) + (offset as usize);
|
||||
pub fn read_word(&self, addr: u64) -> Result<u32, MemoryException> {
|
||||
if !addr.is_multiple_of(4) {
|
||||
let high_hword_addr = addr.wrapping_add(2);
|
||||
|
||||
let low_hword = self.read_hword(addr)?;
|
||||
let high_hword = self.read_hword(high_hword_addr)?;
|
||||
|
||||
return Ok((low_hword as u32) | (high_hword as u32) << 16);
|
||||
}
|
||||
|
||||
let index = (addr / 4) as usize;
|
||||
Ok(unsafe {
|
||||
self.buf_transmuted::<AtomicU32>()
|
||||
.get(index)
|
||||
.ok_or(MemAccessFault)
|
||||
.ok_or(MemoryException {
|
||||
type_: MemoryExceptionType::AccessFault,
|
||||
addr,
|
||||
})
|
||||
}?
|
||||
.load(Relaxed))
|
||||
}
|
||||
#[inline]
|
||||
pub fn read_hword(&self, page: PageNum, offset: u16) -> Result<HWord, MemAccessFault> {
|
||||
debug_assert!(((offset * 2) as usize) < PAGE_SIZE);
|
||||
let index = page * (PAGE_SIZE / 2) + (offset as usize);
|
||||
pub fn read_hword(&self, addr: u64) -> Result<u16, MemoryException> {
|
||||
if !addr.is_multiple_of(2) {
|
||||
let high_byte_addr = addr.wrapping_add(1);
|
||||
|
||||
let low_byte = self.read_byte(addr)?;
|
||||
let high_byte = self.read_byte(high_byte_addr)?;
|
||||
|
||||
return Ok((low_byte as u16) | (high_byte as u16) << 8);
|
||||
}
|
||||
|
||||
let index = (addr / 2) as usize;
|
||||
Ok(unsafe {
|
||||
self.buf_transmuted::<AtomicU16>()
|
||||
.get(index)
|
||||
.ok_or(MemAccessFault)
|
||||
.ok_or(MemoryException {
|
||||
type_: MemoryExceptionType::AccessFault,
|
||||
addr,
|
||||
})
|
||||
}?
|
||||
.load(Relaxed))
|
||||
}
|
||||
#[inline]
|
||||
pub fn read_byte(&self, page: PageNum, offset: u16) -> Result<Byte, MemAccessFault> {
|
||||
debug_assert!((offset as usize) < PAGE_SIZE);
|
||||
let index = page * PAGE_SIZE + (offset as usize);
|
||||
pub fn read_byte(&self, addr: u64) -> Result<u8, MemoryException> {
|
||||
Ok(self
|
||||
.buf_atomic()
|
||||
.get(index)
|
||||
.ok_or(MemAccessFault)?
|
||||
.get(addr as usize)
|
||||
.ok_or(MemoryException {
|
||||
type_: MemoryExceptionType::AccessFault,
|
||||
addr,
|
||||
})?
|
||||
.load(Relaxed))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn write_dword(
|
||||
&self,
|
||||
page: PageNum,
|
||||
offset: u16,
|
||||
value: DWord,
|
||||
) -> Result<(), MemAccessFault> {
|
||||
debug_assert!(((offset * 8) as usize) < PAGE_SIZE);
|
||||
let index = page * (PAGE_SIZE / 8) + (offset as usize);
|
||||
pub fn write_dword(&self, addr: u64, value: u64) -> Result<(), MemoryException> {
|
||||
if !addr.is_multiple_of(8) {
|
||||
let low_word = value as u32;
|
||||
let high_word = (value >> 32) as u32;
|
||||
|
||||
let high_word_address = addr.wrapping_add(4);
|
||||
|
||||
self.write_word(addr, low_word)?;
|
||||
self.write_word(high_word_address, high_word)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.claim_addr_even(addr)
|
||||
.ok_or_else(|| MemoryExceptionType::AccessFault.with_addr(addr))?;
|
||||
|
||||
let index = (addr / 8) as usize;
|
||||
unsafe {
|
||||
self.buf_transmuted::<AtomicU64>()
|
||||
.get(index)
|
||||
.ok_or(MemAccessFault)
|
||||
.ok_or(MemoryException {
|
||||
type_: MemoryExceptionType::AccessFault,
|
||||
addr,
|
||||
})
|
||||
}?
|
||||
.store(value, Relaxed);
|
||||
Ok(())
|
||||
}
|
||||
#[inline]
|
||||
pub fn write_word(
|
||||
&self,
|
||||
page: PageNum,
|
||||
offset: u16,
|
||||
value: Word,
|
||||
) -> Result<(), MemAccessFault> {
|
||||
debug_assert!(((offset * 4) as usize) < PAGE_SIZE);
|
||||
let index = page * (PAGE_SIZE / 4) + (offset as usize);
|
||||
pub fn write_word(&self, addr: u64, value: u32) -> Result<(), MemoryException> {
|
||||
if !addr.is_multiple_of(4) {
|
||||
let low_hword = value as u16;
|
||||
let high_hword = (value >> 16) as u16;
|
||||
|
||||
let high_hword_address = addr.wrapping_add(2);
|
||||
|
||||
self.write_hword(addr, low_hword)?;
|
||||
self.write_hword(high_hword_address, high_hword)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.claim_addr_even(addr)
|
||||
.ok_or_else(|| MemoryExceptionType::AccessFault.with_addr(addr))?;
|
||||
|
||||
let index = (addr / 4) as usize;
|
||||
unsafe {
|
||||
self.buf_transmuted::<AtomicU32>()
|
||||
.get(index)
|
||||
.ok_or(MemAccessFault)
|
||||
.ok_or(MemoryException {
|
||||
type_: MemoryExceptionType::AccessFault,
|
||||
addr,
|
||||
})
|
||||
}?
|
||||
.store(value, Relaxed);
|
||||
Ok(())
|
||||
}
|
||||
#[inline]
|
||||
pub fn write_hword(
|
||||
&self,
|
||||
page: PageNum,
|
||||
offset: u16,
|
||||
value: HWord,
|
||||
) -> Result<(), MemAccessFault> {
|
||||
debug_assert!(((offset * 2) as usize) < PAGE_SIZE);
|
||||
let index = page * (PAGE_SIZE / 2) + (offset as usize);
|
||||
pub fn write_hword(&self, addr: u64, value: u16) -> Result<(), MemoryException> {
|
||||
if !addr.is_multiple_of(2) {
|
||||
let low_byte = value as u8;
|
||||
let high_byte = (value >> 8) as u8;
|
||||
|
||||
let high_byte_address = addr.wrapping_add(1);
|
||||
|
||||
self.write_byte(addr, low_byte)?;
|
||||
self.write_byte(high_byte_address, high_byte)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.claim_addr_even(addr)
|
||||
.ok_or_else(|| MemoryExceptionType::AccessFault.with_addr(addr))?;
|
||||
|
||||
let index = (addr / 2) as usize;
|
||||
unsafe {
|
||||
self.buf_transmuted::<AtomicU16>()
|
||||
.get(index)
|
||||
.ok_or(MemAccessFault)
|
||||
.ok_or(MemoryException {
|
||||
type_: MemoryExceptionType::AccessFault,
|
||||
addr,
|
||||
})
|
||||
}?
|
||||
.store(value, Relaxed);
|
||||
Ok(())
|
||||
}
|
||||
#[inline]
|
||||
pub fn write_byte(
|
||||
&self,
|
||||
page: PageNum,
|
||||
offset: u16,
|
||||
value: Byte,
|
||||
) -> Result<(), MemAccessFault> {
|
||||
debug_assert!((offset as usize) < PAGE_SIZE);
|
||||
let index = page * PAGE_SIZE + (offset as usize);
|
||||
pub fn write_byte(&self, addr: u64, value: u8) -> Result<(), MemoryException> {
|
||||
self.claim_addr_even(addr)
|
||||
.ok_or_else(|| MemoryExceptionType::AccessFault.with_addr(addr))?;
|
||||
self.buf_atomic()
|
||||
.get(index)
|
||||
.ok_or(MemAccessFault)?
|
||||
.get(addr as usize)
|
||||
.ok_or(MemoryException {
|
||||
type_: MemoryExceptionType::AccessFault,
|
||||
addr,
|
||||
})?
|
||||
.store(value, Relaxed);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn claim_addr_even<'a>(&'a self, addr: u64) -> Option<RamVersionClaim<'a>> {
|
||||
let chunk_id = addr as usize / Self::VERSION_CHUNK_SIZE;
|
||||
let chunk_counter = self.version_counters.get(chunk_id)?;
|
||||
Some(RamVersionClaim::claim_even(&chunk_counter))
|
||||
}
|
||||
|
||||
// Tries to create a claim for a specified chunk id with a specific version
|
||||
// Outer Option represents whether the chunk id exists
|
||||
// Inner Option represents whether the claim succeeded
|
||||
pub fn claim_expected<'a>(
|
||||
&'a self,
|
||||
chunk_id: usize,
|
||||
expected: u32,
|
||||
) -> Option<Option<RamVersionClaim<'a>>> {
|
||||
self.version_counters
|
||||
.get(chunk_id)
|
||||
.map(|chunk_counter| RamVersionClaim::claim_expected(chunk_counter, expected))
|
||||
}
|
||||
|
||||
/// Waits for a specific address to have an even (ready) version
|
||||
/// number and returns the version chunk id and version
|
||||
pub fn wait_for_even_version(&self, addr: u64) -> Option<(usize, u32)> {
|
||||
let chunk_id = addr as usize / Self::VERSION_CHUNK_SIZE;
|
||||
let chunk_counter = self.version_counters.get(chunk_id)?;
|
||||
|
||||
loop {
|
||||
let current_version = chunk_counter.load(Ordering::Acquire);
|
||||
if current_version.is_multiple_of(2) {
|
||||
return Some((chunk_id, current_version));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RamVersionClaim<'a> {
|
||||
version_counter: &'a AtomicU32,
|
||||
initial_version: u32,
|
||||
}
|
||||
|
||||
impl<'a> RamVersionClaim<'a> {
|
||||
pub fn claim_even(counter: &'a AtomicU32) -> RamVersionClaim<'a> {
|
||||
loop {
|
||||
let current_version = counter.load(Ordering::Acquire);
|
||||
if !current_version.is_multiple_of(2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Attempt to increment and therefore successfully claim the version
|
||||
let res = counter.compare_exchange(
|
||||
current_version,
|
||||
current_version.wrapping_add(1),
|
||||
Ordering::AcqRel,
|
||||
Ordering::Acquire,
|
||||
);
|
||||
|
||||
if let Ok(initial_version) = res {
|
||||
return RamVersionClaim {
|
||||
version_counter: counter,
|
||||
initial_version,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn claim_expected(counter: &'a AtomicU32, expected: u32) -> Option<RamVersionClaim<'a>> {
|
||||
counter
|
||||
.compare_exchange(
|
||||
expected,
|
||||
expected.wrapping_add(1),
|
||||
Ordering::AcqRel,
|
||||
Ordering::Acquire,
|
||||
)
|
||||
.ok()
|
||||
.map(|initial_version| RamVersionClaim {
|
||||
version_counter: counter,
|
||||
initial_version,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Drop for RamVersionClaim<'a> {
|
||||
fn drop(&mut self) {
|
||||
self.version_counter
|
||||
.store(self.initial_version.wrapping_add(2), Ordering::Release);
|
||||
}
|
||||
}
|
||||
|
||||
pub const MMIO_SECOND_LEVEL_PAGE_SIZE: usize = 64 * 1024;
|
||||
pub const MMIO_ROOT_PAGE_SIZE: usize = MMIO_SECOND_LEVEL_PAGE_SIZE * 64;
|
||||
|
||||
const MMIO_ROOT_ENTRIES: usize = RAM_START as usize / MMIO_ROOT_PAGE_SIZE;
|
||||
const MMIO_SECOND_LEVEL_ENTRIES: usize = MMIO_ROOT_PAGE_SIZE / MMIO_SECOND_LEVEL_PAGE_SIZE;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MmioRoot(Box<[Option<MmioSecondLevel>; MMIO_ROOT_ENTRIES]>);
|
||||
|
||||
impl MmioRoot {
|
||||
pub fn insert(&mut self, base_addr: u64, interface: Arc<dyn MemDeviceInterface>) {
|
||||
assert!(base_addr.is_multiple_of(MMIO_SECOND_LEVEL_PAGE_SIZE as u64));
|
||||
assert!(base_addr < RAM_START);
|
||||
|
||||
let page_id = base_addr as usize / MMIO_SECOND_LEVEL_PAGE_SIZE;
|
||||
let root_page_id = page_id / MMIO_SECOND_LEVEL_ENTRIES;
|
||||
let second_level_page_id = page_id % MMIO_SECOND_LEVEL_ENTRIES;
|
||||
|
||||
let second_level = self.0[root_page_id].get_or_insert_default();
|
||||
|
||||
if let MmioSecondLevel::SubTable(t) = second_level {
|
||||
t[second_level_page_id] = Some(interface);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_full(&mut self, base_addr: u64, interface: Arc<dyn MemDeviceInterface>) {
|
||||
assert!(base_addr.is_multiple_of(MMIO_ROOT_PAGE_SIZE as u64));
|
||||
assert!(base_addr < RAM_START);
|
||||
|
||||
let page_id = base_addr as usize / MMIO_ROOT_PAGE_SIZE;
|
||||
|
||||
self.0[page_id] = Some(MmioSecondLevel::Interface(interface));
|
||||
}
|
||||
|
||||
fn get_device(&self, addr: u64) -> Option<(Arc<dyn MemDeviceInterface>, u64)> {
|
||||
debug_assert!(addr < RAM_START);
|
||||
|
||||
let page_id = addr as usize / MMIO_SECOND_LEVEL_PAGE_SIZE;
|
||||
let root_page_id = page_id / MMIO_SECOND_LEVEL_ENTRIES;
|
||||
|
||||
self.0[root_page_id]
|
||||
.as_ref()
|
||||
.and_then(|s| s.get_device(addr % MMIO_ROOT_PAGE_SIZE as u64))
|
||||
}
|
||||
|
||||
fn crosses_boundary(&self, addr: u64, size: u64) -> bool {
|
||||
if addr >= RAM_START {
|
||||
return false;
|
||||
}
|
||||
|
||||
if addr + size > RAM_START {
|
||||
return true;
|
||||
}
|
||||
|
||||
let page_id = addr as usize / MMIO_SECOND_LEVEL_PAGE_SIZE;
|
||||
let root_page_id = page_id / MMIO_SECOND_LEVEL_ENTRIES;
|
||||
let end = addr + size - 1;
|
||||
|
||||
match self.0[root_page_id].as_ref() {
|
||||
Some(s) => match s {
|
||||
MmioSecondLevel::SubTable(_) => {
|
||||
let end_page_id = end as usize / MMIO_SECOND_LEVEL_PAGE_SIZE;
|
||||
page_id != end_page_id
|
||||
}
|
||||
MmioSecondLevel::Interface(_) => {
|
||||
let end_root_page_id = end as usize / MMIO_ROOT_PAGE_SIZE;
|
||||
root_page_id != end_root_page_id
|
||||
}
|
||||
},
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MmioRoot {
|
||||
fn default() -> Self {
|
||||
Self(Box::new([(); MMIO_ROOT_ENTRIES].map(|_| None)))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DeviceEntry {
|
||||
pub base: PageNum,
|
||||
pub size: PageNum,
|
||||
pub interface: Arc<dyn MemDeviceInterface>,
|
||||
enum MmioSecondLevel {
|
||||
SubTable(Box<[Option<Arc<dyn MemDeviceInterface>>; MMIO_SECOND_LEVEL_ENTRIES]>),
|
||||
Interface(Arc<dyn MemDeviceInterface>),
|
||||
}
|
||||
|
||||
impl MmioSecondLevel {
|
||||
fn get_device(&self, addr: u64) -> Option<(Arc<dyn MemDeviceInterface>, u64)> {
|
||||
let page_id = addr as usize / MMIO_SECOND_LEVEL_PAGE_SIZE;
|
||||
match self {
|
||||
Self::SubTable(t) => t[page_id]
|
||||
.as_ref()
|
||||
.map(|i| (i.clone(), addr % MMIO_SECOND_LEVEL_PAGE_SIZE as u64)),
|
||||
|
||||
Self::Interface(i) => Some((i.clone(), addr)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MmioSecondLevel {
|
||||
fn default() -> Self {
|
||||
Self::SubTable(Box::new([(); MMIO_SECOND_LEVEL_ENTRIES].map(|_| None)))
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
pub trait MemDeviceInterface {
|
||||
fn write_dword(&self, page: PageNum, offset: u16, value: DWord) -> Result<(), MemAccessFault>;
|
||||
fn write_word(&self, page: PageNum, offset: u16, value: Word) -> Result<(), MemAccessFault>;
|
||||
fn write_hword(&self, page: PageNum, offset: u16, value: HWord) -> Result<(), MemAccessFault>;
|
||||
fn write_byte(&self, page: PageNum, offset: u16, value: Byte) -> Result<(), MemAccessFault>;
|
||||
|
||||
fn read_dword(&self, page: PageNum, offset: u16) -> Result<DWord, MemAccessFault>;
|
||||
fn read_word(&self, page: PageNum, offset: u16) -> Result<Word, MemAccessFault>;
|
||||
fn read_hword(&self, page: PageNum, offset: u16) -> Result<HWord, MemAccessFault>;
|
||||
fn read_byte(&self, page: PageNum, offset: u16) -> Result<Byte, MemAccessFault>;
|
||||
|
||||
fn get_atomic_word(&self, page: PageNum, offset: u16) -> Result<&AtomicU32, MemAccessFault>;
|
||||
fn get_atomic_dword(&self, page: PageNum, offset: u16) -> Result<&AtomicU64, MemAccessFault>;
|
||||
fn write_dword(&self, addr: u64, value: u64) -> Result<(), MemoryExceptionType> {
|
||||
Err(MemoryExceptionType::AccessFault)
|
||||
}
|
||||
fn write_word(&self, addr: u64, value: u32) -> Result<(), MemoryExceptionType> {
|
||||
Err(MemoryExceptionType::AccessFault)
|
||||
}
|
||||
fn write_hword(&self, addr: u64, value: u16) -> Result<(), MemoryExceptionType> {
|
||||
Err(MemoryExceptionType::AccessFault)
|
||||
}
|
||||
fn write_byte(&self, addr: u64, value: u8) -> Result<(), MemoryExceptionType> {
|
||||
Err(MemoryExceptionType::AccessFault)
|
||||
}
|
||||
|
||||
/// Error that means something has gone wrong accessing memory
|
||||
/// Examples are: Accessing unmapped memory, accessing an MMIO register at the wrong size
|
||||
#[derive(Debug)]
|
||||
pub struct MemAccessFault;
|
||||
fn read_dword(&self, addr: u64) -> Result<u64, MemoryExceptionType> {
|
||||
Err(MemoryExceptionType::AccessFault)
|
||||
}
|
||||
fn read_word(&self, addr: u64) -> Result<u32, MemoryExceptionType> {
|
||||
Err(MemoryExceptionType::AccessFault)
|
||||
}
|
||||
fn read_hword(&self, addr: u64) -> Result<u16, MemoryExceptionType> {
|
||||
Err(MemoryExceptionType::AccessFault)
|
||||
}
|
||||
fn read_byte(&self, addr: u64) -> Result<u8, MemoryExceptionType> {
|
||||
Err(MemoryExceptionType::AccessFault)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user