Compare commits
29 Commits
528b519ce9
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 6b6b778ab0 | |||
| bbfa20befe | |||
| 36e6ec1006 | |||
| d3e8af85a6 | |||
| 3451a8227c | |||
| d1b4cc7b56 | |||
| 9861187fa6 | |||
| 07e755340e | |||
| ceb7f2f172 | |||
| 21fb6cbc8b | |||
| bbc9e0b9ff | |||
| 7fcfc031ef | |||
| 21a8479ce9 | |||
| 09fe12f516 | |||
| 0f0e844223 | |||
| 5a383956c9 | |||
| 6a0e5e63c1 | |||
| e5c5312566 | |||
| 9a9bef7dd7 | |||
| 8024af6b13 | |||
| 5c008bfc04 | |||
| b5d36b7969 | |||
| 970c1adcb0 | |||
| 6a3920895b | |||
| 67406a9c48 | |||
| 9f8e9ec380 | |||
| a64fcaa3b5 | |||
| 34034dd5db | |||
| 75e843f5f9 |
2
.rust-analyzer.toml
Normal file
2
.rust-analyzer.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[diagnostics]
|
||||
disabled = ["inactive-code"]
|
||||
206
Cargo.lock
generated
206
Cargo.lock
generated
@@ -2,6 +2,56 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell_polyfill",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.100"
|
||||
@@ -26,6 +76,108 @@ 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"
|
||||
@@ -37,6 +189,12 @@ dependencies = [
|
||||
"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"
|
||||
@@ -49,6 +207,12 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.176"
|
||||
@@ -82,6 +246,18 @@ dependencies = [
|
||||
"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"
|
||||
@@ -138,6 +314,12 @@ dependencies = [
|
||||
"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"
|
||||
@@ -154,10 +336,13 @@ name = "trve"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"crossbeam",
|
||||
"goblin",
|
||||
"int-enum",
|
||||
"memmap2",
|
||||
"nix",
|
||||
"oneshot",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -166,8 +351,29 @@ 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",
|
||||
]
|
||||
|
||||
@@ -5,7 +5,10 @@ edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.100"
|
||||
clap = { version = "4.5.53", features = ["derive"] }
|
||||
crossbeam = { version = "0.8.4", features = ["crossbeam-channel"] }
|
||||
goblin = "0.10.4"
|
||||
int-enum = "1.2.0"
|
||||
memmap2 = "0.9.8"
|
||||
nix = { version = "0.30.1", features = ["fs"] }
|
||||
oneshot = "0.1.11"
|
||||
|
||||
19
LICENSE
19
LICENSE
@@ -1,18 +1,9 @@
|
||||
MIT License
|
||||
Copyright 2025-2026 taitep
|
||||
|
||||
Copyright (c) 2025 taitep
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
associated documentation files (the "Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
|
||||
following conditions:
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial
|
||||
portions of the Software.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
||||
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
|
||||
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
10
README.md
10
README.md
@@ -4,6 +4,8 @@ taitep's RISC-V Emulator.
|
||||
The goal is to support at least RV64GC and be able to run Linux,
|
||||
potentially more. No plans for RV32I or RV32/64E.
|
||||
|
||||
Currently implemented RISC-V ISA: `RV64IM-Zalrsc`
|
||||
|
||||
## Current Use
|
||||
Currently, the emulator is nowhere near complete,
|
||||
its not even at rv64i, but it does work for a subset of it.
|
||||
@@ -12,6 +14,10 @@ The emulator will load a raw binary image or static ELF executable from a file s
|
||||
which starts at 0x80000000 and is currently 16MiB,
|
||||
and start execution at the start of the image/ram or the ELF entry point.
|
||||
|
||||
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.
|
||||
|
||||
@@ -22,3 +28,7 @@ There is also a UART at `0x00010000`-`0x00010002`, the interface is quite simple
|
||||
the UART is ready to be written to. Currently always 1.
|
||||
Next least significant is `RX_READY`, indicates whether the read buffer
|
||||
has any data to read.
|
||||
|
||||
## Licensing
|
||||
This project is licensed under the BSD 2-Clause license.
|
||||
See the LICENSE file in the project root
|
||||
|
||||
18
echo.S
18
echo.S
@@ -1,18 +0,0 @@
|
||||
.section .text
|
||||
.globl _start
|
||||
|
||||
.equ UART_DATA, 0
|
||||
.equ UART_STATUS, 1
|
||||
.equ UART_RX_READY, 0b10
|
||||
.equ UART_TX_READY, 0b01
|
||||
|
||||
_start:
|
||||
li a0, 0x10000
|
||||
|
||||
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
|
||||
12
run-riscv-tests.sh
Executable file
12
run-riscv-tests.sh
Executable file
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
|
||||
for f in $(cat torun.txt); do
|
||||
result=$(cargo run $f 2>&1 | tail -n 1 | awk '{print $NF}')
|
||||
if [[ $result != 0 ]]; then
|
||||
testnum=$(( result >> 1 ))
|
||||
echo $f: test $testnum failed
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
echo all tests passed
|
||||
@@ -1,97 +0,0 @@
|
||||
// Copyright (c) 2025 taitep
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
||||
// See LICENSE file in the project root for full license text.
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::io;
|
||||
use std::os::fd::AsFd;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use nix::fcntl::fcntl;
|
||||
use nix::fcntl::{FcntlArg, OFlag};
|
||||
use trve::consts::{Addr, Byte};
|
||||
use trve::exceptions::MemoryExceptionType;
|
||||
use trve::mem::MemDeviceInterface;
|
||||
|
||||
/// byte 0: rx/tx
|
||||
/// byte 1: status (------rt, r=rxready, t=txready)/none
|
||||
pub struct BasicUart {
|
||||
rx_buf: Mutex<VecDeque<u8>>,
|
||||
}
|
||||
|
||||
impl BasicUart {
|
||||
pub fn new() -> Self {
|
||||
// Make sure stdin is nonblocking
|
||||
let stdin = io::stdin();
|
||||
let fd = stdin.as_fd();
|
||||
let flags = fcntl(fd, FcntlArg::F_GETFL).unwrap();
|
||||
fcntl(
|
||||
fd,
|
||||
FcntlArg::F_SETFL(OFlag::from_bits_truncate(flags) | OFlag::O_NONBLOCK),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
BasicUart {
|
||||
rx_buf: Mutex::new(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) {
|
||||
print!("{}", byte as char);
|
||||
}
|
||||
|
||||
fn read(&self) -> u8 {
|
||||
self.rx_buf.lock().unwrap().pop_front().unwrap_or(0)
|
||||
}
|
||||
|
||||
fn can_read(&self) -> bool {
|
||||
!self.rx_buf.lock().unwrap().is_empty()
|
||||
}
|
||||
|
||||
pub fn poll(&self) {
|
||||
let mut rx_buf = self.rx_buf.lock().unwrap();
|
||||
|
||||
let mut buffer = [0u8; 1];
|
||||
while let Ok(1) = nix::unistd::read(io::stdin().as_fd(), &mut buffer) {
|
||||
rx_buf.push_back(buffer[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MemDeviceInterface for BasicUart {
|
||||
fn write_byte(&self, addr: Addr, value: Byte) -> Result<(), MemoryExceptionType> {
|
||||
match addr {
|
||||
0 => {
|
||||
self.write(value);
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(MemoryExceptionType::AccessFault),
|
||||
}
|
||||
}
|
||||
fn read_byte(&self, addr: Addr) -> Result<Byte, MemoryExceptionType> {
|
||||
match addr {
|
||||
0 => Ok(self.read()),
|
||||
1 => Ok(1 | (self.can_read() as u8) << 1),
|
||||
_ => Err(MemoryExceptionType::AccessFault),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
165
src/core.rs
165
src/core.rs
@@ -1,84 +1,197 @@
|
||||
// Copyright (c) 2025 taitep
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2025-2026 taitep
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
//
|
||||
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
||||
// See LICENSE file in the project root for full license text.
|
||||
|
||||
use std::fmt::format;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::{
|
||||
consts::{Addr, RegId, RegValue},
|
||||
core::commands::CoreCmd,
|
||||
decode::Instruction,
|
||||
exceptions::ExceptionType,
|
||||
exceptions::{Exception, ExceptionType, MemoryException},
|
||||
gdb::{self, DebugCommand, DebugStream, StopReason},
|
||||
instructions::find_and_exec,
|
||||
mem::MemConfig,
|
||||
};
|
||||
|
||||
pub struct Core {
|
||||
pub(crate) x_regs: [RegValue; 32],
|
||||
pub(crate) pc: Addr,
|
||||
pub(crate) x_regs: [u64; 32],
|
||||
pub(crate) pc: u64,
|
||||
pub(crate) mem: MemConfig,
|
||||
command_stream: crossbeam::channel::Receiver<CoreCmd>,
|
||||
// LR/SC reservation set. Pair of the RAM version block index and expected version.
|
||||
pub(crate) reservation: Option<(usize, u32)>,
|
||||
}
|
||||
|
||||
pub mod commands;
|
||||
|
||||
impl Core {
|
||||
pub fn new(mem: MemConfig) -> Self {
|
||||
pub fn new(mem: MemConfig, command_stream: crossbeam::channel::Receiver<CoreCmd>) -> Self {
|
||||
Self {
|
||||
x_regs: [0; 32],
|
||||
pc: 0,
|
||||
mem,
|
||||
command_stream,
|
||||
reservation: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(&mut self) {
|
||||
loop {
|
||||
if !self.pc.is_multiple_of(4) {
|
||||
self.throw_exception(ExceptionType::InstructionAddressMisaligned);
|
||||
if let Ok(cmd) = self.command_stream.try_recv() {
|
||||
self.handle_command(cmd);
|
||||
}
|
||||
|
||||
if let Err(e) = self.step() {
|
||||
self.throw_exception(e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn step(&mut self) -> Result<(), Exception> {
|
||||
if !self.pc.is_multiple_of(4) {
|
||||
self.throw_exception(Exception {
|
||||
type_: ExceptionType::InstructionAddressMisaligned,
|
||||
value: self.pc,
|
||||
});
|
||||
}
|
||||
|
||||
let instr = match self.mem.read_word(self.pc) {
|
||||
Ok(i) => i,
|
||||
Err(e) => {
|
||||
self.throw_exception(e.to_exception_instr());
|
||||
break;
|
||||
return Err(e.into_exception_instr());
|
||||
}
|
||||
};
|
||||
|
||||
if instr == 0 {
|
||||
self.throw_exception(ExceptionType::IllegalInstruction);
|
||||
break;
|
||||
}
|
||||
|
||||
if instr & 3 != 3 {
|
||||
// Compressed instruction - (currently) unsupported
|
||||
self.throw_exception(ExceptionType::IllegalInstruction);
|
||||
break;
|
||||
// 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);
|
||||
eprintln!("instr: {:08x}", instr.0);
|
||||
break;
|
||||
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_type: ExceptionType) {
|
||||
eprintln!("Exception: {exception_type:?}");
|
||||
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: Addr) {
|
||||
pub fn reset(&mut self, pc: u64) {
|
||||
self.pc = pc;
|
||||
}
|
||||
|
||||
pub(crate) fn reg_read(&self, id: RegId) -> RegValue {
|
||||
pub(crate) fn reg_read(&self, id: u8) -> u64 {
|
||||
self.x_regs[id as usize]
|
||||
}
|
||||
|
||||
pub(crate) fn reg_write(&mut self, id: RegId, value: RegValue) {
|
||||
pub(crate) fn reg_write(&mut self, id: u8, value: u64) {
|
||||
if id == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
11
src/core/commands.rs
Normal file
11
src/core/commands.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
// Copyright (c) 2025 taitep
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
//
|
||||
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
||||
// See LICENSE file in the project root for full license text.
|
||||
|
||||
use crate::gdb;
|
||||
|
||||
pub enum CoreCmd {
|
||||
EnterDbgMode(gdb::DebugStream),
|
||||
}
|
||||
@@ -1,15 +1,21 @@
|
||||
// Copyright (c) 2025 taitep
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
//
|
||||
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
||||
// See LICENSE file in the project root for full license text.
|
||||
|
||||
use crate::consts::{DWord, RegId, Word};
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
const MASK_REGISTER: Word = 0x1f;
|
||||
const MASK_REGISTER: u32 = 0x1f;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Instruction(pub Word);
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Instruction(pub u32);
|
||||
|
||||
impl std::fmt::Debug for Instruction {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_fmt(format_args!("{:08x}", self.0))
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl Instruction {
|
||||
@@ -26,8 +32,8 @@ impl Instruction {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn rd(self) -> RegId {
|
||||
(self.0 >> 7 & MASK_REGISTER) as RegId
|
||||
pub fn rd(self) -> u8 {
|
||||
(self.0 >> 7 & MASK_REGISTER) as u8
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -36,64 +42,86 @@ impl Instruction {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn rs1(self) -> RegId {
|
||||
(self.0 >> 15 & MASK_REGISTER) as RegId
|
||||
pub fn rs1(self) -> u8 {
|
||||
(self.0 >> 15 & MASK_REGISTER) as u8
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn rs2(self) -> RegId {
|
||||
(self.0 >> 20 & MASK_REGISTER) as RegId
|
||||
pub fn rs2(self) -> u8 {
|
||||
(self.0 >> 20 & MASK_REGISTER) as u8
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn funct7(self) -> u8 {
|
||||
(self.0 >> 25 & 0x7f) as u8
|
||||
(self.0 >> 25) as u8
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn imm_i(self) -> DWord {
|
||||
(self.0 as i32 as i64 >> 20) as DWord
|
||||
pub fn funct5(self) -> u8 {
|
||||
(self.0 >> 27) as u8
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn imm_s(self) -> DWord {
|
||||
(self.0 as i32 as i64 >> (25 - 5) & (0x7f << 5)) as DWord | (self.0 >> 7 & 0b11111) as DWord
|
||||
pub fn imm_i(self) -> u64 {
|
||||
(self.0 as i32 as i64 >> 20) as u64
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn imm_b(self) -> DWord {
|
||||
let imm_12 = ((self.0 & 0x8000_0000) as i32 as i64 >> (31 - 12)) as DWord;
|
||||
let imm_10_5 = ((self.0 >> 25 & 0x3f) << 5) as DWord;
|
||||
let imm_4_1 = ((self.0 >> 8 & 0xf) << 1) as DWord;
|
||||
let imm_11 = ((self.0 >> 7 & 1) << 11) as DWord;
|
||||
pub fn imm_s(self) -> u64 {
|
||||
let imm_11_5 = (self.0 as i32 as i64 >> 25 << 5) as u64;
|
||||
let imm_4_0 = (self.0 >> 7 & 0x1f) as u64;
|
||||
imm_11_5 | imm_4_0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn imm_b(self) -> u64 {
|
||||
let imm_12 = ((self.0 & 0x8000_0000) as i32 as i64 >> (31 - 12)) as u64;
|
||||
let imm_10_5 = ((self.0 >> 25 & 0x3f) << 5) as u64;
|
||||
let imm_4_1 = ((self.0 >> 8 & 0xf) << 1) as u64;
|
||||
let imm_11 = ((self.0 >> 7 & 1) << 11) as u64;
|
||||
|
||||
imm_12 | imm_10_5 | imm_4_1 | imm_11
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn imm_u(self) -> DWord {
|
||||
(self.0 & 0xffff_f000) as i32 as i64 as DWord
|
||||
pub fn imm_u(self) -> u64 {
|
||||
(self.0 & 0xffff_f000) as i32 as i64 as u64
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn imm_j(self) -> DWord {
|
||||
let imm_20 = ((self.0 & 0x8000_0000) as i32 as i64 >> (31 - 20)) as DWord;
|
||||
let imm_10_1 = ((self.0 >> 21 & 0x3ff) << 1) as DWord;
|
||||
let imm_11 = ((self.0 >> 20 & 1) << 11) as DWord;
|
||||
let imm_19_12 = ((self.0 >> 12 & 0xff) << 12) as DWord;
|
||||
pub fn imm_j(self) -> u64 {
|
||||
let imm_20 = ((self.0 & 0x8000_0000) as i32 as i64 >> (31 - 20)) as u64;
|
||||
let imm_10_1 = ((self.0 >> 21 & 0x3ff) << 1) as u64;
|
||||
let imm_11 = ((self.0 >> 20 & 1) << 11) as u64;
|
||||
let imm_19_12 = ((self.0 >> 12 & 0xff) << 12) as u64;
|
||||
|
||||
imm_20 | imm_10_1 | imm_11 | imm_19_12
|
||||
}
|
||||
|
||||
// The following are AFAIK only used for shift by immediate operations
|
||||
|
||||
/// Technically part of immediate. Only used to determine shift type for immediate shifts afaik
|
||||
/// 32bit ones use funct7 in this way
|
||||
#[inline]
|
||||
pub fn funct6(self) -> u8 {
|
||||
(self.0 >> 26 & 0x3f) as u8
|
||||
(self.0 >> 26) as u8
|
||||
}
|
||||
|
||||
/// Mostly/only used for the SYSTEM opcode
|
||||
#[inline]
|
||||
pub fn imm_shamt(self) -> usize {
|
||||
(self.0 >> 20 & 0x3f) as usize
|
||||
pub fn funct12(self) -> u16 {
|
||||
(self.0 >> 20) as u16
|
||||
}
|
||||
|
||||
/// Looks at the aq/rl bits of atomic instructions and converts to an Ordering
|
||||
#[inline]
|
||||
pub fn amo_ordering(self) -> Ordering {
|
||||
let aq = self.0 >> 26 & 1 != 0;
|
||||
let rl = self.0 >> 25 & 1 != 0;
|
||||
|
||||
match (aq, rl) {
|
||||
(false, false) => Ordering::Relaxed,
|
||||
(false, true) => Ordering::Release,
|
||||
(true, false) => Ordering::Acquire,
|
||||
(true, true) => Ordering::AcqRel,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1
src/devices.rs
Normal file
1
src/devices.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod serial;
|
||||
140
src/devices/serial.rs
Normal file
140
src/devices/serial.rs
Normal file
@@ -0,0 +1,140 @@
|
||||
// Copyright (c) 2026 taitep
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
//
|
||||
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
||||
// See LICENSE file in the project root for full license text.
|
||||
|
||||
use std::{
|
||||
io::{Read, Write},
|
||||
sync::{Arc, Mutex},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
mod fifo;
|
||||
use fifo::UartFifo;
|
||||
|
||||
use crate::{exceptions::MemoryExceptionType, mem::MemDeviceInterface};
|
||||
|
||||
pub struct SifiveUart {
|
||||
rx: Mutex<(UartFifo<2048>, bool)>,
|
||||
tx: Mutex<(UartFifo<2048>, bool)>,
|
||||
}
|
||||
|
||||
impl SifiveUart {
|
||||
pub fn new_arc() -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
rx: Mutex::new((UartFifo::default(), false)),
|
||||
tx: Mutex::new((UartFifo::default(), false)),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn spawn_io_thread<R: Read + Send + 'static, T: Write + Send + Sync + 'static>(
|
||||
self: Arc<Self>,
|
||||
mut rx_backend: R,
|
||||
mut tx_backend: T,
|
||||
interval: Duration,
|
||||
) {
|
||||
std::thread::spawn(move || {
|
||||
loop {
|
||||
{
|
||||
// Read data
|
||||
let mut rx_guard = self.rx.lock().expect("could not lock uart RX half");
|
||||
let (rx_buf, rx_en) = &mut *rx_guard;
|
||||
|
||||
if *rx_en {
|
||||
let _ = rx_buf.read_from(&mut rx_backend);
|
||||
}
|
||||
}
|
||||
{
|
||||
// Write data
|
||||
let mut tx_guard = self.tx.lock().expect("could not lock uart RX half");
|
||||
let (tx_buf, tx_en) = &mut *tx_guard;
|
||||
|
||||
if *tx_en {
|
||||
let _ = tx_buf.write_to(&mut tx_backend);
|
||||
let _ = tx_backend.flush();
|
||||
}
|
||||
}
|
||||
|
||||
std::thread::sleep(interval);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl MemDeviceInterface for SifiveUart {
|
||||
fn write_word(&self, addr: u64, value: u32) -> Result<(), MemoryExceptionType> {
|
||||
// dbg!(addr, value);
|
||||
match addr {
|
||||
0x00 => {
|
||||
// TXDATA
|
||||
let (ref mut tx_buf, _) = *self.tx.lock().expect("could not lock uart TX half");
|
||||
tx_buf.push_single_byte(value as u8);
|
||||
Ok(())
|
||||
}
|
||||
0x08 => {
|
||||
// TXCTRL
|
||||
let (_, ref mut tx_en) = *self.tx.lock().expect("could not lock uart TX half");
|
||||
*tx_en = value & 1 != 0;
|
||||
Ok(())
|
||||
}
|
||||
0x04 => Ok(()), // RXDATA
|
||||
0x0c => {
|
||||
// RXCTRL
|
||||
let (_, ref mut rx_en) = *self.rx.lock().expect("could not lock uart RX half");
|
||||
*rx_en = value & 1 != 0;
|
||||
Ok(())
|
||||
}
|
||||
0x10 => Ok(()), // IE
|
||||
0x14 => Ok(()), // IP
|
||||
0x18 => Ok(()), // DIV
|
||||
_ => {
|
||||
if addr < 0x1c {
|
||||
Err(MemoryExceptionType::AddressMisaligned)
|
||||
} else {
|
||||
Err(MemoryExceptionType::AccessFault)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read_word(&self, addr: u64) -> Result<u32, MemoryExceptionType> {
|
||||
// dbg!(addr);
|
||||
match addr {
|
||||
0x00 => {
|
||||
// TXDATA
|
||||
let (ref tx_buf, _) = *self.tx.lock().expect("could not lock uart TX half");
|
||||
Ok(if tx_buf.is_full() { 0x80000000 } else { 0 })
|
||||
}
|
||||
0x08 => {
|
||||
// TXCTRL
|
||||
let (_, tx_en) = *self.tx.lock().expect("could not lock uart TX half");
|
||||
Ok(if tx_en { 1 } else { 0 })
|
||||
}
|
||||
0x04 => {
|
||||
// RXDATA
|
||||
let (ref mut rx_buf, _) = *self.rx.lock().expect("could not lock uart RX half");
|
||||
Ok(match rx_buf.pop_single_byte() {
|
||||
None => 0x80000000,
|
||||
Some(b) => b as u32,
|
||||
})
|
||||
}
|
||||
0x0c => {
|
||||
// RXCTRL
|
||||
let (_, rx_en) = *self.rx.lock().expect("could not lock uart RX half");
|
||||
Ok(if rx_en { 1 } else { 0 })
|
||||
}
|
||||
0x10 => Ok(0), // IE
|
||||
0x14 => Ok(0), // IP
|
||||
0x18 => Ok(1), // DIV
|
||||
|
||||
_ => {
|
||||
if addr < 0x1c {
|
||||
Err(MemoryExceptionType::AddressMisaligned)
|
||||
} else {
|
||||
Err(MemoryExceptionType::AccessFault)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
113
src/devices/serial/fifo.rs
Normal file
113
src/devices/serial/fifo.rs
Normal file
@@ -0,0 +1,113 @@
|
||||
// Copyright (c) 2026 taitep
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
//
|
||||
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
||||
// See LICENSE file in the project root for full license text.
|
||||
|
||||
use std::io::{self, Read, Write};
|
||||
|
||||
pub struct UartFifo<const CAP: usize> {
|
||||
buf: [u8; CAP],
|
||||
head: usize,
|
||||
tail: usize,
|
||||
len: usize,
|
||||
}
|
||||
|
||||
impl<const CAP: usize> UartFifo<CAP> {
|
||||
pub fn pop_single_byte(&mut self) -> Option<u8> {
|
||||
if self.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let value = self.buf[self.tail];
|
||||
self.advance_read(1);
|
||||
Some(value)
|
||||
}
|
||||
|
||||
pub fn push_single_byte(&mut self, value: u8) -> bool {
|
||||
if self.is_full() {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.buf[self.head] = value;
|
||||
self.advance_write(1);
|
||||
true
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len == 0
|
||||
}
|
||||
|
||||
pub fn is_full(&self) -> bool {
|
||||
self.len == CAP
|
||||
}
|
||||
|
||||
fn write_slice(&mut self) -> &mut [u8] {
|
||||
if self.is_full() {
|
||||
return &mut [];
|
||||
}
|
||||
|
||||
if self.head >= self.tail {
|
||||
&mut self.buf[self.head..]
|
||||
} else {
|
||||
&mut self.buf[self.head..self.tail]
|
||||
}
|
||||
}
|
||||
|
||||
fn advance_write(&mut self, n: usize) {
|
||||
debug_assert!(n <= CAP - self.len);
|
||||
self.head = (self.head + n) % CAP;
|
||||
self.len += n;
|
||||
}
|
||||
|
||||
fn read_slice(&self) -> &[u8] {
|
||||
if self.is_empty() {
|
||||
return &[];
|
||||
}
|
||||
|
||||
if self.tail < self.head {
|
||||
&self.buf[self.tail..self.head]
|
||||
} else {
|
||||
&self.buf[self.tail..]
|
||||
}
|
||||
}
|
||||
|
||||
fn advance_read(&mut self, n: usize) {
|
||||
debug_assert!(n <= self.len);
|
||||
self.tail = (self.tail + n) % CAP;
|
||||
self.len -= n;
|
||||
}
|
||||
|
||||
pub fn read_from<R: Read>(&mut self, reader: &mut R) -> io::Result<usize> {
|
||||
let slice = self.write_slice();
|
||||
if slice.is_empty() {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let n = reader.read(slice)?;
|
||||
self.advance_write(n);
|
||||
Ok(n)
|
||||
}
|
||||
|
||||
pub fn write_to<W: Write>(&mut self, writer: &mut W) -> io::Result<usize> {
|
||||
let slice = self.read_slice();
|
||||
if slice.is_empty() {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let n = writer.write(slice)?;
|
||||
self.advance_read(n);
|
||||
Ok(n)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const SIZE: usize> Default for UartFifo<SIZE> {
|
||||
fn default() -> Self {
|
||||
UartFifo {
|
||||
buf: [0; SIZE],
|
||||
head: 0,
|
||||
tail: 0,
|
||||
len: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// Copyright (c) 2025 taitep
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
//
|
||||
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
||||
// See LICENSE file in the project root for full license text.
|
||||
@@ -29,6 +29,22 @@ pub enum ExceptionType {
|
||||
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,
|
||||
@@ -36,27 +52,60 @@ pub enum MemoryExceptionType {
|
||||
}
|
||||
|
||||
impl MemoryExceptionType {
|
||||
pub(crate) fn to_exception_store(&self) -> ExceptionType {
|
||||
match self {
|
||||
Self::AddressMisaligned => ExceptionType::StoreAmoAddressMisaligned,
|
||||
Self::AccessFault => ExceptionType::StoreAmoAccessFault,
|
||||
Self::PageFault => ExceptionType::StoreAmoPageFault,
|
||||
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 to_exception_instr(&self) -> ExceptionType {
|
||||
match self {
|
||||
Self::AddressMisaligned => ExceptionType::InstructionAddressMisaligned,
|
||||
Self::AccessFault => ExceptionType::InstructionAccessFault,
|
||||
Self::PageFault => ExceptionType::InstructionPageFault,
|
||||
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 to_exception_load(&self) -> ExceptionType {
|
||||
match self {
|
||||
Self::AddressMisaligned => ExceptionType::LoadAddressMisaligned,
|
||||
Self::AccessFault => ExceptionType::LoadAccessFault,
|
||||
Self::PageFault => ExceptionType::LoadPageFault,
|
||||
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_
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Copyright (c) 2025 taitep
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
//
|
||||
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
||||
// See LICENSE file in the project root for full license text.
|
||||
@@ -14,9 +14,9 @@ use goblin::{
|
||||
program_header::PT_LOAD,
|
||||
},
|
||||
};
|
||||
use trve::consts::Addr;
|
||||
use trve::mem::RAM_START;
|
||||
|
||||
pub fn load<P: AsRef<Path>>(path: P, ram: &mut [u8], ram_start: Addr) -> Result<Addr> {
|
||||
pub fn load<P: AsRef<Path>>(path: P, ram: &mut [u8]) -> Result<u64> {
|
||||
let buf = fs::read(path)?;
|
||||
|
||||
match Object::parse(&buf)? {
|
||||
@@ -36,11 +36,11 @@ pub fn load<P: AsRef<Path>>(path: P, ram: &mut [u8], ram_start: Addr) -> Result<
|
||||
|
||||
for ph in elf.program_headers {
|
||||
if ph.p_type == PT_LOAD {
|
||||
let start = (ph.p_vaddr - ram_start) as usize;
|
||||
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() {
|
||||
if end > RAM_START as usize + ram.len() {
|
||||
bail!("Segment at 0x{:x} does not fit in RAM", ph.p_vaddr);
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ pub fn load<P: AsRef<Path>>(path: P, ram: &mut [u8], ram_start: Addr) -> Result<
|
||||
bail!("Program too large for RAM");
|
||||
}
|
||||
ram[..buf.len()].copy_from_slice(&buf);
|
||||
Ok(ram_start)
|
||||
Ok(RAM_START)
|
||||
}
|
||||
_ => bail!("Unsupported executable format"),
|
||||
}
|
||||
|
||||
320
src/gdb.rs
Normal file
320
src/gdb.rs
Normal file
@@ -0,0 +1,320 @@
|
||||
// Copyright (c) 2025-2026 taitep
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
//
|
||||
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
||||
// See LICENSE file in the project root for full license text.
|
||||
|
||||
use std::{
|
||||
io::{self, BufRead, ErrorKind, Write},
|
||||
net::{TcpListener, TcpStream},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
core::commands::CoreCmd,
|
||||
exceptions::{ExceptionType, MemoryExceptionType},
|
||||
};
|
||||
|
||||
pub(crate) enum DebugCommand {
|
||||
GetRegs(oneshot::Sender<RegsResponse>),
|
||||
ReadMem {
|
||||
addr: u64,
|
||||
len: u64,
|
||||
responder: oneshot::Sender<Result<Vec<u8>, MemoryExceptionType>>,
|
||||
},
|
||||
Step(oneshot::Sender<StopReason>),
|
||||
Continue(oneshot::Sender<StopReason>, oneshot::Receiver<()>),
|
||||
SetBreakpoint(u64),
|
||||
RemoveBreakpoint(u64),
|
||||
ExitDebugMode,
|
||||
}
|
||||
|
||||
pub struct DebugStream(pub(crate) crossbeam::channel::Receiver<DebugCommand>);
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(crate) enum StopReason {
|
||||
Exception(ExceptionType),
|
||||
Step,
|
||||
Interrupted,
|
||||
}
|
||||
|
||||
impl StopReason {
|
||||
fn to_rsp(self) -> &'static str {
|
||||
match self {
|
||||
StopReason::Step => "S05",
|
||||
StopReason::Exception(e) => {
|
||||
use ExceptionType::*;
|
||||
match e {
|
||||
IllegalInstruction => "S04",
|
||||
InstructionAddressMisaligned
|
||||
| InstructionAccessFault
|
||||
| InstructionPageFault
|
||||
| LoadAddressMisaligned
|
||||
| LoadAccessFault
|
||||
| LoadPageFault
|
||||
| StoreAmoAddressMisaligned
|
||||
| StoreAmoAccessFault
|
||||
| StoreAmoPageFault => "S0b",
|
||||
_ => "S05",
|
||||
}
|
||||
}
|
||||
StopReason::Interrupted => "S02",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct RegsResponse {
|
||||
pub x_regs: [u64; 32],
|
||||
pub pc: u64,
|
||||
}
|
||||
|
||||
pub fn run_stub(cmd_sender: crossbeam::channel::Sender<CoreCmd>) {
|
||||
std::thread::spawn(move || {
|
||||
let listener = TcpListener::bind("127.0.0.1:1234").expect("couldnt start tcp listener");
|
||||
|
||||
for stream in listener.incoming().flatten() {
|
||||
let (dbg_tx, dbg_rx) = crossbeam::channel::bounded(16);
|
||||
|
||||
stream
|
||||
.set_nonblocking(true)
|
||||
.expect("Couldnt set TCP stream to nonblocking");
|
||||
|
||||
cmd_sender
|
||||
.send(CoreCmd::EnterDbgMode(DebugStream(dbg_rx)))
|
||||
.expect("couldnt ask core to enter debug mode");
|
||||
|
||||
handle_gdb_connection(stream, dbg_tx).expect("failure during connection");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn handle_gdb_connection(
|
||||
gdb_stream: TcpStream,
|
||||
dbg_tx: crossbeam::channel::Sender<DebugCommand>,
|
||||
) -> io::Result<()> {
|
||||
eprintln!("gdb connected");
|
||||
let mut reader = io::BufReader::new(gdb_stream.try_clone()?);
|
||||
let mut writer = gdb_stream;
|
||||
|
||||
loop {
|
||||
match read_rsp_packet(&mut reader) {
|
||||
Ok(packet) => {
|
||||
if handle_packet(
|
||||
&packet[..packet.len() - 1],
|
||||
&mut writer,
|
||||
&dbg_tx,
|
||||
&mut reader,
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
let _ = dbg_tx.send(DebugCommand::ExitDebugMode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(ref e) if e.kind() == ErrorKind::WouldBlock => {
|
||||
std::thread::yield_now();
|
||||
}
|
||||
Err(_) => {
|
||||
let _ = dbg_tx.send(DebugCommand::ExitDebugMode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_rsp_packet<R: BufRead>(reader: &mut R) -> io::Result<String> {
|
||||
let mut buf = Vec::new();
|
||||
|
||||
// Wait for leading '$'
|
||||
loop {
|
||||
let mut byte = [0u8];
|
||||
let n = reader.read(&mut byte)?;
|
||||
if n == 0 {
|
||||
return Err(io::Error::new(ErrorKind::UnexpectedEof, "Disconnected"));
|
||||
}
|
||||
if byte[0] == b'$' {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Read until '#'
|
||||
reader.read_until(b'#', &mut buf)?;
|
||||
|
||||
let mut checksum = [0u8; 2];
|
||||
reader.read_exact(&mut checksum)?;
|
||||
|
||||
String::from_utf8(buf).map_err(|e| io::Error::new(ErrorKind::InvalidData, e))
|
||||
}
|
||||
|
||||
fn handle_packet<W: Write, R: BufRead>(
|
||||
packet: &str,
|
||||
writer: &mut W,
|
||||
dbg_tx: &crossbeam::channel::Sender<DebugCommand>,
|
||||
reader: &mut R,
|
||||
) -> io::Result<()> {
|
||||
writer.write_all(b"+")?;
|
||||
if packet.is_empty() {
|
||||
send_packet("", writer)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
match &packet[0..1] {
|
||||
"?" => {
|
||||
send_packet("S05", writer)?;
|
||||
}
|
||||
|
||||
"g" => {
|
||||
let (regs_tx, regs_rx) = oneshot::channel();
|
||||
dbg_tx.send(DebugCommand::GetRegs(regs_tx)).unwrap();
|
||||
let regs = regs_rx.recv().unwrap();
|
||||
let mut hex = String::with_capacity(32 * 8 * 2 + 8 * 2);
|
||||
for ® in ®s.x_regs {
|
||||
hex.push_str(
|
||||
®.to_le_bytes()
|
||||
.iter()
|
||||
.map(|b| format!("{b:02x}"))
|
||||
.collect::<String>(),
|
||||
);
|
||||
}
|
||||
hex.push_str(
|
||||
®s
|
||||
.pc
|
||||
.to_le_bytes()
|
||||
.iter()
|
||||
.map(|b| format!("{b:02x}"))
|
||||
.collect::<String>(),
|
||||
);
|
||||
send_packet(&hex, writer)?;
|
||||
}
|
||||
|
||||
"m" => {
|
||||
if let Some((addr_str, len_str)) = packet[1..].split_once(',') {
|
||||
if let (Ok(addr), Ok(len)) = (
|
||||
u64::from_str_radix(addr_str, 16),
|
||||
u64::from_str_radix(len_str, 16),
|
||||
) {
|
||||
let (responder, response) = oneshot::channel();
|
||||
dbg_tx
|
||||
.send(DebugCommand::ReadMem {
|
||||
addr,
|
||||
len,
|
||||
responder,
|
||||
})
|
||||
.unwrap();
|
||||
let response = response.recv().unwrap();
|
||||
|
||||
match response {
|
||||
Ok(data) => {
|
||||
let hex: String = data.iter().map(|b| format!("{b:02x}")).collect();
|
||||
send_packet(&hex, writer)?;
|
||||
}
|
||||
Err(e) => send_packet(&format!("E.{e:?}"), writer)?,
|
||||
};
|
||||
} else {
|
||||
send_packet("", writer)?;
|
||||
}
|
||||
} else {
|
||||
send_packet("", writer)?;
|
||||
}
|
||||
}
|
||||
|
||||
"s" => {
|
||||
let (responder, stop_reason_rx) = oneshot::channel();
|
||||
dbg_tx.send(DebugCommand::Step(responder)).unwrap();
|
||||
let stop_reason = stop_reason_rx.recv().unwrap();
|
||||
send_packet(stop_reason.to_rsp(), writer)?;
|
||||
}
|
||||
|
||||
"c" => {
|
||||
let (responder, stop_reason_rx) = oneshot::channel();
|
||||
let (stopper, stop_listener) = oneshot::channel();
|
||||
dbg_tx
|
||||
.send(DebugCommand::Continue(responder, stop_listener))
|
||||
.unwrap();
|
||||
|
||||
loop {
|
||||
let mut byte = [0u8];
|
||||
match reader.read(&mut byte) {
|
||||
Ok(0) => {
|
||||
stopper.send(()).unwrap();
|
||||
break;
|
||||
}
|
||||
Ok(1) if byte[0] == 0x03 => {
|
||||
stopper.send(()).unwrap();
|
||||
break;
|
||||
}
|
||||
Ok(_) => {}
|
||||
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {}
|
||||
Err(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(stop_reason) = stop_reason_rx.try_recv() {
|
||||
send_packet(stop_reason.to_rsp(), writer)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
std::thread::yield_now();
|
||||
}
|
||||
|
||||
let stop_reason = stop_reason_rx.recv().unwrap();
|
||||
send_packet(stop_reason.to_rsp(), writer)?;
|
||||
}
|
||||
|
||||
"Z" if packet.chars().nth(1) == Some('0') => {
|
||||
if let Some((addr_str, size_str)) = packet[3..].split_once(',')
|
||||
&& let (Ok(addr), Ok(size)) = (
|
||||
u64::from_str_radix(addr_str, 16),
|
||||
u64::from_str_radix(size_str, 16),
|
||||
)
|
||||
{
|
||||
if size != 4 {
|
||||
send_packet("", writer)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
dbg_tx.send(DebugCommand::SetBreakpoint(addr)).unwrap();
|
||||
send_packet("OK", writer)?;
|
||||
return Ok(());
|
||||
}
|
||||
send_packet("", writer)?;
|
||||
}
|
||||
|
||||
"z" if packet.chars().nth(1) == Some('0') => {
|
||||
if let Some((addr_str, size_str)) = packet[3..].split_once(',')
|
||||
&& let (Ok(addr), Ok(size)) = (
|
||||
u64::from_str_radix(addr_str, 16),
|
||||
u64::from_str_radix(size_str, 16),
|
||||
)
|
||||
{
|
||||
if size != 4 {
|
||||
send_packet("", writer)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
dbg_tx.send(DebugCommand::RemoveBreakpoint(addr)).unwrap();
|
||||
send_packet("OK", writer)?;
|
||||
return Ok(());
|
||||
}
|
||||
send_packet("", writer)?;
|
||||
}
|
||||
|
||||
_ => {
|
||||
send_packet("", writer)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn send_packet<W: Write>(packet: &str, writer: &mut W) -> io::Result<()> {
|
||||
writer.write_all(b"$")?;
|
||||
writer.write_all(packet.as_bytes())?;
|
||||
writer.write_all(b"#")?;
|
||||
let checksum = packet.bytes().fold(0u8, |acc, b| acc.wrapping_add(b));
|
||||
write!(writer, "{checksum:02x}")?;
|
||||
// eprintln!("successfully sent packet {packet:?}");
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,49 +1,104 @@
|
||||
// Copyright (c) 2025 taitep
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
//
|
||||
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
||||
// See LICENSE file in the project root for full license text.
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
mod rva;
|
||||
mod rvi;
|
||||
mod rvm;
|
||||
|
||||
use crate::{
|
||||
core::Core,
|
||||
decode::Instruction,
|
||||
exceptions::ExceptionType::{self, IllegalInstruction},
|
||||
exceptions::{
|
||||
Exception,
|
||||
ExceptionType::{self, IllegalInstruction},
|
||||
},
|
||||
};
|
||||
|
||||
pub(crate) fn find_and_exec(instr: Instruction, core: &mut Core) -> Result<(), ExceptionType> {
|
||||
fn illegal(instr: Instruction) -> Result<(), Exception> {
|
||||
Err(Exception {
|
||||
type_: IllegalInstruction,
|
||||
value: instr.0 as u64,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn find_and_exec(instr: Instruction, core: &mut Core) -> Result<(), Exception> {
|
||||
match instr.opcode_noncompressed() {
|
||||
0b01100 => match (instr.funct7(), instr.funct3()) {
|
||||
0b01100 => match (instr.funct3(), instr.funct7()) {
|
||||
// OP
|
||||
(0b0000000, 0b000) => rvi::add(core, instr),
|
||||
(0b0100000, 0b000) => rvi::sub(core, instr),
|
||||
(0b0000000, 0b111) => rvi::and(core, instr),
|
||||
(0b0000000, 0b110) => rvi::or(core, instr),
|
||||
_ => Err(IllegalInstruction),
|
||||
(0b000, 0b0000000) => rvi::add(core, instr),
|
||||
(0b000, 0b0100000) => rvi::sub(core, instr),
|
||||
(0b010, 0b0000000) => rvi::slt(core, instr),
|
||||
(0b011, 0b0000000) => rvi::sltu(core, instr),
|
||||
(0b001, 0b0000000) => rvi::sll(core, instr),
|
||||
(0b101, 0b0000000) => rvi::srl(core, instr),
|
||||
(0b101, 0b0100000) => rvi::sra(core, instr),
|
||||
(0b111, 0b0000000) => rvi::and(core, instr),
|
||||
(0b100, 0b0000000) => rvi::xor(core, instr),
|
||||
(0b110, 0b0000000) => rvi::or(core, instr),
|
||||
// rvm
|
||||
(0b000, 0b0000001) => rvm::mul(core, instr),
|
||||
(0b001, 0b0000001) => rvm::mulh(core, instr),
|
||||
(0b010, 0b0000001) => rvm::mulhsu(core, instr),
|
||||
(0b011, 0b0000001) => rvm::mulhu(core, instr),
|
||||
(0b100, 0b0000001) => rvm::div(core, instr),
|
||||
(0b101, 0b0000001) => rvm::divu(core, instr),
|
||||
(0b110, 0b0000001) => rvm::rem(core, instr),
|
||||
(0b111, 0b0000001) => rvm::remu(core, instr),
|
||||
_ => illegal(instr),
|
||||
},
|
||||
0b01110 => match (instr.funct3(), instr.funct7()) {
|
||||
// OP_32
|
||||
(0b000, 0b0000000) => rvi::addw(core, instr),
|
||||
(0b000, 0b0100000) => rvi::subw(core, instr),
|
||||
(0b001, 0b0000000) => rvi::sllw(core, instr),
|
||||
(0b101, 0b0000000) => rvi::srlw(core, instr),
|
||||
(0b101, 0b0100000) => rvi::sraw(core, instr),
|
||||
// rvm
|
||||
(0b000, 0b0000001) => rvm::mulw(core, instr),
|
||||
(0b100, 0b0000001) => rvm::divw(core, instr),
|
||||
(0b101, 0b0000001) => rvm::divuw(core, instr),
|
||||
(0b110, 0b0000001) => rvm::remw(core, instr),
|
||||
(0b111, 0b0000001) => rvm::remuw(core, instr),
|
||||
_ => illegal(instr),
|
||||
},
|
||||
0b00100 => match instr.funct3() {
|
||||
// OP_IMM
|
||||
0b000 => rvi::addi(core, instr),
|
||||
0b001 => {
|
||||
if instr.funct6() == 0 {
|
||||
rvi::slli(core, instr)
|
||||
} else {
|
||||
Err(IllegalInstruction)
|
||||
}
|
||||
}
|
||||
0b101 => match instr.funct6() {
|
||||
// immediate right-shift
|
||||
0b000000 => rvi::srli(core, instr),
|
||||
_ => Err(IllegalInstruction),
|
||||
0b010 => rvi::slti(core, instr),
|
||||
0b011 => rvi::sltiu(core, instr),
|
||||
0b001 => match instr.funct6() {
|
||||
0 => rvi::slli(core, instr),
|
||||
_ => illegal(instr),
|
||||
},
|
||||
0b101 => match instr.funct6() {
|
||||
0b000000 => rvi::srli(core, instr),
|
||||
0b010000 => rvi::srai(core, instr),
|
||||
_ => illegal(instr),
|
||||
},
|
||||
0b100 => rvi::xori(core, instr),
|
||||
0b110 => rvi::ori(core, instr),
|
||||
0b111 => rvi::andi(core, instr),
|
||||
_ => Err(IllegalInstruction),
|
||||
_ => illegal(instr),
|
||||
},
|
||||
0b00110 => match instr.funct3() {
|
||||
// OP_IMM_32
|
||||
0b000 => rvi::addiw(core, instr),
|
||||
_ => Err(IllegalInstruction),
|
||||
0b001 => match instr.funct7() {
|
||||
0 => rvi::slliw(core, instr),
|
||||
_ => illegal(instr),
|
||||
},
|
||||
0b101 => match instr.funct7() {
|
||||
0b0000000 => rvi::srliw(core, instr),
|
||||
0b0100000 => rvi::sraiw(core, instr),
|
||||
_ => illegal(instr),
|
||||
},
|
||||
_ => illegal(instr),
|
||||
},
|
||||
0b01000 => match instr.funct3() {
|
||||
// STORE
|
||||
@@ -51,7 +106,7 @@ pub(crate) fn find_and_exec(instr: Instruction, core: &mut Core) -> Result<(), E
|
||||
0b001 => rvi::sh(core, instr),
|
||||
0b010 => rvi::sw(core, instr),
|
||||
0b011 => rvi::sd(core, instr),
|
||||
_ => Err(IllegalInstruction),
|
||||
_ => illegal(instr),
|
||||
},
|
||||
0b00000 => match instr.funct3() {
|
||||
// LOAD
|
||||
@@ -62,16 +117,17 @@ pub(crate) fn find_and_exec(instr: Instruction, core: &mut Core) -> Result<(), E
|
||||
0b010 => rvi::lw(core, instr),
|
||||
0b110 => rvi::lwu(core, instr),
|
||||
0b011 => rvi::ld(core, instr),
|
||||
_ => Err(IllegalInstruction),
|
||||
_ => illegal(instr),
|
||||
},
|
||||
0b11000 => match instr.funct3() {
|
||||
// BRANCH
|
||||
0b000 => rvi::beq(core, instr),
|
||||
0b001 => rvi::bne(core, instr),
|
||||
0b100 => rvi::blt(core, instr),
|
||||
0b101 => rvi::bge(core, instr),
|
||||
0b110 => rvi::bltu(core, instr),
|
||||
0b111 => rvi::bgeu(core, instr),
|
||||
_ => Err(IllegalInstruction),
|
||||
_ => illegal(instr),
|
||||
},
|
||||
0b01101 => rvi::lui(core, instr),
|
||||
0b00101 => rvi::auipc(core, instr),
|
||||
@@ -80,9 +136,38 @@ pub(crate) fn find_and_exec(instr: Instruction, core: &mut Core) -> Result<(), E
|
||||
if instr.funct3() == 0 {
|
||||
rvi::jalr(core, instr)
|
||||
} else {
|
||||
Err(IllegalInstruction)
|
||||
illegal(instr)
|
||||
}
|
||||
}
|
||||
_ => Err(IllegalInstruction),
|
||||
0b00011 => match instr.funct3() {
|
||||
// MISC_MEM
|
||||
0b000 => {
|
||||
// FENCE is just implemented as a SeqCst fence always here
|
||||
// I dont yet care about the potential performance issue this may bring
|
||||
std::sync::atomic::fence(std::sync::atomic::Ordering::SeqCst);
|
||||
core.advance_pc();
|
||||
Ok(())
|
||||
}
|
||||
_ => illegal(instr),
|
||||
},
|
||||
0b11100 => match (instr.funct3(), instr.funct12(), instr.rs1(), instr.rd()) {
|
||||
(0b000, 0b000000000000, 0, 0) => {
|
||||
// TODO: When privilege modes are added, make the exception raised by ecall
|
||||
// depend on privilege mode
|
||||
Err(ExceptionType::EnvironmentCallFromMMode.with_no_value())
|
||||
}
|
||||
(0b000, 0b000000000001, 0, 0) => Err(ExceptionType::Breakpoint.with_no_value()),
|
||||
_ => {
|
||||
// Temporarily allowing unrecognized instructions here to be able to run
|
||||
// the official ISA tests, which perform CSR operations but work just fine
|
||||
// without them
|
||||
eprintln!("Unrecognized instruction within SYSTEM opcode");
|
||||
dbg!(instr);
|
||||
core.advance_pc();
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
0b01011 => rva::find_and_exec(instr, core),
|
||||
_ => illegal(instr),
|
||||
}
|
||||
}
|
||||
|
||||
64
src/instructions/macros.rs
Normal file
64
src/instructions/macros.rs
Normal file
@@ -0,0 +1,64 @@
|
||||
// Copyright (c) 2025 taitep
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
//
|
||||
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
||||
// See LICENSE file in the project root for full license text.
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! instr_branch {
|
||||
($name:ident, $cond:expr) => {
|
||||
pub fn $name(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
||||
let a = core.reg_read(instr.rs1());
|
||||
let b = core.reg_read(instr.rs2());
|
||||
if $cond(a, b) {
|
||||
core.pc = core.pc.wrapping_add(instr.imm_b());
|
||||
} else {
|
||||
core.advance_pc();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! instr_branch_signed {
|
||||
($name:ident, $cond:expr) => {
|
||||
instr_branch!($name, |a, b| $cond((a as i64), (b as i64)));
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! instr_op_r {
|
||||
($name:ident, $op:expr) => {
|
||||
pub fn $name(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
||||
let a = core.reg_read(instr.rs1());
|
||||
let b = core.reg_read(instr.rs2());
|
||||
let res = $op(a, b);
|
||||
core.reg_write(instr.rd(), res);
|
||||
core.advance_pc();
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! instr_op_i {
|
||||
($name:ident, $op:expr) => {
|
||||
pub fn $name(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
||||
let a = core.reg_read(instr.rs1());
|
||||
let b = instr.imm_i();
|
||||
let res = $op(a, b);
|
||||
core.reg_write(instr.rd(), res);
|
||||
core.advance_pc();
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! instr_op {
|
||||
($name:ident, $name_imm:ident, $op:expr) => {
|
||||
instr_op_r!($name, $op);
|
||||
instr_op_i!($name_imm, $op);
|
||||
};
|
||||
}
|
||||
247
src/instructions/rva.rs
Normal file
247
src/instructions/rva.rs
Normal file
@@ -0,0 +1,247 @@
|
||||
// Copyright (c) 2026 taitep
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
//
|
||||
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
||||
// See LICENSE file in the project root for full license text.
|
||||
|
||||
use std::sync::atomic::{AtomicU32, AtomicU64};
|
||||
|
||||
use super::illegal;
|
||||
use crate::{
|
||||
core::Core,
|
||||
decode::Instruction,
|
||||
exceptions::{Exception, ExceptionType},
|
||||
mem::{RAM_START, Ram},
|
||||
};
|
||||
|
||||
pub(super) fn find_and_exec(instr: Instruction, core: &mut Core) -> Result<(), Exception> {
|
||||
match (instr.funct5(), instr.funct3()) {
|
||||
(0b00010, 0b010) if instr.rs2() == 0 => lr_w(core, instr),
|
||||
(0b00010, 0b011) if instr.rs2() == 0 => lr_d(core, instr),
|
||||
(0b00011, 0b010) => sc_w(core, instr),
|
||||
(0b00011, 0b011) => sc_d(core, instr),
|
||||
_ => illegal(instr),
|
||||
}
|
||||
}
|
||||
|
||||
fn lr_d(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
||||
core.reservation = None;
|
||||
|
||||
let addr = core.reg_read(instr.rs1());
|
||||
if !addr.is_multiple_of(8) {
|
||||
return Err(Exception {
|
||||
type_: ExceptionType::LoadAddressMisaligned,
|
||||
value: addr,
|
||||
});
|
||||
}
|
||||
if addr < RAM_START {
|
||||
return Err(Exception {
|
||||
type_: ExceptionType::LoadAccessFault,
|
||||
value: addr,
|
||||
});
|
||||
}
|
||||
let ram_addr = addr - RAM_START;
|
||||
|
||||
let reservation_data = core
|
||||
.mem
|
||||
.ram
|
||||
.wait_for_even_version(ram_addr)
|
||||
.ok_or_else(|| Exception {
|
||||
type_: ExceptionType::LoadAccessFault,
|
||||
value: addr,
|
||||
})?;
|
||||
|
||||
core.reg_write(instr.rd(), unsafe {
|
||||
let index = ram_addr as usize / 8;
|
||||
core.mem
|
||||
.ram
|
||||
.buf_transmuted::<AtomicU64>()
|
||||
.get(index)
|
||||
.ok_or_else(|| Exception {
|
||||
type_: ExceptionType::LoadAccessFault,
|
||||
value: addr,
|
||||
})?
|
||||
.load(instr.amo_ordering())
|
||||
});
|
||||
|
||||
core.reservation = Some(reservation_data);
|
||||
core.advance_pc();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
fn sc_d(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
||||
let res = if let Some((reserved_chunk_id, reserved_version)) = core.reservation {
|
||||
let addr = core.reg_read(instr.rs1());
|
||||
if !addr.is_multiple_of(8) {
|
||||
return Err(Exception {
|
||||
type_: ExceptionType::StoreAmoAddressMisaligned,
|
||||
value: addr,
|
||||
});
|
||||
}
|
||||
if addr < RAM_START {
|
||||
return Err(Exception {
|
||||
type_: ExceptionType::StoreAmoAccessFault,
|
||||
value: addr,
|
||||
});
|
||||
}
|
||||
let ram_addr = addr - RAM_START;
|
||||
let chunk_id = ram_addr as usize / Ram::VERSION_CHUNK_SIZE;
|
||||
|
||||
if chunk_id != reserved_chunk_id {
|
||||
// Mismatched reservation location and address
|
||||
core.reg_write(instr.rd(), 1);
|
||||
core.reservation = None;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let claim_res = core
|
||||
.mem
|
||||
.ram
|
||||
.claim_expected(chunk_id, reserved_version)
|
||||
.ok_or_else(|| Exception {
|
||||
type_: ExceptionType::StoreAmoAccessFault,
|
||||
value: addr,
|
||||
})?;
|
||||
|
||||
if claim_res.is_some() {
|
||||
core.reservation = None;
|
||||
let value = core.reg_read(instr.rs2());
|
||||
unsafe {
|
||||
let index = ram_addr as usize / 8;
|
||||
core.mem
|
||||
.ram
|
||||
.buf_transmuted::<AtomicU64>()
|
||||
.get(index)
|
||||
.ok_or_else(|| Exception {
|
||||
type_: ExceptionType::StoreAmoAccessFault,
|
||||
value: addr,
|
||||
})?
|
||||
.store(value, instr.amo_ordering());
|
||||
|
||||
Ok(0)
|
||||
}
|
||||
} else {
|
||||
core.reservation = None;
|
||||
Ok(1)
|
||||
}
|
||||
} else {
|
||||
core.reservation = None;
|
||||
Ok(1)
|
||||
}
|
||||
.map(|s| core.reg_write(instr.rd(), s));
|
||||
|
||||
core.advance_pc();
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
fn lr_w(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
||||
core.reservation = None;
|
||||
|
||||
let addr = core.reg_read(instr.rs1());
|
||||
if !addr.is_multiple_of(4) {
|
||||
return Err(Exception {
|
||||
type_: ExceptionType::LoadAddressMisaligned,
|
||||
value: addr,
|
||||
});
|
||||
}
|
||||
if addr < RAM_START {
|
||||
return Err(Exception {
|
||||
type_: ExceptionType::LoadAccessFault,
|
||||
value: addr,
|
||||
});
|
||||
}
|
||||
let ram_addr = addr - RAM_START;
|
||||
|
||||
let reservation_data = core
|
||||
.mem
|
||||
.ram
|
||||
.wait_for_even_version(ram_addr)
|
||||
.ok_or_else(|| Exception {
|
||||
type_: ExceptionType::LoadAccessFault,
|
||||
value: addr,
|
||||
})?;
|
||||
|
||||
core.reg_write(instr.rd(), unsafe {
|
||||
let index = ram_addr as usize / 4;
|
||||
core.mem
|
||||
.ram
|
||||
.buf_transmuted::<AtomicU32>()
|
||||
.get(index)
|
||||
.ok_or_else(|| Exception {
|
||||
type_: ExceptionType::LoadAccessFault,
|
||||
value: addr,
|
||||
})?
|
||||
.load(instr.amo_ordering())
|
||||
} as i32 as i64 as u64);
|
||||
|
||||
core.reservation = Some(reservation_data);
|
||||
core.advance_pc();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
fn sc_w(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
||||
let res = if let Some((reserved_chunk_id, reserved_version)) = core.reservation {
|
||||
let addr = core.reg_read(instr.rs1());
|
||||
if !addr.is_multiple_of(4) {
|
||||
return Err(Exception {
|
||||
type_: ExceptionType::StoreAmoAddressMisaligned,
|
||||
value: addr,
|
||||
});
|
||||
}
|
||||
if addr < RAM_START {
|
||||
return Err(Exception {
|
||||
type_: ExceptionType::StoreAmoAccessFault,
|
||||
value: addr,
|
||||
});
|
||||
}
|
||||
let ram_addr = addr - RAM_START;
|
||||
let chunk_id = ram_addr as usize / Ram::VERSION_CHUNK_SIZE;
|
||||
|
||||
if chunk_id != reserved_chunk_id {
|
||||
// Mismatched reservation location and address
|
||||
core.reg_write(instr.rd(), 1);
|
||||
core.reservation = None;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let claim_res = core
|
||||
.mem
|
||||
.ram
|
||||
.claim_expected(chunk_id, reserved_version)
|
||||
.ok_or_else(|| Exception {
|
||||
type_: ExceptionType::StoreAmoAccessFault,
|
||||
value: addr,
|
||||
})?;
|
||||
|
||||
if claim_res.is_some() {
|
||||
core.reservation = None;
|
||||
let value = core.reg_read(instr.rs2());
|
||||
unsafe {
|
||||
let index = ram_addr as usize / 4;
|
||||
core.mem
|
||||
.ram
|
||||
.buf_transmuted::<AtomicU32>()
|
||||
.get(index)
|
||||
.ok_or_else(|| Exception {
|
||||
type_: ExceptionType::StoreAmoAccessFault,
|
||||
value: addr,
|
||||
})?
|
||||
.store(value as u32, instr.amo_ordering());
|
||||
|
||||
Ok(0)
|
||||
}
|
||||
} else {
|
||||
core.reservation = None;
|
||||
Ok(1)
|
||||
}
|
||||
} else {
|
||||
core.reservation = None;
|
||||
Ok(1)
|
||||
}
|
||||
.map(|s| core.reg_write(instr.rd(), s));
|
||||
|
||||
core.advance_pc();
|
||||
|
||||
res
|
||||
}
|
||||
@@ -1,157 +1,78 @@
|
||||
// Copyright (c) 2025 taitep
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
//
|
||||
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
||||
// See LICENSE file in the project root for full license text.
|
||||
|
||||
use crate::{core::Core, decode::Instruction, exceptions::ExceptionType};
|
||||
use crate::{core::Core, decode::Instruction, exceptions::Exception};
|
||||
|
||||
use std::ops::{BitAnd, BitOr, BitXor};
|
||||
|
||||
mod mem;
|
||||
|
||||
pub use mem::*;
|
||||
|
||||
pub fn add(core: &mut Core, instr: Instruction) -> Result<(), ExceptionType> {
|
||||
core.reg_write(
|
||||
instr.rd(),
|
||||
core.reg_read(instr.rs1())
|
||||
.wrapping_add(core.reg_read(instr.rs2())),
|
||||
);
|
||||
core.advance_pc();
|
||||
Ok(())
|
||||
}
|
||||
instr_op!(add, addi, u64::wrapping_add);
|
||||
instr_op!(addw, addiw, |a, b| u64::wrapping_add(a, b) as i32 as i64
|
||||
as u64);
|
||||
instr_op_r!(sub, u64::wrapping_sub);
|
||||
instr_op_r!(subw, |a, b| u64::wrapping_sub(a, b) as i32 as i64 as u64);
|
||||
|
||||
pub fn sub(core: &mut Core, instr: Instruction) -> Result<(), ExceptionType> {
|
||||
core.reg_write(
|
||||
instr.rd(),
|
||||
core.reg_read(instr.rs1())
|
||||
.wrapping_sub(core.reg_read(instr.rs2())),
|
||||
);
|
||||
core.advance_pc();
|
||||
Ok(())
|
||||
}
|
||||
instr_op!(and, andi, u64::bitand);
|
||||
instr_op!(or, ori, u64::bitor);
|
||||
instr_op!(xor, xori, u64::bitxor);
|
||||
|
||||
pub fn addi(core: &mut Core, instr: Instruction) -> Result<(), ExceptionType> {
|
||||
core.reg_write(
|
||||
instr.rd(),
|
||||
core.reg_read(instr.rs1()).wrapping_add(instr.imm_i()),
|
||||
);
|
||||
core.advance_pc();
|
||||
Ok(())
|
||||
}
|
||||
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
|
||||
);
|
||||
|
||||
pub fn addiw(core: &mut Core, instr: Instruction) -> Result<(), ExceptionType> {
|
||||
let res = core.reg_read(instr.rs1()).wrapping_add(instr.imm_i()) as i32;
|
||||
core.reg_write(instr.rd(), res as i64 as u64);
|
||||
core.advance_pc();
|
||||
Ok(())
|
||||
}
|
||||
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 and(core: &mut Core, instr: Instruction) -> Result<(), ExceptionType> {
|
||||
core.reg_write(
|
||||
instr.rd(),
|
||||
core.reg_read(instr.rs1()) & core.reg_read(instr.rs2()),
|
||||
);
|
||||
core.advance_pc();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn andi(core: &mut Core, instr: Instruction) -> Result<(), ExceptionType> {
|
||||
core.reg_write(instr.rd(), core.reg_read(instr.rs1()) & instr.imm_i());
|
||||
core.advance_pc();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn or(core: &mut Core, instr: Instruction) -> Result<(), ExceptionType> {
|
||||
core.reg_write(
|
||||
instr.rd(),
|
||||
core.reg_read(instr.rs1()) | core.reg_read(instr.rs2()),
|
||||
);
|
||||
core.advance_pc();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn slli(core: &mut Core, instr: Instruction) -> Result<(), ExceptionType> {
|
||||
core.reg_write(instr.rd(), core.reg_read(instr.rs1()) << instr.imm_shamt());
|
||||
core.advance_pc();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn srli(core: &mut Core, instr: Instruction) -> Result<(), ExceptionType> {
|
||||
core.reg_write(instr.rd(), core.reg_read(instr.rs1()) >> instr.imm_shamt());
|
||||
core.advance_pc();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn lui(core: &mut Core, instr: Instruction) -> Result<(), ExceptionType> {
|
||||
pub fn lui(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
||||
core.reg_write(instr.rd(), instr.imm_u());
|
||||
core.advance_pc();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn auipc(core: &mut Core, instr: Instruction) -> Result<(), ExceptionType> {
|
||||
pub fn auipc(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
||||
core.reg_write(instr.rd(), core.pc.wrapping_add(instr.imm_u()));
|
||||
core.advance_pc();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn jal(core: &mut Core, instr: Instruction) -> Result<(), ExceptionType> {
|
||||
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<(), ExceptionType> {
|
||||
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 = core.reg_read(instr.rs1()).wrapping_add(instr.imm_i());
|
||||
core.pc = target;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn beq(core: &mut Core, instr: Instruction) -> Result<(), ExceptionType> {
|
||||
if core.reg_read(instr.rs1()) == core.reg_read(instr.rs2()) {
|
||||
core.pc = core.pc.wrapping_add(instr.imm_b());
|
||||
} else {
|
||||
core.advance_pc();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn bne(core: &mut Core, instr: Instruction) -> Result<(), ExceptionType> {
|
||||
if core.reg_read(instr.rs1()) != core.reg_read(instr.rs2()) {
|
||||
core.pc = core.pc.wrapping_add(instr.imm_b());
|
||||
} else {
|
||||
core.advance_pc();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn blt(core: &mut Core, instr: Instruction) -> Result<(), ExceptionType> {
|
||||
if (core.reg_read(instr.rs1()) as i64) < (core.reg_read(instr.rs2()) as i64) {
|
||||
core.pc = core.pc.wrapping_add(instr.imm_b());
|
||||
} else {
|
||||
core.advance_pc();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn bgeu(core: &mut Core, instr: Instruction) -> Result<(), ExceptionType> {
|
||||
if core.reg_read(instr.rs1()) >= core.reg_read(instr.rs2()) {
|
||||
core.pc = core.pc.wrapping_add(instr.imm_b());
|
||||
} else {
|
||||
core.advance_pc();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn bltu(core: &mut Core, instr: Instruction) -> Result<(), ExceptionType> {
|
||||
if core.reg_read(instr.rs1()) < core.reg_read(instr.rs2()) {
|
||||
core.pc = core.pc.wrapping_add(instr.imm_b());
|
||||
} else {
|
||||
core.advance_pc();
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@@ -1,135 +1,130 @@
|
||||
// Copyright (c) 2025 taitep
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
//
|
||||
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
||||
// See LICENSE file in the project root for full license text.
|
||||
|
||||
use crate::{
|
||||
consts::{Byte, DWord, HWord, Word},
|
||||
core::Core,
|
||||
exceptions::ExceptionType,
|
||||
instructions::Instruction,
|
||||
};
|
||||
use crate::{core::Core, exceptions::Exception, instructions::Instruction};
|
||||
|
||||
pub fn sd(core: &mut Core, instr: Instruction) -> Result<(), ExceptionType> {
|
||||
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.to_exception_store())?;
|
||||
.map_err(|e| e.into_exception_store())?;
|
||||
core.advance_pc();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn ld(core: &mut Core, instr: Instruction) -> Result<(), ExceptionType> {
|
||||
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.to_exception_load())?,
|
||||
.map_err(|e| e.into_exception_load())?,
|
||||
);
|
||||
core.advance_pc();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn sw(core: &mut Core, instr: Instruction) -> Result<(), ExceptionType> {
|
||||
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 Word;
|
||||
let value = core.reg_read(instr.rs2()) as u32;
|
||||
core.mem
|
||||
.write_word(addr, value)
|
||||
.map_err(|e| e.to_exception_store())?;
|
||||
.map_err(|e| e.into_exception_store())?;
|
||||
core.advance_pc();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn lw(core: &mut Core, instr: Instruction) -> Result<(), ExceptionType> {
|
||||
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.to_exception_load())? as i32 as i64 as DWord,
|
||||
.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<(), ExceptionType> {
|
||||
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.to_exception_load())? as DWord,
|
||||
.map_err(|e| e.into_exception_load())? as u64,
|
||||
);
|
||||
core.advance_pc();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn sh(core: &mut Core, instr: Instruction) -> Result<(), ExceptionType> {
|
||||
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 HWord;
|
||||
let value = core.reg_read(instr.rs2()) as u16;
|
||||
core.mem
|
||||
.write_hword(addr, value)
|
||||
.map_err(|e| e.to_exception_store())?;
|
||||
.map_err(|e| e.into_exception_store())?;
|
||||
core.advance_pc();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn lh(core: &mut Core, instr: Instruction) -> Result<(), ExceptionType> {
|
||||
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.to_exception_load())? as i16 as i64 as DWord,
|
||||
.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<(), ExceptionType> {
|
||||
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.to_exception_load())? as DWord,
|
||||
.map_err(|e| e.into_exception_load())? as u64,
|
||||
);
|
||||
core.advance_pc();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn sb(core: &mut Core, instr: Instruction) -> Result<(), ExceptionType> {
|
||||
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 Byte;
|
||||
let value = core.reg_read(instr.rs2()) as u8;
|
||||
core.mem
|
||||
.write_byte(addr, value)
|
||||
.map_err(|e| e.to_exception_store())?;
|
||||
.map_err(|e| e.into_exception_store())?;
|
||||
core.advance_pc();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn lb(core: &mut Core, instr: Instruction) -> Result<(), ExceptionType> {
|
||||
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.to_exception_load())? as i8 as i64 as DWord,
|
||||
.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<(), ExceptionType> {
|
||||
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.to_exception_load())? as DWord,
|
||||
.map_err(|e| e.into_exception_load())? as u64,
|
||||
);
|
||||
core.advance_pc();
|
||||
Ok(())
|
||||
|
||||
52
src/instructions/rvm.rs
Normal file
52
src/instructions/rvm.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright (c) 2025 taitep
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
//
|
||||
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
||||
// See LICENSE file in the project root for full license text.
|
||||
//
|
||||
use crate::{core::Core, decode::Instruction, exceptions::Exception};
|
||||
|
||||
// multiplication
|
||||
instr_op_r!(mul, u64::wrapping_mul);
|
||||
instr_op_r!(mulw, |a, b| u32::wrapping_mul(a as u32, b as u32) as u64);
|
||||
|
||||
instr_op_r!(mulh, |a, b| ((a as i64 as i128 * b as i64 as i128) >> 64)
|
||||
as u64);
|
||||
instr_op_r!(mulhsu, |a, b| ((a as i64 as i128 * b as i128) >> 64) as u64);
|
||||
instr_op_r!(mulhu, |a, b| ((a as u128 * b as u128) >> 64) as u64);
|
||||
|
||||
// division
|
||||
instr_op_r!(div, |a, b| match b {
|
||||
0 => -1,
|
||||
_ => i64::wrapping_div(a as i64, b as i64),
|
||||
} as u64);
|
||||
instr_op_r!(divu, |a, b| match b {
|
||||
0 => u64::MAX,
|
||||
_ => a / b,
|
||||
});
|
||||
instr_op_r!(divw, |a, b| match b {
|
||||
0 => -1,
|
||||
_ => i32::wrapping_div(a as i32, b as i32),
|
||||
} as i64 as u64);
|
||||
instr_op_r!(divuw, |a, b| match b {
|
||||
0 => u32::MAX,
|
||||
_ => a as u32 / b as u32,
|
||||
} as i32 as i64 as u64);
|
||||
|
||||
// remainder
|
||||
instr_op_r!(rem, |a, b| match b {
|
||||
0 => a,
|
||||
_ => i64::wrapping_rem(a as i64, b as i64) as u64,
|
||||
});
|
||||
instr_op_r!(remu, |a, b| match b {
|
||||
0 => a,
|
||||
_ => a % b,
|
||||
});
|
||||
instr_op_r!(remw, |a, b| match b {
|
||||
0 => a as i32,
|
||||
_ => i32::wrapping_rem(a as i32, b as i32),
|
||||
} as i64 as u64);
|
||||
instr_op_r!(remuw, |a, b| match b {
|
||||
0 => a as u32,
|
||||
_ => a as u32 % b as u32,
|
||||
} as i32 as i64 as u64);
|
||||
@@ -1,6 +1,7 @@
|
||||
pub mod consts;
|
||||
pub mod core;
|
||||
mod decode;
|
||||
pub mod devices;
|
||||
pub mod exceptions;
|
||||
pub mod gdb;
|
||||
mod instructions;
|
||||
pub mod mem;
|
||||
|
||||
67
src/main.rs
67
src/main.rs
@@ -1,42 +1,54 @@
|
||||
// Copyright (c) 2025 taitep
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2025-2026 taitep
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
//
|
||||
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
||||
// See LICENSE file in the project root for full license text.
|
||||
|
||||
use std::{env, sync::Arc, time::Duration};
|
||||
use std::{io, path::PathBuf, sync::Arc, time::Duration};
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
use nix::fcntl::{FcntlArg, OFlag, fcntl};
|
||||
use trve::{
|
||||
consts::{Addr, Byte, DWord, HWord, Word},
|
||||
core::Core,
|
||||
devices,
|
||||
exceptions::MemoryExceptionType,
|
||||
gdb,
|
||||
mem::{MemConfig, MemDeviceInterface, MmioRoot, Ram},
|
||||
};
|
||||
|
||||
use anyhow::{Result, bail};
|
||||
|
||||
use crate::basic_uart::BasicUart;
|
||||
use anyhow::Result;
|
||||
|
||||
mod execload;
|
||||
|
||||
/// Taitep's RISC-V Emulator
|
||||
#[derive(Parser)]
|
||||
struct Args {
|
||||
/// Path to ELF or raw binary executable to load
|
||||
executable: PathBuf,
|
||||
/// Make CPU wait for a GDB connection before starting execution
|
||||
#[arg(long)]
|
||||
wait: bool,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let args = Args::parse();
|
||||
|
||||
let mut ram = Ram::try_new(16 * 1024 * 1024)?;
|
||||
let buf = ram.buf_mut();
|
||||
|
||||
let args: Vec<String> = env::args().collect();
|
||||
|
||||
if args.len() != 2 {
|
||||
eprintln!("USAGE: trve <ram_image>");
|
||||
bail!("Wrong number of arguments");
|
||||
}
|
||||
|
||||
let entry_point = execload::load(&args[1], buf, 0x8000_0000)?;
|
||||
let entry_point = execload::load(args.executable, buf)?;
|
||||
|
||||
let mut mmio_root = MmioRoot::default();
|
||||
mmio_root.insert(0, Arc::new(DbgOut));
|
||||
|
||||
let uart = BasicUart::new();
|
||||
let uart = uart.spawn_poller(Duration::from_millis(10));
|
||||
if let Err(e) = fcntl(io::stdin(), FcntlArg::F_SETFL(OFlag::O_NONBLOCK)) {
|
||||
eprintln!("Could not make stdout nonblocking, skipping. Error: {e}");
|
||||
}
|
||||
|
||||
let uart = devices::serial::SifiveUart::new_arc();
|
||||
uart.clone()
|
||||
.spawn_io_thread(io::stdin(), io::stdout(), Duration::from_millis(10));
|
||||
mmio_root.insert(0x10000, uart);
|
||||
|
||||
let mem_cfg = MemConfig {
|
||||
@@ -44,34 +56,41 @@ fn main() -> Result<()> {
|
||||
mmio_root,
|
||||
};
|
||||
|
||||
let mut core = Core::new(mem_cfg);
|
||||
let (cmd_sender, cmd_reciever) = crossbeam::channel::bounded(16);
|
||||
|
||||
gdb::run_stub(cmd_sender.clone());
|
||||
|
||||
let mut core = Core::new(mem_cfg, cmd_reciever);
|
||||
core.reset(entry_point);
|
||||
|
||||
if args.wait {
|
||||
core.run_waiting_for_cmd();
|
||||
} else {
|
||||
core.run();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
mod basic_uart;
|
||||
|
||||
struct DbgOut;
|
||||
|
||||
impl MemDeviceInterface for DbgOut {
|
||||
fn write_dword(&self, addr: Addr, value: DWord) -> Result<(), MemoryExceptionType> {
|
||||
fn write_dword(&self, addr: u64, value: u64) -> Result<(), MemoryExceptionType> {
|
||||
eprintln!("Wrote DWord {value:016x} to Debug-Out address {addr:x}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_word(&self, addr: Addr, value: Word) -> Result<(), MemoryExceptionType> {
|
||||
fn write_word(&self, addr: u64, value: u32) -> Result<(), MemoryExceptionType> {
|
||||
eprintln!("Wrote Word {value:08x} to Debug-Out address {addr:x}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_hword(&self, addr: Addr, value: HWord) -> Result<(), MemoryExceptionType> {
|
||||
fn write_hword(&self, addr: u64, value: u16) -> Result<(), MemoryExceptionType> {
|
||||
eprintln!("Wrote HWord {value:04x} to Debug-Out address {addr:x}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_byte(&self, addr: Addr, value: Byte) -> Result<(), MemoryExceptionType> {
|
||||
fn write_byte(&self, addr: u64, value: u8) -> Result<(), MemoryExceptionType> {
|
||||
eprintln!("Wrote Byte {value:02x} to Debug-Out address {addr:x}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
407
src/mem.rs
407
src/mem.rs
@@ -1,24 +1,24 @@
|
||||
// Copyright (c) 2025 taitep
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2025-2026 taitep
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
//
|
||||
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
||||
// See LICENSE file in the project root for full license text.
|
||||
|
||||
use std::sync::{
|
||||
Arc,
|
||||
atomic::{AtomicU8, AtomicU16, AtomicU32, AtomicU64, Ordering::Relaxed},
|
||||
atomic::{
|
||||
AtomicU8, AtomicU16, AtomicU32, AtomicU64,
|
||||
Ordering::{self, Relaxed},
|
||||
},
|
||||
};
|
||||
|
||||
use memmap2::MmapMut;
|
||||
|
||||
use crate::{
|
||||
consts::{Addr, Byte, DWord, HWord, Word},
|
||||
exceptions::MemoryExceptionType,
|
||||
};
|
||||
use crate::exceptions::{MemoryException, MemoryExceptionType};
|
||||
|
||||
pub type PageNum = usize;
|
||||
|
||||
pub const RAM_START: Addr = 0x8000_0000;
|
||||
pub const RAM_START: u64 = 0x8000_0000;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MemConfig {
|
||||
@@ -27,7 +27,7 @@ pub struct MemConfig {
|
||||
}
|
||||
|
||||
impl MemConfig {
|
||||
pub fn memory_mapping_type(&self, addr: Addr) -> Option<MemoryMappingType> {
|
||||
pub fn memory_mapping_type(&self, addr: u64) -> Option<MemoryMappingType> {
|
||||
if addr >= RAM_START {
|
||||
Some(MemoryMappingType::RAM)
|
||||
} else {
|
||||
@@ -37,144 +37,139 @@ impl MemConfig {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_dword(&self, addr: Addr) -> Result<DWord, MemoryExceptionType> {
|
||||
pub fn read_dword(&self, addr: u64) -> Result<u64, MemoryException> {
|
||||
if addr >= RAM_START {
|
||||
self.ram.read_dword(addr - RAM_START)
|
||||
} else {
|
||||
if !addr.is_multiple_of(8) && self.mmio_root.crosses_boundary(addr, 8) {
|
||||
return Err(MemoryExceptionType::AddressMisaligned);
|
||||
return Err(MemoryException {
|
||||
type_: MemoryExceptionType::AddressMisaligned,
|
||||
addr,
|
||||
});
|
||||
}
|
||||
let (interface, addr) = self
|
||||
.mmio_root
|
||||
.get_device(addr)
|
||||
.ok_or(MemoryExceptionType::AccessFault)?;
|
||||
let (interface, addr) = self.mmio_root.get_device(addr).ok_or(MemoryException {
|
||||
type_: MemoryExceptionType::AccessFault,
|
||||
addr,
|
||||
})?;
|
||||
|
||||
interface.read_dword(addr)
|
||||
interface.read_dword(addr).map_err(|e| e.with_addr(addr))
|
||||
}
|
||||
}
|
||||
pub fn read_word(&self, addr: Addr) -> Result<Word, MemoryExceptionType> {
|
||||
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(MemoryExceptionType::AddressMisaligned);
|
||||
return Err(MemoryException {
|
||||
type_: MemoryExceptionType::AddressMisaligned,
|
||||
addr,
|
||||
});
|
||||
}
|
||||
let (interface, addr) = self
|
||||
.mmio_root
|
||||
.get_device(addr)
|
||||
.ok_or(MemoryExceptionType::AccessFault)?;
|
||||
let (interface, addr) = self.mmio_root.get_device(addr).ok_or(MemoryException {
|
||||
type_: MemoryExceptionType::AccessFault,
|
||||
addr,
|
||||
})?;
|
||||
|
||||
interface.read_word(addr)
|
||||
interface.read_word(addr).map_err(|e| e.with_addr(addr))
|
||||
}
|
||||
}
|
||||
pub fn read_hword(&self, addr: Addr) -> Result<HWord, MemoryExceptionType> {
|
||||
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(MemoryExceptionType::AddressMisaligned);
|
||||
return Err(MemoryException {
|
||||
type_: MemoryExceptionType::AddressMisaligned,
|
||||
addr,
|
||||
});
|
||||
}
|
||||
let (interface, addr) = self
|
||||
.mmio_root
|
||||
.get_device(addr)
|
||||
.ok_or(MemoryExceptionType::AccessFault)?;
|
||||
interface.read_hword(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: Addr) -> Result<Byte, MemoryExceptionType> {
|
||||
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(MemoryExceptionType::AccessFault)?;
|
||||
interface.read_byte(addr)
|
||||
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: Addr, value: DWord) -> Result<(), MemoryExceptionType> {
|
||||
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(MemoryExceptionType::AddressMisaligned);
|
||||
return Err(MemoryException {
|
||||
type_: MemoryExceptionType::AddressMisaligned,
|
||||
addr,
|
||||
});
|
||||
}
|
||||
let (interface, addr) = self
|
||||
.mmio_root
|
||||
.get_device(addr)
|
||||
.ok_or(MemoryExceptionType::AccessFault)?;
|
||||
interface.write_dword(addr, value)
|
||||
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: Addr, value: Word) -> Result<(), MemoryExceptionType> {
|
||||
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(MemoryExceptionType::AddressMisaligned);
|
||||
return Err(MemoryException {
|
||||
type_: MemoryExceptionType::AddressMisaligned,
|
||||
addr,
|
||||
});
|
||||
}
|
||||
let (interface, addr) = self
|
||||
.mmio_root
|
||||
.get_device(addr)
|
||||
.ok_or(MemoryExceptionType::AccessFault)?;
|
||||
interface.write_word(addr, value)
|
||||
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: Addr, value: HWord) -> Result<(), MemoryExceptionType> {
|
||||
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(MemoryExceptionType::AddressMisaligned);
|
||||
return Err(MemoryException {
|
||||
type_: MemoryExceptionType::AddressMisaligned,
|
||||
addr,
|
||||
});
|
||||
}
|
||||
let (interface, addr) = self
|
||||
.mmio_root
|
||||
.get_device(addr)
|
||||
.ok_or(MemoryExceptionType::AccessFault)?;
|
||||
interface.write_hword(addr, value)
|
||||
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: Addr, value: Byte) -> Result<(), MemoryExceptionType> {
|
||||
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(MemoryExceptionType::AccessFault)?;
|
||||
interface.write_byte(addr, value)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_atomic_dword(&self, addr: Addr) -> Result<&AtomicU64, MemoryExceptionType> {
|
||||
if !addr.is_multiple_of(8) {
|
||||
return Err(MemoryExceptionType::AddressMisaligned);
|
||||
}
|
||||
|
||||
let index = ((addr - RAM_START) / 8) as usize;
|
||||
unsafe {
|
||||
self.ram
|
||||
.buf_transmuted::<AtomicU64>()
|
||||
.get(index)
|
||||
.ok_or(MemoryExceptionType::AccessFault)
|
||||
}
|
||||
}
|
||||
pub fn get_atomic_word(&self, addr: Addr) -> Result<&AtomicU32, MemoryExceptionType> {
|
||||
if !addr.is_multiple_of(4) {
|
||||
return Err(MemoryExceptionType::AddressMisaligned);
|
||||
}
|
||||
|
||||
if addr < RAM_START {
|
||||
return Err(MemoryExceptionType::AccessFault);
|
||||
}
|
||||
|
||||
let index = ((addr - RAM_START) / 4) as usize;
|
||||
unsafe {
|
||||
self.ram
|
||||
.buf_transmuted::<AtomicU32>()
|
||||
.get(index)
|
||||
.ok_or(MemoryExceptionType::AccessFault)
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -187,6 +182,7 @@ pub enum MemoryMappingType {
|
||||
|
||||
pub struct Ram {
|
||||
buf: MmapMut,
|
||||
version_counters: Arc<[AtomicU32]>,
|
||||
}
|
||||
|
||||
#[cfg(target_endian = "big")]
|
||||
@@ -194,19 +190,33 @@ compile_error!("Current RAM implementation requires a little-endian host.");
|
||||
|
||||
impl Ram {
|
||||
pub fn try_new(size: usize) -> Result<Self, std::io::Error> {
|
||||
if !size.is_multiple_of(8) {
|
||||
return Err(std::io::Error::other("ram size must be a multiple of 8"));
|
||||
}
|
||||
Ok(Self {
|
||||
buf: MmapMut::map_anon(size)?,
|
||||
// SAFETY: We do not care about the initial version counts. Wrapping is fine. Only
|
||||
// equality is ever checked for, not magnitude.
|
||||
version_counters: unsafe {
|
||||
Arc::new_uninit_slice(size.div_ceil(Self::VERSION_CHUNK_SIZE)).assume_init()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
pub const VERSION_CHUNK_SIZE: usize = 64;
|
||||
|
||||
pub fn buf_mut(&mut self) -> &mut [u8] {
|
||||
self.buf.as_mut()
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// Safe if T has a size divisible by page size (4kb) (or is known to have a size divisible by the full ram size) and you know that the RAM is made up of valid naturally aligned values of T
|
||||
/// Safe if the size of the memory in bytes is divisible by the size of T
|
||||
/// Assuming try_new is used, RAM size is guaranteed to be a multiple of 8
|
||||
/// meaning anything with size 1, 2, 4, or 8 bytes is valid.
|
||||
/// It must also be known that the contents of RAM are made up of naturally
|
||||
/// aligned valid instances of T.
|
||||
#[inline]
|
||||
pub unsafe fn buf_transmuted<T>(&self) -> &[T] {
|
||||
pub(crate) unsafe fn buf_transmuted<T>(&self) -> &[T] {
|
||||
debug_assert!(self.buf.len().is_multiple_of(std::mem::size_of::<T>()));
|
||||
unsafe {
|
||||
std::slice::from_raw_parts(
|
||||
@@ -222,76 +232,88 @@ impl Ram {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn read_dword(&self, addr: Addr) -> Result<DWord, MemoryExceptionType> {
|
||||
pub fn read_dword(&self, addr: u64) -> Result<u64, MemoryException> {
|
||||
if !addr.is_multiple_of(8) {
|
||||
let high_word_addr = addr.wrapping_add(4);
|
||||
|
||||
let low_word = self.read_byte(addr)?;
|
||||
let high_word = self.read_byte(high_word_addr)?;
|
||||
let low_word = self.read_word(addr)?;
|
||||
let high_word = self.read_word(high_word_addr)?;
|
||||
|
||||
return Ok((low_word as DWord) | (high_word as DWord) << 32);
|
||||
return Ok((low_word as u64) | (high_word as u64) << 32);
|
||||
}
|
||||
|
||||
let index = (addr / 8) as usize;
|
||||
Ok(unsafe {
|
||||
self.buf_transmuted::<AtomicU64>()
|
||||
.get(index)
|
||||
.ok_or(MemoryExceptionType::AccessFault)
|
||||
.ok_or(MemoryException {
|
||||
type_: MemoryExceptionType::AccessFault,
|
||||
addr,
|
||||
})
|
||||
}?
|
||||
.load(Relaxed))
|
||||
}
|
||||
#[inline]
|
||||
pub fn read_word(&self, addr: Addr) -> Result<Word, MemoryExceptionType> {
|
||||
pub fn read_word(&self, addr: u64) -> Result<u32, MemoryException> {
|
||||
if !addr.is_multiple_of(4) {
|
||||
let high_hword_addr = addr.wrapping_add(2);
|
||||
|
||||
let low_hword = self.read_hword(addr)?;
|
||||
let high_hword = self.read_hword(high_hword_addr)?;
|
||||
|
||||
return Ok((low_hword as Word) | (high_hword as Word) << 16);
|
||||
return Ok((low_hword as u32) | (high_hword as u32) << 16);
|
||||
}
|
||||
|
||||
let index = (addr / 4) as usize;
|
||||
Ok(unsafe {
|
||||
self.buf_transmuted::<AtomicU32>()
|
||||
.get(index)
|
||||
.ok_or(MemoryExceptionType::AccessFault)
|
||||
.ok_or(MemoryException {
|
||||
type_: MemoryExceptionType::AccessFault,
|
||||
addr,
|
||||
})
|
||||
}?
|
||||
.load(Relaxed))
|
||||
}
|
||||
#[inline]
|
||||
pub fn read_hword(&self, addr: Addr) -> Result<HWord, MemoryExceptionType> {
|
||||
pub fn read_hword(&self, addr: u64) -> Result<u16, MemoryException> {
|
||||
if !addr.is_multiple_of(2) {
|
||||
let high_byte_addr = addr.wrapping_add(1);
|
||||
|
||||
let low_byte = self.read_byte(addr)?;
|
||||
let high_byte = self.read_byte(high_byte_addr)?;
|
||||
|
||||
return Ok((low_byte as HWord) | (high_byte as HWord) << 8);
|
||||
return Ok((low_byte as u16) | (high_byte as u16) << 8);
|
||||
}
|
||||
|
||||
let index = (addr / 2) as usize;
|
||||
Ok(unsafe {
|
||||
self.buf_transmuted::<AtomicU16>()
|
||||
.get(index)
|
||||
.ok_or(MemoryExceptionType::AccessFault)
|
||||
.ok_or(MemoryException {
|
||||
type_: MemoryExceptionType::AccessFault,
|
||||
addr,
|
||||
})
|
||||
}?
|
||||
.load(Relaxed))
|
||||
}
|
||||
#[inline]
|
||||
pub fn read_byte(&self, addr: Addr) -> Result<Byte, MemoryExceptionType> {
|
||||
pub fn read_byte(&self, addr: u64) -> Result<u8, MemoryException> {
|
||||
Ok(self
|
||||
.buf_atomic()
|
||||
.get(addr as usize)
|
||||
.ok_or(MemoryExceptionType::AccessFault)?
|
||||
.ok_or(MemoryException {
|
||||
type_: MemoryExceptionType::AccessFault,
|
||||
addr,
|
||||
})?
|
||||
.load(Relaxed))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn write_dword(&self, addr: Addr, value: DWord) -> Result<(), MemoryExceptionType> {
|
||||
pub fn write_dword(&self, addr: u64, value: u64) -> Result<(), MemoryException> {
|
||||
if !addr.is_multiple_of(8) {
|
||||
let low_word = value as Word;
|
||||
let high_word = (value >> 32) as Word;
|
||||
let low_word = value as u32;
|
||||
let high_word = (value >> 32) as u32;
|
||||
|
||||
let high_word_address = addr.wrapping_add(4);
|
||||
|
||||
@@ -300,20 +322,26 @@ impl Ram {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.claim_addr_even(addr)
|
||||
.ok_or_else(|| MemoryExceptionType::AccessFault.with_addr(addr))?;
|
||||
|
||||
let index = (addr / 8) as usize;
|
||||
unsafe {
|
||||
self.buf_transmuted::<AtomicU64>()
|
||||
.get(index)
|
||||
.ok_or(MemoryExceptionType::AccessFault)
|
||||
.ok_or(MemoryException {
|
||||
type_: MemoryExceptionType::AccessFault,
|
||||
addr,
|
||||
})
|
||||
}?
|
||||
.store(value, Relaxed);
|
||||
Ok(())
|
||||
}
|
||||
#[inline]
|
||||
pub fn write_word(&self, addr: Addr, value: Word) -> Result<(), MemoryExceptionType> {
|
||||
pub fn write_word(&self, addr: u64, value: u32) -> Result<(), MemoryException> {
|
||||
if !addr.is_multiple_of(4) {
|
||||
let low_hword = value as HWord;
|
||||
let high_hword = (value >> 16) as HWord;
|
||||
let low_hword = value as u16;
|
||||
let high_hword = (value >> 16) as u16;
|
||||
|
||||
let high_hword_address = addr.wrapping_add(2);
|
||||
|
||||
@@ -322,20 +350,26 @@ impl Ram {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.claim_addr_even(addr)
|
||||
.ok_or_else(|| MemoryExceptionType::AccessFault.with_addr(addr))?;
|
||||
|
||||
let index = (addr / 4) as usize;
|
||||
unsafe {
|
||||
self.buf_transmuted::<AtomicU32>()
|
||||
.get(index)
|
||||
.ok_or(MemoryExceptionType::AccessFault)
|
||||
.ok_or(MemoryException {
|
||||
type_: MemoryExceptionType::AccessFault,
|
||||
addr,
|
||||
})
|
||||
}?
|
||||
.store(value, Relaxed);
|
||||
Ok(())
|
||||
}
|
||||
#[inline]
|
||||
pub fn write_hword(&self, addr: Addr, value: HWord) -> Result<(), MemoryExceptionType> {
|
||||
pub fn write_hword(&self, addr: u64, value: u16) -> Result<(), MemoryException> {
|
||||
if !addr.is_multiple_of(2) {
|
||||
let low_byte = value as Byte;
|
||||
let high_byte = (value >> 8) as Byte;
|
||||
let low_byte = value as u8;
|
||||
let high_byte = (value >> 8) as u8;
|
||||
|
||||
let high_byte_address = addr.wrapping_add(1);
|
||||
|
||||
@@ -344,23 +378,120 @@ impl Ram {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.claim_addr_even(addr)
|
||||
.ok_or_else(|| MemoryExceptionType::AccessFault.with_addr(addr))?;
|
||||
|
||||
let index = (addr / 2) as usize;
|
||||
unsafe {
|
||||
self.buf_transmuted::<AtomicU16>()
|
||||
.get(index)
|
||||
.ok_or(MemoryExceptionType::AccessFault)
|
||||
.ok_or(MemoryException {
|
||||
type_: MemoryExceptionType::AccessFault,
|
||||
addr,
|
||||
})
|
||||
}?
|
||||
.store(value, Relaxed);
|
||||
Ok(())
|
||||
}
|
||||
#[inline]
|
||||
pub fn write_byte(&self, addr: Addr, value: Byte) -> Result<(), MemoryExceptionType> {
|
||||
pub fn write_byte(&self, addr: u64, value: u8) -> Result<(), MemoryException> {
|
||||
self.claim_addr_even(addr)
|
||||
.ok_or_else(|| MemoryExceptionType::AccessFault.with_addr(addr))?;
|
||||
self.buf_atomic()
|
||||
.get(addr as usize)
|
||||
.ok_or(MemoryExceptionType::AccessFault)?
|
||||
.ok_or(MemoryException {
|
||||
type_: MemoryExceptionType::AccessFault,
|
||||
addr,
|
||||
})?
|
||||
.store(value, Relaxed);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn claim_addr_even<'a>(&'a self, addr: u64) -> Option<RamVersionClaim<'a>> {
|
||||
let chunk_id = addr as usize / Self::VERSION_CHUNK_SIZE;
|
||||
let chunk_counter = self.version_counters.get(chunk_id)?;
|
||||
Some(RamVersionClaim::claim_even(&chunk_counter))
|
||||
}
|
||||
|
||||
// Tries to create a claim for a specified chunk id with a specific version
|
||||
// Outer Option represents whether the chunk id exists
|
||||
// Inner Option represents whether the claim succeeded
|
||||
pub fn claim_expected<'a>(
|
||||
&'a self,
|
||||
chunk_id: usize,
|
||||
expected: u32,
|
||||
) -> Option<Option<RamVersionClaim<'a>>> {
|
||||
self.version_counters
|
||||
.get(chunk_id)
|
||||
.map(|chunk_counter| RamVersionClaim::claim_expected(chunk_counter, expected))
|
||||
}
|
||||
|
||||
/// Waits for a specific address to have an even (ready) version
|
||||
/// number and returns the version chunk id and version
|
||||
pub fn wait_for_even_version(&self, addr: u64) -> Option<(usize, u32)> {
|
||||
let chunk_id = addr as usize / Self::VERSION_CHUNK_SIZE;
|
||||
let chunk_counter = self.version_counters.get(chunk_id)?;
|
||||
|
||||
loop {
|
||||
let current_version = chunk_counter.load(Ordering::Acquire);
|
||||
if current_version.is_multiple_of(2) {
|
||||
return Some((chunk_id, current_version));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RamVersionClaim<'a> {
|
||||
version_counter: &'a AtomicU32,
|
||||
initial_version: u32,
|
||||
}
|
||||
|
||||
impl<'a> RamVersionClaim<'a> {
|
||||
pub fn claim_even(counter: &'a AtomicU32) -> RamVersionClaim<'a> {
|
||||
loop {
|
||||
let current_version = counter.load(Ordering::Acquire);
|
||||
if !current_version.is_multiple_of(2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Attempt to increment and therefore successfully claim the version
|
||||
let res = counter.compare_exchange(
|
||||
current_version,
|
||||
current_version.wrapping_add(1),
|
||||
Ordering::AcqRel,
|
||||
Ordering::Acquire,
|
||||
);
|
||||
|
||||
if let Ok(initial_version) = res {
|
||||
return RamVersionClaim {
|
||||
version_counter: counter,
|
||||
initial_version,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn claim_expected(counter: &'a AtomicU32, expected: u32) -> Option<RamVersionClaim<'a>> {
|
||||
counter
|
||||
.compare_exchange(
|
||||
expected,
|
||||
expected.wrapping_add(1),
|
||||
Ordering::AcqRel,
|
||||
Ordering::Acquire,
|
||||
)
|
||||
.ok()
|
||||
.map(|initial_version| RamVersionClaim {
|
||||
version_counter: counter,
|
||||
initial_version,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Drop for RamVersionClaim<'a> {
|
||||
fn drop(&mut self) {
|
||||
self.version_counter
|
||||
.store(self.initial_version.wrapping_add(2), Ordering::Release);
|
||||
}
|
||||
}
|
||||
|
||||
pub const MMIO_SECOND_LEVEL_PAGE_SIZE: usize = 64 * 1024;
|
||||
@@ -373,7 +504,7 @@ const MMIO_SECOND_LEVEL_ENTRIES: usize = MMIO_ROOT_PAGE_SIZE / MMIO_SECOND_LEVEL
|
||||
pub struct MmioRoot(Box<[Option<MmioSecondLevel>; MMIO_ROOT_ENTRIES]>);
|
||||
|
||||
impl MmioRoot {
|
||||
pub fn insert(&mut self, base_addr: Addr, interface: Arc<dyn MemDeviceInterface>) {
|
||||
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);
|
||||
|
||||
@@ -388,7 +519,7 @@ impl MmioRoot {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_full(&mut self, base_addr: Addr, interface: Arc<dyn MemDeviceInterface>) {
|
||||
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);
|
||||
|
||||
@@ -397,7 +528,7 @@ impl MmioRoot {
|
||||
self.0[page_id] = Some(MmioSecondLevel::Interface(interface));
|
||||
}
|
||||
|
||||
fn get_device(&self, addr: Addr) -> Option<(Arc<dyn MemDeviceInterface>, Addr)> {
|
||||
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;
|
||||
@@ -405,10 +536,10 @@ impl MmioRoot {
|
||||
|
||||
self.0[root_page_id]
|
||||
.as_ref()
|
||||
.and_then(|s| s.get_device(addr % MMIO_ROOT_PAGE_SIZE as Addr))
|
||||
.and_then(|s| s.get_device(addr % MMIO_ROOT_PAGE_SIZE as u64))
|
||||
}
|
||||
|
||||
fn crosses_boundary(&self, addr: Addr, size: Addr) -> bool {
|
||||
fn crosses_boundary(&self, addr: u64, size: u64) -> bool {
|
||||
if addr >= RAM_START {
|
||||
return false;
|
||||
}
|
||||
@@ -450,12 +581,12 @@ enum MmioSecondLevel {
|
||||
}
|
||||
|
||||
impl MmioSecondLevel {
|
||||
fn get_device(&self, addr: Addr) -> Option<(Arc<dyn MemDeviceInterface>, Addr)> {
|
||||
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 Addr)),
|
||||
.map(|i| (i.clone(), addr % MMIO_SECOND_LEVEL_PAGE_SIZE as u64)),
|
||||
|
||||
Self::Interface(i) => Some((i.clone(), addr)),
|
||||
}
|
||||
@@ -470,29 +601,29 @@ impl Default for MmioSecondLevel {
|
||||
|
||||
#[allow(unused_variables)]
|
||||
pub trait MemDeviceInterface {
|
||||
fn write_dword(&self, addr: Addr, value: DWord) -> Result<(), MemoryExceptionType> {
|
||||
fn write_dword(&self, addr: u64, value: u64) -> Result<(), MemoryExceptionType> {
|
||||
Err(MemoryExceptionType::AccessFault)
|
||||
}
|
||||
fn write_word(&self, addr: Addr, value: Word) -> Result<(), MemoryExceptionType> {
|
||||
fn write_word(&self, addr: u64, value: u32) -> Result<(), MemoryExceptionType> {
|
||||
Err(MemoryExceptionType::AccessFault)
|
||||
}
|
||||
fn write_hword(&self, addr: Addr, value: HWord) -> Result<(), MemoryExceptionType> {
|
||||
fn write_hword(&self, addr: u64, value: u16) -> Result<(), MemoryExceptionType> {
|
||||
Err(MemoryExceptionType::AccessFault)
|
||||
}
|
||||
fn write_byte(&self, addr: Addr, value: Byte) -> Result<(), MemoryExceptionType> {
|
||||
fn write_byte(&self, addr: u64, value: u8) -> Result<(), MemoryExceptionType> {
|
||||
Err(MemoryExceptionType::AccessFault)
|
||||
}
|
||||
|
||||
fn read_dword(&self, addr: Addr) -> Result<DWord, MemoryExceptionType> {
|
||||
fn read_dword(&self, addr: u64) -> Result<u64, MemoryExceptionType> {
|
||||
Err(MemoryExceptionType::AccessFault)
|
||||
}
|
||||
fn read_word(&self, addr: Addr) -> Result<Word, MemoryExceptionType> {
|
||||
fn read_word(&self, addr: u64) -> Result<u32, MemoryExceptionType> {
|
||||
Err(MemoryExceptionType::AccessFault)
|
||||
}
|
||||
fn read_hword(&self, addr: Addr) -> Result<HWord, MemoryExceptionType> {
|
||||
fn read_hword(&self, addr: u64) -> Result<u16, MemoryExceptionType> {
|
||||
Err(MemoryExceptionType::AccessFault)
|
||||
}
|
||||
fn read_byte(&self, addr: Addr) -> Result<Byte, MemoryExceptionType> {
|
||||
fn read_byte(&self, addr: u64) -> Result<u8, MemoryExceptionType> {
|
||||
Err(MemoryExceptionType::AccessFault)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user