summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortaitep <taitep@taitep.se>2026-01-29 19:07:43 +0100
committertaitep <taitep@taitep.se>2026-01-29 19:07:43 +0100
commitbbfa20befe163c04d0a99278107f2608639318d3 (patch)
treeada64685dcefcf587fb2016a77a78a47465cd290
parent36e6ec10069fe84aa677ab9ea4446e7fa3332886 (diff)
Replace custom UART with a sifive uart subset
-rw-r--r--src/basic_uart.rs96
-rw-r--r--src/devices.rs1
-rw-r--r--src/devices/serial.rs140
-rw-r--r--src/devices/serial/fifo.rs113
-rw-r--r--src/lib.rs1
-rw-r--r--src/main.rs20
6 files changed, 268 insertions, 103 deletions
diff --git a/src/basic_uart.rs b/src/basic_uart.rs
deleted file mode 100644
index 0e209ba..0000000
--- a/src/basic_uart.rs
+++ /dev/null
@@ -1,96 +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::exceptions::MemoryExceptionType;
-use trve::mem::MemDeviceInterface;
-
-/// byte 0: rx/tx
-/// byte 1: status (------rt, r=rxready, t=txready)/none
-pub struct BasicUart {
- rx_buf: Mutex<VecDeque<u8>>,
-}
-
-impl BasicUart {
- pub fn new() -> Self {
- // Make sure stdin is nonblocking
- let stdin = io::stdin();
- let fd = stdin.as_fd();
- let flags = fcntl(fd, FcntlArg::F_GETFL).unwrap();
- fcntl(
- fd,
- FcntlArg::F_SETFL(OFlag::from_bits_truncate(flags) | OFlag::O_NONBLOCK),
- )
- .unwrap();
-
- BasicUart {
- rx_buf: Mutex::new(VecDeque::new()),
- }
- }
-
- pub fn spawn_poller(self, poll_interval: Duration) -> Arc<Self> {
- let shared = Arc::new(self);
-
- let uart_clone = shared.clone();
-
- thread::spawn(move || {
- loop {
- uart_clone.poll();
- thread::sleep(poll_interval);
- }
- });
-
- shared
- }
-
- fn write(&self, byte: u8) {
- print!("{}", byte as char);
- }
-
- fn read(&self) -> u8 {
- self.rx_buf.lock().unwrap().pop_front().unwrap_or(0)
- }
-
- fn can_read(&self) -> bool {
- !self.rx_buf.lock().unwrap().is_empty()
- }
-
- pub fn poll(&self) {
- let mut rx_buf = self.rx_buf.lock().unwrap();
-
- let mut buffer = [0u8; 1];
- while let Ok(1) = nix::unistd::read(io::stdin().as_fd(), &mut buffer) {
- rx_buf.push_back(buffer[0]);
- }
- }
-}
-
-impl MemDeviceInterface for BasicUart {
- fn write_byte(&self, addr: u64, value: u8) -> Result<(), MemoryExceptionType> {
- match addr {
- 0 => {
- self.write(value);
- Ok(())
- }
- _ => Err(MemoryExceptionType::AccessFault),
- }
- }
- fn read_byte(&self, addr: u64) -> Result<u8, MemoryExceptionType> {
- match addr {
- 0 => Ok(self.read()),
- 1 => Ok(1 | (self.can_read() as u8) << 1),
- _ => Err(MemoryExceptionType::AccessFault),
- }
- }
-}
diff --git a/src/devices.rs b/src/devices.rs
new file mode 100644
index 0000000..b1fc0cf
--- /dev/null
+++ b/src/devices.rs
@@ -0,0 +1 @@
+pub mod serial;
diff --git a/src/devices/serial.rs b/src/devices/serial.rs
new file mode 100644
index 0000000..7e90748
--- /dev/null
+++ b/src/devices/serial.rs
@@ -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)
+ }
+ }
+ }
+ }
+}
diff --git a/src/devices/serial/fifo.rs b/src/devices/serial/fifo.rs
new file mode 100644
index 0000000..dbf4659
--- /dev/null
+++ b/src/devices/serial/fifo.rs
@@ -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,
+ }
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index a74dffb..9d4865c 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,5 +1,6 @@
pub mod core;
mod decode;
+pub mod devices;
pub mod exceptions;
pub mod gdb;
mod instructions;
diff --git a/src/main.rs b/src/main.rs
index 7be2d20..50d5de0 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -4,12 +4,14 @@
// 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::{path::PathBuf, sync::Arc, time::Duration};
+use std::{io, os::fd::AsFd, path::PathBuf, sync::Arc, time::Duration};
use clap::Parser;
+use nix::fcntl::{FcntlArg, OFlag, fcntl};
use trve::{
core::Core,
+ devices,
exceptions::MemoryExceptionType,
gdb,
mem::{MemConfig, MemDeviceInterface, MmioRoot, Ram},
@@ -17,8 +19,6 @@ use trve::{
use anyhow::Result;
-use crate::basic_uart::BasicUart;
-
mod execload;
/// Taitep's RISC-V Emulator
@@ -42,8 +42,16 @@ fn main() -> Result<()> {
let mut mmio_root = MmioRoot::default();
mmio_root.insert(0, Arc::new(DbgOut));
- let uart = BasicUart::new();
- let uart = uart.spawn_poller(Duration::from_millis(10));
+ if let Err(e) = fcntl(io::stdin().as_fd(), FcntlArg::F_SETFL(OFlag::O_NONBLOCK)) {
+ eprintln!("Could not make stdout nonblocking, skipping. Error: {e}");
+ }
+
+ let uart = devices::serial::SifiveUart::new_arc();
+ uart.clone().spawn_io_thread(
+ std::io::stdin(),
+ std::io::stdout(),
+ Duration::from_millis(10),
+ );
mmio_root.insert(0x10000, uart);
let mem_cfg = MemConfig {
@@ -67,8 +75,6 @@ fn main() -> Result<()> {
Ok(())
}
-mod basic_uart;
-
struct DbgOut;
impl MemDeviceInterface for DbgOut {