refactor: fully moved to tile-based UI layout and removed most remnants of old layout (aside from editor which needs to be implemented properly before it's fully replaced.

This commit is contained in:
2026-07-26 19:55:06 +01:00
parent d73ec47410
commit 708743ef64
30 changed files with 1421 additions and 1362 deletions
+4 -4
View File
@@ -1,8 +1,8 @@
use serde::{Deserialize, Serialize};
use crate::config::default::{DEFAULT_IO_MAP, DEFAULT_MEMORY_MAP};
use crate::memory::mmu::MMU;
use crate::memory::PhysAddr;
use crate::processor::processor::Emulator;
use crate::memory::mmu::MMU;
use serde::{Deserialize, Serialize};
use crate::processor::DsaProcessor;
mod default;
@@ -68,7 +68,7 @@ impl MemoryMap {
}
}
pub fn apply(&self, cpu: &mut Emulator) {
pub fn apply(&self, cpu: &mut DsaProcessor) {
for reg in &self.regions {
if reg.identity_map_on_boot {
Self::identity_map(cpu.mmu_mut(), reg);
+208 -1
View File
@@ -1,5 +1,212 @@
#![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 config;
#[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 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 emulator's POST/boot state can be useful for
/// integration testing.
fn boot(&mut self) -> Result<(), DsaError>;
/// main run method. the main-loop of the program execution
fn run(&mut self) -> Result<(), DsaError>;
/// memory mapping is required for 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 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 emulator
fn get_memory_handle(&mut self) -> &mut MemoryBank;
/// returns a wrapper around the shared state of the 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(&mut self, addr: PhysAddr) -> u8 {
self.memory.read_byte(addr)
}
pub fn write_byte(&mut self, addr: PhysAddr, val: u8) {
self.memory.write_byte(addr, val);
}
pub fn mem_read(&mut self, addr: PhysAddr, size: u32) -> Vec<u8> {
self.memory.read_bytes(addr, size as usize).to_vec()
}
pub 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 request_update(&mut self) {
self.state.update_req.store(true, Ordering::Relaxed);
}
pub fn status(&self) -> RunningState {
self.state.proc.load().running.clone()
}
pub fn pause(&mut self) {
self.state.paused.store(true, Ordering::Relaxed);
}
pub fn resume(&mut self) {
self.state.paused.store(false, Ordering::Relaxed);
}
pub fn reset(&mut self) {
self.state.reseting.store(true, Ordering::Relaxed);
self.memory.clear();
self.state.reseting.store(false, Ordering::Relaxed);
}
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()
}
pub fn clock(&self) -> usize {
self.state.proc.load().clock
}
pub fn time(&self) -> Duration {
self.state.proc.load().time
}
}
enum InternalImplementation {
Owned(Box<dyn DsaImplementation + Send>),
Handle(JoinHandle<()>),
}
pub struct EmulatorController {
// the 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 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;
}
}
+11 -1
View File
@@ -136,7 +136,17 @@ impl MemoryBank {
pub fn read_byte(&self, addr: PhysAddr) -> u8 {
unsafe { self.heap.add(addr as usize).read_volatile() }
}
#[inline]
pub fn read_bytes(&self, addr: PhysAddr, size: usize) -> &[u8] {
let start = addr as usize;
assert!(start + size <= u32::MAX as usize, "read out of bounds");
unsafe {
std::slice::from_raw_parts(self.heap.add(start), size)
}
}
#[inline(always)]
pub fn read_page(&self, addr: PhysAddr) -> &Page {
debug_assert_eq!(addr % 4096, 0);
@@ -1,321 +1,48 @@
use std::{
hint::unlikely,
ops::AddAssign,
sync::{
Arc,
atomic::Ordering,
mpsc::{self, TryRecvError},
},
thread,
time::{Duration, Instant},
};
use common::isa::instructions::Opcode;
use common::prelude::{Instruction, Register};
use crate::config::{IoMap, MemoryMap, RegionType};
use crate::DsaError;
use crate::DsaError::BootFailure;
use crate::memory::mmu::MMU;
use crate::memory::ram::{MemoryBank, Page};
use crate::processor::interrupts::Interrupt;
use crate::processor::state::{ProcessorSnapshot, SharedState};
use crate::processor::state::RunningState::Running;
use common::{
isa::instructions::{Instruction, Opcode},
isa::register::Register
};
use crate::{
memory::{
mmu::MMU,
ram::MemoryBank
},
processor::{
interrupts::Interrupt,
state::SharedState
}
};
use crate::config::{MemoryMap, RegionType};
use crate::memory::ram::Page;
use crate::processor::state::{ProcessorSnapshot, RunningState};
use crate::processor::state::RunningState::{Halted, Paused, Running, Shutdown};
pub struct Emulator {
internal_state: ProcessorSnapshot,
shared_state: Arc<SharedState>,
// Interrupts
interrupts: mpsc::Receiver<Interrupt>,
pending_fault: Option<Interrupt>,
// memory
mmu: MMU,
mainstore: MemoryBank,
// IO
mmio_region: Option<(u32, u32)>, // start end end of segment
// optionals - not necessarily set on boot.
memory_map: Option<MemoryMap>,
// Config params
__mmap_configured: bool,
time: Instant,
#[derive(Copy, Clone, Debug)]
pub enum ExecutionResult {
Continue,
Halt,
Interrupt(Interrupt),
}
unsafe impl Send for Emulator {}
impl Emulator {
pub struct DsaProcessor {
pub internal_state: ProcessorSnapshot,
pub(crate) mmu: MMU,
pub(crate) mainstore: MemoryBank,
mmio_region: Option<(u32, u32)>,
memory_map: Option<MemoryMap>,
__mmap_configured: bool,
}
impl DsaProcessor {
pub fn new() -> Self {
let (sender, receiver) = mpsc::channel::<Interrupt>();
Self {
interrupts: receiver,
pending_fault: None,
internal_state: ProcessorSnapshot::default(),
shared_state: Arc::new(SharedState::new(sender)),
memory_map: None,
mmu: MMU::new(),
mainstore: MemoryBank::new(),
// mmio
mmio_region: None,
// Config params
memory_map: None,
__mmap_configured: false,
time: Instant::now(),
}
}
pub fn state_handle(&self) -> Arc<SharedState> {
self.shared_state.clone()
}
#[inline]
pub fn mmu_mut(&mut self) -> &mut MMU {
&mut self.mmu
}
#[inline]
pub fn memory_mut(&mut self) -> &mut MemoryBank {
&mut self.mainstore
}
#[must_use]
#[inline]
pub fn reg(&self, reg: Register) -> u32 {
debug_assert!((reg as u8) < Register::COUNT);
unsafe { *self.internal_state.registers.get_unchecked(reg as usize) }
}
#[inline]
pub fn mut_reg(&mut self, reg: Register) -> &mut u32 {
debug_assert!((reg as u8) < Register::COUNT);
unsafe {
self.internal_state
.registers
.get_unchecked_mut(reg as usize)
}
}
#[cold]
pub fn apply_memory_map(mut self, map: MemoryMap) -> Self {
self.mmio_region = map
.regions
.iter()
.find(|r| matches!(r.region_type, RegionType::MMIO))
.map(|r| (r.base, r.base + r.size));
map.apply(&mut self);
self.memory_map = Some(map);
self.__mmap_configured = true;
self
}
#[cold]
fn share_state(&mut self) {
self.shared_state
.proc
.store(Arc::new(self.internal_state.clone()));
}
#[cold]
fn boot(&mut self) -> Result<(), String> {
if !(self.__mmap_configured) {
return Err("Emulator<Mem> not configured".to_string());
}
if let Some(map) = &self.memory_map {
MemoryMap::identity_map(&mut self.mmu, map.regions.get(0).unwrap());
}
self.internal_state.running = Running;
Ok(())
}
#[cold]
pub fn run_state_halted(&mut self, prior: RunningState) {
self.internal_state.running = Halted;
// Responsibility: keep track of the time spent in the running state across multiple hlt/interrupt cycles
self.internal_state.time += self.time.elapsed();
loop {
// Transition: interrupt received
if let Ok(int) = self.interrupts.recv_timeout(Duration::from_millis(100)) {
self.internal_state.running = Running;
self.interrupt(int);
break;
}
// Transition: pause signal received
if self.shared_state.paused.load(Ordering::Relaxed) {
self.run_state_paused(Halted);
}
// Responsibility: update UI when requested
if self.shared_state.update_req.load(Ordering::Relaxed) == true {
self.share_state();
}
}
// Responsibility: reset `private.time` on state exit
self.time = Instant::now();
self.internal_state.running = prior;
}
#[cold]
pub fn run_state_paused(&mut self, prior: RunningState) {
self.internal_state.running = Paused;
// Responsibility: save the running-time since last pause state.
self.internal_state.time = self.time.elapsed();
self.share_state();
loop {
// Responsibility: update UI when requested
if self.shared_state.update_req.load(Ordering::Relaxed) == true {
self.share_state();
}
// Responsibility: reset when requested by the UI
if self.shared_state.reseting.load(Ordering::Relaxed) == true {
self.internal_state.registers = [0; Register::COUNT as usize];
self.internal_state.clock = 0;
while self.shared_state.reseting.load(Ordering::Relaxed) == true {
thread::sleep(Duration::from_millis(100));
}
}
// Transition: run signal received
if self.shared_state.paused.load(Ordering::Relaxed) == false {
break;
}
}
// Responsibility: reset private.time, shared.time, shared.cycles on state exit
self.time = Instant::now();
self.internal_state.time = Duration::new(0, 0);
self.internal_state.clock = 0;
self.internal_state.running = prior;
}
pub fn run(&mut self) -> Result<(), String> {
// wait for UI to initialise.
if self.shared_state.paused.load(Ordering::Relaxed) {
self.run_state_paused(Running)
}
self.boot()?;
'emu: loop {
// so that it always returns zero. this is more performance friendly than checking
// for a zero register each access as it's branchless
*self.mut_reg(Register::Zero) = 0;
// IMPORTANT
// do not change anything about this loop. it's fully optimised afaik.
// Check for commands or hardware Interrupts every 32k cycles
if unlikely(self.internal_state.clock & 0x7FFF == 0) {
// Responsibility: keep the UI thread up to date periodically
// Update UI thread (roughly every 512k cycles targeting 60 UPS)
if unlikely(self.internal_state.clock & 0x7FFFF == 0) {
if self.shared_state.update_req.load(Ordering::Relaxed) {
self.share_state();
}
}
// Transition: Paused
if unlikely(self.shared_state.paused.load(Ordering::Relaxed)) {
self.run_state_paused(Running)
}
// Responsibility: Handle interrupts
match self.interrupts.try_recv() {
Ok(int) => self.interrupt(int),
Err(TryRecvError::Disconnected) => break 'emu,
Err(TryRecvError::Empty) => {}
}
}
// Responsibility: run fetch-decode-execute cycle.
let pc = self.reg(Register::Pcx);
let instruction = self.mem_read_word(pc);
if cfg!(debug_assertions) {
println!(
"Clock: {} Executing {:?} PCX: {}, reg: {:?}",
self.internal_state.clock,
Instruction(instruction),
pc,
self.internal_state.registers
);
thread::sleep(Duration::from_micros(10));
}
// Check if the previous instruction caused a fault.
if let Some(fault) = self.pending_fault {
println!("WARN fault: {:?}", fault);
self.pending_fault = None;
self.interrupt(fault);
}
self.mut_reg(Register::Pcx).add_assign(4);
self.execute(Instruction(instruction));
// always increment clock.
self.internal_state.clock += 1;
}
self.internal_state.running = Shutdown;
Ok(())
}
#[inline]
fn interrupt(&mut self, int: Interrupt) {
println!("Interrupt while at address {:04X}", self.reg(Register::Pcx));
self.push(Register::Pcx);
*self.mut_reg(Register::Pcx) =
self.mem_read_word(self.reg(Register::Idr) + int.code() as u32 * 4);
println!(
"Jumping to interrupt at address {:04X}",
self.reg(Register::Pcx)
);
}
#[inline]
fn push(&mut self, reg: Register) {
*self.mut_reg(Register::Spr) -= 4;
self.mem_write_word(self.reg(Register::Spr), self.reg(reg));
}
#[inline]
fn pop(&mut self, reg: Register) {
*self.mut_reg(reg) = self.mem_read_word(self.reg(Register::Spr));
*self.mut_reg(Register::Spr) += 4;
}
#[inline]
fn execute(&mut self, word: Instruction) {
pub fn execute(&mut self, word: Instruction) -> ExecutionResult {
// This needs to be unsafe as we're using word.xxxx_uc() functions for decoding which are unsafe
// as they perform unchecked transmute operations (performance critical)
unsafe {
@@ -334,8 +61,8 @@ impl Emulator {
}
// load
Some(Opcode::Ldbs) => todo!(),
Some(Opcode::Ldhs) => todo!(),
Some(Opcode::Ldbs) => return ExecutionResult::Interrupt(Interrupt::InvalidOpcode),
Some(Opcode::Ldhs) => return ExecutionResult::Interrupt(Interrupt::InvalidOpcode),
Some(Opcode::Ldb) => {
*self.mut_reg(word.dest_uc()) = u32::from(
self.mem_read_byte(self.reg(word.src1_uc()) + word.imm16() as u32),
@@ -434,9 +161,8 @@ impl Emulator {
self.reg(word.dest_uc()) + word.imm16() as u32
}
}
Some(Opcode::Jnc) => todo!(),
Some(Opcode::Jic) => todo!(),
Some(Opcode::Jnc) => return ExecutionResult::Interrupt(Interrupt::InvalidOpcode),
Some(Opcode::Jic) => return ExecutionResult::Interrupt(Interrupt::InvalidOpcode),
// Bitwise
Some(Opcode::And) => {
*self.mut_reg(word.dest_uc()) =
@@ -512,13 +238,6 @@ impl Emulator {
self.pop(Register::Ret);
*self.mut_reg(Register::Pcx) = self.reg(Register::Ret);
}
Some(Opcode::Int) => {
self.interrupt(Interrupt::Software(word.imm16() as u8));
}
Some(Opcode::Hlt) => {
self.run_state_halted(Running);
},
Some(Opcode::IRet) => {
self.pop(Register::Ret);
*self.mut_reg(Register::Pcx) = self.reg(Register::Ret);
@@ -528,50 +247,134 @@ impl Emulator {
self.reg(Register::Pcx)
);
}
None => {}
Some(Opcode::Int) => return ExecutionResult::Interrupt(Interrupt::Software(word.imm16() as u8)),
Some(Opcode::Hlt) => return ExecutionResult::Halt,
None => return ExecutionResult::Interrupt(Interrupt::InvalidOpcode),
}
};
ExecutionResult::Continue
}
#[inline]
pub fn interrupt(&mut self, int: Interrupt) {
self.push(Register::Pcx);
*self.mut_reg(Register::Pcx) =
self.mem_read_word(self.reg(Register::Idr) + int.code() as u32 * 4);
}
#[must_use]
#[inline]
pub fn reg(&self, reg: Register) -> u32 {
debug_assert!((reg as u8) < Register::COUNT);
unsafe { *self.internal_state.registers.get_unchecked(reg as usize) }
}
#[inline]
pub fn mut_reg(&mut self, reg: Register) -> &mut u32 {
debug_assert!((reg as u8) < Register::COUNT);
unsafe {
self.internal_state
.registers
.get_unchecked_mut(reg as usize)
}
}
#[inline]
fn mem_read_byte(&mut self, addr: u32) -> u8 {
pub fn push(&mut self, reg: Register) {
*self.mut_reg(Register::Spr) -= 4;
self.mem_write_word(self.reg(Register::Spr), self.reg(reg));
}
#[inline]
pub fn pop(&mut self, reg: Register) {
*self.mut_reg(reg) = self.mem_read_word(self.reg(Register::Spr));
*self.mut_reg(Register::Spr) += 4;
}
#[inline]
pub fn mem_read_byte(&mut self, addr: u32) -> u8 {
self.mainstore.read_byte(addr)
}
#[inline]
fn mem_write_byte(&mut self, addr: u32, val: u8) {
if addr == 0x40000 || addr == 0x40001 || addr == 0x40002 || addr == 0x40003 {
eprintln!("ui wrote 0x{:08x} to uart + {}", val, addr - 0x40000);
if addr == 0x40001 || addr == 0x40003 {
eprintln!("writing char {} to uart + {}", val as char, addr - 0x40000);
}
}
pub fn mem_write_byte(&mut self, addr: u32, val: u8) {
// TODO: how can we extract this method for debugging?
// if addr == 0x40000 || addr == 0x40001 || addr == 0x40002 || addr == 0x40003 {
// if addr == 0x40001 || addr == 0x40003 {
// eprintln!("writing char {} to uart + {}", val as char, addr - 0x40000);
// }
// }
self.mainstore.write_byte(addr, val);
}
#[inline]
fn mem_read_word(&mut self, addr: u32) -> u32 {
pub fn mem_read_word(&mut self, addr: u32) -> u32 {
self.mainstore.read_word(addr)
}
#[inline]
fn mem_write_word(&mut self, addr: u32, val: u32) {
pub fn mem_write_word(&mut self, addr: u32, val: u32) {
self.mainstore.write_word(addr, val);
}
#[inline]
fn mem_read_page(&mut self, addr: u32) -> &Page {
pub fn mem_read_page(&mut self, addr: u32) -> &Page {
self.mainstore.read_page(addr)
}
#[inline]
fn mem_write_page(&mut self, addr: u32, val: &Page) {
pub fn mem_write_page(&mut self, addr: u32, val: &Page) {
self.mainstore.write_page(addr, val);
}
fn fault(&mut self, interrupt: Interrupt) {
self.pending_fault = Some(interrupt);
#[inline]
pub fn mmu_mut(&mut self) -> &mut MMU {
&mut self.mmu
}
#[inline]
pub fn memory_mut(&mut self) -> &mut MemoryBank {
&mut self.mainstore
}
pub fn apply_memory_map(&mut self, map: MemoryMap) {
self.mmio_region = map
.regions
.iter()
.find(|r| matches!(r.region_type, RegionType::MMIO))
.map(|r| (r.base, r.base + r.size));
map.apply(self);
self.memory_map = Some(map);
self.__mmap_configured = true;
}
pub fn boot(&mut self) -> Result<(), DsaError> {
if !(self.__mmap_configured) {
return Err(BootFailure("Emulator<Mem> not configured".to_string()));
}
if let Some(map) = &self.memory_map {
MemoryMap::identity_map(&mut self.mmu, map.regions.get(0).unwrap());
}
self.internal_state.running = Running;
Ok(())
}
pub fn apply_io_mapping(&mut self, map: IoMap) {
todo!()
}
pub fn get_io_map(&self) -> IoMap {
todo!()
}
}
impl Drop for DsaProcessor {
fn drop(&mut self) {
// drop is only ever called once on an emulator instance, so we can be sure a double free will not happen.
unsafe { self.mainstore.dealloc() }
}
}
@@ -0,0 +1,241 @@
use crate::config::{IoMap, MemoryMap};
use crate::memory::ram::MemoryBank;
use crate::processor::dsa_core::{DsaProcessor, ExecutionResult};
use crate::processor::interrupts::Interrupt;
use crate::processor::state::RunningState::{Halted, Paused, Running, Shutdown};
use crate::processor::state::{RunningState, SharedState};
use crate::{DsaError, DsaImplementation};
use common::prelude::{Instruction, Register};
use std::hint::unlikely;
use std::ops::AddAssign;
use std::sync::atomic::Ordering;
use std::sync::{Arc, mpsc};
use std::sync::mpsc::TryRecvError;
use std::thread;
use std::time::{Duration, Instant};
pub struct Emulator {
core: DsaProcessor,
shared_state: Arc<SharedState>,
// Interrupts
interrupts: mpsc::Receiver<Interrupt>,
pending_fault: Option<Interrupt>,
time: Instant,
}
impl DsaImplementation for Emulator {
#[cold]
fn boot(&mut self) -> Result<(), DsaError> {
self.core.boot()
}
fn run(&mut self) -> Result<(), DsaError> {
// wait for UI to initialise.
if self.shared_state.paused.load(Ordering::Relaxed) {
self.run_state_paused(Running)
}
self.boot()?;
'emu: loop {
// so that it always returns zero. this is more performance friendly than checking
// for a zero register each access as it's branchless
*self.core.mut_reg(Register::Zero) = 0;
// IMPORTANT
// do not change anything about this loop. it's fully optimised afaik.
// Check for commands or hardware Interrupts every 32k cycles
if unlikely(self.core.internal_state.clock & 0x7FFF == 0) {
// Responsibility: keep the UI thread up to date periodically
// Update UI thread (roughly every 512k cycles targeting 60 UPS)
if unlikely(self.core.internal_state.clock & 0x7FFFF == 0) {
if self.shared_state.update_req.load(Ordering::Relaxed) {
self.core.internal_state.time = self.time.elapsed();
self.share_state();
}
}
// Transition: Paused
if unlikely(self.shared_state.paused.load(Ordering::Relaxed)) {
self.run_state_paused(Running);
}
// Responsibility: Handle interrupts
match self.interrupts.try_recv() {
Ok(int) => self.core.interrupt(int),
Err(TryRecvError::Disconnected) => break 'emu,
Err(TryRecvError::Empty) => {}
}
}
// Responsibility: run fetch-decode-execute cycle.
let pc = self.core.reg(Register::Pcx);
let instruction = self.core.mem_read_word(pc);
if cfg!(debug_assertions) {
println!(
"Clock: {} Executing {:?} PCX: {}, reg: {:?}",
self.core.internal_state.clock,
Instruction(instruction),
pc,
self.core.internal_state.registers
);
thread::sleep(Duration::from_micros(10));
}
// Check if the previous instruction caused a fault.
if let Some(fault) = self.pending_fault {
println!("WARN fault: {:?}", fault);
self.pending_fault = None;
self.core.interrupt(fault);
}
self.core.mut_reg(Register::Pcx).add_assign(4);
match self.core.execute(Instruction(instruction)) {
ExecutionResult::Continue => {},
ExecutionResult::Halt => self.run_state_halted(Running),
ExecutionResult::Interrupt(interrupt) => {
if cfg!(debug_assertions) {
println!("Interrupt: {:?}", interrupt);
println!("Interrupt while at address {:04X}", self.core.reg(Register::Pcx));
}
self.core.interrupt(interrupt);
if cfg!(debug_assertions) {
println!(
"Jumping to interrupt at address {:04X}",
self.core.reg(Register::Pcx)
);
}
}
}
self.core.internal_state.clock += 1;
}
self.core.internal_state.running = Shutdown;
Ok(())
}
fn apply_memory_map(&mut self, map: MemoryMap) {
self.core.apply_memory_map(map)
}
fn apply_io_mapping(&mut self, map: IoMap) {
self.core.apply_io_mapping(map)
}
fn get_io_map(&self) -> IoMap {
self.core.get_io_map()
}
fn get_memory_handle(&mut self) -> &mut MemoryBank {
self.core.memory_mut()
}
fn get_state_handle(&mut self) -> Arc<SharedState> {
self.shared_state.clone()
}
}
impl Emulator {
pub fn new() -> Self {
let (sender, receiver) = mpsc::channel::<Interrupt>();
Self {
core: DsaProcessor::new(),
shared_state: Arc::new(SharedState::new(sender)),
interrupts: receiver,
pending_fault: None,
time: Instant::now(),
}
}
pub fn state_handle(&self) -> Arc<SharedState> {
self.shared_state.clone()
}
#[cold]
fn share_state(&mut self) {
self.shared_state
.proc
.store(Arc::new(self.core.internal_state.clone()));
}
#[cold]
pub fn run_state_halted(&mut self, prior: RunningState) {
self.core.internal_state.running = Halted;
// Responsibility: keep track of the time spent in the running state across multiple hlt/interrupt cycles
self.core.internal_state.time += self.time.elapsed();
loop {
// Transition: interrupt received
if let Ok(int) = self.interrupts.recv_timeout(Duration::from_millis(100)) {
self.core.internal_state.running = Running;
self.core.interrupt(int);
break;
}
// Transition: pause signal received
if self.shared_state.paused.load(Ordering::Relaxed) {
self.run_state_paused(Halted);
}
// Responsibility: update UI when requested
if self.shared_state.update_req.load(Ordering::Relaxed) == true {
self.share_state();
}
}
// Responsibility: reset `private.time` on state exit
self.time = Instant::now();
self.core.internal_state.running = prior;
}
#[cold]
pub fn run_state_paused(&mut self, prior: RunningState) {
self.core.internal_state.running = Paused;
// Responsibility: save the running-time since last pause state.
self.core.internal_state.time = self.time.elapsed();
self.share_state();
loop {
// Responsibility: update UI when requested
if self.shared_state.update_req.load(Ordering::Relaxed) == true {
self.share_state();
}
// Responsibility: reset when requested by the UI
if self.shared_state.reseting.load(Ordering::Relaxed) == true {
self.core.internal_state.registers = [0; Register::COUNT as usize];
self.core.internal_state.clock = 0;
while self.shared_state.reseting.load(Ordering::Relaxed) == true {
thread::sleep(Duration::from_millis(100));
}
}
// Transition: run signal received
if self.shared_state.paused.load(Ordering::Relaxed) == false {
break;
}
}
// Responsibility: reset private.time, shared.time, shared.cycles on state exit
self.time = Instant::now();
self.core.internal_state.time = Duration::new(0, 0);
self.core.internal_state.clock = 0;
self.core.internal_state.running = prior;
}
}
@@ -1,3 +1,13 @@
pub mod interrupts;
pub mod processor;
pub mod state;
mod state;
mod dsa_core;
mod dsa_debug;
mod dsa_optimised;
mod interrupts;
pub use dsa_core::DsaProcessor;
pub use interrupts::Interrupt;
pub use dsa_optimised::Emulator;
pub use state::{
RunningState,
SharedState,
};