Compare commits

...

68 Commits

Author SHA1 Message Date
6b6b778ab0 some cleanup 2026-01-29 21:26:44 +01:00
bbfa20befe Replace custom UART with a sifive uart subset 2026-01-29 19:07:43 +01:00
36e6ec1006 Implement Zalrsc 2026-01-13 16:46:53 +01:00
d3e8af85a6 Add the files and decode logic for RVA 2026-01-12 17:25:02 +01:00
3451a8227c Remove RamVersionClaim::reset as ive figured out it wont be needed 2026-01-09 20:23:11 +01:00
d1b4cc7b56 Add some documentation in the cli help output 2026-01-06 23:14:37 +01:00
9861187fa6 Implement the memory version system that will be necessary for LR/SC 2026-01-06 21:50:47 +01:00
07e755340e remove get_atomic_(d)word because its not used yet and the idea for how atomics will be done will have to be reworked 2026-01-02 16:21:20 +01:00
ceb7f2f172 Apply some clippy-suggested fixes 2026-01-02 12:55:42 +01:00
21fb6cbc8b Switch from std::mpsc channels to crossbeam 2026-01-02 12:44:50 +01:00
bbc9e0b9ff 2026! Updating LICENSE file now and individual file comments when those files are updated 2026-01-01 09:15:25 +01:00
7fcfc031ef Stop rust-analyzer from complaining about the big endian host error 2025-12-31 13:19:04 +01:00
21a8479ce9 Make MMIO devices not have control of the address of exceptions 2025-12-31 13:16:32 +01:00
09fe12f516 Change some ordering in core.rs and deduplicate core command handling 2025-12-31 09:33:14 +01:00
0f0e844223 Finish RV64M 2025-12-30 20:53:57 +01:00
5a383956c9 Improve exception dumps and general debug info, make the emulator capable of running the riscv ISA tests, and perform some general fixes i found while making it pass the tests for RV64I 2025-12-30 20:18:23 +01:00
6a0e5e63c1 Implement DIV 2025-12-30 17:27:42 +01:00
e5c5312566 Implement MUL 2025-12-30 16:56:09 +01:00
9a9bef7dd7 Remove consts.rs and just use plain types 2025-12-28 12:01:39 +01:00
8024af6b13 Implement ECALL and EBREAK, the final RV64I instructions! 2025-12-27 21:47:22 +01:00
5c008bfc04 Add exception values (what will go in mtval/stval) 2025-12-27 21:33:39 +01:00
b5d36b7969 Initial FENCE implementation 2025-12-27 21:03:24 +01:00
970c1adcb0 Add checks to make sure that ram has a size that is a multiple of 8 2025-12-27 20:52:32 +01:00
6a3920895b Relicense to BSD 2-Clause to align better with the RISC-V community 2025-12-27 12:44:55 +01:00
67406a9c48 Fix some warnings 2025-12-27 11:55:19 +01:00
9f8e9ec380 Implement a GDB stub and fix another huge issue in S-type immediate decoding 2025-12-27 11:48:36 +01:00
a64fcaa3b5 Make execload respect the static ram start 2025-12-26 19:32:55 +01:00
34034dd5db Make macros for R/I-type operations and use them to implement basically every single one i think 2025-12-26 18:14:32 +01:00
75e843f5f9 Make branches macros and implement all of them 2025-12-26 16:06:30 +01:00
528b519ce9 (BIG CHANGE) memory handling has changed, MMIO is now a 2 level page table, misaligned access supported, addresses not internally split to page and offset immediately, all load/store instructions implemented. Might still have bugs 2025-12-26 14:20:27 +01:00
6d9efb7eb8 Small refactor in exception handling in core.rs 2025-12-24 16:14:54 +01:00
44394b3d19 Update README to mention ELF support 2025-12-24 14:11:29 +01:00
66c63ab63c Add a default implementation for the memory device interface that just returns access faults 2025-12-24 14:06:16 +01:00
09d9064372 EXCEPTION SYSTEM (initial version - may change later) 2025-12-24 13:56:41 +01:00
3f789442c0 some linker script updates to work even more properly for newlib i think 2025-12-24 11:42:55 +01:00
96c2cbf7ae remove unused imports in main.rs 2025-12-23 20:04:14 +01:00
8ed4845d58 ADD ELF SUPPORT 2025-12-23 19:56:42 +01:00
36faa1e39c Add license headers to files missing them 2025-12-23 19:22:11 +01:00
43bae12ea0 Comment out the unused 'Pause' instruction result 2025-12-23 18:46:38 +01:00
0c6a540a85 Implement SRLI 2025-12-23 18:42:50 +01:00
23392a55df Implement SH 2025-12-23 18:31:04 +01:00
f38114dbd7 Remove some debug messages i forgot 2025-12-23 11:01:28 +01:00
c6da147d50 Implement BLT 2025-12-23 09:51:53 +01:00
643a39c24a Fix s-type immediate decoding 2025-12-23 09:51:32 +01:00
1b409cd14e Improve error messaging 2025-12-23 09:51:09 +01:00
976bd688b0 Remove an unused import in main.rs 2025-12-23 08:57:43 +01:00
0ac363e203 Implement LW 2025-12-22 22:48:57 +01:00
7a22570a0f Improve the debug messages when invalid instructions are found (again) 2025-12-22 22:46:45 +01:00
2b5eb96187 Implement BLTU 2025-12-22 21:17:38 +01:00
be1b1b9fe6 Implement LH 2025-12-22 21:15:24 +01:00
5cbaf2dc66 Implement BGEU 2025-12-22 20:08:16 +01:00
ae57cdc691 Improve the debug messages when invalid instructions are found 2025-12-22 19:57:33 +01:00
bac68d7118 Pull out memory access instructions from rvi.rs to their own file 2025-12-22 19:51:21 +01:00
8cce960b29 Implement SW 2025-12-22 19:44:37 +01:00
cb100e92ac Implement SUB 2025-12-22 19:33:40 +01:00
d0d3775b88 Implement OR 2025-12-22 19:29:31 +01:00
1ddda6614a Implement AND and improve formatting and ordering in rvi.rs 2025-12-22 19:25:19 +01:00
ff161a69e6 Implement ADD 2025-12-22 19:19:19 +01:00
e00103375d Fix page offset miscalculation in instruction fetch 2025-12-22 18:28:31 +01:00
7177633477 WHY WAS I USING S-TYPE IMMEDIATE IN LD (also add some more debugging info on an exception) 2025-12-22 18:00:15 +01:00
48477bd8b1 Make echo.S compatible with the C-compatible linker script 2025-12-21 22:51:29 +01:00
24dcf5d5bd Improve UART by using nonblocking stdin 2025-12-21 21:25:29 +01:00
209e44ae64 Implement LD and BNE 2025-12-21 21:00:25 +01:00
5b2d6a1af0 Fix memory size in link.ld 2025-12-21 20:04:06 +01:00
a2d4dec417 Add some stuff to help with using C in link.ld 2025-12-21 19:38:32 +01:00
6c39a5eef2 Implement JALR, fix JAL, change how some stuff in instructions.rs is expressed 2025-12-21 19:36:25 +01:00
944ed573c6 Switch the current binary to use anyhow errors and add a proper argument number check 2025-12-21 19:06:23 +01:00
2e1c0a7dce Implement AUIPC 2025-12-21 19:01:02 +01:00
28 changed files with 2655 additions and 800 deletions

