progress: new assembler works, instruction set fully implemented with call/ret and push/pop, bf.dsa works which means the dsa assembly language works reliably. still a few bugs to fix. might be able to squeeze out a tiny bit more performance
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{Emulator, RandomAccessMemory, processor::interrupts::Interrupt};
|
||||
|
||||
pub mod display;
|
||||
|
||||
pub struct MappedDevice {
|
||||
pub base: u32,
|
||||
pub device: Box<dyn IoDevice>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct IoAccess(u8);
|
||||
|
||||
impl IoAccess {
|
||||
pub const READ: Self = Self(0b01);
|
||||
pub const WRITE: Self = Self(0b10);
|
||||
|
||||
/// Convenience alias for Read + Write.
|
||||
pub const READWRITE: Self = Self(Self::READ.0 | Self::WRITE.0);
|
||||
pub const NONE: Self = Self(0);
|
||||
|
||||
/// Bitwise OR – returns a new `IoAccess` that contains all flags from both operands.
|
||||
#[inline]
|
||||
pub fn or(self, other: Self) -> Self {
|
||||
Self(self.0 | other.0)
|
||||
}
|
||||
|
||||
/// Check if a flag is present.
|
||||
#[inline]
|
||||
pub fn contains(&self, flag: Self) -> bool {
|
||||
self.0 & flag.0 != 0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum DeviceId {
|
||||
Display,
|
||||
Serial,
|
||||
Random,
|
||||
Timer,
|
||||
}
|
||||
|
||||
/// Trait representing an input/output device that can be mapped into a memory space.
|
||||
///
|
||||
/// - Default methods return dummy data.
|
||||
pub trait IoDevice: Send {
|
||||
/// Return the size (in bytes) of the device's addressable region.
|
||||
fn size(&self) -> u32;
|
||||
|
||||
/// Return the access permissions for the device.
|
||||
fn access(&self) -> IoAccess;
|
||||
|
||||
/// Return the DeviceId
|
||||
fn id(&self) -> DeviceId;
|
||||
|
||||
#[inline]
|
||||
/// Read a single byte from the specified address.
|
||||
///
|
||||
/// By default, write‑only devices return `None`. All other devices
|
||||
/// return `Some(0)` as placeholder data. Implementations should override
|
||||
/// this method to provide actual read logic.
|
||||
fn read_byte(&self, offset: u32) -> Result<u8, Interrupt> {
|
||||
if self.access().contains(IoAccess::READ) {
|
||||
Ok(0)
|
||||
} else {
|
||||
Err(Interrupt::ReadFromWriteOnly)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Write a single byte to the specified address.
|
||||
///
|
||||
/// By default, read‑only devices return `false` indicating failure.
|
||||
/// All other devices return `true`. Implementations should override
|
||||
/// this method to perform real write operations and return whether
|
||||
/// the operation succeeded.
|
||||
fn write_byte(&mut self, offset: u32, val: u8) -> Result<(), Interrupt> {
|
||||
if self.access().contains(IoAccess::WRITE) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Interrupt::WriteToReadOnly)
|
||||
}
|
||||
}
|
||||
|
||||
/// Read a 32‑bit word from the specified address in little‑endian order.
|
||||
///
|
||||
/// The default implementation composes four consecutive byte reads using
|
||||
/// `read_byte`. If any byte read fails, the whole operation returns `None`.
|
||||
/// Write‑only devices return `None` immediately. Override this method for
|
||||
/// more efficient word reads.
|
||||
#[inline]
|
||||
fn read_word(&self, offset: u32) -> Result<u32, Interrupt> {
|
||||
if self.access().contains(IoAccess::READ) {
|
||||
// default: compose from bytes
|
||||
let b0 = self.read_byte(offset)? as u32;
|
||||
let b1 = self.read_byte(offset + 1)? as u32;
|
||||
let b2 = self.read_byte(offset + 2)? as u32;
|
||||
let b3 = self.read_byte(offset + 3)? as u32;
|
||||
return Ok(b0 | b1 << 8 | b2 << 16 | b3 << 24);
|
||||
}
|
||||
|
||||
Err(Interrupt::ReadFromWriteOnly)
|
||||
}
|
||||
|
||||
/// Write a 32‑bit word to the specified address in little‑endian order.
|
||||
///
|
||||
/// The default implementation splits the value into bytes and writes each
|
||||
/// using `write_byte`. Read‑only devices return `false`; otherwise returns
|
||||
/// `true` after attempting all byte writes. Override for more efficient
|
||||
/// word writes or to handle partial failures.
|
||||
fn write_word(&mut self, offset: u32, val: u32) -> Result<(), Interrupt> {
|
||||
if self.access().contains(IoAccess::WRITE) {
|
||||
let bytes = val.to_le_bytes();
|
||||
self.write_byte(offset, bytes[0]);
|
||||
self.write_byte(offset + 1, bytes[1]);
|
||||
self.write_byte(offset + 2, bytes[2]);
|
||||
self.write_byte(offset + 3, bytes[3]);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(Interrupt::WriteToReadOnly)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user