Files
DSA/emulator/dsa_emulator/src/lib.rs
T
zxq5 27d38eaf89 mega refactor:
essentially completely reorganised the project.
previous contents of the dsa folder have now been split into emulator/ tooling/ and libraries/
libraries is for common functionality across crates, tooling is for core binaries needed for building and debugging DSA projects, including dsx client/server/build
emulator contains the dsa_emulator crate that implements the core DSA emulator, the dsa_gui crate that implements the graphical user interface for the emulator & all future tooling under a unified application, and finally the dsa_cli crate which will eventually provide lightweight wrapper for the core emulator, simply providing a tool for running DSA binaries without the overhead of a heavy GUI.
2026-08-01 01:28:11 +01:00

236 lines
6.3 KiB
Rust

#![feature(likely_unlikely)]
use std::sync::Arc;
use std::sync::atomic::Ordering;
use std::thread;
use std::thread::{JoinHandle, Thread};
use std::time::Duration;
use common::prelude::{Instruction, Register};
use crate::config::{IoMap, MemoryMap};
use crate::InternalImplementation::{Handle, Owned};
use crate::memory::PhysAddr;
use crate::memory::ram::MemoryBank;
use crate::processor::{Interrupt, RunningState, SharedState};
pub mod processor;
pub mod memory;
pub mod config;
pub mod devices;
#[derive(Debug)]
pub enum DsaError {
BootFailure(String),
CommunicationError(String),
CriticalException(String),
}
/// This trait should be implemented for any type that can act as a fully functional DSA dsa_emulator with the ability to share state
pub trait DsaImplementation: Send {
/// may be modified in future with extra args like config
/// having direct control over the dsa_emulator's POST/boot state can be useful for
/// integration testing.
fn boot(&mut self) -> Result<(), DsaError>;
fn reset(&mut self);
/// main run method. the main-loop of the program execution
fn run(&mut self) -> Result<(), DsaError>;
/// memory mapping is required for dsa_emulator memory sections
fn apply_memory_map(&mut self, map: MemoryMap);
/// IO mapping also
fn apply_io_mapping(&mut self, map: IoMap);
/// List all memory io_devices
/// returns a copy of the IOmap (cannot be a reference since across-threads)
fn get_io_map(&self) -> IoMap;
/// returns a wrapper around the physical memory used by the dsa_emulator
fn get_memory_handle(&mut self) -> &mut MemoryBank;
/// returns a wrapper around the shared state of the dsa_emulator
fn get_state_handle(&mut self) -> Arc<SharedState>;
}
// pub trait DsaDebugImplementation: DsaImplementation {
// fn step()
// }
#[derive(Clone)]
pub struct EmulatorHandle {
memory: MemoryBank,
state: Arc<SharedState>,
}
impl EmulatorHandle {
pub fn read_byte(&self, addr: PhysAddr) -> u8 {
self.memory.read_byte(addr)
}
pub unsafe fn write_byte(&mut self, addr: PhysAddr, val: u8) {
self.memory.write_byte(addr, val);
}
pub fn mem_read(&self, addr: PhysAddr, size: u32) -> Vec<u8> {
self.memory.read_bytes(addr, size as usize).to_vec()
}
pub unsafe fn mem_write(&mut self, addr: PhysAddr, data: &[u8]) {
self.memory.write_bytes(addr, data);
}
pub fn interrupt(&mut self, code: Interrupt) -> Result<(), DsaError> {
self.state.interrupt_queue.send(code)
.map_err(|_| DsaError::CommunicationError("interrupt failed".into()) )
}
pub fn status(&self) -> RunningState {
self.state.proc.load().running.clone()
}
pub fn clock(&self) -> usize {
self.state.proc.load().clock
}
pub fn time(&self) -> Duration {
self.state.proc.load().time
}
/// Request that the dsa_emulator backend provide an updated copy of its current internal state to `state.proc`
pub fn request_update(&mut self) {
self.state.update_requested.store(true, Ordering::Relaxed);
}
/// Request that the dsa_emulator backend pauses it's current execution
/// no-op if in the `Paused` state
pub fn request_pause(&mut self) {
self.state.pause_requested.store(true, Ordering::Relaxed);
}
pub fn when_paused(&mut self, f: impl FnOnce(&mut Self)) {
self.request_pause();
while self.state.proc.load().running != RunningState::Paused {
continue;
}
f(self);
}
/// Request that the dsa_emulator backend resumes it's current execution
/// no-op if not in the `Paused` state
pub fn request_resume(&mut self) {
self.state.pause_requested.store(false, Ordering::Relaxed);
}
/// Request that the dsa_emulator backend performs a full reset, clearing all memory, registers and other internal state.
pub fn reset(&mut self) {
self.state.reset_requested.store(true, Ordering::Relaxed);
// wait until the reset is completed - this should be extremely quick.
while self.state.reset_requested.load(Ordering::Relaxed) {
continue;
}
}
pub fn get_register(&self, rg: Register) -> u32 {
self.state.proc.load().registers[rg as usize]
}
pub fn registers(&self) -> [u32; Register::COUNT as usize] {
self.state.proc.load().registers.clone()
}
}
enum InternalImplementation {
Owned(Box<dyn DsaImplementation + Send>),
Handle(JoinHandle<()>),
}
pub struct EmulatorController {
// the dsa_emulator itself
internal: Option<InternalImplementation>,
handle: EmulatorHandle,
}
impl AsMut<EmulatorHandle> for EmulatorController {
fn as_mut(&mut self) -> &mut EmulatorHandle {
&mut self.handle
}
}
impl EmulatorController {
pub fn new(mut emulator: Box<dyn DsaImplementation>) -> Self {
Self {
handle: EmulatorHandle {
memory: emulator.get_memory_handle().clone(),
state: emulator.get_state_handle().clone(),
},
internal: Some(Owned(emulator)),
}
}
pub fn run(&mut self) -> Result<(), DsaError> {
const STACK_SIZE: usize = 1024 * 1024 * 16;
let Some(Owned(mut handle)) = self.internal.take() else {
return Err(DsaError::CriticalException(String::from("owned dsa_emulator returned none!")))
};
self.internal = Some(Handle(
thread::Builder::new()
.stack_size(STACK_SIZE)
.spawn(move || {handle.run().unwrap() })
.unwrap()
));
Ok(())
}
pub fn get_handle(&mut self) -> &mut EmulatorHandle {
&mut self.handle
}
pub fn map_memory(&mut self, map: MemoryMap) -> Result<(), DsaError> {
let Some(Owned(t)) = &self.internal else {
return Err(DsaError::BootFailure("Emulator already initialised".into()));
};
Ok(())
}
pub fn shutdown(&mut self) {
// if let Some(handle) = &self.handle {
// handle.request_shutdown(); // sets an atomic the run loop checks each poll
// }
todo!();
if let Some(InternalImplementation::Handle(join_handle)) = self.internal.take() {
let _ = join_handle.join(); // wait for the thread to actually exit
}
self.internal = None;
}
}