2
.rust-analyzer.toml Normal file
View File

@@ -0,0 +1,2 @@
[diagnostics]
disabled = ["inactive-code"]

354
Cargo.lock generated
View File

@@ -2,12 +2,229 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 4 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]] [[package]]
name = "libc" name = "libc"
version = "0.2.176" version = "0.2.176"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174"
[[package]]
name = "log"
version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]] [[package]]
name = "memmap2" name = "memmap2"
version = "0.9.8" version = "0.9.8"
@@ -17,9 +234,146 @@ dependencies = [
"libc", "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]] [[package]]
name = "trve" name = "trve"
version = "0.0.0" version = "0.0.0"
dependencies = [ dependencies = [
"anyhow",
"clap",
"crossbeam",
"goblin",
"int-enum",
"memmap2", "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",
] ]

View File

@@ -4,4 +4,11 @@ version = "0.0.0"
edition = "2024" edition = "2024"
[dependencies] [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" memmap2 = "0.9.8"
nix = { version = "0.30.1", features = ["fs"] }
oneshot = "0.1.11"

19
LICENSE
View File

@@ -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 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
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:
The above copyright notice and this permission notice shall be included in all copies or substantial 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.
portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 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.
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.

View File

@@ -4,21 +4,31 @@ taitep's RISC-V Emulator.
The goal is to support at least RV64GC and be able to run Linux, The goal is to support at least RV64GC and be able to run Linux,
potentially more. No plans for RV32I or RV32/64E. potentially more. No plans for RV32I or RV32/64E.
Currently implemented RISC-V ISA: `RV64IM-Zalrsc`
## Current Use ## Current Use
Currently, the emulator is nowhere near complete, Currently, the emulator is nowhere near complete,
its not even at rv64i, but it does work for a subset of it. 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, 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. 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 - byte `0`: Data. When written, writes out the character
When read, reads a character from the buffer, or 0 if empty. 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 - 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. the UART is ready to be written to. Currently always 1.
Next least significant is `RX_READY`, indicates whether the read buffer Next least significant is `RX_READY`, indicates whether the read buffer
has any data to read. 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
View File

@@ -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
View File

@@ -1,22 +1,46 @@
ENTRY(_start) ENTRY(_start)
SECTIONS { MEMORY {
. = 0x80000000; RAM (rwx) : ORIGIN = 0x80000000, LENGTH = 16M
}
SECTIONS {
.text : ALIGN(4) { .text : ALIGN(4) {
*(.text*) *(.text*)
} } > RAM
.rodata : ALIGN(8) { .rodata : ALIGN(8) {
*(.rodata*) *(.rodata*)
} } > RAM
.data : ALIGN(8) { .data : ALIGN(8) {
_data_start = .;
*(.data*) *(.data*)
} _data_end = .;
} > RAM
.bss : ALIGN(8) { .bss : ALIGN(8) {
_bss_start = .;
*(.bss*) *(.bss*)
*(COMMON) *(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
View 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

View File

@@ -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)
}
}

View File

@@ -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;

View File

@@ -1,92 +1,197 @@
// Copyright (c) 2025 taitep // Copyright (c) 2025-2026 taitep
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: BSD-2-Clause
// //
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve) // This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
// See LICENSE file in the project root for full license text. // See LICENSE file in the project root for full license text.
use std::collections::HashSet;
use crate::{ use crate::{
consts::{Addr, RegId, RegValue}, core::commands::CoreCmd,
decode::Instruction, decode::Instruction,
exceptions::{Exception, ExceptionType, MemoryException},
gdb::{self, DebugCommand, DebugStream, StopReason},
instructions::find_and_exec, instructions::find_and_exec,
mem::MemConfig, 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 struct Core {
pub(crate) x_regs: [RegValue; 32], pub(crate) x_regs: [u64; 32],
pub(crate) pc: Addr, pub(crate) pc: u64,
pub(crate) mem: MemConfig, 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 { impl Core {
pub fn new(mem: MemConfig) -> Self { pub fn new(mem: MemConfig, command_stream: crossbeam::channel::Receiver<CoreCmd>) -> Self {
Self { Self {
x_regs: [0; 32], x_regs: [0; 32],
pc: 0, pc: 0,
mem, mem,
command_stream,
reservation: None,
} }
} }
pub fn run(&mut self) { pub fn run(&mut self) {
loop { loop {
let page = (self.pc / 4096) as usize; if let Ok(cmd) = self.command_stream.try_recv() {
let offset = (self.pc / 4) as u16; self.handle_command(cmd);
if !self.pc.is_multiple_of(4) {
//replace eprint with logging, replace break with exception
eprintln!("PC not aligned");
break;
} }
let instr = match self.mem.read_word(page, offset) { if let Err(e) = self.step() {
Ok(i) => i, self.throw_exception(e);
Err(_) => {
eprintln!("Memory access fault while fetching instruction");
break;
}
};
assert_eq!(instr & 3, 3, "Compressed instructions not supported");
let instr = Instruction(instr);
let res = find_and_exec(instr, self);
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;
}
}
} else {
eprintln!("Invalid Instruction 0x{:08x} 0b{:032b}", instr.0, instr.0);
break; break;
} }
} }
} }
pub fn reset(&mut self, pc: Addr) { 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());
}
};
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);
if let Err(e) = find_and_exec(instr, self) {
dbg!(instr);
return Err(e);
}
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!("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());
}
}
}
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; 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] 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 { if id == 0 {
return; return;
} }

