fixed a bug in the emulator backend where it would try to reset the memory twice.

This commit is contained in:
2026-07-29 23:43:06 +01:00
parent 17b80c1246
commit d616a116f5
6 changed files with 107 additions and 73 deletions
+42 -23
View File
@@ -68,7 +68,7 @@ impl EmulatorHandle {
self.memory.read_byte(addr)
}
pub fn write_byte(&mut self, addr: PhysAddr, val: u8) {
pub unsafe fn write_byte(&mut self, addr: PhysAddr, val: u8) {
self.memory.write_byte(addr, val);
}
@@ -76,35 +76,62 @@ impl EmulatorHandle {
self.memory.read_bytes(addr, size as usize).to_vec()
}
pub fn mem_write(&mut self, addr: PhysAddr, data: &[u8]) {
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 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 clock(&self) -> usize {
self.state.proc.load().clock
}
pub fn resume(&mut self) {
self.state.paused.store(false, Ordering::Relaxed);
pub fn time(&self) -> Duration {
self.state.proc.load().time
}
/// Request that the 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 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 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 emulator backend performs a full reset, clearing all memory, registers and other internal state.
pub fn reset(&mut self) {
self.state.resetting.store(true, Ordering::Relaxed);
self.memory.clear();
self.state.proc.store(Arc::new(Default::default()));
self.state.resetting.store(false, Ordering::Relaxed);
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 {
@@ -114,14 +141,6 @@ impl EmulatorHandle {
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 {
@@ -38,7 +38,7 @@ impl DsaImplementation for Emulator {
fn run(&mut self) -> Result<(), DsaError> {
// wait for UI to initialise.
if self.shared_state.paused.load(Ordering::Relaxed) {
if self.shared_state.pause_requested.load(Ordering::Relaxed) {
self.run_state_paused(Running)
}
@@ -57,14 +57,17 @@ impl DsaImplementation for Emulator {
// 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) {
if self.shared_state.update_requested.load(Ordering::Relaxed) {
self.core.internal_state.time = self.time.elapsed();
self.share_state();
// Acknowledge update completed
self.shared_state.update_requested.store(false, Ordering::Relaxed);
}
}
// Transition: Paused
if unlikely(self.shared_state.paused.load(Ordering::Relaxed)) {
if unlikely(self.shared_state.pause_requested.load(Ordering::Relaxed)) {
self.run_state_paused(Running);
}
@@ -79,35 +82,29 @@ impl DsaImplementation for Emulator {
// 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));
// }
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.take() {
println!("WARN fault: {:?}", fault);
self.core.interrupt(fault);
}
self.core.mut_reg(Register::Pcx).add_assign(4);
*self.core.mut_reg(Register::Pcx) = self.core.reg(Register::Pcx).wrapping_add(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) {
@@ -189,13 +186,16 @@ impl Emulator {
}
// Transition: pause signal received
if self.shared_state.paused.load(Ordering::Relaxed) {
if self.shared_state.pause_requested.load(Ordering::Relaxed) {
self.run_state_paused(Halted);
}
// Responsibility: update UI when requested
if self.shared_state.update_req.load(Ordering::Relaxed) == true {
if self.shared_state.update_requested.load(Ordering::Relaxed) == true {
self.share_state();
// Acknowledge update completed
self.shared_state.update_requested.store(false, Ordering::Relaxed);
}
}
@@ -214,21 +214,21 @@ impl Emulator {
loop {
// Responsibility: update UI when requested
if self.shared_state.update_req.load(Ordering::Relaxed) == true {
if self.shared_state.update_requested.load(Ordering::Relaxed) == true {
self.share_state();
// Acknowledge update completed
self.shared_state.update_requested.store(false, Ordering::Relaxed);
}
// Responsibility: reset when requested by the UI
if self.shared_state.resetting.load(Ordering::Relaxed) == true {
if self.shared_state.reset_requested.load(Ordering::Relaxed) == true {
self.reset();
while self.shared_state.resetting.load(Ordering::Relaxed) == true {
thread::sleep(Duration::from_millis(100));
}
self.shared_state.reset_requested.store(false, Ordering::Relaxed);
}
// Transition: run signal received
if self.shared_state.paused.load(Ordering::Relaxed) == false {
if self.shared_state.pause_requested.load(Ordering::Relaxed) == false {
break;
}
}
@@ -8,21 +8,21 @@ use super::interrupts::Interrupt;
pub struct SharedState {
pub proc: ArcSwap<ProcessorSnapshot>,
pub resetting: AtomicBool,
pub paused: AtomicBool,
pub update_req: AtomicBool,
pub reset_requested: AtomicBool,
pub pause_requested: AtomicBool,
pub update_requested: AtomicBool,
pub interrupt_queue: mpsc::Sender<Interrupt>,
}
impl SharedState {
pub fn new(sender: mpsc::Sender<Interrupt>) -> Self {
Self {
resetting: AtomicBool::new(false),
reset_requested: AtomicBool::new(false),
proc: ArcSwap::new(Arc::new(ProcessorSnapshot::default())),
paused: AtomicBool::new(true),
pause_requested: AtomicBool::new(true),
interrupt_queue: sender,
update_req: AtomicBool::new(false),
update_requested: AtomicBool::new(false),
}
}
}
@@ -34,13 +34,13 @@ impl Controller {
match status {
RunningState::Paused | RunningState::Breakpoint(_) => {
if PaddedWidget::button("▶ Run").color(Color32::GREEN).ui(ui).clicked() {
self.emulator.resume()
self.emulator.request_resume()
}
},
RunningState::Running | RunningState::Halted => {
if PaddedWidget::button("⏸ Pause").color(Color32::YELLOW).ui(ui).clicked() {
self.emulator.pause()
self.emulator.request_pause()
}
}
@@ -54,7 +54,7 @@ impl Controller {
status.clone().ui(ui, padding);
if PaddedWidget::button("⟳ Reset All").color(Color32::YELLOW).ui(ui).clicked() {
self.emulator.reset();
self.emulator.when_paused(|emu| emu.reset());
}
// Step
+7 -2
View File
@@ -223,7 +223,9 @@ impl DamnSimpleUI {
}
Action::LoadBinary(DataSource::Data(exe)) => {
self.emulator.get_handle().mem_write(0, &exe);
self.emulator.get_handle().when_paused(|emu| unsafe {
emu.mem_write(0, &exe);
});
}
Action::LoadBinary(DataSource::File(path)) => {
@@ -239,7 +241,10 @@ impl DamnSimpleUI {
return;
};
self.emulator.get_handle().mem_write(0, &binary.to_memory_image())
self.emulator.get_handle().when_paused(|emu| unsafe {
emu.reset();
emu.mem_write(0, &binary.to_memory_image());
});
}
}
@@ -40,27 +40,37 @@ impl Serial {
write_buffer.push_back(byte);
}
let ser_out_valid = handle.read_byte(uart_base + 0);
// Kernel => ui: valid flag in byte 0, data in byte 1
let ser_out_valid = handle.read_byte(uart_base + 0);
if ser_out_valid == 0x01 {
let ser_out = handle.read_byte(uart_base + 1);
let _ = tx.send(ser_out);
handle.write_byte(uart_base, 0x00); // clear the whole word
unsafe {
handle.write_byte(uart_base, 0x00); // clear the whole word
}
}
// Emulator => kernel: valid flag in byte 2, data in byte 3
if !write_buffer.is_empty() {
// Emulator => kernel: valid flag in byte 2, data in byte 3
let ser_in_valid = handle.read_byte(uart_base + 2);
if ser_in_valid == 0x00 {
if let Some(byte) = write_buffer.pop_front() {
handle.write_byte(uart_base + 3, byte);
handle.write_byte(uart_base + 2, 0x01);
unsafe {
handle.write_byte(uart_base + 3, byte);
handle.write_byte(uart_base + 2, 0x01);
}
// Presumably emulator thread has shut down by this point.
if handle.interrupt(Interrupt::SerialIn).is_err() {
return;
}
}
}
std::thread::sleep(std::time::Duration::from_millis(2));
handle.interrupt(Interrupt::SerialIn).unwrap();
}
std::thread::sleep(std::time::Duration::from_millis(1));
@@ -101,7 +111,7 @@ impl TilePane for Serial {
if ui.button("Send").clicked() {
self.write_buff.push(0 as char);
for byte in self.write_buff.bytes() {
self.write_tx.send(byte).unwrap();
let _ = self.write_tx.send(byte);
}
self.write_buff.clear();
}