Compare commits
20 Commits
5c008bfc04
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 6b6b778ab0 | |||
| bbfa20befe | |||
| 36e6ec1006 | |||
| d3e8af85a6 | |||
| 3451a8227c | |||
| d1b4cc7b56 | |||
| 9861187fa6 | |||
| 07e755340e | |||
| ceb7f2f172 | |||
| 21fb6cbc8b | |||
| bbc9e0b9ff | |||
| 7fcfc031ef | |||
| 21a8479ce9 | |||
| 09fe12f516 | |||
| 0f0e844223 | |||
| 5a383956c9 | |||
| 6a0e5e63c1 | |||
| e5c5312566 | |||
| 9a9bef7dd7 | |||
| 8024af6b13 |
2
.rust-analyzer.toml
Normal file
2
.rust-analyzer.toml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[diagnostics]
|
||||||
|
disabled = ["inactive-code"]
|
||||||
57
Cargo.lock
generated
57
Cargo.lock
generated
@@ -122,6 +122,62 @@ version = "1.0.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
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]]
|
[[package]]
|
||||||
name = "goblin"
|
name = "goblin"
|
||||||
version = "0.10.4"
|
version = "0.10.4"
|
||||||
@@ -281,6 +337,7 @@ version = "0.0.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
|
"crossbeam",
|
||||||
"goblin",
|
"goblin",
|
||||||
"int-enum",
|
"int-enum",
|
||||||
"memmap2",
|
"memmap2",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ edition = "2024"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.100"
|
anyhow = "1.0.100"
|
||||||
clap = { version = "4.5.53", features = ["derive"] }
|
clap = { version = "4.5.53", features = ["derive"] }
|
||||||
|
crossbeam = { version = "0.8.4", features = ["crossbeam-channel"] }
|
||||||
goblin = "0.10.4"
|
goblin = "0.10.4"
|
||||||
int-enum = "1.2.0"
|
int-enum = "1.2.0"
|
||||||
memmap2 = "0.9.8"
|
memmap2 = "0.9.8"
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
|||||||
Copyright 2025 taitep
|
Copyright 2025-2026 taitep
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ taitep's RISC-V Emulator.
|
|||||||
The goal is to support at least RV64GC and be able to run Linux,
|
The goal is to support at least RV64GC and be able to run Linux,
|
||||||
potentially more. No plans for RV32I or RV32/64E.
|
potentially more. No plans for RV32I or RV32/64E.
|
||||||
|
|
||||||
|
Currently implemented RISC-V ISA: `RV64IM-Zalrsc`
|
||||||
|
|
||||||
## Current Use
|
## Current Use
|
||||||
Currently, the emulator is nowhere near complete,
|
Currently, the emulator is nowhere near complete,
|
||||||
its not even at rv64i, but it does work for a subset of it.
|
its not even at rv64i, but it does work for a subset of it.
|
||||||
|
|||||||
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,103 +0,0 @@
|
|||||||
// Copyright (c) 2025 taitep
|
|
||||||
// SPDX-License-Identifier: BSD-2-Clause
|
|
||||||
//
|
|
||||||
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
|
||||||
// See LICENSE file in the project root for full license text.
|
|
||||||
|
|
||||||
use std::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::{MemoryException, 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<(), MemoryException> {
|
|
||||||
match addr {
|
|
||||||
0 => {
|
|
||||||
self.write(value);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
_ => Err(MemoryException {
|
|
||||||
type_: MemoryExceptionType::AccessFault,
|
|
||||||
addr,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn read_byte(&self, addr: Addr) -> Result<Byte, MemoryException> {
|
|
||||||
match addr {
|
|
||||||
0 => Ok(self.read()),
|
|
||||||
1 => Ok(1 | (self.can_read() as u8) << 1),
|
|
||||||
_ => Err(MemoryException {
|
|
||||||
type_: MemoryExceptionType::AccessFault,
|
|
||||||
addr,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
122
src/core.rs
122
src/core.rs
@@ -1,13 +1,12 @@
|
|||||||
// Copyright (c) 2025 taitep
|
// Copyright (c) 2025-2026 taitep
|
||||||
// SPDX-License-Identifier: BSD-2-Clause
|
// SPDX-License-Identifier: BSD-2-Clause
|
||||||
//
|
//
|
||||||
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
||||||
// See LICENSE file in the project root for full license text.
|
// See LICENSE file in the project root for full license text.
|
||||||
|
|
||||||
use std::{collections::HashSet, sync::mpsc};
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
consts::{Addr, RegId, RegValue},
|
|
||||||
core::commands::CoreCmd,
|
core::commands::CoreCmd,
|
||||||
decode::Instruction,
|
decode::Instruction,
|
||||||
exceptions::{Exception, ExceptionType, MemoryException},
|
exceptions::{Exception, ExceptionType, MemoryException},
|
||||||
@@ -17,32 +16,31 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub struct Core {
|
pub struct Core {
|
||||||
pub(crate) x_regs: [RegValue; 32],
|
pub(crate) x_regs: [u64; 32],
|
||||||
pub(crate) pc: Addr,
|
pub(crate) pc: u64,
|
||||||
pub(crate) mem: MemConfig,
|
pub(crate) mem: MemConfig,
|
||||||
command_stream: mpsc::Receiver<CoreCmd>,
|
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;
|
pub mod commands;
|
||||||
|
|
||||||
impl Core {
|
impl Core {
|
||||||
pub fn new(mem: MemConfig, command_stream: mpsc::Receiver<CoreCmd>) -> Self {
|
pub fn new(mem: MemConfig, command_stream: crossbeam::channel::Receiver<CoreCmd>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
x_regs: [0; 32],
|
x_regs: [0; 32],
|
||||||
pc: 0,
|
pc: 0,
|
||||||
mem,
|
mem,
|
||||||
command_stream,
|
command_stream,
|
||||||
|
reservation: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(&mut self) {
|
pub fn run(&mut self) {
|
||||||
loop {
|
loop {
|
||||||
if let Ok(cmd) = self.command_stream.try_recv() {
|
if let Ok(cmd) = self.command_stream.try_recv() {
|
||||||
match cmd {
|
self.handle_command(cmd);
|
||||||
CoreCmd::EnterDbgMode(DebugStream(dbg_stream)) => {
|
|
||||||
let _ = self.debug_loop(dbg_stream);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = self.step() {
|
if let Err(e) = self.step() {
|
||||||
@@ -52,15 +50,45 @@ impl Core {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn step(&mut self) -> Result<(), Exception> {
|
||||||
|
if !self.pc.is_multiple_of(4) {
|
||||||
|
self.throw_exception(Exception {
|
||||||
|
type_: ExceptionType::InstructionAddressMisaligned,
|
||||||
|
value: self.pc,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let instr = match self.mem.read_word(self.pc) {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(e) => {
|
||||||
|
return Err(e.into_exception_instr());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if instr & 3 != 3 {
|
||||||
|
// Compressed instruction - (currently) unsupported
|
||||||
|
// Could also be zero instruction, that also matches this
|
||||||
|
return Err(Exception {
|
||||||
|
type_: ExceptionType::IllegalInstruction,
|
||||||
|
value: instr as u64,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let instr = Instruction(instr);
|
||||||
|
|
||||||
|
if let Err(e) = find_and_exec(instr, self) {
|
||||||
|
dbg!(instr);
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn run_waiting_for_cmd(&mut self) {
|
pub fn run_waiting_for_cmd(&mut self) {
|
||||||
eprintln!("Waiting for any core command...");
|
eprintln!("Waiting for any core command...");
|
||||||
if let Ok(cmd) = self.command_stream.recv() {
|
if let Ok(cmd) = self.command_stream.recv() {
|
||||||
eprintln!("Recieved a command");
|
eprintln!("Recieved a command");
|
||||||
match cmd {
|
self.handle_command(cmd);
|
||||||
CoreCmd::EnterDbgMode(DebugStream(dbg_stream)) => {
|
|
||||||
let _ = self.debug_loop(dbg_stream);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} else {
|
} else {
|
||||||
eprintln!("Error recieving command, starting anyway");
|
eprintln!("Error recieving command, starting anyway");
|
||||||
}
|
}
|
||||||
@@ -70,13 +98,24 @@ impl Core {
|
|||||||
self.run();
|
self.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn debug_loop(&mut self, dbg_stream: mpsc::Receiver<gdb::DebugCommand>) -> anyhow::Result<()> {
|
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();
|
let mut breakpoints = HashSet::new();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match dbg_stream.recv()? {
|
match dbg_stream.recv()? {
|
||||||
DebugCommand::GetRegs(sender) => sender.send(gdb::RegsResponse {
|
DebugCommand::GetRegs(sender) => sender.send(gdb::RegsResponse {
|
||||||
x_regs: self.x_regs.clone(),
|
x_regs: self.x_regs,
|
||||||
pc: self.pc,
|
pc: self.pc,
|
||||||
})?,
|
})?,
|
||||||
DebugCommand::ReadMem {
|
DebugCommand::ReadMem {
|
||||||
@@ -119,7 +158,7 @@ impl Core {
|
|||||||
|
|
||||||
fn continue_loop(
|
fn continue_loop(
|
||||||
&mut self,
|
&mut self,
|
||||||
breakpoints: &HashSet<Addr>,
|
breakpoints: &HashSet<u64>,
|
||||||
stopper: oneshot::Receiver<()>,
|
stopper: oneshot::Receiver<()>,
|
||||||
) -> StopReason {
|
) -> StopReason {
|
||||||
loop {
|
loop {
|
||||||
@@ -127,7 +166,7 @@ impl Core {
|
|||||||
return StopReason::Exception(ExceptionType::Breakpoint);
|
return StopReason::Exception(ExceptionType::Breakpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(_) = stopper.try_recv() {
|
if stopper.try_recv().is_ok() {
|
||||||
return StopReason::Interrupted;
|
return StopReason::Interrupted;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,54 +177,21 @@ impl Core {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn step(&mut self) -> Result<(), Exception> {
|
|
||||||
if !self.pc.is_multiple_of(4) {
|
|
||||||
self.throw_exception(Exception {
|
|
||||||
type_: ExceptionType::InstructionAddressMisaligned,
|
|
||||||
value: self.pc,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let instr = match self.mem.read_word(self.pc) {
|
|
||||||
Ok(i) => i,
|
|
||||||
Err(e) => {
|
|
||||||
return Err(e.to_exception_instr());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if instr & 3 != 3 {
|
|
||||||
// Compressed instruction - (currently) unsupported
|
|
||||||
// Could also be zero instruction, that also matches this
|
|
||||||
return Err(Exception {
|
|
||||||
type_: ExceptionType::IllegalInstruction,
|
|
||||||
value: instr as u64,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let instr = Instruction(instr);
|
|
||||||
|
|
||||||
if let Err(e) = find_and_exec(instr, self) {
|
|
||||||
eprintln!("instr: {:08x}", instr.0);
|
|
||||||
return Err(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn throw_exception(&mut self, exception: Exception) {
|
fn throw_exception(&mut self, exception: Exception) {
|
||||||
eprintln!("Exception: {exception:?}");
|
eprintln!("Exception: {exception:?}");
|
||||||
dbg!(self.pc, self.x_regs);
|
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;
|
self.pc = pc;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn reg_read(&self, id: RegId) -> RegValue {
|
pub(crate) fn reg_read(&self, id: u8) -> u64 {
|
||||||
self.x_regs[id as usize]
|
self.x_regs[id as usize]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn reg_write(&mut self, id: RegId, value: RegValue) {
|
pub(crate) fn reg_write(&mut self, id: u8, value: u64) {
|
||||||
if id == 0 {
|
if id == 0 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,18 @@
|
|||||||
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
||||||
// See LICENSE file in the project root for full license text.
|
// See LICENSE file in the project root for full license text.
|
||||||
|
|
||||||
use crate::consts::{DWord, RegId, Word};
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
const MASK_REGISTER: Word = 0x1f;
|
const MASK_REGISTER: u32 = 0x1f;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct Instruction(pub Word);
|
pub struct Instruction(pub u32);
|
||||||
|
|
||||||
|
impl std::fmt::Debug for Instruction {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_fmt(format_args!("{:08x}", self.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
impl Instruction {
|
impl Instruction {
|
||||||
@@ -26,8 +32,8 @@ impl Instruction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn rd(self) -> RegId {
|
pub fn rd(self) -> u8 {
|
||||||
(self.0 >> 7 & MASK_REGISTER) as RegId
|
(self.0 >> 7 & MASK_REGISTER) as u8
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@@ -36,53 +42,58 @@ impl Instruction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn rs1(self) -> RegId {
|
pub fn rs1(self) -> u8 {
|
||||||
(self.0 >> 15 & MASK_REGISTER) as RegId
|
(self.0 >> 15 & MASK_REGISTER) as u8
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn rs2(self) -> RegId {
|
pub fn rs2(self) -> u8 {
|
||||||
(self.0 >> 20 & MASK_REGISTER) as RegId
|
(self.0 >> 20 & MASK_REGISTER) as u8
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn funct7(self) -> u8 {
|
pub fn funct7(self) -> u8 {
|
||||||
(self.0 >> 25 & 0x7f) as u8
|
(self.0 >> 25) as u8
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn imm_i(self) -> DWord {
|
pub fn funct5(self) -> u8 {
|
||||||
(self.0 as i32 as i64 >> 20) as DWord
|
(self.0 >> 27) as u8
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn imm_s(self) -> DWord {
|
pub fn imm_i(self) -> u64 {
|
||||||
let imm_11_5 = (self.0 as i32 as i64 >> 25 << 5) as DWord;
|
(self.0 as i32 as i64 >> 20) as u64
|
||||||
let imm_4_0 = (self.0 >> 7 & 0x1f) as DWord;
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
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
|
imm_11_5 | imm_4_0
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn imm_b(self) -> DWord {
|
pub fn imm_b(self) -> u64 {
|
||||||
let imm_12 = ((self.0 & 0x8000_0000) as i32 as i64 >> (31 - 12)) as DWord;
|
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 DWord;
|
let imm_10_5 = ((self.0 >> 25 & 0x3f) << 5) as u64;
|
||||||
let imm_4_1 = ((self.0 >> 8 & 0xf) << 1) as DWord;
|
let imm_4_1 = ((self.0 >> 8 & 0xf) << 1) as u64;
|
||||||
let imm_11 = ((self.0 >> 7 & 1) << 11) as DWord;
|
let imm_11 = ((self.0 >> 7 & 1) << 11) as u64;
|
||||||
|
|
||||||
imm_12 | imm_10_5 | imm_4_1 | imm_11
|
imm_12 | imm_10_5 | imm_4_1 | imm_11
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn imm_u(self) -> DWord {
|
pub fn imm_u(self) -> u64 {
|
||||||
(self.0 & 0xffff_f000) as i32 as i64 as DWord
|
(self.0 & 0xffff_f000) as i32 as i64 as u64
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn imm_j(self) -> DWord {
|
pub fn imm_j(self) -> u64 {
|
||||||
let imm_20 = ((self.0 & 0x8000_0000) as i32 as i64 >> (31 - 20)) as DWord;
|
let imm_20 = ((self.0 & 0x8000_0000) as i32 as i64 >> (31 - 20)) as u64;
|
||||||
let imm_10_1 = ((self.0 >> 21 & 0x3ff) << 1) as DWord;
|
let imm_10_1 = ((self.0 >> 21 & 0x3ff) << 1) as u64;
|
||||||
let imm_11 = ((self.0 >> 20 & 1) << 11) as DWord;
|
let imm_11 = ((self.0 >> 20 & 1) << 11) as u64;
|
||||||
let imm_19_12 = ((self.0 >> 12 & 0xff) << 12) as DWord;
|
let imm_19_12 = ((self.0 >> 12 & 0xff) << 12) as u64;
|
||||||
|
|
||||||
imm_20 | imm_10_1 | imm_11 | imm_19_12
|
imm_20 | imm_10_1 | imm_11 | imm_19_12
|
||||||
}
|
}
|
||||||
@@ -91,6 +102,26 @@ impl Instruction {
|
|||||||
/// 32bit ones use funct7 in this way
|
/// 32bit ones use funct7 in this way
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn funct6(self) -> u8 {
|
pub fn funct6(self) -> u8 {
|
||||||
(self.0 >> 26 & 0x3f) as u8
|
(self.0 >> 26) as u8
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mostly/only used for the SYSTEM opcode
|
||||||
|
#[inline]
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,8 +6,6 @@
|
|||||||
|
|
||||||
use int_enum::IntEnum;
|
use int_enum::IntEnum;
|
||||||
|
|
||||||
use crate::consts::{Addr, DWord};
|
|
||||||
|
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, IntEnum)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, IntEnum)]
|
||||||
@@ -31,10 +29,19 @@ pub enum ExceptionType {
|
|||||||
HardwareError = 19,
|
HardwareError = 19,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ExceptionType {
|
||||||
|
pub fn with_no_value(self) -> Exception {
|
||||||
|
Exception {
|
||||||
|
type_: self,
|
||||||
|
value: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct Exception {
|
pub struct Exception {
|
||||||
pub type_: ExceptionType,
|
pub type_: ExceptionType,
|
||||||
pub value: DWord,
|
pub value: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
@@ -44,14 +51,20 @@ pub enum MemoryExceptionType {
|
|||||||
PageFault,
|
PageFault,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl MemoryExceptionType {
|
||||||
|
pub(crate) fn with_addr(self, addr: u64) -> MemoryException {
|
||||||
|
MemoryException { type_: self, addr }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct MemoryException {
|
pub struct MemoryException {
|
||||||
pub type_: MemoryExceptionType,
|
pub type_: MemoryExceptionType,
|
||||||
pub addr: Addr,
|
pub addr: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MemoryException {
|
impl MemoryException {
|
||||||
pub(crate) fn to_exception_store(&self) -> Exception {
|
pub(crate) fn into_exception_store(self) -> Exception {
|
||||||
Exception {
|
Exception {
|
||||||
type_: match self.type_ {
|
type_: match self.type_ {
|
||||||
MemoryExceptionType::AddressMisaligned => ExceptionType::StoreAmoAddressMisaligned,
|
MemoryExceptionType::AddressMisaligned => ExceptionType::StoreAmoAddressMisaligned,
|
||||||
@@ -62,7 +75,7 @@ impl MemoryException {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn to_exception_instr(&self) -> Exception {
|
pub(crate) fn into_exception_instr(self) -> Exception {
|
||||||
Exception {
|
Exception {
|
||||||
type_: match self.type_ {
|
type_: match self.type_ {
|
||||||
MemoryExceptionType::AddressMisaligned => ExceptionType::InstructionAccessFault,
|
MemoryExceptionType::AddressMisaligned => ExceptionType::InstructionAccessFault,
|
||||||
@@ -73,7 +86,7 @@ impl MemoryException {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn to_exception_load(&self) -> Exception {
|
pub(crate) fn into_exception_load(self) -> Exception {
|
||||||
Exception {
|
Exception {
|
||||||
type_: match self.type_ {
|
type_: match self.type_ {
|
||||||
MemoryExceptionType::AddressMisaligned => ExceptionType::LoadAddressMisaligned,
|
MemoryExceptionType::AddressMisaligned => ExceptionType::LoadAddressMisaligned,
|
||||||
@@ -85,14 +98,14 @@ impl MemoryException {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<MemoryExceptionType> for MemoryException {
|
impl From<MemoryException> for MemoryExceptionType {
|
||||||
fn into(self) -> MemoryExceptionType {
|
fn from(val: MemoryException) -> Self {
|
||||||
self.type_
|
val.type_
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<ExceptionType> for Exception {
|
impl From<Exception> for ExceptionType {
|
||||||
fn into(self) -> ExceptionType {
|
fn from(val: Exception) -> Self {
|
||||||
self.type_
|
val.type_
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ use goblin::{
|
|||||||
program_header::PT_LOAD,
|
program_header::PT_LOAD,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use trve::{consts::Addr, mem::RAM_START};
|
use trve::mem::RAM_START;
|
||||||
|
|
||||||
pub fn load<P: AsRef<Path>>(path: P, ram: &mut [u8]) -> Result<Addr> {
|
pub fn load<P: AsRef<Path>>(path: P, ram: &mut [u8]) -> Result<u64> {
|
||||||
let buf = fs::read(path)?;
|
let buf = fs::read(path)?;
|
||||||
|
|
||||||
match Object::parse(&buf)? {
|
match Object::parse(&buf)? {
|
||||||
|
|||||||
59
src/gdb.rs
59
src/gdb.rs
@@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2025 taitep
|
// Copyright (c) 2025-2026 taitep
|
||||||
// SPDX-License-Identifier: BSD-2-Clause
|
// SPDX-License-Identifier: BSD-2-Clause
|
||||||
//
|
//
|
||||||
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
||||||
@@ -7,11 +7,9 @@
|
|||||||
use std::{
|
use std::{
|
||||||
io::{self, BufRead, ErrorKind, Write},
|
io::{self, BufRead, ErrorKind, Write},
|
||||||
net::{TcpListener, TcpStream},
|
net::{TcpListener, TcpStream},
|
||||||
sync::mpsc,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
consts::{Addr, RegValue},
|
|
||||||
core::commands::CoreCmd,
|
core::commands::CoreCmd,
|
||||||
exceptions::{ExceptionType, MemoryExceptionType},
|
exceptions::{ExceptionType, MemoryExceptionType},
|
||||||
};
|
};
|
||||||
@@ -19,18 +17,18 @@ use crate::{
|
|||||||
pub(crate) enum DebugCommand {
|
pub(crate) enum DebugCommand {
|
||||||
GetRegs(oneshot::Sender<RegsResponse>),
|
GetRegs(oneshot::Sender<RegsResponse>),
|
||||||
ReadMem {
|
ReadMem {
|
||||||
addr: Addr,
|
addr: u64,
|
||||||
len: u64,
|
len: u64,
|
||||||
responder: oneshot::Sender<Result<Vec<u8>, MemoryExceptionType>>,
|
responder: oneshot::Sender<Result<Vec<u8>, MemoryExceptionType>>,
|
||||||
},
|
},
|
||||||
Step(oneshot::Sender<StopReason>),
|
Step(oneshot::Sender<StopReason>),
|
||||||
Continue(oneshot::Sender<StopReason>, oneshot::Receiver<()>),
|
Continue(oneshot::Sender<StopReason>, oneshot::Receiver<()>),
|
||||||
SetBreakpoint(Addr),
|
SetBreakpoint(u64),
|
||||||
RemoveBreakpoint(Addr),
|
RemoveBreakpoint(u64),
|
||||||
ExitDebugMode,
|
ExitDebugMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DebugStream(pub(crate) mpsc::Receiver<DebugCommand>);
|
pub struct DebugStream(pub(crate) crossbeam::channel::Receiver<DebugCommand>);
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub(crate) enum StopReason {
|
pub(crate) enum StopReason {
|
||||||
@@ -65,17 +63,16 @@ impl StopReason {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct RegsResponse {
|
pub(crate) struct RegsResponse {
|
||||||
pub x_regs: [RegValue; 32],
|
pub x_regs: [u64; 32],
|
||||||
pub pc: Addr,
|
pub pc: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_stub(cmd_sender: mpsc::Sender<CoreCmd>) {
|
pub fn run_stub(cmd_sender: crossbeam::channel::Sender<CoreCmd>) {
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
let listener = TcpListener::bind("127.0.0.1:1234").expect("couldnt start tcp listener");
|
let listener = TcpListener::bind("127.0.0.1:1234").expect("couldnt start tcp listener");
|
||||||
|
|
||||||
for stream_res in listener.incoming() {
|
for stream in listener.incoming().flatten() {
|
||||||
if let Ok(stream) = stream_res {
|
let (dbg_tx, dbg_rx) = crossbeam::channel::bounded(16);
|
||||||
let (dbg_tx, dbg_rx) = mpsc::channel();
|
|
||||||
|
|
||||||
stream
|
stream
|
||||||
.set_nonblocking(true)
|
.set_nonblocking(true)
|
||||||
@@ -87,13 +84,12 @@ pub fn run_stub(cmd_sender: mpsc::Sender<CoreCmd>) {
|
|||||||
|
|
||||||
handle_gdb_connection(stream, dbg_tx).expect("failure during connection");
|
handle_gdb_connection(stream, dbg_tx).expect("failure during connection");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_gdb_connection(
|
fn handle_gdb_connection(
|
||||||
gdb_stream: TcpStream,
|
gdb_stream: TcpStream,
|
||||||
dbg_tx: mpsc::Sender<DebugCommand>,
|
dbg_tx: crossbeam::channel::Sender<DebugCommand>,
|
||||||
) -> io::Result<()> {
|
) -> io::Result<()> {
|
||||||
eprintln!("gdb connected");
|
eprintln!("gdb connected");
|
||||||
let mut reader = io::BufReader::new(gdb_stream.try_clone()?);
|
let mut reader = io::BufReader::new(gdb_stream.try_clone()?);
|
||||||
@@ -101,18 +97,19 @@ fn handle_gdb_connection(
|
|||||||
|
|
||||||
loop {
|
loop {
|
||||||
match read_rsp_packet(&mut reader) {
|
match read_rsp_packet(&mut reader) {
|
||||||
Ok(packet) => match handle_packet(
|
Ok(packet) => {
|
||||||
|
if handle_packet(
|
||||||
&packet[..packet.len() - 1],
|
&packet[..packet.len() - 1],
|
||||||
&mut writer,
|
&mut writer,
|
||||||
&dbg_tx,
|
&dbg_tx,
|
||||||
&mut reader,
|
&mut reader,
|
||||||
) {
|
)
|
||||||
Err(_) => {
|
.is_err()
|
||||||
|
{
|
||||||
let _ = dbg_tx.send(DebugCommand::ExitDebugMode);
|
let _ = dbg_tx.send(DebugCommand::ExitDebugMode);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
_ => {}
|
}
|
||||||
},
|
|
||||||
Err(ref e) if e.kind() == ErrorKind::WouldBlock => {
|
Err(ref e) if e.kind() == ErrorKind::WouldBlock => {
|
||||||
std::thread::yield_now();
|
std::thread::yield_now();
|
||||||
}
|
}
|
||||||
@@ -153,7 +150,7 @@ fn read_rsp_packet<R: BufRead>(reader: &mut R) -> io::Result<String> {
|
|||||||
fn handle_packet<W: Write, R: BufRead>(
|
fn handle_packet<W: Write, R: BufRead>(
|
||||||
packet: &str,
|
packet: &str,
|
||||||
writer: &mut W,
|
writer: &mut W,
|
||||||
dbg_tx: &mpsc::Sender<DebugCommand>,
|
dbg_tx: &crossbeam::channel::Sender<DebugCommand>,
|
||||||
reader: &mut R,
|
reader: &mut R,
|
||||||
) -> io::Result<()> {
|
) -> io::Result<()> {
|
||||||
writer.write_all(b"+")?;
|
writer.write_all(b"+")?;
|
||||||
@@ -226,7 +223,7 @@ fn handle_packet<W: Write, R: BufRead>(
|
|||||||
let (responder, stop_reason_rx) = oneshot::channel();
|
let (responder, stop_reason_rx) = oneshot::channel();
|
||||||
dbg_tx.send(DebugCommand::Step(responder)).unwrap();
|
dbg_tx.send(DebugCommand::Step(responder)).unwrap();
|
||||||
let stop_reason = stop_reason_rx.recv().unwrap();
|
let stop_reason = stop_reason_rx.recv().unwrap();
|
||||||
send_packet(&stop_reason.to_rsp(), writer)?;
|
send_packet(stop_reason.to_rsp(), writer)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
"c" => {
|
"c" => {
|
||||||
@@ -255,7 +252,7 @@ fn handle_packet<W: Write, R: BufRead>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(stop_reason) = stop_reason_rx.try_recv() {
|
if let Ok(stop_reason) = stop_reason_rx.try_recv() {
|
||||||
send_packet(&stop_reason.to_rsp(), writer)?;
|
send_packet(stop_reason.to_rsp(), writer)?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,11 +264,12 @@ fn handle_packet<W: Write, R: BufRead>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
"Z" if packet.chars().nth(1) == Some('0') => {
|
"Z" if packet.chars().nth(1) == Some('0') => {
|
||||||
if let Some((addr_str, size_str)) = packet[3..].split_once(',') {
|
if let Some((addr_str, size_str)) = packet[3..].split_once(',')
|
||||||
if let (Ok(addr), Ok(size)) = (
|
&& let (Ok(addr), Ok(size)) = (
|
||||||
u64::from_str_radix(addr_str, 16),
|
u64::from_str_radix(addr_str, 16),
|
||||||
u64::from_str_radix(size_str, 16),
|
u64::from_str_radix(size_str, 16),
|
||||||
) {
|
)
|
||||||
|
{
|
||||||
if size != 4 {
|
if size != 4 {
|
||||||
send_packet("", writer)?;
|
send_packet("", writer)?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@@ -281,16 +279,16 @@ fn handle_packet<W: Write, R: BufRead>(
|
|||||||
send_packet("OK", writer)?;
|
send_packet("OK", writer)?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
send_packet("", writer)?;
|
send_packet("", writer)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
"z" if packet.chars().nth(1) == Some('0') => {
|
"z" if packet.chars().nth(1) == Some('0') => {
|
||||||
if let Some((addr_str, size_str)) = packet[3..].split_once(',') {
|
if let Some((addr_str, size_str)) = packet[3..].split_once(',')
|
||||||
if let (Ok(addr), Ok(size)) = (
|
&& let (Ok(addr), Ok(size)) = (
|
||||||
u64::from_str_radix(addr_str, 16),
|
u64::from_str_radix(addr_str, 16),
|
||||||
u64::from_str_radix(size_str, 16),
|
u64::from_str_radix(size_str, 16),
|
||||||
) {
|
)
|
||||||
|
{
|
||||||
if size != 4 {
|
if size != 4 {
|
||||||
send_packet("", writer)?;
|
send_packet("", writer)?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@@ -300,7 +298,6 @@ fn handle_packet<W: Write, R: BufRead>(
|
|||||||
send_packet("OK", writer)?;
|
send_packet("OK", writer)?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
send_packet("", writer)?;
|
send_packet("", writer)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,19 +7,23 @@
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod macros;
|
mod macros;
|
||||||
|
|
||||||
|
mod rva;
|
||||||
mod rvi;
|
mod rvi;
|
||||||
|
mod rvm;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
consts::DWord,
|
|
||||||
core::Core,
|
core::Core,
|
||||||
decode::Instruction,
|
decode::Instruction,
|
||||||
exceptions::{Exception, ExceptionType::IllegalInstruction},
|
exceptions::{
|
||||||
|
Exception,
|
||||||
|
ExceptionType::{self, IllegalInstruction},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
fn illegal(instr: Instruction) -> Result<(), Exception> {
|
fn illegal(instr: Instruction) -> Result<(), Exception> {
|
||||||
Err(Exception {
|
Err(Exception {
|
||||||
type_: IllegalInstruction,
|
type_: IllegalInstruction,
|
||||||
value: instr.0 as DWord,
|
value: instr.0 as u64,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,6 +41,15 @@ pub(crate) fn find_and_exec(instr: Instruction, core: &mut Core) -> Result<(), E
|
|||||||
(0b111, 0b0000000) => rvi::and(core, instr),
|
(0b111, 0b0000000) => rvi::and(core, instr),
|
||||||
(0b100, 0b0000000) => rvi::xor(core, instr),
|
(0b100, 0b0000000) => rvi::xor(core, instr),
|
||||||
(0b110, 0b0000000) => rvi::or(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),
|
_ => illegal(instr),
|
||||||
},
|
},
|
||||||
0b01110 => match (instr.funct3(), instr.funct7()) {
|
0b01110 => match (instr.funct3(), instr.funct7()) {
|
||||||
@@ -46,6 +59,12 @@ pub(crate) fn find_and_exec(instr: Instruction, core: &mut Core) -> Result<(), E
|
|||||||
(0b001, 0b0000000) => rvi::sllw(core, instr),
|
(0b001, 0b0000000) => rvi::sllw(core, instr),
|
||||||
(0b101, 0b0000000) => rvi::srlw(core, instr),
|
(0b101, 0b0000000) => rvi::srlw(core, instr),
|
||||||
(0b101, 0b0100000) => rvi::sraw(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),
|
_ => illegal(instr),
|
||||||
},
|
},
|
||||||
0b00100 => match instr.funct3() {
|
0b00100 => match instr.funct3() {
|
||||||
@@ -126,10 +145,29 @@ pub(crate) fn find_and_exec(instr: Instruction, core: &mut Core) -> Result<(), E
|
|||||||
// FENCE is just implemented as a SeqCst fence always here
|
// FENCE is just implemented as a SeqCst fence always here
|
||||||
// I dont yet care about the potential performance issue this may bring
|
// I dont yet care about the potential performance issue this may bring
|
||||||
std::sync::atomic::fence(std::sync::atomic::Ordering::SeqCst);
|
std::sync::atomic::fence(std::sync::atomic::Ordering::SeqCst);
|
||||||
|
core.advance_pc();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
_ => illegal(instr),
|
_ => 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),
|
_ => illegal(instr),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
||||||
// See LICENSE file in the project root for full license text.
|
// See LICENSE file in the project root for full license text.
|
||||||
|
|
||||||
use crate::{consts::RegValue, core::Core, decode::Instruction, exceptions::Exception};
|
use crate::{core::Core, decode::Instruction, exceptions::Exception};
|
||||||
|
|
||||||
use std::ops::{BitAnd, BitOr, BitXor};
|
use std::ops::{BitAnd, BitOr, BitXor};
|
||||||
|
|
||||||
@@ -12,42 +12,38 @@ mod mem;
|
|||||||
|
|
||||||
pub use mem::*;
|
pub use mem::*;
|
||||||
|
|
||||||
instr_op!(add, addi, RegValue::wrapping_add);
|
instr_op!(add, addi, u64::wrapping_add);
|
||||||
instr_op!(
|
instr_op!(addw, addiw, |a, b| u64::wrapping_add(a, b) as i32 as i64
|
||||||
addw,
|
as u64);
|
||||||
addiw,
|
instr_op_r!(sub, u64::wrapping_sub);
|
||||||
|a, b| RegValue::wrapping_add(a, b) as i32 as i64 as RegValue
|
instr_op_r!(subw, |a, b| u64::wrapping_sub(a, b) as i32 as i64 as u64);
|
||||||
);
|
|
||||||
instr_op_r!(sub, RegValue::wrapping_sub);
|
|
||||||
instr_op_r!(subw, |a, b| RegValue::wrapping_sub(a, b) as i32 as i64
|
|
||||||
as RegValue);
|
|
||||||
|
|
||||||
instr_op!(and, andi, RegValue::bitand);
|
instr_op!(and, andi, u64::bitand);
|
||||||
instr_op!(or, ori, RegValue::bitor);
|
instr_op!(or, ori, u64::bitor);
|
||||||
instr_op!(xor, xori, RegValue::bitxor);
|
instr_op!(xor, xori, u64::bitxor);
|
||||||
|
|
||||||
instr_op!(sll, slli, |x, shamt| x << (shamt & 0b111111));
|
instr_op!(sll, slli, |x, shamt| x << (shamt & 0b111111));
|
||||||
instr_op!(
|
instr_op!(
|
||||||
sllw,
|
sllw,
|
||||||
slliw,
|
slliw,
|
||||||
|x, shamt| (x << (shamt & 0b11111)) as i32 as i64 as RegValue
|
|x, shamt| (x << (shamt & 0b11111)) as i32 as i64 as u64
|
||||||
);
|
);
|
||||||
instr_op!(srl, srli, |x, shamt| x >> (shamt & 0b111111));
|
instr_op!(srl, srli, |x, shamt| x >> (shamt & 0b111111));
|
||||||
instr_op!(
|
instr_op!(
|
||||||
srlw,
|
srlw,
|
||||||
srliw,
|
srliw,
|
||||||
|x, shamt| (x >> (shamt & 0b11111)) as i32 as i64 as RegValue
|
|x, shamt| (x as u32 >> (shamt & 0b11111)) as i32 as i64 as u64
|
||||||
);
|
);
|
||||||
instr_op!(sra, srai, |x, shamt| (x as i64 >> (shamt & 0b111111))
|
instr_op!(sra, srai, |x, shamt| (x as i64 >> (shamt & 0b111111))
|
||||||
as RegValue);
|
as u64);
|
||||||
instr_op!(
|
instr_op!(
|
||||||
sraw,
|
sraw,
|
||||||
sraiw,
|
sraiw,
|
||||||
|x, shamt| (x as i32 >> (shamt & 0b11111)) as i64 as RegValue
|
|x, shamt| (x as i32 >> (shamt & 0b11111)) as i64 as u64
|
||||||
);
|
);
|
||||||
|
|
||||||
instr_op!(sltu, sltiu, |a, b| (a < b) as RegValue);
|
instr_op!(sltu, sltiu, |a, b| (a < b) as u64);
|
||||||
instr_op!(slt, slti, |a, b| ((a as i64) < (b as i64)) as RegValue);
|
instr_op!(slt, slti, |a, b| ((a as i64) < (b as i64)) as u64);
|
||||||
|
|
||||||
pub fn lui(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
pub fn lui(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
||||||
core.reg_write(instr.rd(), instr.imm_u());
|
core.reg_write(instr.rd(), instr.imm_u());
|
||||||
@@ -68,8 +64,9 @@ pub fn jal(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn jalr(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
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.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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,19 +4,14 @@
|
|||||||
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
||||||
// See LICENSE file in the project root for full license text.
|
// See LICENSE file in the project root for full license text.
|
||||||
|
|
||||||
use crate::{
|
use crate::{core::Core, exceptions::Exception, instructions::Instruction};
|
||||||
consts::{Byte, DWord, HWord, Word},
|
|
||||||
core::Core,
|
|
||||||
exceptions::Exception,
|
|
||||||
instructions::Instruction,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn sd(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
pub fn sd(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
||||||
let addr = core.reg_read(instr.rs1()).wrapping_add(instr.imm_s());
|
let addr = core.reg_read(instr.rs1()).wrapping_add(instr.imm_s());
|
||||||
let value = core.reg_read(instr.rs2());
|
let value = core.reg_read(instr.rs2());
|
||||||
core.mem
|
core.mem
|
||||||
.write_dword(addr, value)
|
.write_dword(addr, value)
|
||||||
.map_err(|e| e.to_exception_store())?;
|
.map_err(|e| e.into_exception_store())?;
|
||||||
core.advance_pc();
|
core.advance_pc();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -27,7 +22,7 @@ pub fn ld(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
|||||||
instr.rd(),
|
instr.rd(),
|
||||||
core.mem
|
core.mem
|
||||||
.read_dword(addr)
|
.read_dword(addr)
|
||||||
.map_err(|e| e.to_exception_load())?,
|
.map_err(|e| e.into_exception_load())?,
|
||||||
);
|
);
|
||||||
core.advance_pc();
|
core.advance_pc();
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -35,10 +30,10 @@ pub fn ld(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
|||||||
|
|
||||||
pub fn sw(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
pub fn sw(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
||||||
let addr = core.reg_read(instr.rs1()).wrapping_add(instr.imm_s());
|
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
|
core.mem
|
||||||
.write_word(addr, value)
|
.write_word(addr, value)
|
||||||
.map_err(|e| e.to_exception_store())?;
|
.map_err(|e| e.into_exception_store())?;
|
||||||
core.advance_pc();
|
core.advance_pc();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -49,7 +44,7 @@ pub fn lw(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
|||||||
instr.rd(),
|
instr.rd(),
|
||||||
core.mem
|
core.mem
|
||||||
.read_word(addr)
|
.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();
|
core.advance_pc();
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -61,7 +56,7 @@ pub fn lwu(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
|||||||
instr.rd(),
|
instr.rd(),
|
||||||
core.mem
|
core.mem
|
||||||
.read_word(addr)
|
.read_word(addr)
|
||||||
.map_err(|e| e.to_exception_load())? as DWord,
|
.map_err(|e| e.into_exception_load())? as u64,
|
||||||
);
|
);
|
||||||
core.advance_pc();
|
core.advance_pc();
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -69,10 +64,10 @@ pub fn lwu(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
|||||||
|
|
||||||
pub fn sh(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
pub fn sh(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
||||||
let addr = core.reg_read(instr.rs1()).wrapping_add(instr.imm_s());
|
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
|
core.mem
|
||||||
.write_hword(addr, value)
|
.write_hword(addr, value)
|
||||||
.map_err(|e| e.to_exception_store())?;
|
.map_err(|e| e.into_exception_store())?;
|
||||||
core.advance_pc();
|
core.advance_pc();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -83,7 +78,7 @@ pub fn lh(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
|||||||
instr.rd(),
|
instr.rd(),
|
||||||
core.mem
|
core.mem
|
||||||
.read_hword(addr)
|
.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();
|
core.advance_pc();
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -95,7 +90,7 @@ pub fn lhu(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
|||||||
instr.rd(),
|
instr.rd(),
|
||||||
core.mem
|
core.mem
|
||||||
.read_hword(addr)
|
.read_hword(addr)
|
||||||
.map_err(|e| e.to_exception_load())? as DWord,
|
.map_err(|e| e.into_exception_load())? as u64,
|
||||||
);
|
);
|
||||||
core.advance_pc();
|
core.advance_pc();
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -103,10 +98,10 @@ pub fn lhu(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
|||||||
|
|
||||||
pub fn sb(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
pub fn sb(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
||||||
let addr = core.reg_read(instr.rs1()).wrapping_add(instr.imm_s());
|
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
|
core.mem
|
||||||
.write_byte(addr, value)
|
.write_byte(addr, value)
|
||||||
.map_err(|e| e.to_exception_store())?;
|
.map_err(|e| e.into_exception_store())?;
|
||||||
core.advance_pc();
|
core.advance_pc();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -117,7 +112,7 @@ pub fn lb(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
|||||||
instr.rd(),
|
instr.rd(),
|
||||||
core.mem
|
core.mem
|
||||||
.read_byte(addr)
|
.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();
|
core.advance_pc();
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -129,7 +124,7 @@ pub fn lbu(core: &mut Core, instr: Instruction) -> Result<(), Exception> {
|
|||||||
instr.rd(),
|
instr.rd(),
|
||||||
core.mem
|
core.mem
|
||||||
.read_byte(addr)
|
.read_byte(addr)
|
||||||
.map_err(|e| e.to_exception_load())? as DWord,
|
.map_err(|e| e.into_exception_load())? as u64,
|
||||||
);
|
);
|
||||||
core.advance_pc();
|
core.advance_pc();
|
||||||
Ok(())
|
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,6 @@
|
|||||||
pub mod consts;
|
|
||||||
pub mod core;
|
pub mod core;
|
||||||
mod decode;
|
mod decode;
|
||||||
|
pub mod devices;
|
||||||
pub mod exceptions;
|
pub mod exceptions;
|
||||||
pub mod gdb;
|
pub mod gdb;
|
||||||
mod instructions;
|
mod instructions;
|
||||||
|
|||||||
35
src/main.rs
35
src/main.rs
@@ -1,30 +1,32 @@
|
|||||||
// Copyright (c) 2025 taitep
|
// Copyright (c) 2025-2026 taitep
|
||||||
// SPDX-License-Identifier: BSD-2-Clause
|
// SPDX-License-Identifier: BSD-2-Clause
|
||||||
//
|
//
|
||||||
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
||||||
// See LICENSE file in the project root for full license text.
|
// See LICENSE file in the project root for full license text.
|
||||||
|
|
||||||
use std::{path::PathBuf, sync::Arc, time::Duration};
|
use std::{io, path::PathBuf, sync::Arc, time::Duration};
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|
||||||
|
use nix::fcntl::{FcntlArg, OFlag, fcntl};
|
||||||
use trve::{
|
use trve::{
|
||||||
consts::{Addr, Byte, DWord, HWord, Word},
|
|
||||||
core::Core,
|
core::Core,
|
||||||
exceptions::MemoryException,
|
devices,
|
||||||
|
exceptions::MemoryExceptionType,
|
||||||
gdb,
|
gdb,
|
||||||
mem::{MemConfig, MemDeviceInterface, MmioRoot, Ram},
|
mem::{MemConfig, MemDeviceInterface, MmioRoot, Ram},
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
use crate::basic_uart::BasicUart;
|
|
||||||
|
|
||||||
mod execload;
|
mod execload;
|
||||||
|
|
||||||
|
/// Taitep's RISC-V Emulator
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
struct Args {
|
struct Args {
|
||||||
|
/// Path to ELF or raw binary executable to load
|
||||||
executable: PathBuf,
|
executable: PathBuf,
|
||||||
|
/// Make CPU wait for a GDB connection before starting execution
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
wait: bool,
|
wait: bool,
|
||||||
}
|
}
|
||||||
@@ -40,8 +42,13 @@ fn main() -> Result<()> {
|
|||||||
let mut mmio_root = MmioRoot::default();
|
let mut mmio_root = MmioRoot::default();
|
||||||
mmio_root.insert(0, Arc::new(DbgOut));
|
mmio_root.insert(0, Arc::new(DbgOut));
|
||||||
|
|
||||||
let uart = BasicUart::new();
|
if let Err(e) = fcntl(io::stdin(), FcntlArg::F_SETFL(OFlag::O_NONBLOCK)) {
|
||||||
let uart = uart.spawn_poller(Duration::from_millis(10));
|
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);
|
mmio_root.insert(0x10000, uart);
|
||||||
|
|
||||||
let mem_cfg = MemConfig {
|
let mem_cfg = MemConfig {
|
||||||
@@ -49,7 +56,7 @@ fn main() -> Result<()> {
|
|||||||
mmio_root,
|
mmio_root,
|
||||||
};
|
};
|
||||||
|
|
||||||
let (cmd_sender, cmd_reciever) = std::sync::mpsc::channel();
|
let (cmd_sender, cmd_reciever) = crossbeam::channel::bounded(16);
|
||||||
|
|
||||||
gdb::run_stub(cmd_sender.clone());
|
gdb::run_stub(cmd_sender.clone());
|
||||||
|
|
||||||
@@ -65,27 +72,25 @@ fn main() -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
mod basic_uart;
|
|
||||||
|
|
||||||
struct DbgOut;
|
struct DbgOut;
|
||||||
|
|
||||||
impl MemDeviceInterface for DbgOut {
|
impl MemDeviceInterface for DbgOut {
|
||||||
fn write_dword(&self, addr: Addr, value: DWord) -> Result<(), MemoryException> {
|
fn write_dword(&self, addr: u64, value: u64) -> Result<(), MemoryExceptionType> {
|
||||||
eprintln!("Wrote DWord {value:016x} to Debug-Out address {addr:x}");
|
eprintln!("Wrote DWord {value:016x} to Debug-Out address {addr:x}");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_word(&self, addr: Addr, value: Word) -> Result<(), MemoryException> {
|
fn write_word(&self, addr: u64, value: u32) -> Result<(), MemoryExceptionType> {
|
||||||
eprintln!("Wrote Word {value:08x} to Debug-Out address {addr:x}");
|
eprintln!("Wrote Word {value:08x} to Debug-Out address {addr:x}");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_hword(&self, addr: Addr, value: HWord) -> Result<(), MemoryException> {
|
fn write_hword(&self, addr: u64, value: u16) -> Result<(), MemoryExceptionType> {
|
||||||
eprintln!("Wrote HWord {value:04x} to Debug-Out address {addr:x}");
|
eprintln!("Wrote HWord {value:04x} to Debug-Out address {addr:x}");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_byte(&self, addr: Addr, value: Byte) -> Result<(), MemoryException> {
|
fn write_byte(&self, addr: u64, value: u8) -> Result<(), MemoryExceptionType> {
|
||||||
eprintln!("Wrote Byte {value:02x} to Debug-Out address {addr:x}");
|
eprintln!("Wrote Byte {value:02x} to Debug-Out address {addr:x}");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
317
src/mem.rs
317
src/mem.rs
@@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2025 taitep
|
// Copyright (c) 2025-2026 taitep
|
||||||
// SPDX-License-Identifier: BSD-2-Clause
|
// SPDX-License-Identifier: BSD-2-Clause
|
||||||
//
|
//
|
||||||
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
||||||
@@ -6,19 +6,19 @@
|
|||||||
|
|
||||||
use std::sync::{
|
use std::sync::{
|
||||||
Arc,
|
Arc,
|
||||||
atomic::{AtomicU8, AtomicU16, AtomicU32, AtomicU64, Ordering::Relaxed},
|
atomic::{
|
||||||
|
AtomicU8, AtomicU16, AtomicU32, AtomicU64,
|
||||||
|
Ordering::{self, Relaxed},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use memmap2::MmapMut;
|
use memmap2::MmapMut;
|
||||||
|
|
||||||
use crate::{
|
use crate::exceptions::{MemoryException, MemoryExceptionType};
|
||||||
consts::{Addr, Byte, DWord, HWord, Word},
|
|
||||||
exceptions::{MemoryException, MemoryExceptionType},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub type PageNum = usize;
|
pub type PageNum = usize;
|
||||||
|
|
||||||
pub const RAM_START: Addr = 0x8000_0000;
|
pub const RAM_START: u64 = 0x8000_0000;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct MemConfig {
|
pub struct MemConfig {
|
||||||
@@ -27,7 +27,7 @@ pub struct MemConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl 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 {
|
if addr >= RAM_START {
|
||||||
Some(MemoryMappingType::RAM)
|
Some(MemoryMappingType::RAM)
|
||||||
} else {
|
} else {
|
||||||
@@ -37,7 +37,7 @@ impl MemConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_dword(&self, addr: Addr) -> Result<DWord, MemoryException> {
|
pub fn read_dword(&self, addr: u64) -> Result<u64, MemoryException> {
|
||||||
if addr >= RAM_START {
|
if addr >= RAM_START {
|
||||||
self.ram.read_dword(addr - RAM_START)
|
self.ram.read_dword(addr - RAM_START)
|
||||||
} else {
|
} else {
|
||||||
@@ -52,10 +52,10 @@ impl MemConfig {
|
|||||||
addr,
|
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, MemoryException> {
|
pub fn read_word(&self, addr: u64) -> Result<u32, MemoryException> {
|
||||||
if addr >= RAM_START {
|
if addr >= RAM_START {
|
||||||
self.ram.read_word(addr - RAM_START)
|
self.ram.read_word(addr - RAM_START)
|
||||||
} else {
|
} else {
|
||||||
@@ -70,10 +70,10 @@ impl MemConfig {
|
|||||||
addr,
|
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, MemoryException> {
|
pub fn read_hword(&self, addr: u64) -> Result<u16, MemoryException> {
|
||||||
if addr >= RAM_START {
|
if addr >= RAM_START {
|
||||||
self.ram.read_hword(addr - RAM_START)
|
self.ram.read_hword(addr - RAM_START)
|
||||||
} else {
|
} else {
|
||||||
@@ -87,10 +87,10 @@ impl MemConfig {
|
|||||||
type_: MemoryExceptionType::AccessFault,
|
type_: MemoryExceptionType::AccessFault,
|
||||||
addr,
|
addr,
|
||||||
})?;
|
})?;
|
||||||
interface.read_hword(addr)
|
interface.read_hword(addr).map_err(|e| e.with_addr(addr))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn read_byte(&self, addr: Addr) -> Result<Byte, MemoryException> {
|
pub fn read_byte(&self, addr: u64) -> Result<u8, MemoryException> {
|
||||||
if addr >= RAM_START {
|
if addr >= RAM_START {
|
||||||
self.ram.read_byte(addr - RAM_START)
|
self.ram.read_byte(addr - RAM_START)
|
||||||
} else {
|
} else {
|
||||||
@@ -98,11 +98,11 @@ impl MemConfig {
|
|||||||
type_: MemoryExceptionType::AccessFault,
|
type_: MemoryExceptionType::AccessFault,
|
||||||
addr,
|
addr,
|
||||||
})?;
|
})?;
|
||||||
interface.read_byte(addr)
|
interface.read_byte(addr).map_err(|e| e.with_addr(addr))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_dword(&self, addr: Addr, value: DWord) -> Result<(), MemoryException> {
|
pub fn write_dword(&self, addr: u64, value: u64) -> Result<(), MemoryException> {
|
||||||
if addr >= RAM_START {
|
if addr >= RAM_START {
|
||||||
self.ram.write_dword(addr - RAM_START, value)
|
self.ram.write_dword(addr - RAM_START, value)
|
||||||
} else {
|
} else {
|
||||||
@@ -116,10 +116,12 @@ impl MemConfig {
|
|||||||
type_: MemoryExceptionType::AccessFault,
|
type_: MemoryExceptionType::AccessFault,
|
||||||
addr,
|
addr,
|
||||||
})?;
|
})?;
|
||||||
interface.write_dword(addr, value)
|
interface
|
||||||
|
.write_dword(addr, value)
|
||||||
|
.map_err(|e| e.with_addr(addr))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn write_word(&self, addr: Addr, value: Word) -> Result<(), MemoryException> {
|
pub fn write_word(&self, addr: u64, value: u32) -> Result<(), MemoryException> {
|
||||||
if addr >= RAM_START {
|
if addr >= RAM_START {
|
||||||
self.ram.write_word(addr - RAM_START, value)
|
self.ram.write_word(addr - RAM_START, value)
|
||||||
} else {
|
} else {
|
||||||
@@ -133,10 +135,12 @@ impl MemConfig {
|
|||||||
type_: MemoryExceptionType::AccessFault,
|
type_: MemoryExceptionType::AccessFault,
|
||||||
addr,
|
addr,
|
||||||
})?;
|
})?;
|
||||||
interface.write_word(addr, value)
|
interface
|
||||||
|
.write_word(addr, value)
|
||||||
|
.map_err(|e| e.with_addr(addr))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn write_hword(&self, addr: Addr, value: HWord) -> Result<(), MemoryException> {
|
pub fn write_hword(&self, addr: u64, value: u16) -> Result<(), MemoryException> {
|
||||||
if addr >= RAM_START {
|
if addr >= RAM_START {
|
||||||
self.ram.write_hword(addr - RAM_START, value)
|
self.ram.write_hword(addr - RAM_START, value)
|
||||||
} else {
|
} else {
|
||||||
@@ -150,10 +154,12 @@ impl MemConfig {
|
|||||||
type_: MemoryExceptionType::AccessFault,
|
type_: MemoryExceptionType::AccessFault,
|
||||||
addr,
|
addr,
|
||||||
})?;
|
})?;
|
||||||
interface.write_hword(addr, value)
|
interface
|
||||||
|
.write_hword(addr, value)
|
||||||
|
.map_err(|e| e.with_addr(addr))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn write_byte(&self, addr: Addr, value: Byte) -> Result<(), MemoryException> {
|
pub fn write_byte(&self, addr: u64, value: u8) -> Result<(), MemoryException> {
|
||||||
if addr >= RAM_START {
|
if addr >= RAM_START {
|
||||||
self.ram.write_byte(addr - RAM_START, value)
|
self.ram.write_byte(addr - RAM_START, value)
|
||||||
} else {
|
} else {
|
||||||
@@ -161,53 +167,9 @@ impl MemConfig {
|
|||||||
type_: MemoryExceptionType::AccessFault,
|
type_: MemoryExceptionType::AccessFault,
|
||||||
addr,
|
addr,
|
||||||
})?;
|
})?;
|
||||||
interface.write_byte(addr, value)
|
interface
|
||||||
}
|
.write_byte(addr, value)
|
||||||
}
|
.map_err(|e| e.with_addr(addr))
|
||||||
|
|
||||||
pub fn get_atomic_dword(&self, addr: Addr) -> Result<&AtomicU64, MemoryException> {
|
|
||||||
if !addr.is_multiple_of(8) {
|
|
||||||
return Err(MemoryException {
|
|
||||||
type_: MemoryExceptionType::AddressMisaligned,
|
|
||||||
addr,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let index = ((addr - RAM_START) / 8) as usize;
|
|
||||||
unsafe {
|
|
||||||
self.ram
|
|
||||||
.buf_transmuted::<AtomicU64>()
|
|
||||||
.get(index)
|
|
||||||
.ok_or(MemoryException {
|
|
||||||
type_: MemoryExceptionType::AccessFault,
|
|
||||||
addr,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn get_atomic_word(&self, addr: Addr) -> Result<&AtomicU32, MemoryException> {
|
|
||||||
if !addr.is_multiple_of(4) {
|
|
||||||
return Err(MemoryException {
|
|
||||||
type_: MemoryExceptionType::AddressMisaligned,
|
|
||||||
addr,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if addr < RAM_START {
|
|
||||||
return Err(MemoryException {
|
|
||||||
type_: MemoryExceptionType::AccessFault,
|
|
||||||
addr,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let index = ((addr - RAM_START) / 4) as usize;
|
|
||||||
unsafe {
|
|
||||||
self.ram
|
|
||||||
.buf_transmuted::<AtomicU32>()
|
|
||||||
.get(index)
|
|
||||||
.ok_or(MemoryException {
|
|
||||||
type_: MemoryExceptionType::AccessFault,
|
|
||||||
addr,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -220,6 +182,7 @@ pub enum MemoryMappingType {
|
|||||||
|
|
||||||
pub struct Ram {
|
pub struct Ram {
|
||||||
buf: MmapMut,
|
buf: MmapMut,
|
||||||
|
version_counters: Arc<[AtomicU32]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_endian = "big")]
|
#[cfg(target_endian = "big")]
|
||||||
@@ -232,9 +195,16 @@ impl Ram {
|
|||||||
}
|
}
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
buf: MmapMut::map_anon(size)?,
|
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] {
|
pub fn buf_mut(&mut self) -> &mut [u8] {
|
||||||
self.buf.as_mut()
|
self.buf.as_mut()
|
||||||
}
|
}
|
||||||
@@ -246,7 +216,7 @@ impl Ram {
|
|||||||
/// It must also be known that the contents of RAM are made up of naturally
|
/// It must also be known that the contents of RAM are made up of naturally
|
||||||
/// aligned valid instances of T.
|
/// aligned valid instances of T.
|
||||||
#[inline]
|
#[inline]
|
||||||
unsafe fn buf_transmuted<T>(&self) -> &[T] {
|
pub(crate) unsafe fn buf_transmuted<T>(&self) -> &[T] {
|
||||||
debug_assert!(self.buf.len().is_multiple_of(std::mem::size_of::<T>()));
|
debug_assert!(self.buf.len().is_multiple_of(std::mem::size_of::<T>()));
|
||||||
unsafe {
|
unsafe {
|
||||||
std::slice::from_raw_parts(
|
std::slice::from_raw_parts(
|
||||||
@@ -262,14 +232,14 @@ impl Ram {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn read_dword(&self, addr: Addr) -> Result<DWord, MemoryException> {
|
pub fn read_dword(&self, addr: u64) -> Result<u64, MemoryException> {
|
||||||
if !addr.is_multiple_of(8) {
|
if !addr.is_multiple_of(8) {
|
||||||
let high_word_addr = addr.wrapping_add(4);
|
let high_word_addr = addr.wrapping_add(4);
|
||||||
|
|
||||||
let low_word = self.read_byte(addr)?;
|
let low_word = self.read_word(addr)?;
|
||||||
let high_word = self.read_byte(high_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;
|
let index = (addr / 8) as usize;
|
||||||
@@ -284,14 +254,14 @@ impl Ram {
|
|||||||
.load(Relaxed))
|
.load(Relaxed))
|
||||||
}
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn read_word(&self, addr: Addr) -> Result<Word, MemoryException> {
|
pub fn read_word(&self, addr: u64) -> Result<u32, MemoryException> {
|
||||||
if !addr.is_multiple_of(4) {
|
if !addr.is_multiple_of(4) {
|
||||||
let high_hword_addr = addr.wrapping_add(2);
|
let high_hword_addr = addr.wrapping_add(2);
|
||||||
|
|
||||||
let low_hword = self.read_hword(addr)?;
|
let low_hword = self.read_hword(addr)?;
|
||||||
let high_hword = self.read_hword(high_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;
|
let index = (addr / 4) as usize;
|
||||||
@@ -306,14 +276,14 @@ impl Ram {
|
|||||||
.load(Relaxed))
|
.load(Relaxed))
|
||||||
}
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn read_hword(&self, addr: Addr) -> Result<HWord, MemoryException> {
|
pub fn read_hword(&self, addr: u64) -> Result<u16, MemoryException> {
|
||||||
if !addr.is_multiple_of(2) {
|
if !addr.is_multiple_of(2) {
|
||||||
let high_byte_addr = addr.wrapping_add(1);
|
let high_byte_addr = addr.wrapping_add(1);
|
||||||
|
|
||||||
let low_byte = self.read_byte(addr)?;
|
let low_byte = self.read_byte(addr)?;
|
||||||
let high_byte = self.read_byte(high_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;
|
let index = (addr / 2) as usize;
|
||||||
@@ -328,7 +298,7 @@ impl Ram {
|
|||||||
.load(Relaxed))
|
.load(Relaxed))
|
||||||
}
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn read_byte(&self, addr: Addr) -> Result<Byte, MemoryException> {
|
pub fn read_byte(&self, addr: u64) -> Result<u8, MemoryException> {
|
||||||
Ok(self
|
Ok(self
|
||||||
.buf_atomic()
|
.buf_atomic()
|
||||||
.get(addr as usize)
|
.get(addr as usize)
|
||||||
@@ -340,10 +310,10 @@ impl Ram {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn write_dword(&self, addr: Addr, value: DWord) -> Result<(), MemoryException> {
|
pub fn write_dword(&self, addr: u64, value: u64) -> Result<(), MemoryException> {
|
||||||
if !addr.is_multiple_of(8) {
|
if !addr.is_multiple_of(8) {
|
||||||
let low_word = value as Word;
|
let low_word = value as u32;
|
||||||
let high_word = (value >> 32) as Word;
|
let high_word = (value >> 32) as u32;
|
||||||
|
|
||||||
let high_word_address = addr.wrapping_add(4);
|
let high_word_address = addr.wrapping_add(4);
|
||||||
|
|
||||||
@@ -352,6 +322,9 @@ impl Ram {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.claim_addr_even(addr)
|
||||||
|
.ok_or_else(|| MemoryExceptionType::AccessFault.with_addr(addr))?;
|
||||||
|
|
||||||
let index = (addr / 8) as usize;
|
let index = (addr / 8) as usize;
|
||||||
unsafe {
|
unsafe {
|
||||||
self.buf_transmuted::<AtomicU64>()
|
self.buf_transmuted::<AtomicU64>()
|
||||||
@@ -365,10 +338,10 @@ impl Ram {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn write_word(&self, addr: Addr, value: Word) -> Result<(), MemoryException> {
|
pub fn write_word(&self, addr: u64, value: u32) -> Result<(), MemoryException> {
|
||||||
if !addr.is_multiple_of(4) {
|
if !addr.is_multiple_of(4) {
|
||||||
let low_hword = value as HWord;
|
let low_hword = value as u16;
|
||||||
let high_hword = (value >> 16) as HWord;
|
let high_hword = (value >> 16) as u16;
|
||||||
|
|
||||||
let high_hword_address = addr.wrapping_add(2);
|
let high_hword_address = addr.wrapping_add(2);
|
||||||
|
|
||||||
@@ -377,6 +350,9 @@ impl Ram {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.claim_addr_even(addr)
|
||||||
|
.ok_or_else(|| MemoryExceptionType::AccessFault.with_addr(addr))?;
|
||||||
|
|
||||||
let index = (addr / 4) as usize;
|
let index = (addr / 4) as usize;
|
||||||
unsafe {
|
unsafe {
|
||||||
self.buf_transmuted::<AtomicU32>()
|
self.buf_transmuted::<AtomicU32>()
|
||||||
@@ -390,10 +366,10 @@ impl Ram {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn write_hword(&self, addr: Addr, value: HWord) -> Result<(), MemoryException> {
|
pub fn write_hword(&self, addr: u64, value: u16) -> Result<(), MemoryException> {
|
||||||
if !addr.is_multiple_of(2) {
|
if !addr.is_multiple_of(2) {
|
||||||
let low_byte = value as Byte;
|
let low_byte = value as u8;
|
||||||
let high_byte = (value >> 8) as Byte;
|
let high_byte = (value >> 8) as u8;
|
||||||
|
|
||||||
let high_byte_address = addr.wrapping_add(1);
|
let high_byte_address = addr.wrapping_add(1);
|
||||||
|
|
||||||
@@ -402,6 +378,9 @@ impl Ram {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.claim_addr_even(addr)
|
||||||
|
.ok_or_else(|| MemoryExceptionType::AccessFault.with_addr(addr))?;
|
||||||
|
|
||||||
let index = (addr / 2) as usize;
|
let index = (addr / 2) as usize;
|
||||||
unsafe {
|
unsafe {
|
||||||
self.buf_transmuted::<AtomicU16>()
|
self.buf_transmuted::<AtomicU16>()
|
||||||
@@ -415,7 +394,9 @@ impl Ram {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn write_byte(&self, addr: Addr, value: Byte) -> Result<(), MemoryException> {
|
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()
|
self.buf_atomic()
|
||||||
.get(addr as usize)
|
.get(addr as usize)
|
||||||
.ok_or(MemoryException {
|
.ok_or(MemoryException {
|
||||||
@@ -425,6 +406,92 @@ impl Ram {
|
|||||||
.store(value, Relaxed);
|
.store(value, Relaxed);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn claim_addr_even<'a>(&'a self, addr: u64) -> Option<RamVersionClaim<'a>> {
|
||||||
|
let chunk_id = addr as usize / Self::VERSION_CHUNK_SIZE;
|
||||||
|
let chunk_counter = self.version_counters.get(chunk_id)?;
|
||||||
|
Some(RamVersionClaim::claim_even(&chunk_counter))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tries to create a claim for a specified chunk id with a specific version
|
||||||
|
// Outer Option represents whether the chunk id exists
|
||||||
|
// Inner Option represents whether the claim succeeded
|
||||||
|
pub fn claim_expected<'a>(
|
||||||
|
&'a self,
|
||||||
|
chunk_id: usize,
|
||||||
|
expected: u32,
|
||||||
|
) -> Option<Option<RamVersionClaim<'a>>> {
|
||||||
|
self.version_counters
|
||||||
|
.get(chunk_id)
|
||||||
|
.map(|chunk_counter| RamVersionClaim::claim_expected(chunk_counter, expected))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Waits for a specific address to have an even (ready) version
|
||||||
|
/// number and returns the version chunk id and version
|
||||||
|
pub fn wait_for_even_version(&self, addr: u64) -> Option<(usize, u32)> {
|
||||||
|
let chunk_id = addr as usize / Self::VERSION_CHUNK_SIZE;
|
||||||
|
let chunk_counter = self.version_counters.get(chunk_id)?;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let current_version = chunk_counter.load(Ordering::Acquire);
|
||||||
|
if current_version.is_multiple_of(2) {
|
||||||
|
return Some((chunk_id, current_version));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RamVersionClaim<'a> {
|
||||||
|
version_counter: &'a AtomicU32,
|
||||||
|
initial_version: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> RamVersionClaim<'a> {
|
||||||
|
pub fn claim_even(counter: &'a AtomicU32) -> RamVersionClaim<'a> {
|
||||||
|
loop {
|
||||||
|
let current_version = counter.load(Ordering::Acquire);
|
||||||
|
if !current_version.is_multiple_of(2) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to increment and therefore successfully claim the version
|
||||||
|
let res = counter.compare_exchange(
|
||||||
|
current_version,
|
||||||
|
current_version.wrapping_add(1),
|
||||||
|
Ordering::AcqRel,
|
||||||
|
Ordering::Acquire,
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Ok(initial_version) = res {
|
||||||
|
return RamVersionClaim {
|
||||||
|
version_counter: counter,
|
||||||
|
initial_version,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn claim_expected(counter: &'a AtomicU32, expected: u32) -> Option<RamVersionClaim<'a>> {
|
||||||
|
counter
|
||||||
|
.compare_exchange(
|
||||||
|
expected,
|
||||||
|
expected.wrapping_add(1),
|
||||||
|
Ordering::AcqRel,
|
||||||
|
Ordering::Acquire,
|
||||||
|
)
|
||||||
|
.ok()
|
||||||
|
.map(|initial_version| RamVersionClaim {
|
||||||
|
version_counter: counter,
|
||||||
|
initial_version,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Drop for RamVersionClaim<'a> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.version_counter
|
||||||
|
.store(self.initial_version.wrapping_add(2), Ordering::Release);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const MMIO_SECOND_LEVEL_PAGE_SIZE: usize = 64 * 1024;
|
pub const MMIO_SECOND_LEVEL_PAGE_SIZE: usize = 64 * 1024;
|
||||||
@@ -437,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]>);
|
pub struct MmioRoot(Box<[Option<MmioSecondLevel>; MMIO_ROOT_ENTRIES]>);
|
||||||
|
|
||||||
impl MmioRoot {
|
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.is_multiple_of(MMIO_SECOND_LEVEL_PAGE_SIZE as u64));
|
||||||
assert!(base_addr < RAM_START);
|
assert!(base_addr < RAM_START);
|
||||||
|
|
||||||
@@ -452,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.is_multiple_of(MMIO_ROOT_PAGE_SIZE as u64));
|
||||||
assert!(base_addr < RAM_START);
|
assert!(base_addr < RAM_START);
|
||||||
|
|
||||||
@@ -461,7 +528,7 @@ impl MmioRoot {
|
|||||||
self.0[page_id] = Some(MmioSecondLevel::Interface(interface));
|
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);
|
debug_assert!(addr < RAM_START);
|
||||||
|
|
||||||
let page_id = addr as usize / MMIO_SECOND_LEVEL_PAGE_SIZE;
|
let page_id = addr as usize / MMIO_SECOND_LEVEL_PAGE_SIZE;
|
||||||
@@ -469,10 +536,10 @@ impl MmioRoot {
|
|||||||
|
|
||||||
self.0[root_page_id]
|
self.0[root_page_id]
|
||||||
.as_ref()
|
.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 {
|
if addr >= RAM_START {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -514,12 +581,12 @@ enum MmioSecondLevel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl 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;
|
let page_id = addr as usize / MMIO_SECOND_LEVEL_PAGE_SIZE;
|
||||||
match self {
|
match self {
|
||||||
Self::SubTable(t) => t[page_id]
|
Self::SubTable(t) => t[page_id]
|
||||||
.as_ref()
|
.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)),
|
Self::Interface(i) => Some((i.clone(), addr)),
|
||||||
}
|
}
|
||||||
@@ -534,53 +601,29 @@ impl Default for MmioSecondLevel {
|
|||||||
|
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
pub trait MemDeviceInterface {
|
pub trait MemDeviceInterface {
|
||||||
fn write_dword(&self, addr: Addr, value: DWord) -> Result<(), MemoryException> {
|
fn write_dword(&self, addr: u64, value: u64) -> Result<(), MemoryExceptionType> {
|
||||||
Err(MemoryException {
|
Err(MemoryExceptionType::AccessFault)
|
||||||
type_: MemoryExceptionType::AccessFault,
|
|
||||||
addr,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
fn write_word(&self, addr: Addr, value: Word) -> Result<(), MemoryException> {
|
fn write_word(&self, addr: u64, value: u32) -> Result<(), MemoryExceptionType> {
|
||||||
Err(MemoryException {
|
Err(MemoryExceptionType::AccessFault)
|
||||||
type_: MemoryExceptionType::AccessFault,
|
|
||||||
addr,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
fn write_hword(&self, addr: Addr, value: HWord) -> Result<(), MemoryException> {
|
fn write_hword(&self, addr: u64, value: u16) -> Result<(), MemoryExceptionType> {
|
||||||
Err(MemoryException {
|
Err(MemoryExceptionType::AccessFault)
|
||||||
type_: MemoryExceptionType::AccessFault,
|
|
||||||
addr,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
fn write_byte(&self, addr: Addr, value: Byte) -> Result<(), MemoryException> {
|
fn write_byte(&self, addr: u64, value: u8) -> Result<(), MemoryExceptionType> {
|
||||||
Err(MemoryException {
|
Err(MemoryExceptionType::AccessFault)
|
||||||
type_: MemoryExceptionType::AccessFault,
|
|
||||||
addr,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_dword(&self, addr: Addr) -> Result<DWord, MemoryException> {
|
fn read_dword(&self, addr: u64) -> Result<u64, MemoryExceptionType> {
|
||||||
Err(MemoryException {
|
Err(MemoryExceptionType::AccessFault)
|
||||||
type_: MemoryExceptionType::AccessFault,
|
|
||||||
addr,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
fn read_word(&self, addr: Addr) -> Result<Word, MemoryException> {
|
fn read_word(&self, addr: u64) -> Result<u32, MemoryExceptionType> {
|
||||||
Err(MemoryException {
|
Err(MemoryExceptionType::AccessFault)
|
||||||
type_: MemoryExceptionType::AccessFault,
|
|
||||||
addr,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
fn read_hword(&self, addr: Addr) -> Result<HWord, MemoryException> {
|
fn read_hword(&self, addr: u64) -> Result<u16, MemoryExceptionType> {
|
||||||
Err(MemoryException {
|
Err(MemoryExceptionType::AccessFault)
|
||||||
type_: MemoryExceptionType::AccessFault,
|
|
||||||
addr,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
fn read_byte(&self, addr: Addr) -> Result<Byte, MemoryException> {
|
fn read_byte(&self, addr: u64) -> Result<u8, MemoryExceptionType> {
|
||||||
Err(MemoryException {
|
Err(MemoryExceptionType::AccessFault)
|
||||||
type_: MemoryExceptionType::AccessFault,
|
|
||||||
addr,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user