11
src/core/commands.rs Normal file
View 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),
}

View File

@@ -1,15 +1,21 @@
// Copyright (c) 2025 taitep // 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) // This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
// See LICENSE file in the project root for full license text. // 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)] #[derive(Clone, Copy)]
pub struct Instruction(pub Word); 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)] #[allow(dead_code)]
impl Instruction { impl Instruction {
@@ -26,8 +32,8 @@ impl Instruction {
} }
#[inline] #[inline]
pub fn rd(self) -> RegId { pub fn rd(self) -> u8 {
(self.0 >> 7 & MASK_REGISTER) as RegId (self.0 >> 7 & MASK_REGISTER) as u8
} }
#[inline] #[inline]
@@ -36,64 +42,86 @@ impl Instruction {
} }
#[inline] #[inline]
pub fn rs1(self) -> RegId { pub fn rs1(self) -> u8 {
(self.0 >> 15 & MASK_REGISTER) as RegId (self.0 >> 15 & MASK_REGISTER) as u8
} }
#[inline] #[inline]
pub fn rs2(self) -> RegId { pub fn rs2(self) -> u8 {
(self.0 >> 20 & MASK_REGISTER) as RegId (self.0 >> 20 & MASK_REGISTER) as u8
} }
#[inline] #[inline]
pub fn funct7(self) -> u8 { pub fn funct7(self) -> u8 {
(self.0 >> 25 & 0x7f) as u8 (self.0 >> 25) as u8
} }
#[inline] #[inline]
pub fn imm_i(self) -> DWord { pub fn funct5(self) -> u8 {
(self.0 as i32 as i64 >> 20) as DWord (self.0 >> 27) as u8
} }
#[inline] #[inline]
pub fn imm_s(self) -> DWord { pub fn imm_i(self) -> u64 {
(self.0 as i32 as i64 >> (25 - 5) & (0x7f << 5)) as DWord | (self.0 >> 7 & 0b1111) as DWord (self.0 as i32 as i64 >> 20) as u64
} }
#[inline] #[inline]
pub fn imm_b(self) -> DWord { pub fn imm_s(self) -> u64 {
let imm_12 = ((self.0 & 0x8000_0000) as i32 as i64 >> (31 - 12)) as DWord; let imm_11_5 = (self.0 as i32 as i64 >> 25 << 5) as u64;
let imm_10_5 = ((self.0 >> 25 & 0x3f) << 5) as DWord; let imm_4_0 = (self.0 >> 7 & 0x1f) as u64;
let imm_4_1 = ((self.0 >> 8 & 0xf) << 1) as DWord; imm_11_5 | imm_4_0
let imm_11 = ((self.0 >> 7 & 1) << 11) as DWord; }
#[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 imm_12 | imm_10_5 | imm_4_1 | imm_11
} }
#[inline] #[inline]
pub fn imm_u(self) -> DWord { pub fn imm_u(self) -> u64 {
(self.0 & 0xffff_f000) as i32 as i64 as DWord (self.0 & 0xffff_f000) as i32 as i64 as u64
} }
#[inline] #[inline]
pub fn imm_j(self) -> DWord { pub fn imm_j(self) -> u64 {
let imm_20 = ((self.0 & 0x8000_0000) as i32 as i64 >> (31 - 20)) as DWord; 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 DWord; let imm_10_1 = ((self.0 >> 21 & 0x3ff) << 1) as u64;
let imm_11 = ((self.0 >> 20 & 1) << 11) as DWord; let imm_11 = ((self.0 >> 20 & 1) << 11) as u64;
let imm_19_12 = ((self.0 >> 12 & 0xff) << 12) as DWord; let imm_19_12 = ((self.0 >> 12 & 0xff) << 12) as u64;
imm_20 | imm_10_1 | imm_11 | imm_19_12 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] #[inline]
pub fn funct6(self) -> u8 { pub fn funct6(self) -> u8 {
(self.0 >> 26 & 0x3f) as u8 (self.0 >> 26) as u8
} }
/// Mostly/only used for the SYSTEM opcode
#[inline] #[inline]
pub fn imm_shamt(self) -> usize { pub fn funct12(self) -> u16 {
(self.0 >> 20 & 0x3f) as usize (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
View File

@@ -0,0 +1 @@
pub mod serial;

140
src/devices/serial.rs Normal file
View 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
View 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
View 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
View 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
View 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 &reg in &regs.x_regs {
hex.push_str(
&reg.to_le_bytes()
.iter()
.map(|b| format!("{b:02x}"))
.collect::<String>(),
);
}
hex.push_str(
&regs
.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(())
}

View File

@@ -1,53 +1,173 @@
// Copyright (c) 2025 taitep // 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) // This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
// See LICENSE file in the project root for full license text. // See LICENSE file in the project root for full license text.
#[macro_use]
mod macros;
mod rva;
mod rvi; mod rvi;
mod rvm;
use crate::{ use crate::{
core::{Core, InstructionResult}, core::Core,
decode::Instruction, 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() { 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() { 0b00100 => match instr.funct3() {
// OP_IMM // 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() { 0b001 => match instr.funct6() {
// left-shift immediate 0 => rvi::slli(core, instr),
0b000000 => Some(rvi::slli(core, instr)), _ => illegal(instr),
_ => None,
}, },
0b111 => Some(rvi::andi(core, instr)), 0b101 => match instr.funct6() {
_ => None, 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() { 0b00110 => match instr.funct3() {
// OP_IMM_32 // OP_IMM_32
0b000 => Some(rvi::addiw(core, instr)), 0b000 => rvi::addiw(core, instr),
_ => None, 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() { 0b01000 => match instr.funct3() {
// STORE // STORE
0b000 => Some(rvi::sb(core, instr)), 0b000 => rvi::sb(core, instr),
0b011 => Some(rvi::sd(core, instr)), 0b001 => rvi::sh(core, instr),
_ => None, 0b010 => rvi::sw(core, instr),
0b011 => rvi::sd(core, instr),
_ => illegal(instr),
}, },
0b00000 => match instr.funct3() { 0b00000 => match instr.funct3() {
// LOAD // LOAD
0b000 => Some(rvi::lb(core, instr)), 0b000 => rvi::lb(core, instr),
0b100 => Some(rvi::lbu(core, instr)), 0b100 => rvi::lbu(core, instr),
_ => None, 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() { 0b11000 => match instr.funct3() {
// BRANCH // BRANCH
0b000 => Some(rvi::beq(core, instr)), 0b000 => rvi::beq(core, instr),
_ => None, 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)), 0b01101 => rvi::lui(core, instr),
0b11011 => Some(rvi::jal(core, instr)), 0b00101 => rvi::auipc(core, instr),
_ => None, 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),
} }
} }

View 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
View 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
}

View File

@@ -1,142 +1,78 @@
// Copyright (c) 2025 taitep // 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) // This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
// See LICENSE file in the project root for full license text. // See LICENSE file in the project root for full license text.
use crate::{ use crate::{core::Core, decode::Instruction, exceptions::Exception};
consts::{Addr, DWord},
core::{Core, InstructionResult},
decode::Instruction,
mem::PageNum,
};
pub fn addi(core: &mut Core, instr: Instruction) -> InstructionResult { use std::ops::{BitAnd, BitOr, BitXor};
core.reg_write(
instr.rd(),
core.reg_read(instr.rs1()).wrapping_add(instr.imm_i()),
);
core.advance_pc(); mod mem;
InstructionResult::Normal pub use mem::*;
}
pub fn addiw(core: &mut Core, instr: Instruction) -> InstructionResult { instr_op!(add, addi, u64::wrapping_add);
let res = core.reg_read(instr.rs1()).wrapping_add(instr.imm_i()) as i32; 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);
core.reg_write(instr.rd(), res as i64 as u64); instr_op!(and, andi, u64::bitand);
instr_op!(or, ori, u64::bitor);
instr_op!(xor, xori, u64::bitxor);
core.advance_pc(); 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
);
InstructionResult::Normal instr_op!(sltu, sltiu, |a, b| (a < b) as u64);
} instr_op!(slt, slti, |a, b| ((a as i64) < (b as i64)) as u64);
pub fn andi(core: &mut Core, instr: Instruction) -> InstructionResult { pub fn lui(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
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 {
core.reg_write(instr.rd(), instr.imm_u()); core.reg_write(instr.rd(), instr.imm_u());
core.advance_pc(); core.advance_pc();
InstructionResult::Normal Ok(())
} }
pub fn jal(core: &mut Core, instr: Instruction) -> InstructionResult { pub fn auipc(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
core.reg_write(instr.rd(), core.pc); core.reg_write(instr.rd(), core.pc.wrapping_add(instr.imm_u()));
core.pc = core.pc.wrapping_add(instr.imm_j());
InstructionResult::Normal
}
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();
}
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(); core.advance_pc();
Ok(())
InstructionResult::Normal
} }
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());
Ok(())
}
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(())
}
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
View 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
View 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);

