diff --git a/Cargo.lock b/Cargo.lock index 1cfb591c0..4b6f74747 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -685,6 +685,18 @@ dependencies = [ "hashbrown 0.14.3", ] +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "itoa" version = "1.0.10" @@ -1038,6 +1050,7 @@ dependencies = [ "gloo-events 0.1.2", "gloo-utils 0.1.7", "humantime", + "instant", "js-sys", "levenshtein", "log", diff --git a/Cargo.toml b/Cargo.toml index 14e4e94e3..c55cb3290 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,3 +38,4 @@ cfg-if = "0.1" log = "0.4" console_log = { version = "1", features = ["color"] } humantime = "2.1.0" +instant = { version = "0.1.12" , features = ["wasm-bindgen"] } diff --git a/src/agent.rs b/src/agent.rs index 82d7599e8..1b50b1d01 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -9,6 +9,7 @@ use crate::emulation_core::mips::datapath::MipsDatapath; use crate::emulation_core::riscv::datapath::RiscDatapath; use futures::{FutureExt, SinkExt, StreamExt}; use gloo_console::log; +use instant::Instant; use messages::DatapathUpdate; use std::collections::HashSet; use std::time::Duration; @@ -43,6 +44,8 @@ macro_rules! send_update_riscv { }; } +const UPDATE_INTERVAL: Duration = Duration::from_millis(250); + /// The main logic for the emulation core agent. All code within this function runs on a worker thread as opposed to /// the UI thread. #[reactor(EmulationCoreAgent)] @@ -84,79 +87,84 @@ pub async fn emulation_core_agent(scope: ReactorScope) state.execute_syscall_stage().await; // Part 4: Processing State/Sending Updates to UI - match state.current_datapath.as_datapath_ref() { - DatapathRef::MIPS(datapath) => { - log!(format!("Updates: {:?}", state.updates)); - // Stage always updates - send_update_mips!( - state.scope, - true, - MipsStateUpdate::UpdateStage(datapath.current_stage) - ); - - // Send all other updates based on the state.updates variable. - send_update_mips!( - state.scope, - state.updates.changed_state, - MipsStateUpdate::UpdateState(datapath.state.clone()) - ); - send_update_mips!( - state.scope, - state.updates.changed_registers, - MipsStateUpdate::UpdateRegisters(datapath.registers) - ); - send_update_mips!( - state.scope, - state.updates.changed_coprocessor_state, - MipsStateUpdate::UpdateCoprocessorState(datapath.coprocessor.state.clone()) - ); - send_update_mips!( - state.scope, - state.updates.changed_coprocessor_registers, - MipsStateUpdate::UpdateCoprocessorRegisters(datapath.coprocessor.registers) - ); - send_update_mips!( - state.scope, - state.updates.changed_memory, - MipsStateUpdate::UpdateMemory(datapath.memory.clone()) - ); - send_update_mips!( - state.scope, - state.updates.changed_stack, - MipsStateUpdate::UpdateStack(datapath.stack.clone()) - ); - } - DatapathRef::RISCV(datapath) => { - // Stage always updates - send_update_riscv!( - state.scope, - true, - RiscStateUpdate::UpdateStage(datapath.current_stage) - ); - - // Send all other updates based on the state.updates variable. - send_update_riscv!( - state.scope, - state.updates.changed_state, - RiscStateUpdate::UpdateState(datapath.state.clone()) - ); - send_update_riscv!( - state.scope, - state.updates.changed_registers, - RiscStateUpdate::UpdateRegisters(datapath.registers) - ); - send_update_riscv!( - state.scope, - state.updates.changed_memory, - RiscStateUpdate::UpdateMemory(datapath.memory.clone()) - ); - send_update_riscv!( - state.scope, - state.updates.changed_stack, - RiscStateUpdate::UpdateStack(datapath.stack.clone()) - ); + if state.should_send_datapath_update() { + match state.current_datapath.as_datapath_ref() { + DatapathRef::MIPS(datapath) => { + log!(format!("Updates: {:?}", state.updates)); + // Stage always updates + send_update_mips!( + state.scope, + true, + MipsStateUpdate::UpdateStage(datapath.current_stage) + ); + + // Send all other updates based on the state.updates variable. + send_update_mips!( + state.scope, + state.updates.changed_state, + MipsStateUpdate::UpdateState(datapath.state.clone()) + ); + send_update_mips!( + state.scope, + state.updates.changed_registers, + MipsStateUpdate::UpdateRegisters(datapath.registers) + ); + send_update_mips!( + state.scope, + state.updates.changed_coprocessor_state, + MipsStateUpdate::UpdateCoprocessorState(datapath.coprocessor.state.clone()) + ); + send_update_mips!( + state.scope, + state.updates.changed_coprocessor_registers, + MipsStateUpdate::UpdateCoprocessorRegisters(datapath.coprocessor.registers) + ); + send_update_mips!( + state.scope, + state.updates.changed_memory, + MipsStateUpdate::UpdateMemory(datapath.memory.clone()) + ); + send_update_mips!( + state.scope, + state.updates.changed_stack, + MipsStateUpdate::UpdateStack(datapath.stack.clone()) + ); + } + DatapathRef::RISCV(datapath) => { + // Stage always updates + send_update_riscv!( + state.scope, + true, + RiscStateUpdate::UpdateStage(datapath.current_stage) + ); + + // Send all other updates based on the state.updates variable. + send_update_riscv!( + state.scope, + state.updates.changed_state, + RiscStateUpdate::UpdateState(datapath.state.clone()) + ); + send_update_riscv!( + state.scope, + state.updates.changed_registers, + RiscStateUpdate::UpdateRegisters(datapath.registers) + ); + send_update_riscv!( + state.scope, + state.updates.changed_memory, + RiscStateUpdate::UpdateMemory(datapath.memory.clone()) + ); + send_update_riscv!( + state.scope, + state.updates.changed_stack, + RiscStateUpdate::UpdateStack(datapath.stack.clone()) + ); + } } + state.updates = Default::default(); + state.last_update = Some(Instant::now()); } + // Part 5: Sending Non-Syscall System Updates to UI send_update!( state.scope, @@ -173,7 +181,6 @@ pub async fn emulation_core_agent(scope: ReactorScope) state.speed != curr_speed, DatapathUpdate::System(SystemUpdate::UpdateSpeed(state.speed)) ); - state.updates = Default::default(); } } @@ -192,6 +199,7 @@ struct EmulatorCoreAgentState { pub updates: DatapathUpdateSignal, pub scope: ReactorScope, speed: u32, + last_update: Option, executing: bool, initialized: bool, messages: Vec, @@ -207,6 +215,7 @@ impl EmulatorCoreAgentState { updates: DatapathUpdateSignal::default(), scope, speed: 0, + last_update: None, executing: false, initialized: false, messages: Vec::new(), @@ -295,8 +304,10 @@ impl EmulatorCoreAgentState { DatapathRef::MIPS(datapath) => datapath.registers.pc, DatapathRef::RISCV(datapath) => datapath.registers.pc, }; - if self.breakpoints.contains(¤t_pc) { + if self.breakpoints.contains(¤t_pc) && self.updates.hit_breakpoint { self.executing = false; + // Unset the hit_breakpoint flag after processing + self.updates.hit_breakpoint = false; } } @@ -482,6 +493,21 @@ impl EmulatorCoreAgentState { } } } + + // Now that the syscall is processed, unset the update signal + self.updates.hit_syscall = false; + } + + /// Determines of datapath updates should be sent. Datapath updates should be sent at most once + /// per second when executing as fast as possible. If the last cycle was executed using the + /// debug buttons or we're going at at a specific speed, always send an update. + pub fn should_send_datapath_update(&self) -> bool { + if self.executing && self.speed == 0 { + self.last_update.is_none() + || Instant::now().duration_since(self.last_update.unwrap()) > UPDATE_INTERVAL + } else { + true + } } async fn reset_system(&mut self) { @@ -500,6 +526,7 @@ impl EmulatorCoreAgentState { } async fn add_message(&mut self, msg: String) { + log!("Pushed message"); self.messages.push(msg); self.scope .send(DatapathUpdate::System(SystemUpdate::UpdateMessages( diff --git a/static/assembly_examples/fibonacci.asm b/static/assembly_examples/fibonacci.asm index 19e62420a..fd01dc889 100644 --- a/static/assembly_examples/fibonacci.asm +++ b/static/assembly_examples/fibonacci.asm @@ -66,8 +66,8 @@ main: # PC starts here lw $ra,36($sp) lw $fp,32($sp) addiu $sp,$sp,40 + li $a0, 0 # Load 0 into $a0 to run the exit syscall syscall # replaced jr $ra with syscall to prevent infinite execution - nop # Here's the demo code in C: #