Replace custom UART with a sifive uart subset
This commit is contained in:
@@ -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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
1
src/devices.rs
Normal file
1
src/devices.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub mod serial;
|
||||||
140
src/devices/serial.rs
Normal file
140
src/devices/serial.rs
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
// Copyright (c) 2026 taitep
|
||||||
|
// SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
//
|
||||||
|
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
||||||
|
// See LICENSE file in the project root for full license text.
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
io::{Read, Write},
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
|
mod fifo;
|
||||||
|
use fifo::UartFifo;
|
||||||
|
|
||||||
|
use crate::{exceptions::MemoryExceptionType, mem::MemDeviceInterface};
|
||||||
|
|
||||||
|
pub struct SifiveUart {
|
||||||
|
rx: Mutex<(UartFifo<2048>, bool)>,
|
||||||
|
tx: Mutex<(UartFifo<2048>, bool)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SifiveUart {
|
||||||
|
pub fn new_arc() -> Arc<Self> {
|
||||||
|
Arc::new(Self {
|
||||||
|
rx: Mutex::new((UartFifo::default(), false)),
|
||||||
|
tx: Mutex::new((UartFifo::default(), false)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spawn_io_thread<R: Read + Send + 'static, T: Write + Send + Sync + 'static>(
|
||||||
|
self: Arc<Self>,
|
||||||
|
mut rx_backend: R,
|
||||||
|
mut tx_backend: T,
|
||||||
|
interval: Duration,
|
||||||
|
) {
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
loop {
|
||||||
|
{
|
||||||
|
// Read data
|
||||||
|
let mut rx_guard = self.rx.lock().expect("could not lock uart RX half");
|
||||||
|
let (rx_buf, rx_en) = &mut *rx_guard;
|
||||||
|
|
||||||
|
if *rx_en {
|
||||||
|
let _ = rx_buf.read_from(&mut rx_backend);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Write data
|
||||||
|
let mut tx_guard = self.tx.lock().expect("could not lock uart RX half");
|
||||||
|
let (tx_buf, tx_en) = &mut *tx_guard;
|
||||||
|
|
||||||
|
if *tx_en {
|
||||||
|
let _ = tx_buf.write_to(&mut tx_backend);
|
||||||
|
let _ = tx_backend.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::thread::sleep(interval);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MemDeviceInterface for SifiveUart {
|
||||||
|
fn write_word(&self, addr: u64, value: u32) -> Result<(), MemoryExceptionType> {
|
||||||
|
// dbg!(addr, value);
|
||||||
|
match addr {
|
||||||
|
0x00 => {
|
||||||
|
// TXDATA
|
||||||
|
let (ref mut tx_buf, _) = *self.tx.lock().expect("could not lock uart TX half");
|
||||||
|
tx_buf.push_single_byte(value as u8);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
0x08 => {
|
||||||
|
// TXCTRL
|
||||||
|
let (_, ref mut tx_en) = *self.tx.lock().expect("could not lock uart TX half");
|
||||||
|
*tx_en = value & 1 != 0;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
0x04 => Ok(()), // RXDATA
|
||||||
|
0x0c => {
|
||||||
|
// RXCTRL
|
||||||
|
let (_, ref mut rx_en) = *self.rx.lock().expect("could not lock uart RX half");
|
||||||
|
*rx_en = value & 1 != 0;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
0x10 => Ok(()), // IE
|
||||||
|
0x14 => Ok(()), // IP
|
||||||
|
0x18 => Ok(()), // DIV
|
||||||
|
_ => {
|
||||||
|
if addr < 0x1c {
|
||||||
|
Err(MemoryExceptionType::AddressMisaligned)
|
||||||
|
} else {
|
||||||
|
Err(MemoryExceptionType::AccessFault)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_word(&self, addr: u64) -> Result<u32, MemoryExceptionType> {
|
||||||
|
// dbg!(addr);
|
||||||
|
match addr {
|
||||||
|
0x00 => {
|
||||||
|
// TXDATA
|
||||||
|
let (ref tx_buf, _) = *self.tx.lock().expect("could not lock uart TX half");
|
||||||
|
Ok(if tx_buf.is_full() { 0x80000000 } else { 0 })
|
||||||
|
}
|
||||||
|
0x08 => {
|
||||||
|
// TXCTRL
|
||||||
|
let (_, tx_en) = *self.tx.lock().expect("could not lock uart TX half");
|
||||||
|
Ok(if tx_en { 1 } else { 0 })
|
||||||
|
}
|
||||||
|
0x04 => {
|
||||||
|
// RXDATA
|
||||||
|
let (ref mut rx_buf, _) = *self.rx.lock().expect("could not lock uart RX half");
|
||||||
|
Ok(match rx_buf.pop_single_byte() {
|
||||||
|
None => 0x80000000,
|
||||||
|
Some(b) => b as u32,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
0x0c => {
|
||||||
|
// RXCTRL
|
||||||
|
let (_, rx_en) = *self.rx.lock().expect("could not lock uart RX half");
|
||||||
|
Ok(if rx_en { 1 } else { 0 })
|
||||||
|
}
|
||||||
|
0x10 => Ok(0), // IE
|
||||||
|
0x14 => Ok(0), // IP
|
||||||
|
0x18 => Ok(1), // DIV
|
||||||
|
|
||||||
|
_ => {
|
||||||
|
if addr < 0x1c {
|
||||||
|
Err(MemoryExceptionType::AddressMisaligned)
|
||||||
|
} else {
|
||||||
|
Err(MemoryExceptionType::AccessFault)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
113
src/devices/serial/fifo.rs
Normal file
113
src/devices/serial/fifo.rs
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
// Copyright (c) 2026 taitep
|
||||||
|
// SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
//
|
||||||
|
// This file is part of TRVE (https://gitea.taitep.se/taitep/trve)
|
||||||
|
// See LICENSE file in the project root for full license text.
|
||||||
|
|
||||||
|
use std::io::{self, Read, Write};
|
||||||
|
|
||||||
|
pub struct UartFifo<const CAP: usize> {
|
||||||
|
buf: [u8; CAP],
|
||||||
|
head: usize,
|
||||||
|
tail: usize,
|
||||||
|
len: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const CAP: usize> UartFifo<CAP> {
|
||||||
|
pub fn pop_single_byte(&mut self) -> Option<u8> {
|
||||||
|
if self.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let value = self.buf[self.tail];
|
||||||
|
self.advance_read(1);
|
||||||
|
Some(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_single_byte(&mut self, value: u8) -> bool {
|
||||||
|
if self.is_full() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.buf[self.head] = value;
|
||||||
|
self.advance_write(1);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.len == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_full(&self) -> bool {
|
||||||
|
self.len == CAP
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_slice(&mut self) -> &mut [u8] {
|
||||||
|
if self.is_full() {
|
||||||
|
return &mut [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.head >= self.tail {
|
||||||
|
&mut self.buf[self.head..]
|
||||||
|
} else {
|
||||||
|
&mut self.buf[self.head..self.tail]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn advance_write(&mut self, n: usize) {
|
||||||
|
debug_assert!(n <= CAP - self.len);
|
||||||
|
self.head = (self.head + n) % CAP;
|
||||||
|
self.len += n;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_slice(&self) -> &[u8] {
|
||||||
|
if self.is_empty() {
|
||||||
|
return &[];
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.tail < self.head {
|
||||||
|
&self.buf[self.tail..self.head]
|
||||||
|
} else {
|
||||||
|
&self.buf[self.tail..]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn advance_read(&mut self, n: usize) {
|
||||||
|
debug_assert!(n <= self.len);
|
||||||
|
self.tail = (self.tail + n) % CAP;
|
||||||
|
self.len -= n;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_from<R: Read>(&mut self, reader: &mut R) -> io::Result<usize> {
|
||||||
|
let slice = self.write_slice();
|
||||||
|
if slice.is_empty() {
|
||||||
|
return Ok(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let n = reader.read(slice)?;
|
||||||
|
self.advance_write(n);
|
||||||
|
Ok(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_to<W: Write>(&mut self, writer: &mut W) -> io::Result<usize> {
|
||||||
|
let slice = self.read_slice();
|
||||||
|
if slice.is_empty() {
|
||||||
|
return Ok(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let n = writer.write(slice)?;
|
||||||
|
self.advance_read(n);
|
||||||
|
Ok(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const SIZE: usize> Default for UartFifo<SIZE> {
|
||||||
|
fn default() -> Self {
|
||||||
|
UartFifo {
|
||||||
|
buf: [0; SIZE],
|
||||||
|
head: 0,
|
||||||
|
tail: 0,
|
||||||
|
len: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
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;
|
||||||
|
|||||||
20
src/main.rs
20
src/main.rs
@@ -4,12 +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 std::{path::PathBuf, sync::Arc, time::Duration};
|
use std::{io, os::fd::AsFd, path::PathBuf, sync::Arc, time::Duration};
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|
||||||
|
use nix::fcntl::{FcntlArg, OFlag, fcntl};
|
||||||
use trve::{
|
use trve::{
|
||||||
core::Core,
|
core::Core,
|
||||||
|
devices,
|
||||||
exceptions::MemoryExceptionType,
|
exceptions::MemoryExceptionType,
|
||||||
gdb,
|
gdb,
|
||||||
mem::{MemConfig, MemDeviceInterface, MmioRoot, Ram},
|
mem::{MemConfig, MemDeviceInterface, MmioRoot, Ram},
|
||||||
@@ -17,8 +19,6 @@ use trve::{
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
use crate::basic_uart::BasicUart;
|
|
||||||
|
|
||||||
mod execload;
|
mod execload;
|
||||||
|
|
||||||
/// Taitep's RISC-V Emulator
|
/// Taitep's RISC-V Emulator
|
||||||
@@ -42,8 +42,16 @@ 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().as_fd(), 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(
|
||||||
|
std::io::stdin(),
|
||||||
|
std::io::stdout(),
|
||||||
|
Duration::from_millis(10),
|
||||||
|
);
|
||||||
mmio_root.insert(0x10000, uart);
|
mmio_root.insert(0x10000, uart);
|
||||||
|
|
||||||
let mem_cfg = MemConfig {
|
let mem_cfg = MemConfig {
|
||||||
@@ -67,8 +75,6 @@ fn main() -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
mod basic_uart;
|
|
||||||
|
|
||||||
struct DbgOut;
|
struct DbgOut;
|
||||||
|
|
||||||
impl MemDeviceInterface for DbgOut {
|
impl MemDeviceInterface for DbgOut {
|
||||||
|
|||||||
Reference in New Issue
Block a user