View File

@@ -1,5 +1,7 @@
pub mod consts;
pub mod core; pub mod core;
mod decode; mod decode;
pub mod devices;
pub mod exceptions;
pub mod gdb;
mod instructions; mod instructions;
pub mod mem; pub mod mem;

View File

@@ -1,168 +1,97 @@
// Copyright (c) 2025 taitep // Copyright (c) 2025-2026 taitep
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: BSD-2-Clause
// //
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve) // This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
// See LICENSE file in the project root for full license text. // See LICENSE file in the project root for full license text.
use std::{ use std::{io, path::PathBuf, sync::Arc, time::Duration};
env,
error::Error,
fs::File,
io::{self, Read},
sync::Arc,
time::Duration,
};
use clap::Parser;
use nix::fcntl::{FcntlArg, OFlag, fcntl};
use trve::{ use trve::{
consts::{Byte, DWord, HWord, Word},
core::Core, 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> { mod execload;
let mut file = File::open(path)?;
let mut total_read = 0;
while total_read < buffer.len() { /// Taitep's RISC-V Emulator
let n = file.read(&mut buffer[total_read..])?; #[derive(Parser)]
if n == 0 { struct Args {
return Ok(total_read); /// Path to ELF or raw binary executable to load
} executable: PathBuf,
total_read += n; /// 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"));
}
Ok(total_read)
} }
fn main() -> Result<(), Box<dyn Error>> { fn main() -> Result<()> {
let mut ram = Ram::try_new(16 * 1024 * 1024 / 4096)?; let args = Args::parse();
let mut ram = Ram::try_new(16 * 1024 * 1024)?;
let buf = ram.buf_mut(); let buf = ram.buf_mut();
let args: Vec<String> = env::args().collect(); let entry_point = execload::load(args.executable, buf)?;
if args.len() != 2 { let mut mmio_root = MmioRoot::default();
eprintln!("USAGE: trve <ram_image>") 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 = devices::serial::SifiveUart::new_arc();
uart.clone()
let uart = BasicUart::new(); .spawn_io_thread(io::stdin(), io::stdout(), Duration::from_millis(10));
let uart = uart.spawn_poller(Duration::from_millis(10)); mmio_root.insert(0x10000, uart);
let mem_cfg = MemConfig { let mem_cfg = MemConfig {
ram: Arc::new(ram), ram: Arc::new(ram),
ram_start: 0x8000_0000 / 4096, mmio_root,
devices: Box::new([
DeviceEntry {
base: 0,
size: 1,
interface: Arc::new(DbgOut),
},
DeviceEntry {
base: 1,
size: 1,
interface: uart,
},
]),
}; };
let mut core = Core::new(mem_cfg); let (cmd_sender, cmd_reciever) = crossbeam::channel::bounded(16);
core.reset(0x8000_0000);
core.run(); 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(()) Ok(())
} }
mod basic_uart;
struct DbgOut; struct DbgOut;
impl MemDeviceInterface for DbgOut { impl MemDeviceInterface for DbgOut {
fn write_dword( fn write_dword(&self, addr: u64, value: u64) -> Result<(), MemoryExceptionType> {
&self, eprintln!("Wrote DWord {value:016x} to Debug-Out address {addr:x}");
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
);
Ok(()) Ok(())
} }
fn write_word( fn write_word(&self, addr: u64, value: u32) -> Result<(), MemoryExceptionType> {
&self, eprintln!("Wrote Word {value:08x} to Debug-Out address {addr:x}");
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
);
Ok(()) Ok(())
} }
fn write_hword( fn write_hword(&self, addr: u64, value: u16) -> Result<(), MemoryExceptionType> {
&self, eprintln!("Wrote HWord {value:04x} to Debug-Out address {addr:x}");
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
);
Ok(()) Ok(())
} }
fn write_byte( fn write_byte(&self, addr: u64, value: u8) -> Result<(), MemoryExceptionType> {
&self, eprintln!("Wrote Byte {value:02x} to Debug-Out address {addr:x}");
page: PageNum,
offset: u16,
value: Byte,
) -> Result<(), trve::mem::MemAccessFault> {
eprintln!("Wrote Byte {value:02x} to Debug-Out page {page}, offset {offset}");
Ok(()) 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)
}
} }

