fixed a bug in the emulator where halting would break the state.

This commit is contained in:
2026-08-09 09:16:30 +01:00
parent 8b7f29cd48
commit e3a87ca908
10 changed files with 159 additions and 154 deletions
+21 -39
View File
@@ -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<EmulatorHandle> for EmulatorController {
}
impl EmulatorController {
pub fn new(mut emulator: Box<dyn DsaImplementation>) -> 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;
}
}
@@ -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
@@ -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;
}
}
}
+3 -3
View File
@@ -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),
}
+1
View File
@@ -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" }
+71 -61
View File
@@ -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);
});
});
}
}
+4 -2
View File
@@ -44,13 +44,14 @@ impl ProcessHandle {
id: usize,
name: impl Into<String>,
process: P,
input: P::Input,
on_complete: impl FnOnce(Result<P::Output, P::Error>) -> Action + Send + 'static,
) -> Self {
let (tx, rx) = mpsc::channel::<String>();
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<String>,
process: P,
input: P::Input,
on_complete: impl FnOnce(Result<P::Output, P::Error>) -> 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);
}
+17 -11
View File
@@ -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()
}
+1 -21
View File
@@ -76,23 +76,15 @@ impl Behavior<Pane> for TileBehavior {
}
fn on_tab_close(&mut self, tiles: &mut Tiles<Pane>, 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<Action> {
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()
}
}
+2 -2
View File
@@ -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 {
}
}
}