From e3a87ca908b0a554293457ee234dcc7674809629 Mon Sep 17 00:00:00 2001 From: zxq5 Date: Sun, 9 Aug 2026 09:16:30 +0100 Subject: [PATCH] fixed a bug in the emulator where halting would break the state. --- emulator/dsa_emulator/src/lib.rs | 60 +++----- .../dsa_emulator/src/processor/dsa_core.rs | 12 +- .../src/processor/dsa_optimised.rs | 42 ++++-- emulator/dsa_emulator/src/processor/state.rs | 6 +- emulator/dsa_gui/Cargo.toml | 1 + emulator/dsa_gui/src/ui/controller.rs | 132 ++++++++++-------- emulator/dsa_gui/src/ui/process_manager.rs | 6 +- emulator/dsa_gui/src/ui/tiles/editor/mod.rs | 28 ++-- emulator/dsa_gui/src/ui/tiles/mod.rs | 22 +-- emulator/dsa_gui/src/ui/ui_helpers/mod.rs | 4 +- 10 files changed, 159 insertions(+), 154 deletions(-) diff --git a/emulator/dsa_emulator/src/lib.rs b/emulator/dsa_emulator/src/lib.rs index b13bebc..bd400d5 100644 --- a/emulator/dsa_emulator/src/lib.rs +++ b/emulator/dsa_emulator/src/lib.rs @@ -1,21 +1,21 @@ #![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::config::{IoMap, MemoryMap}; use crate::memory::PhysAddr; use crate::memory::ram::MemoryBank; use crate::processor::{Interrupt, RunningState, SharedState}; +use common::prelude::Register; +use std::sync::Arc; +use std::sync::atomic::Ordering; +use std::thread; +use std::thread::JoinHandle; +use std::time::Duration; -pub mod processor; -pub mod memory; pub mod config; pub mod devices; +pub mod memory; +pub mod processor; #[derive(Debug)] pub enum DsaError { @@ -64,7 +64,6 @@ pub struct EmulatorHandle { } impl EmulatorHandle { - pub fn read_byte(&self, addr: PhysAddr) -> u8 { self.memory.read_byte(addr) } @@ -81,10 +80,11 @@ impl EmulatorHandle { 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()) ) + self.state + .interrupt_queue + .send(code) + .map_err(|_| DsaError::CommunicationError("interrupt failed".into())) } pub fn status(&self) -> RunningState { @@ -97,7 +97,6 @@ impl EmulatorHandle { 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); @@ -112,7 +111,9 @@ impl EmulatorHandle { pub fn when_paused(&mut self, f: impl FnOnce(&mut Self)) { self.request_pause(); - while self.state.proc.load().running != RunningState::Paused { + while self.state.proc.load().running != RunningState::Paused + && self.state.proc.load().running != RunningState::Ready + { continue; } @@ -162,7 +163,6 @@ impl AsMut for EmulatorController { } impl EmulatorController { - pub fn new(mut emulator: Box) -> Self { Self { handle: EmulatorHandle { @@ -177,14 +177,16 @@ impl EmulatorController { 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!"))) + 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() + .spawn(move || handle.run().unwrap()) + .unwrap(), )); Ok(()) @@ -212,24 +214,4 @@ impl EmulatorController { } self.internal = None; } - } - - - - - - - - - - - - - - - - - - - diff --git a/emulator/dsa_emulator/src/processor/dsa_core.rs b/emulator/dsa_emulator/src/processor/dsa_core.rs index adeef2c..e4df1db 100644 --- a/emulator/dsa_emulator/src/processor/dsa_core.rs +++ b/emulator/dsa_emulator/src/processor/dsa_core.rs @@ -3,8 +3,8 @@ use crate::config::{IoMap, MemoryMap, RegionType}; use crate::memory::mmu::MMU; use crate::memory::ram::{MemoryBank, Page}; use crate::processor::interrupts::Interrupt; -use crate::processor::state::RunningState::Running; use crate::processor::state::ProcessorSnapshot; +use crate::processor::state::RunningState::Running; use common::isa::instructions::Opcode; use common::prelude::{Instruction, Register}; @@ -36,6 +36,8 @@ impl DsaProcessor { self.mmio_region = None; self.memory_map = None; self.__mmap_configured = false; + + self.internal_state = ProcessorSnapshot::default(); } pub fn new() -> Self { @@ -209,8 +211,9 @@ impl DsaProcessor { } Some(Opcode::Sub) => { - *self.mut_reg(word.dest_uc()) = - self.reg(word.src1_uc()) - self.reg(word.src2_uc()); + *self.mut_reg(word.dest_uc()) = self + .reg(word.src1_uc()) + .wrapping_sub(self.reg(word.src2_uc())); } Some(Opcode::Shl) => { @@ -228,7 +231,8 @@ impl DsaProcessor { } Some(Opcode::Subi) => { - *self.mut_reg(word.dest_uc()) = self.reg(word.src1_uc()) - word.imm16() as u32; + *self.mut_reg(word.dest_uc()) = + self.reg(word.src1_uc()).wrapping_sub(word.imm16() as u32); } // Utility diff --git a/emulator/dsa_emulator/src/processor/dsa_optimised.rs b/emulator/dsa_emulator/src/processor/dsa_optimised.rs index 3978d0e..2712c65 100644 --- a/emulator/dsa_emulator/src/processor/dsa_optimised.rs +++ b/emulator/dsa_emulator/src/processor/dsa_optimised.rs @@ -1,5 +1,6 @@ use crate::config::{IoMap, MemoryMap}; use crate::memory::ram::MemoryBank; +use crate::processor::RunningState::Ready; use crate::processor::dsa_core::{DsaProcessor, ExecutionResult}; use crate::processor::interrupts::Interrupt; use crate::processor::state::RunningState::{Halted, Paused, Running, Shutdown}; @@ -7,11 +8,9 @@ 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::sync::{Arc, mpsc}; use std::time::{Duration, Instant}; pub struct Emulator { @@ -53,7 +52,6 @@ impl DsaImplementation for Emulator { // 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) { @@ -62,7 +60,9 @@ impl DsaImplementation for Emulator { self.share_state(); // Acknowledge update completed - self.shared_state.update_requested.store(false, Ordering::Relaxed); + self.shared_state + .update_requested + .store(false, Ordering::Relaxed); } } @@ -97,12 +97,15 @@ impl DsaImplementation for Emulator { *self.core.mut_reg(Register::Pcx) = self.core.reg(Register::Pcx).wrapping_add(4); match self.core.execute(Instruction(instruction)) { - ExecutionResult::Continue => {}, + 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)); + println!( + "Interrupt while at address {:04X}", + self.core.reg(Register::Pcx) + ); } self.core.interrupt(interrupt); @@ -188,6 +191,10 @@ impl Emulator { // Transition: pause signal received if self.shared_state.pause_requested.load(Ordering::Relaxed) { self.run_state_paused(Halted); + + if self.core.internal_state.running == Ready { + break; + } } // Responsibility: update UI when requested @@ -195,12 +202,15 @@ impl Emulator { self.share_state(); // Acknowledge update completed - self.shared_state.update_requested.store(false, Ordering::Relaxed); + self.shared_state + .update_requested + .store(false, Ordering::Relaxed); } } // Responsibility: reset `private.time` on state exit self.time = Instant::now(); + self.core.internal_state.running = prior; } @@ -218,13 +228,19 @@ impl Emulator { self.share_state(); // Acknowledge update completed - self.shared_state.update_requested.store(false, Ordering::Relaxed); + self.shared_state + .update_requested + .store(false, Ordering::Relaxed); } // Responsibility: reset when requested by the UI if self.shared_state.reset_requested.load(Ordering::Relaxed) == true { self.reset(); - self.shared_state.reset_requested.store(false, Ordering::Relaxed); + self.shared_state + .reset_requested + .store(false, Ordering::Relaxed); + + self.core.internal_state.running = Ready; } // Transition: run signal received @@ -238,6 +254,10 @@ impl Emulator { self.core.internal_state.time = Duration::new(0, 0); self.core.internal_state.clock = 0; + if self.core.internal_state.running == Ready { + return; + } + self.core.internal_state.running = prior; } -} \ No newline at end of file +} diff --git a/emulator/dsa_emulator/src/processor/state.rs b/emulator/dsa_emulator/src/processor/state.rs index 14361c9..a6a65a0 100644 --- a/emulator/dsa_emulator/src/processor/state.rs +++ b/emulator/dsa_emulator/src/processor/state.rs @@ -1,8 +1,7 @@ -use std::sync::{Arc, atomic::AtomicBool, mpsc}; -use std::sync::atomic::AtomicIsize; -use std::time::Duration; use arc_swap::ArcSwap; use common::prelude::Register; +use std::sync::{Arc, atomic::AtomicBool, mpsc}; +use std::time::Duration; use super::interrupts::Interrupt; pub struct SharedState { @@ -32,6 +31,7 @@ pub enum RunningState { Paused, Halted, Running, + Ready, Shutdown, Breakpoint(u32), } diff --git a/emulator/dsa_gui/Cargo.toml b/emulator/dsa_gui/Cargo.toml index 9c88fd6..15eed05 100644 --- a/emulator/dsa_gui/Cargo.toml +++ b/emulator/dsa_gui/Cargo.toml @@ -19,6 +19,7 @@ dsa_tasks = { path = "../../libraries/dsa_tasks" } zxq5_egui = { path = "../../libraries/zxq5_egui" } assembler = { path = "../../tooling/assembler" } +dsfl_compiler = { path = "../../tooling/dsfl_compiler" } dsa_emulator = { version = "0.3.0", path = "../dsa_emulator" } disassembler = { version = "0.3.0", path = "../../tooling/disassembler" } diff --git a/emulator/dsa_gui/src/ui/controller.rs b/emulator/dsa_gui/src/ui/controller.rs index ed7020e..9fe424f 100644 --- a/emulator/dsa_gui/src/ui/controller.rs +++ b/emulator/dsa_gui/src/ui/controller.rs @@ -1,9 +1,10 @@ +use crate::ui::ui_helpers::StateWrapper; use common::prelude::Register; use dsa_emulator::EmulatorHandle; use dsa_emulator::processor::RunningState; +use dsa_emulator::processor::RunningState::Ready; use eframe::epaint::Color32; use zxq5_egui::buttons::PaddedWidget; -use crate::ui::ui_helpers::StateWrapper; pub struct Controller { emulator: EmulatorHandle, @@ -11,82 +12,91 @@ pub struct Controller { impl Controller { pub fn new(state: EmulatorHandle) -> Self { - Self { - emulator: state, - } + Self { emulator: state } } pub fn ui(&mut self, ui: &mut egui::Ui) { - self.emulator.request_update(); - egui::Frame::new() - .inner_margin(10.0) - .show(ui, |ui| { - ui.horizontal(|ui| { - use RunningState::*; - let status = self.emulator.status(); + egui::Frame::new().inner_margin(10.0).show(ui, |ui| { + ui.horizontal(|ui| { + use RunningState::*; + let status = self.emulator.status(); - match status { - Paused | Breakpoint(_) => - if PaddedWidget::button("▶ Run").color(Color32::GREEN).ui(ui).clicked() { - self.emulator.request_resume() - } - Running | Halted => - if PaddedWidget::button("⏸ Pause").color(Color32::YELLOW).ui(ui).clicked() { - self.emulator.request_pause() - } - Shutdown => { - ui.add_enabled_ui(false, |ui| { - PaddedWidget::button("▶ Run").color(Color32::GREEN).ui(ui); - }); + match status { + Paused | Breakpoint(_) | Ready => { + if PaddedWidget::button("▶ Run") + .color(Color32::GREEN) + .ui(ui) + .clicked() + { + self.emulator.request_resume() } } - - PaddedWidget::indicator(StateWrapper(status)).ui(ui); - - if PaddedWidget::button("⟳ Reset All").color(Color32::ORANGE).ui(ui).clicked() { - self.emulator.when_paused(|emu| emu.reset()); + Running | Halted => { + if PaddedWidget::button("⏸ Pause") + .color(Color32::YELLOW) + .ui(ui) + .clicked() + { + self.emulator.request_pause() + } } + Shutdown => { + ui.add_enabled_ui(false, |ui| { + PaddedWidget::button("▶ Run").color(Color32::GREEN).ui(ui); + }); + } + } - // Step - // if ui.button("Step").clicked() { - // state - // .cmd_sender - // .send(Command::Step(self.step_amount)) - // .unwrap_or_else(|_| { - // state.error_log.push("Failed to send command".to_string()); - // }); - // } + PaddedWidget::indicator(StateWrapper(status)).ui(ui); - // if ui - // .text_edit_singleline(&mut self.step_amount_input) - // .changed() - // { - // self.step_amount = if let Ok(amount) = self.step_amount_input.parse() { - // amount - // } else { - // state - // .error_log - // .push("Unable to parse step amount".to_string()); - // 1 - // } - // } + if PaddedWidget::button("⟳ Reset All") + .color(Color32::ORANGE) + .ui(ui) + .clicked() + { + self.emulator.when_paused(|emu| emu.reset()); + } - ui.separator(); + // Step + // if ui.button("Step").clicked() { + // state + // .cmd_sender + // .send(Command::Step(self.step_amount)) + // .unwrap_or_else(|_| { + // state.error_log.push("Failed to send command".to_string()); + // }); + // } - let clock = self.emulator.clock(); - PaddedWidget::label(format!("Clock: {clock}")).ui(ui); + // if ui + // .text_edit_singleline(&mut self.step_amount_input) + // .changed() + // { + // self.step_amount = if let Ok(amount) = self.step_amount_input.parse() { + // amount + // } else { + // state + // .error_log + // .push("Unable to parse step amount".to_string()); + // 1 + // } + // } - let pcx = self.emulator.get_register(Register::Pcx); - PaddedWidget::label( format!("Pcx: 0x{pcx:08X}")).ui(ui); + ui.separator(); - let time = self.emulator.time().as_micros(); - PaddedWidget::label( format!("Elapsed: {} micros", time)).ui(ui); + let clock = self.emulator.clock(); + PaddedWidget::label(format!("Clock: {clock}")).ui(ui); - let mips = (clock as u128).checked_div(time).unwrap_or(0); - PaddedWidget::label(format!("Freq: {}MHz", mips)).ui(ui); - }); + let pcx = self.emulator.get_register(Register::Pcx); + PaddedWidget::label(format!("Pcx: 0x{pcx:08X}")).ui(ui); + + let time = self.emulator.time().as_micros(); + PaddedWidget::label(format!("Elapsed: {} micros", time)).ui(ui); + + let mips = (clock as u128).checked_div(time).unwrap_or(0); + PaddedWidget::label(format!("Freq: {}MHz", mips)).ui(ui); }); + }); } } diff --git a/emulator/dsa_gui/src/ui/process_manager.rs b/emulator/dsa_gui/src/ui/process_manager.rs index 85bb976..a6a09c7 100644 --- a/emulator/dsa_gui/src/ui/process_manager.rs +++ b/emulator/dsa_gui/src/ui/process_manager.rs @@ -44,13 +44,14 @@ impl ProcessHandle { id: usize, name: impl Into, process: P, + input: P::Input, on_complete: impl FnOnce(Result) -> Action + Send + 'static, ) -> Self { let (tx, rx) = mpsc::channel::(); let agent = ProcessAgent::new(tx); let join_handle = std::thread::spawn(move || { - let result = TraceableProcess::run(Box::new(process), agent); + let result = TraceableProcess::run(Box::new(process), input, agent); on_complete(result) }); @@ -92,9 +93,10 @@ impl ProcessManager { &mut self, name: impl Into, process: P, + input: P::Input, on_complete: impl FnOnce(Result) -> Action + Send + 'static, ) { - let handle = ProcessHandle::spawn(self.new_id(), name, process, on_complete); + let handle = ProcessHandle::spawn(self.new_id(), name, process, input, on_complete); self.handles.push(handle); } diff --git a/emulator/dsa_gui/src/ui/tiles/editor/mod.rs b/emulator/dsa_gui/src/ui/tiles/editor/mod.rs index 5fe5154..2542cd0 100644 --- a/emulator/dsa_gui/src/ui/tiles/editor/mod.rs +++ b/emulator/dsa_gui/src/ui/tiles/editor/mod.rs @@ -67,12 +67,13 @@ impl Editor { &mut self.buffer } - pub fn get_build_command(filename: PathBuf) -> impl TraceableProcess { - match filename.extension().unwrap().to_str().unwrap() { - "dsa" => Assembler::new(filename), - _ => todo!(), - } - } + // pub fn get_build_command(file_path: PathBuf) -> impl TraceableProcess { + // let filename = file_path.file_name().unwrap().to_str().unwrap(); + // match file_path.extension().unwrap().to_str().unwrap() { + // "dsa" => Assembler::new(filename), + // "dsfl" => DsflCompiler::new(filename), + // } + // } pub fn create_build_action( &self, @@ -80,11 +81,16 @@ impl Editor { on_complete: impl FnOnce(DseExecutable) -> Action + Send + 'static, ) -> Action { ProcessSpawner::new(move |pm| { - let builder = Assembler::new(filename); - pm.spawn(builder.label(), builder, move |result| match result { - Ok(bin) => on_complete(bin), - Err(e) => Action::ShowError(e.to_string()), - }); + let builder = Assembler::new(filename.file_name().unwrap().to_str().unwrap()); + pm.spawn( + builder.label(), + builder, + filename, + move |result| match result { + Ok(bin) => on_complete(bin), + Err(e) => Action::ShowError(e.to_string()), + }, + ); }) .into_action() } diff --git a/emulator/dsa_gui/src/ui/tiles/mod.rs b/emulator/dsa_gui/src/ui/tiles/mod.rs index c04e0f4..54fbc77 100644 --- a/emulator/dsa_gui/src/ui/tiles/mod.rs +++ b/emulator/dsa_gui/src/ui/tiles/mod.rs @@ -76,23 +76,15 @@ impl Behavior for TileBehavior { } fn on_tab_close(&mut self, tiles: &mut Tiles, tile_id: TileId) -> bool { - println!("on_tab_close_1"); - let Some(Tile::Pane(pane)) = tiles.get_mut(tile_id) else { - println!("on_tab_close_ret1"); return false; }; - println!("on_tab_close_2"); - if pane.close_requires_confirmation(tile_id) { - println!("on_tab_close_ret2"); self.pending_action = Some(RequestCloseTab(tile_id)); return false; } - println!("on_tab_close_3"); - tiles.remove(tile_id); true } @@ -220,20 +212,8 @@ impl CentralTiles { } pub fn ui(&mut self, ui: &mut egui::Ui) -> Option { - println!("centraltiles_ui_1"); - - // self.behavior.pending_action = None; self.tree.ui(&mut self.behavior, ui); - - println!("centraltiles_ui_2"); - - let action = self.behavior.pending_action.take(); - - println!("centraltiles_ui_3 {action:?}"); - - action - - // self.behavior.pending_action.take() + self.behavior.pending_action.take() } } diff --git a/emulator/dsa_gui/src/ui/ui_helpers/mod.rs b/emulator/dsa_gui/src/ui/ui_helpers/mod.rs index 812078a..d0627e8 100644 --- a/emulator/dsa_gui/src/ui/ui_helpers/mod.rs +++ b/emulator/dsa_gui/src/ui/ui_helpers/mod.rs @@ -8,7 +8,7 @@ pub struct StateWrapper(pub RunningState); impl StatusIndicator for StateWrapper { fn color(&self) -> egui::Color32 { match self.0 { - RunningState::Running => egui::Color32::GREEN, + RunningState::Ready | RunningState::Running => egui::Color32::GREEN, RunningState::Paused => egui::Color32::YELLOW, RunningState::Halted => egui::Color32::ORANGE, RunningState::Breakpoint(_) => egui::Color32::RED, @@ -18,6 +18,7 @@ impl StatusIndicator for StateWrapper { fn label(&self) -> String { match self.0 { + RunningState::Ready => "Ready".to_string(), RunningState::Running => "Running".into(), RunningState::Paused => "Paused".into(), RunningState::Halted => "Halted".into(), @@ -26,4 +27,3 @@ impl StatusIndicator for StateWrapper { } } } -