View File

@@ -1,186 +1,177 @@
// Copyright (c) 2025 taitep // Copyright (c) 2025-2026 taitep
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: BSD-2-Clause
// //
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve) // This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
// See LICENSE file in the project root for full license text. // See LICENSE file in the project root for full license text.
use std::sync::{ use std::sync::{
Arc, Arc,
atomic::{AtomicU8, AtomicU16, AtomicU32, AtomicU64, Ordering::Relaxed}, atomic::{
AtomicU8, AtomicU16, AtomicU32, AtomicU64,
Ordering::{self, Relaxed},
},
}; };
use memmap2::MmapMut; use memmap2::MmapMut;
use crate::consts::{Byte, DWord, HWord, Word}; use crate::exceptions::{MemoryException, MemoryExceptionType};
pub type PageNum = usize; pub type PageNum = usize;
const PAGE_SIZE: usize = 4096; pub const RAM_START: u64 = 0x8000_0000;
#[derive(Clone)] #[derive(Clone)]
pub struct MemConfig { pub struct MemConfig {
pub ram: Arc<Ram>, pub ram: Arc<Ram>,
pub ram_start: PageNum, pub mmio_root: MmioRoot,
pub devices: Box<[DeviceEntry]>,
} }
impl MemConfig { impl MemConfig {
#[allow(clippy::needless_borrow)] pub fn memory_mapping_type(&self, addr: u64) -> Option<MemoryMappingType> {
pub fn find_device_by_page(&self, page: PageNum) -> Option<&DeviceEntry> { if addr >= RAM_START {
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) {
Some(MemoryMappingType::RAM) Some(MemoryMappingType::RAM)
} else { } else {
self.find_device_by_page(page) self.mmio_root
.map(|_x| MemoryMappingType::MMIO) .get_device(addr)
.map(|_| MemoryMappingType::MMIO)
} }
} }
pub fn read_dword(&self, page: PageNum, offset: u16) -> Result<DWord, MemAccessFault> { pub fn read_dword(&self, addr: u64) -> Result<u64, MemoryException> {
if page_in_range(page, self.ram_start, self.ram.pages) { if addr >= RAM_START {
self.ram.read_dword(page - self.ram_start, offset) self.ram.read_dword(addr - RAM_START)
} else { } 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 {
entry.interface.read_dword(page - entry.base, offset) type_: MemoryExceptionType::AddressMisaligned,
} 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)
} else {
let entry = self.find_device_by_page(page).ok_or(MemAccessFault)?;
entry.interface.read_word(page - entry.base, offset)
}
}
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)
} else {
let entry = self.find_device_by_page(page).ok_or(MemAccessFault)?;
entry.interface.read_hword(page - entry.base, offset)
}
}
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)
} else {
let entry = self.find_device_by_page(page).ok_or(MemAccessFault)?;
entry.interface.read_byte(page - entry.base, offset)
}
}
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)
} else {
let entry = self.find_device_by_page(page).ok_or(MemAccessFault)?;
entry
.interface
.write_dword(page - entry.base, offset, value)
}
}
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)
} else {
let entry = self.find_device_by_page(page).ok_or(MemAccessFault)?;
entry.interface.write_word(page - entry.base, offset, value)
}
}
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)
} else {
let entry = self.find_device_by_page(page).ok_or(MemAccessFault)?;
entry
.interface
.write_hword(page - entry.base, offset, value)
}
}
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)
} else {
let entry = self.find_device_by_page(page).ok_or(MemAccessFault)?;
entry.interface.write_byte(page - entry.base, offset, value)
}
}
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 (interface, addr) = self.mmio_root.get_device(addr).ok_or(MemoryException {
let entry = self.find_device_by_page(page).ok_or(MemAccessFault)?; type_: MemoryExceptionType::AccessFault,
entry.interface.get_atomic_dword(page - entry.base, offset) addr,
} })?;
}
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 { interface.read_dword(addr).map_err(|e| e.with_addr(addr))
page >= start && page - start < pages }
}
pub fn read_word(&self, addr: u64) -> Result<u32, MemoryException> {
if addr >= RAM_START {
self.ram.read_word(addr - RAM_START)
} else {
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.read_word(addr).map_err(|e| e.with_addr(addr))
}
}
pub fn read_hword(&self, addr: u64) -> Result<u16, MemoryException> {
if addr >= RAM_START {
self.ram.read_hword(addr - RAM_START)
} else {
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, addr: u64) -> Result<u8, MemoryException> {
if addr >= RAM_START {
self.ram.read_byte(addr - RAM_START)
} else {
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, addr: u64, value: u64) -> Result<(), MemoryException> {
if addr >= RAM_START {
self.ram.write_dword(addr - RAM_START, value)
} else {
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, addr: u64, value: u32) -> Result<(), MemoryException> {
if addr >= RAM_START {
self.ram.write_word(addr - RAM_START, value)
} else {
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, addr: u64, value: u16) -> Result<(), MemoryException> {
if addr >= RAM_START {
self.ram.write_hword(addr - RAM_START, value)
} else {
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, addr: u64, value: u8) -> Result<(), MemoryException> {
if addr >= RAM_START {
self.ram.write_byte(addr - RAM_START, value)
} else {
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))
}
}
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -191,32 +182,41 @@ pub enum MemoryMappingType {
pub struct Ram { pub struct Ram {
buf: MmapMut, buf: MmapMut,
pages: PageNum, version_counters: Arc<[AtomicU32]>,
} }
#[cfg(target_endian = "big")] #[cfg(target_endian = "big")]
compile_error!("Current RAM implementation requires a little-endian host."); compile_error!("Current RAM implementation requires a little-endian host.");
impl Ram { 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 { Ok(Self {
buf: MmapMut::map_anon(pages * PAGE_SIZE)?, buf: MmapMut::map_anon(size)?,
pages, // 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] { pub fn buf_mut(&mut self) -> &mut [u8] {
self.buf.as_mut() self.buf.as_mut()
} }
pub fn pages(&self) -> PageNum {
self.pages
}
/// # Safety /// # 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] #[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>())); debug_assert!(self.buf.len().is_multiple_of(std::mem::size_of::<T>()));
unsafe { unsafe {
std::slice::from_raw_parts( std::slice::from_raw_parts(
@@ -232,140 +232,398 @@ impl Ram {
} }
#[inline] #[inline]
pub fn read_dword(&self, page: PageNum, offset: u16) -> Result<DWord, MemAccessFault> { pub fn read_dword(&self, addr: u64) -> Result<u64, MemoryException> {
debug_assert!(((offset * 8) as usize) < PAGE_SIZE); if !addr.is_multiple_of(8) {
let index = page * (PAGE_SIZE / 8) + (offset as usize); 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 { Ok(unsafe {
self.buf_transmuted::<AtomicU64>() self.buf_transmuted::<AtomicU64>()
.get(index) .get(index)
.ok_or(MemAccessFault) .ok_or(MemoryException {
type_: MemoryExceptionType::AccessFault,
addr,
})
}? }?
.load(Relaxed)) .load(Relaxed))
} }
#[inline] #[inline]
pub fn read_word(&self, page: PageNum, offset: u16) -> Result<Word, MemAccessFault> { pub fn read_word(&self, addr: u64) -> Result<u32, MemoryException> {
debug_assert!(((offset * 4) as usize) < PAGE_SIZE); if !addr.is_multiple_of(4) {
let index = page * (PAGE_SIZE / 4) + (offset as usize); 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 { Ok(unsafe {
self.buf_transmuted::<AtomicU32>() self.buf_transmuted::<AtomicU32>()
.get(index) .get(index)
.ok_or(MemAccessFault) .ok_or(MemoryException {
type_: MemoryExceptionType::AccessFault,
addr,
})
}? }?
.load(Relaxed)) .load(Relaxed))
} }
#[inline] #[inline]
pub fn read_hword(&self, page: PageNum, offset: u16) -> Result<HWord, MemAccessFault> { pub fn read_hword(&self, addr: u64) -> Result<u16, MemoryException> {
debug_assert!(((offset * 2) as usize) < PAGE_SIZE); if !addr.is_multiple_of(2) {
let index = page * (PAGE_SIZE / 2) + (offset as usize); 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 { Ok(unsafe {
self.buf_transmuted::<AtomicU16>() self.buf_transmuted::<AtomicU16>()
.get(index) .get(index)
.ok_or(MemAccessFault) .ok_or(MemoryException {
type_: MemoryExceptionType::AccessFault,
addr,
})
}? }?
.load(Relaxed)) .load(Relaxed))
} }
#[inline] #[inline]
pub fn read_byte(&self, page: PageNum, offset: u16) -> Result<Byte, MemAccessFault> { pub fn read_byte(&self, addr: u64) -> Result<u8, MemoryException> {
debug_assert!((offset as usize) < PAGE_SIZE);
let index = page * PAGE_SIZE + (offset as usize);
Ok(self Ok(self
.buf_atomic() .buf_atomic()
.get(index) .get(addr as usize)
.ok_or(MemAccessFault)? .ok_or(MemoryException {
type_: MemoryExceptionType::AccessFault,
addr,
})?
.load(Relaxed)) .load(Relaxed))
} }
#[inline] #[inline]
pub fn write_dword( pub fn write_dword(&self, addr: u64, value: u64) -> Result<(), MemoryException> {
&self, if !addr.is_multiple_of(8) {
page: PageNum, let low_word = value as u32;
offset: u16, let high_word = (value >> 32) as u32;
value: DWord,
) -> Result<(), MemAccessFault> { let high_word_address = addr.wrapping_add(4);
debug_assert!(((offset * 8) as usize) < PAGE_SIZE);
let index = page * (PAGE_SIZE / 8) + (offset as usize); 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 { unsafe {
self.buf_transmuted::<AtomicU64>() self.buf_transmuted::<AtomicU64>()
.get(index) .get(index)
.ok_or(MemAccessFault) .ok_or(MemoryException {
type_: MemoryExceptionType::AccessFault,
addr,
})
}? }?
.store(value, Relaxed); .store(value, Relaxed);
Ok(()) Ok(())
} }
#[inline] #[inline]
pub fn write_word( pub fn write_word(&self, addr: u64, value: u32) -> Result<(), MemoryException> {
&self, if !addr.is_multiple_of(4) {
page: PageNum, let low_hword = value as u16;
offset: u16, let high_hword = (value >> 16) as u16;
value: Word,
) -> Result<(), MemAccessFault> { let high_hword_address = addr.wrapping_add(2);
debug_assert!(((offset * 4) as usize) < PAGE_SIZE);
let index = page * (PAGE_SIZE / 4) + (offset as usize); 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 { unsafe {
self.buf_transmuted::<AtomicU32>() self.buf_transmuted::<AtomicU32>()
.get(index) .get(index)
.ok_or(MemAccessFault) .ok_or(MemoryException {
type_: MemoryExceptionType::AccessFault,
addr,
})
}? }?
.store(value, Relaxed); .store(value, Relaxed);
Ok(()) Ok(())
} }
#[inline] #[inline]
pub fn write_hword( pub fn write_hword(&self, addr: u64, value: u16) -> Result<(), MemoryException> {
&self, if !addr.is_multiple_of(2) {
page: PageNum, let low_byte = value as u8;
offset: u16, let high_byte = (value >> 8) as u8;
value: HWord,
) -> Result<(), MemAccessFault> { let high_byte_address = addr.wrapping_add(1);
debug_assert!(((offset * 2) as usize) < PAGE_SIZE);
let index = page * (PAGE_SIZE / 2) + (offset as usize); 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 { unsafe {
self.buf_transmuted::<AtomicU16>() self.buf_transmuted::<AtomicU16>()
.get(index) .get(index)
.ok_or(MemAccessFault) .ok_or(MemoryException {
type_: MemoryExceptionType::AccessFault,
addr,
})
}? }?
.store(value, Relaxed); .store(value, Relaxed);
Ok(()) Ok(())
} }
#[inline] #[inline]
pub fn write_byte( pub fn write_byte(&self, addr: u64, value: u8) -> Result<(), MemoryException> {
&self, self.claim_addr_even(addr)
page: PageNum, .ok_or_else(|| MemoryExceptionType::AccessFault.with_addr(addr))?;
offset: u16,
value: Byte,
) -> Result<(), MemAccessFault> {
debug_assert!((offset as usize) < PAGE_SIZE);
let index = page * PAGE_SIZE + (offset as usize);
self.buf_atomic() self.buf_atomic()
.get(index) .get(addr as usize)
.ok_or(MemAccessFault)? .ok_or(MemoryException {
type_: MemoryExceptionType::AccessFault,
addr,
})?
.store(value, Relaxed); .store(value, Relaxed);
Ok(()) 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)] #[derive(Clone)]
pub struct DeviceEntry { enum MmioSecondLevel {
pub base: PageNum, SubTable(Box<[Option<Arc<dyn MemDeviceInterface>>; MMIO_SECOND_LEVEL_ENTRIES]>),
pub size: PageNum, Interface(Arc<dyn MemDeviceInterface>),
pub 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 { pub trait MemDeviceInterface {
fn write_dword(&self, page: PageNum, offset: u16, value: DWord) -> Result<(), MemAccessFault>; fn write_dword(&self, addr: u64, value: u64) -> Result<(), MemoryExceptionType> {
fn write_word(&self, page: PageNum, offset: u16, value: Word) -> Result<(), MemAccessFault>; Err(MemoryExceptionType::AccessFault)
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 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)
}
fn read_dword(&self, page: PageNum, offset: u16) -> Result<DWord, MemAccessFault>; fn read_dword(&self, addr: u64) -> Result<u64, MemoryExceptionType> {
fn read_word(&self, page: PageNum, offset: u16) -> Result<Word, MemAccessFault>; Err(MemoryExceptionType::AccessFault)
fn read_hword(&self, page: PageNum, offset: u16) -> Result<HWord, MemAccessFault>; }
fn read_byte(&self, page: PageNum, offset: u16) -> Result<Byte, MemAccessFault>; fn read_word(&self, addr: u64) -> Result<u32, MemoryExceptionType> {
Err(MemoryExceptionType::AccessFault)
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 read_hword(&self, addr: u64) -> Result<u16, MemoryExceptionType> {
Err(MemoryExceptionType::AccessFault)
}
fn read_byte(&self, addr: u64) -> Result<u8, 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;