CPU can now decode instructions, just waiting on the assembler
This commit is contained in:
@@ -1,6 +1,12 @@
|
||||
use std::sync::mpsc::Sender;
|
||||
|
||||
use crate::{common::instructions::Register, emulator::{system::model::{Command, Running, State}, ui::interface::Component}};
|
||||
use crate::{
|
||||
common::instructions::Register,
|
||||
emulator::{
|
||||
system::model::{Command, Running, State},
|
||||
ui::interface::Component,
|
||||
},
|
||||
};
|
||||
|
||||
pub struct ControlPanel {
|
||||
visible: bool,
|
||||
@@ -25,66 +31,63 @@ impl Component for ControlPanel {
|
||||
&mut self.visible
|
||||
}
|
||||
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
"Control Panel"
|
||||
}
|
||||
|
||||
|
||||
fn render(&mut self, state: &mut State, ui: &mut egui::Ui, ctx: &egui::Context) {
|
||||
ui.horizontal(|ui| {
|
||||
// Pause / Run
|
||||
if ui
|
||||
.button(if state.running == Running::Running {
|
||||
"Pause"
|
||||
} else {
|
||||
"Run"
|
||||
})
|
||||
.clicked()
|
||||
{
|
||||
if state.running == Running::Running {
|
||||
self.sender.send(Command::Stop).unwrap_or_else(|_| state.error = Some("Failed to send command".to_string()));
|
||||
} else {
|
||||
self.sender.send(Command::Start).unwrap_or_else(|_| state.error = Some("Failed to send command".to_string()));
|
||||
ui.horizontal(|ui| {
|
||||
// Pause / Run
|
||||
if ui
|
||||
.button(if state.running == Running::Running {
|
||||
"Pause"
|
||||
} else {
|
||||
"Run"
|
||||
})
|
||||
.clicked()
|
||||
{
|
||||
if state.running == Running::Running {
|
||||
self.sender.send(Command::Stop).unwrap_or_else(|_| {
|
||||
state.error = Some("Failed to send command".to_string())
|
||||
});
|
||||
} else {
|
||||
self.sender.send(Command::Start).unwrap_or_else(|_| {
|
||||
state.error = Some("Failed to send command".to_string())
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step
|
||||
if ui.button("Step").clicked() {
|
||||
self.sender.send(Command::Step).unwrap_or_else(|_| state.error = Some("Failed to send command".to_string()));
|
||||
}
|
||||
|
||||
ui.separator();
|
||||
|
||||
// Status info
|
||||
ui.label(format!(
|
||||
"Status: {}",
|
||||
match state.running {
|
||||
Running::Running => "Running",
|
||||
Running::Paused => "Paused",
|
||||
Running::Halted => "Halted",
|
||||
// Step
|
||||
if ui.button("Step").clicked() {
|
||||
self.sender
|
||||
.send(Command::Step)
|
||||
.unwrap_or_else(|_| state.error = Some("Failed to send command".to_string()));
|
||||
}
|
||||
));
|
||||
ui.label(format!(
|
||||
"Instructions: {}",
|
||||
state.instructions
|
||||
));
|
||||
ui.label(format!("PC: 0x{:08X}", state.reg_file.get(Register::Pcx)));
|
||||
ui.label(format!(
|
||||
"Last Instruction: {:?}",
|
||||
"TODO: DECODE INSTRUCTION" // TODO: decode instruction
|
||||
// Instruction::decode(state.current_state.cir)
|
||||
));
|
||||
});
|
||||
|
||||
render_register_table(state, ui, ctx);
|
||||
ui.separator();
|
||||
|
||||
// Status info
|
||||
ui.label(format!(
|
||||
"Status: {}",
|
||||
match state.running {
|
||||
Running::Running => "Running",
|
||||
Running::Paused => "Paused",
|
||||
Running::Halted => "Halted",
|
||||
}
|
||||
));
|
||||
ui.label(format!("Instructions: {}", state.instructions));
|
||||
ui.label(format!("PC: 0x{:08X}", state.reg_file.get(Register::Pcx)));
|
||||
ui.label(format!(
|
||||
"Last Instruction: {:?}",
|
||||
"TODO: DECODE INSTRUCTION" // TODO: decode instruction
|
||||
// Instruction::decode(state.current_state.cir)
|
||||
));
|
||||
});
|
||||
|
||||
render_register_table(state, ui, ctx);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
fn render_register_table(state: &mut State, ui: &mut egui::Ui, _ctx: &egui::Context) {
|
||||
// Left column - Registers
|
||||
ui.vertical(|ui| {
|
||||
@@ -111,12 +114,8 @@ fn render_register_table(state: &mut State, ui: &mut egui::Ui, _ctx: &egui::Cont
|
||||
// iterate over state.reg_file.iter() in chunks of 4 registers
|
||||
for chunk in state.reg_file.all().chunks(4) {
|
||||
for reg in chunk {
|
||||
ui.label(format!("{:?}", reg.0));
|
||||
ui.label(format!(
|
||||
"0x{:08X} ({})",
|
||||
reg.1,
|
||||
reg.1,
|
||||
));
|
||||
ui.label(format!("{}", reg.0));
|
||||
ui.label(format!("0x{:08X} ({})", reg.1, reg.1,));
|
||||
}
|
||||
ui.end_row();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use crate::{common::instructions::{Address, Interrupt}, emulator::system::model::{Command, Running, State}};
|
||||
use crate::{
|
||||
common::instructions::{Address, Interrupt},
|
||||
emulator::system::model::{Command, Running, State},
|
||||
};
|
||||
use std::sync::mpsc::{Receiver, Sender};
|
||||
|
||||
pub trait Component {
|
||||
@@ -56,10 +59,18 @@ impl EmulatorUI {
|
||||
pub fn add_component(&mut self, component: Box<dyn Component>) {
|
||||
self.components.push(component);
|
||||
}
|
||||
|
||||
fn update_state(&mut self) {
|
||||
while let Ok(state) = self.receiver.try_recv() {
|
||||
self.state = state;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl eframe::App for EmulatorUI {
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||
self.update_state();
|
||||
|
||||
if let Running::Running = self.state.running {
|
||||
ctx.request_repaint();
|
||||
}
|
||||
@@ -89,10 +100,10 @@ impl eframe::App for EmulatorUI {
|
||||
let mut visible = *c.visible();
|
||||
if visible == true {
|
||||
egui::Window::new(c.name())
|
||||
.open(&mut visible)
|
||||
.show(ctx, |ui| {
|
||||
c.render(&mut self.state, ui, ctx);
|
||||
});
|
||||
.open(&mut visible)
|
||||
.show(ctx, |ui| {
|
||||
c.render(&mut self.state, ui, ctx);
|
||||
});
|
||||
}
|
||||
*c.visible() = visible;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
use std::{num::ParseIntError, sync::mpsc::Sender};
|
||||
|
||||
use crate::emulator::{system::model::{Command, State}, ui::interface::Component};
|
||||
|
||||
pub struct MemoryInspector {
|
||||
view_size: u32,
|
||||
view_addr: u32,
|
||||
visible: bool,
|
||||
addr_input: String,
|
||||
sender: Sender<Command>,
|
||||
}
|
||||
|
||||
impl MemoryInspector {
|
||||
pub fn new(sender: Sender<Command>) -> Self {
|
||||
Self {
|
||||
view_size: 256,
|
||||
view_addr: 0,
|
||||
visible: false,
|
||||
addr_input: String::new(),
|
||||
sender,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for MemoryInspector {
|
||||
fn category(&self) -> super::interface::Category {
|
||||
super::interface::Category::Memory
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
"Memory Inspector"
|
||||
}
|
||||
|
||||
fn visible(&mut self) -> &mut bool {
|
||||
&mut self.visible
|
||||
}
|
||||
|
||||
fn render(&mut self, state: &mut State, ui: &mut egui::Ui, ctx: &egui::Context) {
|
||||
// Right column - Memory
|
||||
ui.vertical(|ui| {
|
||||
ui.heading("Memory Inspector");
|
||||
ui.add_space(10.0);
|
||||
|
||||
// Address input section
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Address:");
|
||||
|
||||
let address_response = ui.add(
|
||||
egui::TextEdit::singleline(&mut self.addr_input)
|
||||
.hint_text("0x1000 or 4096")
|
||||
.desired_width(150.0),
|
||||
);
|
||||
|
||||
ui.add_space(10.0);
|
||||
|
||||
// Search button
|
||||
let search_clicked = ui.button("🔍 Search").clicked();
|
||||
|
||||
// Handle Enter key in text field
|
||||
let enter_pressed =
|
||||
address_response.lost_focus() && ctx.input(|i| i.key_pressed(egui::Key::Enter));
|
||||
|
||||
if search_clicked || enter_pressed {
|
||||
if let Ok(new) = parse_address(&self.addr_input) {
|
||||
self.view_addr = new;
|
||||
self.sender.send(Command::Read(new, self.view_size)).unwrap();
|
||||
} else {
|
||||
state.error = Some("Invalid address".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
ui.label("(hex or decimal)");
|
||||
});
|
||||
|
||||
// Show input error if any
|
||||
if let Some(error) = &state.error {
|
||||
ui.colored_label(egui::Color32::RED, format!("Error: {}", error));
|
||||
}
|
||||
|
||||
ui.add_space(10.0);
|
||||
|
||||
// Memory table
|
||||
egui::ScrollArea::vertical()
|
||||
.auto_shrink(true)
|
||||
.id_salt("memory_inspector_scroll")
|
||||
.show(ui, |ui| {
|
||||
egui::Grid::new("memory_grid")
|
||||
.spacing([12.0, 2.0])
|
||||
.min_col_width(5.0)
|
||||
.striped(true)
|
||||
.show(ui, |ui| {
|
||||
// Header
|
||||
ui.strong("Address");
|
||||
|
||||
for i in 0..4 {
|
||||
ui.strong(format!("{:X}", i));
|
||||
}
|
||||
|
||||
ui.strong("Decimal");
|
||||
ui.strong("Instruction");
|
||||
|
||||
ui.end_row();
|
||||
|
||||
// Memory data (8 bytes per row)
|
||||
for (row, chunk) in state.memory_view.chunks(4).enumerate() {
|
||||
let row_address = self.view_addr + (row * 4) as u32;
|
||||
ui.monospace(format!("0x{:08X} ({})", row_address, row_address));
|
||||
for &byte in chunk {
|
||||
ui.monospace(format!("{:02X}", byte));
|
||||
}
|
||||
|
||||
// Fill remaining columns if last row is incomplete
|
||||
for _ in chunk.len()..4 {
|
||||
ui.label("");
|
||||
}
|
||||
|
||||
// combine all 4 bytes in the chunk into a u32
|
||||
let combined =
|
||||
chunk.iter().fold(0u32, |acc, &byte| acc << 8 | byte as u32);
|
||||
|
||||
ui.monospace(format!("{}", combined));
|
||||
// ui.monospace(format!("{:?}", Instruction::decode(combined)));
|
||||
ui.monospace("TODO! instruction");
|
||||
|
||||
ui.end_row();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_address(address: &str) -> Result<u32, ParseIntError> {
|
||||
if address.starts_with("0x") {
|
||||
return u32::from_str_radix(&address[2..], 16);
|
||||
}
|
||||
|
||||
if address.starts_with("0b") {
|
||||
return u32::from_str_radix(&address[2..], 2);
|
||||
}
|
||||
|
||||
if address.starts_with("0o") {
|
||||
return u32::from_str_radix(&address[1..], 8);
|
||||
}
|
||||
|
||||
u32::from_str_radix(address, 10)
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
pub mod control_unit;
|
||||
pub mod interface;
|
||||
pub mod menu;
|
||||
pub mod control_unit;
|
||||
pub mod memory_inspector;
|
||||
pub mod stack_inspector;
|
||||
@@ -0,0 +1,65 @@
|
||||
use crate::{common::instructions::Register, emulator::{system::model::State, ui::interface::{Component, EmulatorUI}}};
|
||||
|
||||
pub struct StackInspector {
|
||||
visible: bool
|
||||
}
|
||||
|
||||
impl StackInspector {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
visible: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for StackInspector {
|
||||
fn visible(&mut self) -> &mut bool {
|
||||
&mut self.visible
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
"Stack Inspector"
|
||||
}
|
||||
|
||||
fn category(&self) -> super::interface::Category {
|
||||
super::interface::Category::Memory
|
||||
}
|
||||
|
||||
fn render(&mut self, state: &mut State, ui: &mut egui::Ui, ctx: &egui::Context) {
|
||||
ui.vertical(|ui| {
|
||||
ui.heading("Stack Inspector");
|
||||
egui::ScrollArea::vertical()
|
||||
.id_salt("stack_inspector_scroll")
|
||||
.show(ui, |ui| {
|
||||
egui::Grid::new("stack_grid")
|
||||
.num_columns(2)
|
||||
.spacing([40.0, 4.0])
|
||||
.striped(true)
|
||||
.show(ui, |ui| {
|
||||
ui.label("Address");
|
||||
ui.label("Value");
|
||||
ui.end_row();
|
||||
|
||||
for (i, value) in state.stack_view.iter().take(32).enumerate() {
|
||||
ui.label(format!(
|
||||
"{} [{}]",
|
||||
i,
|
||||
state.reg_file.get(Register::Spr) - i as u32 * 4
|
||||
));
|
||||
ui.label(format!("0x{:08X} ({})", value, value));
|
||||
ui.end_row();
|
||||
}
|
||||
|
||||
if state.stack_view.is_empty() {
|
||||
ui.label("(empty)");
|
||||
ui.label("-");
|
||||
ui.end_row();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user