From 7cc4132c25f106dfd5955defa2124b5127300f11 Mon Sep 17 00:00:00 2001 From: David Isaksson Date: Thu, 18 Jul 2024 20:57:00 +0200 Subject: [PATCH] emu: Add simulated clock which updates the screen --- src/emulator/tui.rs | 1 + src/emulator/tui/app.rs | 33 +++++++++++++++++++++++++++++---- src/emulator/tui/event.rs | 15 +++++++++++++-- src/emulator/tui/ui.rs | 31 ++++++++++++++++++++++++++++--- src/emulator/tui/update.rs | 2 +- 5 files changed, 72 insertions(+), 10 deletions(-) diff --git a/src/emulator/tui.rs b/src/emulator/tui.rs index d8ea5ee..c4bd40b 100644 --- a/src/emulator/tui.rs +++ b/src/emulator/tui.rs @@ -36,6 +36,7 @@ pub fn exec(bytes: &[u8], program_start: u16) -> anyhow::Result<()> { Event::Key(key_event) => update(&mut app, key_event), Event::Mouse(_mouse_event) => {} Event::Resize(_w, _h) => {} + Event::Clock => app.clock(), }; } diff --git a/src/emulator/tui/app.rs b/src/emulator/tui/app.rs index d1ff20d..1c52507 100644 --- a/src/emulator/tui/app.rs +++ b/src/emulator/tui/app.rs @@ -2,7 +2,7 @@ use crate::{ disassembler::{disassemble_code, listing}, emulator::{ bus::{Bus, Readable, Writeable}, - cpu::{self, Cpu, RunOption, STACK_BASE, STACK_PAGE}, + cpu::{self, Cpu, STACK_BASE, STACK_PAGE}, }, }; use anyhow::Result; @@ -15,6 +15,13 @@ pub mod widget; const MEMORY_SCROLL_PAGE: usize = 0x10; const MEMORY_SCROLL_MAX: usize = 0xff; +#[derive(Default, Debug, Clone, Copy, PartialEq)] +pub enum RunMode { + #[default] + Step, + Run, +} + #[derive(Default)] pub struct App { /// Emulated CPU @@ -40,6 +47,8 @@ pub struct App { pub selected_widget: AppWidget, + pub run_mode: RunMode, + /// If the app should quit should_quit: bool, } @@ -91,6 +100,8 @@ impl App { self.cpu = Cpu::new(); self.cpu.reset(); self.state.invalidate(); + + self.run_mode = RunMode::Step; } /// Quits the application. @@ -98,15 +109,29 @@ impl App { self.should_quit = true; } + /// Clock the CPU by one cycle. + pub fn clock(&mut self) { + match self.run_mode { + RunMode::Run => self.step_cpu(), + RunMode::Step => (), + } + } + /// Steps the CPU by one instruction. pub fn step_cpu(&mut self) { - self.cpu.step(&mut self.memory); + match self.run_mode { + RunMode::Run => self.cpu.clock(&mut self.memory), + RunMode::Step => self.cpu.step(&mut self.memory), + } self.state.invalidate(); } /// Run CPU execution until a break instruction is reached. - pub fn continue_execution(&mut self) { - self.cpu.run(&mut self.memory, RunOption::StopOnJumpToSelf); + pub fn toggle_run_step_mode(&mut self) { + match self.run_mode { + RunMode::Run => self.run_mode = RunMode::Step, + RunMode::Step => self.run_mode = RunMode::Run, + } } /// Get the last and current state of the emulation diff --git a/src/emulator/tui/event.rs b/src/emulator/tui/event.rs index 76ead19..375c143 100644 --- a/src/emulator/tui/event.rs +++ b/src/emulator/tui/event.rs @@ -18,6 +18,8 @@ pub enum Event { Mouse(MouseEvent), /// Terminal resize. Resize(u16, u16), + /// Clock tick. + Clock, } /// Terminal event handler. @@ -37,14 +39,16 @@ impl EventHandler { /// Constructs a new instance of [`EventHandler`]. pub fn new(tick_rate: u64) -> Self { let tick_rate = Duration::from_millis(tick_rate); + let clock_rate = Duration::from_millis(10); let (sender, receiver) = mpsc::channel(); let handler = { let sender = sender.clone(); thread::spawn(move || { let mut last_tick = Instant::now(); + let mut last_clock = Instant::now(); loop { - let timeout = tick_rate - .checked_sub(last_tick.elapsed()) + let timeout = clock_rate + .checked_sub(last_clock.elapsed()) .unwrap_or(tick_rate); if event::poll(timeout).expect("unable to poll for event") { @@ -67,6 +71,13 @@ impl EventHandler { sender.send(Event::Tick).expect("failed to send tick event"); last_tick = Instant::now(); } + + if last_clock.elapsed() >= clock_rate { + sender + .send(Event::Clock) + .expect("failed to send clock event"); + last_clock = Instant::now(); + } } }) }; diff --git a/src/emulator/tui/ui.rs b/src/emulator/tui/ui.rs index 2f28d7f..babe891 100644 --- a/src/emulator/tui/ui.rs +++ b/src/emulator/tui/ui.rs @@ -2,7 +2,7 @@ use ratatui::{prelude::*, widgets::*}; use crate::emulator::cpu::STACK_BASE; -use super::app::{widget::AppWidget, App}; +use super::app::{widget::AppWidget, App, RunMode}; fn is_selected_style(selected: AppWidget, current_widget: AppWidget) -> Style { if selected == current_widget { @@ -246,7 +246,7 @@ fn render_top_bar(_app: &mut App, frame: &mut Frame, layout: Rect) { ); } -fn render_bottom_bar(_app: &mut App, frame: &mut Frame, layout: Rect) { +fn render_help_text_widget(_app: &mut App, frame: &mut Frame, layout: Rect) { frame.render_widget( Paragraph::new(vec![ "Press `Esc`, `Ctrl-C` or `q` to stop running.".into(), @@ -260,6 +260,23 @@ fn render_bottom_bar(_app: &mut App, frame: &mut Frame, layout: Rect) { ); } +fn render_simulation_state_widget(app: &mut App, frame: &mut Frame, layout: Rect) { + let is_step_mode = app.run_mode == RunMode::Step; + let text: Vec> = vec![vec![ + Span::raw("Run mode: "), + Span::styled("Step", style_active_text(is_step_mode)), + Span::raw("/"), + Span::styled("Continuous", style_active_text(!is_step_mode)), + ] + .into()]; + frame.render_widget( + Paragraph::new(text) + .style(Style::default().fg(Color::Yellow)) + .alignment(Alignment::Left), + layout, + ); +} + pub fn render(app: &mut App, frame: &mut Frame) { let main_layout = Layout::default() .direction(Direction::Vertical) @@ -292,11 +309,19 @@ pub fn render(app: &mut App, frame: &mut Frame) { Constraint::Max(18), // Memory viewer ]) .split(app_layout[2]); + let bottom_bar_layout = Layout::default() + .direction(Direction::Horizontal) + .constraints([ + Constraint::Fill(2), // Help text + Constraint::Fill(1), // Simulation state + ]) + .split(main_layout[2]); render_top_bar(app, frame, main_layout[0]); render_registers_widget(app, frame, left_app_layout[0]); render_stack_widget(app, frame, left_app_layout[1]); render_disassembly_widget(app, frame, app_layout[1]); render_memory_widget(app, frame, right_app_layout[0]); - render_bottom_bar(app, frame, main_layout[2]) + render_help_text_widget(app, frame, bottom_bar_layout[0]); + render_simulation_state_widget(app, frame, bottom_bar_layout[1]); } diff --git a/src/emulator/tui/update.rs b/src/emulator/tui/update.rs index 126e956..8849cfb 100644 --- a/src/emulator/tui/update.rs +++ b/src/emulator/tui/update.rs @@ -9,7 +9,7 @@ pub fn update(app: &mut App, key_event: KeyEvent) { if key_event.modifiers == KeyModifiers::CONTROL { app.quit() } else { - app.continue_execution(); + app.toggle_run_step_mode(); } } KeyCode::Char('s') => app.step_cpu(),