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.
236 lines
6.3 KiB
Rust
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;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|