feat: Add real-time data display for CPU registers, RAM, and ROM

This commit is contained in:
Jan-Peter von Hunnius 2026-02-26 19:36:59 +01:00
parent 3530ea5f28
commit 6636417d18
22 changed files with 1233 additions and 188 deletions

View file

@ -1,3 +1,4 @@
use std::any::Any;
use std::collections::HashMap;
use std::sync::{
atomic::{AtomicBool, Ordering},
@ -40,6 +41,11 @@ pub trait Component: Send + Sync {
/// Check if the component is currently running
/// Returns: true if component is running, false otherwise
fn is_running(&self) -> bool;
/// Get a reference to this component as Any for downcasting
/// This allows runtime type inspection to access component-specific methods
/// Returns: A reference to self as &dyn Any
fn as_any(&self) -> &dyn Any;
}
/// Extended trait for components that can be run in their own threads
/// Provides automatic thread spawning functionality for components
@ -157,6 +163,10 @@ impl Component for BaseComponent {
fn is_running(&self) -> bool {
self.running.load(Ordering::SeqCst)
}
fn as_any(&self) -> &dyn Any {
self
}
}
impl RunnableComponent for BaseComponent {}

View file

@ -1,3 +1,4 @@
use std::any::Any;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::thread;
@ -171,6 +172,10 @@ impl Component for GenericClock {
fn is_running(&self) -> bool {
self.base.is_running()
}
fn as_any(&self) -> &dyn Any {
self
}
}
// Advanced clock features

View file

@ -1,3 +1,4 @@
use std::any::Any;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::thread;
@ -182,4 +183,8 @@ impl Component for TwoPhaseClock {
fn is_running(&self) -> bool {
self.base.is_running()
}
fn as_any(&self) -> &dyn Any {
self
}
}

View file

@ -592,7 +592,12 @@ pub enum Intel400xEvent {
///
/// # Example
///
/// ```rust
/// ```rust,ignore
/// use std::time::Duration;
/// use rusty_emu::components::common::intel_400x::{
/// Intel400xComponentBehavior, Intel400xComponentType, Intel400xUnifiedState
/// };
///
/// struct MyCustomComponent {
/// // Component fields...
/// }
@ -641,7 +646,7 @@ pub trait Intel400xComponentBehavior {
///
/// # Example
///
/// ```rust
/// ```rust,ignore
/// fn on_state_entry(&mut self, state: Intel400xUnifiedState) {
/// match state {
/// Intel400xUnifiedState::AddressPhase => {
@ -699,7 +704,7 @@ pub trait Intel400xComponentBehavior {
///
/// # Example
///
/// ```rust
/// ```rust,ignore
/// fn execute_state_logic(&mut self, state: Intel400xUnifiedState) {
/// match state {
/// Intel400xUnifiedState::AddressPhase => {
@ -769,6 +774,12 @@ pub trait Intel400xComponentBehavior {
/// # Example
///
/// ```rust
/// use std::time::Duration;
/// use rusty_emu::components::common::intel_400x::{
/// Intel400xStateMachine, Intel400xComponentType, Intel400xEvent,
/// Intel400xUnifiedState, process_event_direct
/// };
///
/// // Create a state machine for an Intel 4001 ROM chip
/// let mut state_machine = Intel400xStateMachine::new(
/// Intel400xComponentType::Intel4001,
@ -826,6 +837,11 @@ impl Intel400xStateMachine {
/// # Example
///
/// ```rust
/// use std::time::Duration;
/// use rusty_emu::components::common::intel_400x::{
/// Intel400xStateMachine, Intel400xComponentType, Intel400xUnifiedState
/// };
///
/// // Create a state machine for an Intel 4002 RAM chip with 500ns access time
/// let state_machine = Intel400xStateMachine::new(
/// Intel400xComponentType::Intel4002,
@ -1092,21 +1108,28 @@ impl Intel400xComponentBehavior for NoOpBehavior {
/// # Example
///
/// ```rust
/// use std::time::Duration;
/// use rusty_emu::components::common::intel_400x::{
/// Intel400xStateMachine, Intel400xComponentType, Intel400xEvent,
/// Intel400xUnifiedState, process_event
/// };
///
/// let mut state_machine = Intel400xStateMachine::new(
/// Intel400xComponentType::Intel4001,
/// Duration::from_nanos(500)
/// );
/// let mut component = Intel4001Component::new();
/// // Note: Intel4001Component would be imported from its module
/// // let mut component = Intel4001Component::new();
///
/// // Process a chip select event
/// let handled = process_event(
/// &mut state_machine,
/// Intel400xEvent::ChipSelect,
/// &mut component
/// );
/// // let handled = process_event(
/// // &mut state_machine,
/// // Intel400xEvent::ChipSelect,
/// // &mut component
/// // );
///
/// assert!(handled);
/// assert_eq!(state_machine.get_state(), Intel400xUnifiedState::AddressPhase);
/// // assert!(handled);
/// // assert_eq!(state_machine.get_state(), Intel400xUnifiedState::AddressPhase);
/// ```
pub fn process_event<T: Intel400xComponentBehavior>(
state_machine: &mut Intel400xStateMachine,
@ -1163,6 +1186,12 @@ pub fn process_event<T: Intel400xComponentBehavior>(
/// # Example
///
/// ```rust
/// use std::time::Duration;
/// use rusty_emu::components::common::intel_400x::{
/// Intel400xStateMachine, Intel400xComponentType, Intel400xEvent,
/// Intel400xUnifiedState, process_event_direct
/// };
///
/// let mut state_machine = Intel400xStateMachine::new(
/// Intel400xComponentType::Intel4001,
/// Duration::from_nanos(500)

View file

@ -1,3 +1,4 @@
use std::any::Any;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::thread;
@ -1577,6 +1578,10 @@ impl Component for Intel4004 {
fn is_running(&self) -> bool {
self.base.is_running()
}
fn as_any(&self) -> &dyn Any {
self
}
}
impl RunnableComponent for Intel4004 {

View file

@ -1,3 +1,4 @@
use std::any::Any;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::thread;
@ -231,6 +232,10 @@ impl Component for MOS6502 {
fn is_running(&self) -> bool {
self.is_running
}
fn as_any(&self) -> &dyn Any {
self
}
}
// 6502-specific methods

View file

@ -1,3 +1,4 @@
use std::any::Any;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
@ -107,6 +108,10 @@ impl Component for WDC65C02 {
fn is_running(&self) -> bool {
self.base.is_running()
}
fn as_any(&self) -> &dyn Any {
self
}
}
// 65C02-specific enhancements

View file

@ -1,3 +1,4 @@
use std::any::Any;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::thread;
@ -924,6 +925,10 @@ impl Component for Intel4001 {
fn is_running(&self) -> bool {
self.base.is_running()
}
fn as_any(&self) -> &dyn Any {
self
}
}
impl RunnableComponent for Intel4001 {}

View file

@ -1,3 +1,4 @@
use std::any::Any;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::thread;
@ -47,22 +48,6 @@ pub struct Intel4002 {
pending_operation: PendingOperation,
}
/// RAM register operations (for WRM/WRR instructions)
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum RamOperation {
Read, // Read from RAM register
Write, // Write to RAM register
WriteOr, // Write to RAM register with OR operation
WriteAnd, // Write to RAM register with AND operation
}
/// RAM access modes (for SRC/SRC instructions)
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum RamAccessMode {
Direct, // Direct access to RAM
Indirect, // Indirect access via register pair
}
impl Intel400xClockHandling for Intel4002 {
fn get_base(&self) -> &BaseComponent {
&self.base
@ -403,17 +388,6 @@ impl Intel4002 {
}
}
/// Read from a specific I/O port
/// Parameters: port - I/O port number (0-3)
/// Returns: 4-bit value from I/O port
pub fn read_io_port(&self, port: usize) -> u8 {
if port < 4 {
self.io_ports[port]
} else {
0
}
}
/// Write to a specific I/O port
/// Parameters: port - I/O port number (0-3), data - 4-bit data to write
/// Returns: Ok(()) on success, Err(String) on failure
@ -453,25 +427,6 @@ impl Intel4002 {
}
}
/// Drive status character on status pins
fn drive_status_character(&self) {
let status = self.status_character & 0x7F; // Ensure only 7 bits
for i in 0..4 {
if let Ok(pin) = self.base.get_pin(&format!("S{}", i)) {
if let Ok(mut pin_guard) = pin.lock() {
let bit_value = (status >> i) & 1;
let pin_value = if bit_value == 1 {
PinValue::High
} else {
PinValue::Low
};
pin_guard.set_driver(Some(self.base.name()), pin_value);
}
}
}
}
/// Handle Φ1 rising edge - Address and control phase
/// Hardware: Φ1 high = CPU drives bus with address/control information
/// Memory operations start when SYNC goes high during Φ1 rising edge
@ -699,13 +654,6 @@ impl Intel4002 {
self.on_state_entry(new_state);
}
}
/// Helper function to execute unified state machine logic
fn execute_state_machine(&mut self) {
// Execute the current state logic using the unified state machine
let current_state = self.state_machine.get_state();
self.execute_state_logic(current_state);
}
}
impl Component for Intel4002 {
@ -800,6 +748,10 @@ impl Component for Intel4002 {
fn is_running(&self) -> bool {
self.base.is_running()
}
fn as_any(&self) -> &dyn Any {
self
}
}
impl RunnableComponent for Intel4002 {}

View file

@ -1,3 +1,4 @@
use std::any::Any;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::thread;
@ -619,6 +620,10 @@ impl Component for Intel4003 {
fn is_running(&self) -> bool {
self.base.is_running()
}
fn as_any(&self) -> &dyn Any {
self
}
}
impl RunnableComponent for Intel4003 {}

View file

@ -74,6 +74,8 @@ pub struct GuiComponents {
control_panel: ControlPanel,
/// RAM content inspection and visualization
memory_viewer: MemoryViewer,
/// ROM content inspection and visualization
rom_viewer: RomViewer,
/// CPU register state display
register_viewer: RegisterViewer,
/// ROM file loading and management
@ -103,6 +105,7 @@ impl GuiComponents {
Self {
control_panel: ControlPanel::new(),
memory_viewer: MemoryViewer::new(),
rom_viewer: RomViewer::new(),
register_viewer: RegisterViewer::new(),
rom_loader: RomLoader::new(),
status_bar: StatusBar::new(),
@ -128,11 +131,23 @@ impl GuiComponents {
///
/// # Arguments
/// * `ui` - egui UI context for rendering
/// * `state` - Immutable reference to GUI state for display
pub fn render_memory_viewer(&self, ui: &mut egui::Ui, state: &GuiState) {
/// * `state` - Mutable reference to GUI state for display and interaction
pub fn render_memory_viewer(&self, ui: &mut egui::Ui, state: &mut GuiState) {
self.memory_viewer.render(ui, state);
}
/// Render the ROM viewer component
///
/// Displays ROM contents in a tabular format with address selection
/// and hex/decimal viewing options.
///
/// # Arguments
/// * `ui` - egui UI context for rendering
/// * `state` - Mutable reference to GUI state for display and interaction
pub fn render_rom_viewer(&self, ui: &mut egui::Ui, state: &mut GuiState) {
self.rom_viewer.render(ui, state);
}
/// Render the register viewer component
///
/// Shows CPU register state including accumulator, program counter,
@ -140,8 +155,8 @@ impl GuiComponents {
///
/// # Arguments
/// * `ui` - egui UI context for rendering
/// * `state` - Immutable reference to GUI state for display
pub fn render_register_viewer(&self, ui: &mut egui::Ui, state: &GuiState) {
/// * `state` - Mutable reference to GUI state for display and interaction
pub fn render_register_viewer(&self, ui: &mut egui::Ui, state: &mut GuiState) {
self.register_viewer.render(ui, state);
}
@ -330,8 +345,6 @@ impl ControlPanel {
/// - **4 Bytes per Bank**: Addresses 0x00-0x03 in each bank
/// - **4-bit Values**: Each memory location stores a 4-bit nibble
pub struct MemoryViewer {
/// Display mode: true for hexadecimal, false for decimal
show_hex: bool,
/// Number of bytes to display per row (currently fixed at 16)
#[allow(dead_code)]
// bytes_per_row is currently fixed at 16 but kept for future flexibility
@ -341,16 +354,12 @@ pub struct MemoryViewer {
impl MemoryViewer {
/// Create a new memory viewer with default configuration
///
/// Initializes the viewer with hexadecimal display mode and standard
/// layout optimized for the Intel 4002 RAM structure.
/// Initializes the viewer with standard layout optimized for the Intel 4002 RAM structure.
///
/// # Returns
/// A new `MemoryViewer` instance ready for RAM content display
pub fn new() -> Self {
Self {
show_hex: true,
bytes_per_row: 16,
}
Self { bytes_per_row: 16 }
}
/// Render the memory viewer interface
@ -360,7 +369,7 @@ impl MemoryViewer {
///
/// # Arguments
/// * `ui` - egui UI context for rendering
/// * `state` - Immutable reference to GUI state containing memory data
/// * `state` - Mutable reference to GUI state containing memory data
///
/// # Layout Structure
/// ```text
@ -375,7 +384,7 @@ impl MemoryViewer {
/// │ [03] [55] [66] [77] [88] │
/// └─────────────────────────────────────────────────┘
/// ```
pub fn render(&self, ui: &mut egui::Ui, state: &GuiState) {
pub fn render(&self, ui: &mut egui::Ui, state: &mut GuiState) {
ui.vertical(|ui| {
// Section header
ui.heading("Memory Viewer");
@ -385,10 +394,9 @@ impl MemoryViewer {
ui.label("Bank:");
// Bank selector (0-3 for Intel 4002)
ui.add(
egui::DragValue::new(&mut state.memory_state.selected_bank.clone())
.clamp_range(0..=3),
egui::DragValue::new(&mut state.memory_state.selected_bank).clamp_range(0..=3),
);
ui.checkbox(&mut self.show_hex.clone(), "Hex View");
ui.checkbox(&mut state.memory_state.show_hex, "Hex View");
});
ui.separator();
@ -411,7 +419,7 @@ impl MemoryViewer {
// Bank data columns
for bank in 0..4 {
let value = state.memory_state.ram_contents[bank][addr];
if self.show_hex {
if state.memory_state.show_hex {
ui.label(format!("{:02X}", value));
} else {
ui.label(format!("{}", value));
@ -458,22 +466,19 @@ impl MemoryViewer {
/// - **Flags**: System status indicators
/// - **Index Registers**: General-purpose register file
pub struct RegisterViewer {
/// Currently selected index register for detailed view (0-15)
selected_register: usize,
// No internal state - all UI state is stored in GuiState
}
impl RegisterViewer {
/// Create a new register viewer with default configuration
///
/// Initializes the viewer with index register 0 selected and standard
/// display formatting optimized for the Intel 4004 architecture.
/// Initializes the viewer with standard display formatting optimized
/// for the Intel 4004 architecture.
///
/// # Returns
/// A new `RegisterViewer` instance ready for CPU state display
pub fn new() -> Self {
Self {
selected_register: 0,
}
Self {}
}
/// Render the register viewer interface
@ -483,7 +488,7 @@ impl RegisterViewer {
///
/// # Arguments
/// * `ui` - egui UI context for rendering
/// * `state` - Immutable reference to GUI state containing register data
/// * `state` - Mutable reference to GUI state containing register data
///
/// # Layout Structure
/// ```text
@ -499,7 +504,7 @@ impl RegisterViewer {
/// │ Stack Pointer │ 08 │ 8 │
/// └─────────────────────────────────────────────────┘
/// ```
pub fn render(&self, ui: &mut egui::Ui, state: &GuiState) {
pub fn render(&self, ui: &mut egui::Ui, state: &mut GuiState) {
ui.vertical(|ui| {
// Section header
ui.heading("CPU Registers");
@ -508,7 +513,8 @@ impl RegisterViewer {
ui.horizontal(|ui| {
ui.label("Index Register:");
ui.add(
egui::DragValue::new(&mut self.selected_register.clone()).clamp_range(0..=15),
egui::DragValue::new(&mut state.register_state.selected_register)
.clamp_range(0..=15),
);
});
@ -526,7 +532,7 @@ impl RegisterViewer {
// Core CPU registers
ui.label("Accumulator");
ui.label(format!("{:02X}", state.register_state.accumulator));
ui.label(format!("{:X}", state.register_state.accumulator));
ui.label(format!("{}", state.register_state.accumulator));
ui.end_row();
@ -551,20 +557,21 @@ impl RegisterViewer {
ui.end_row();
// Selected index register (highlighted)
ui.label(format!("Index R{}", self.selected_register));
let selected = state.register_state.selected_register;
ui.label(format!("Index R{}", selected));
ui.label(format!(
"{:02X}",
state.register_state.index_registers[self.selected_register]
"{:X}",
state.register_state.index_registers[selected]
));
ui.label(format!(
"{}",
state.register_state.index_registers[self.selected_register]
state.register_state.index_registers[selected]
));
ui.end_row();
// Stack management
ui.label("Stack Pointer");
ui.label(format!("{:02X}", state.register_state.stack_pointer));
ui.label(format!("{:X}", state.register_state.stack_pointer));
ui.label(format!("{}", state.register_state.stack_pointer));
ui.end_row();
});
@ -574,6 +581,132 @@ impl RegisterViewer {
}
}
/// ROM viewer component for displaying ROM contents
///
/// The ROM viewer provides comprehensive ROM inspection capabilities,
/// allowing users to examine program memory contents with flexible
/// display options and intuitive navigation.
///
/// ## Features
///
/// - **Program Memory Display**: View ROM contents from Intel 4001 chips
/// - **Flexible Formatting**: Toggle between hexadecimal and decimal display
/// - **Address Navigation**: Jump to specific memory addresses
/// - **Scrollable Interface**: Navigate through memory contents efficiently
/// - **Real-time Updates**: Live ROM content display
///
/// ## Display Format
///
/// The ROM viewer shows data in a structured grid:
/// - **Address Column**: Memory addresses in hexadecimal format
/// - **Data Columns**: 16 bytes per row for easy viewing
/// - **Value Display**: Configurable hex or decimal representation
///
/// ## Memory Organization
///
/// The Intel 4001 ROM has the following structure:
/// - **256 Bytes**: Total ROM capacity per chip
/// - **8-bit Values**: Each memory location stores a full byte
pub struct RomViewer {
/// Number of bytes to display per row
bytes_per_row: usize,
}
impl RomViewer {
/// Create a new ROM viewer with default configuration
///
/// Initializes the viewer with standard layout optimized for the Intel 4001 ROM structure.
///
/// # Returns
/// A new `RomViewer` instance ready for ROM content display
pub fn new() -> Self {
Self { bytes_per_row: 16 }
}
/// Render the ROM viewer interface
///
/// Creates a comprehensive ROM inspection interface with address navigation,
/// display options, and a scrollable memory contents grid.
///
/// # Arguments
/// * `ui` - egui UI context for rendering
/// * `state` - Mutable reference to GUI state containing ROM data
///
/// # Layout Structure
/// ```text
/// ┌─────────────────────────────────────────────────┐
/// │ ROM Viewer (Chip: ROM_4001_1) [─] [□] │
/// │ □ Hex View │
/// ├─────────────────────────────────────────────────┤
/// │ Address 00 01 02 03 04 05 06 07 08 09 0A 0B... │
/// │ 0x000 D4 C3 B2 A1 00 11 22 33 44 55 66 77... │
/// │ 0x010 88 99 AA BB CC DD EE FF 00 11 22 33... │
/// │ ... │
/// └─────────────────────────────────────────────────┘
/// ```
pub fn render(&self, ui: &mut egui::Ui, state: &mut GuiState) {
ui.vertical(|ui| {
// Section header with chip name
let header = if state.rom_state.chip_name.is_empty() {
"ROM Viewer".to_string()
} else {
format!("ROM Viewer ({})", state.rom_state.chip_name)
};
ui.heading(header);
// Control bar with display options
ui.horizontal(|ui| {
ui.checkbox(&mut state.rom_state.show_hex, "Hex View");
});
ui.separator();
// ROM contents in scrollable area
egui::ScrollArea::vertical()
.max_height(200.0)
.show(ui, |ui| {
egui::Grid::new("rom_grid").striped(true).show(ui, |ui| {
// Header row with column labels
ui.label("Addr");
for i in 0..self.bytes_per_row {
ui.label(format!("{:02X}", i));
}
ui.end_row();
// ROM contents rows
let total_rows = (state.rom_state.rom_contents.len() + self.bytes_per_row
- 1)
/ self.bytes_per_row;
for row in 0..total_rows {
let addr = row * self.bytes_per_row;
// Address column
ui.label(format!("{:03X}", addr));
// Data columns
for col in 0..self.bytes_per_row {
let idx = addr + col;
if idx < state.rom_state.rom_contents.len() {
let value = state.rom_state.rom_contents[idx];
if state.rom_state.show_hex {
ui.label(format!("{:02X}", value));
} else {
ui.label(format!("{:3}", value));
}
} else {
ui.label("--");
}
}
ui.end_row();
}
});
});
});
ui.separator();
}
}
/// ROM loader component for file dialog integration and program management
///
/// The ROM loader handles program file selection, loading, and management.

View file

@ -11,7 +11,7 @@
//! - Graceful interrupt handling
use crossterm::{
event::{self, Event, KeyCode},
event::{self, Event, KeyCode, KeyModifiers},
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
@ -31,6 +31,44 @@ use std::time::{Duration, Instant};
use crate::system_config::ConfigurableSystem;
/// Display format for memory and register values
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
pub enum DisplayFormat {
#[default]
Hex,
Decimal,
Binary,
}
impl DisplayFormat {
/// Format a byte value according to the display format
pub fn format_byte(&self, value: u8) -> String {
match self {
DisplayFormat::Hex => format!("0x{:02X}", value),
DisplayFormat::Decimal => format!("{}", value),
DisplayFormat::Binary => format!("0b{:08b}", value),
}
}
/// Format a nibble (4-bit) value according to the display format
pub fn format_nibble(&self, value: u8) -> String {
match self {
DisplayFormat::Hex => format!("0x{:X}", value & 0x0F),
DisplayFormat::Decimal => format!("{}", value & 0x0F),
DisplayFormat::Binary => format!("0b{:04b}", value & 0x0F),
}
}
/// Format a 12-bit address according to the display format
pub fn format_address(&self, value: u16) -> String {
match self {
DisplayFormat::Hex => format!("0x{:03X}", value & 0x0FFF),
DisplayFormat::Decimal => format!("{}", value & 0x0FFF),
DisplayFormat::Binary => format!("0b{:012b}", value & 0x0FFF),
}
}
}
/// Console configuration structure
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConsoleConfig {
@ -39,8 +77,12 @@ pub struct ConsoleConfig {
pub show_ram: bool,
pub show_registers: bool,
pub show_system_info: bool,
pub show_rom: bool,
pub show_cpu_registers: bool,
pub display_format: DisplayFormat,
pub ram_banks_per_row: usize,
pub max_ram_rows: usize,
pub max_rom_rows: usize,
}
impl Default for ConsoleConfig {
@ -51,8 +93,12 @@ impl Default for ConsoleConfig {
show_ram: true,
show_registers: true,
show_system_info: true,
show_rom: true,
show_cpu_registers: true,
display_format: DisplayFormat::Hex,
ram_banks_per_row: 4,
max_ram_rows: 5,
max_rom_rows: 5,
}
}
}
@ -99,7 +145,7 @@ impl ConsoleApp {
// Handle input
if let Ok(true) = event::poll(Duration::from_millis(10)) {
if let Ok(Event::Key(key)) = event::read() {
self.handle_key_event(key.code);
self.handle_key_event(key.code, key.modifiers);
}
}
@ -139,7 +185,18 @@ impl ConsoleApp {
Ok(())
}
fn handle_key_event(&mut self, key: KeyCode) {
fn handle_key_event(&mut self, key: KeyCode, modifiers: KeyModifiers) {
// Handle Ctrl-Q to quit
if key == KeyCode::Char('q') || key == KeyCode::Char('Q') {
if modifiers.contains(KeyModifiers::CONTROL) {
self.running = false;
if let Ok(mut system) = self.system.lock() {
system.stop();
}
return;
}
}
match key {
KeyCode::Char('q') | KeyCode::Char('Q') | KeyCode::Esc => {
println!("DEBUG: Quit key pressed, stopping console");
@ -448,16 +505,12 @@ impl ConsoleApp {
.wrap(Wrap { trim: true });
f.render_widget(system_widget, info_chunks[0]);
// CPU register display (enhanced with execution status)
let register_info = vec![
Line::from(vec![Span::raw("CPU Registers:")]),
Line::from(vec![Span::raw("Status: Running (check console output)")]),
Line::from(vec![Span::raw("PC: 0x000 (see DEBUG output)")]),
Line::from(vec![Span::raw("ACC: 0x0 (see DEBUG output)")]),
Line::from(vec![Span::raw(
"Instructions: Executing (see DEBUG output)",
)]),
];
// CPU register display with real data
let register_info = if self.config.show_cpu_registers {
self.get_cpu_register_info()
} else {
vec![Line::from(vec![Span::raw("CPU register display disabled")])]
};
let register_widget = Paragraph::new(register_info)
.block(
@ -469,33 +522,101 @@ impl ConsoleApp {
f.render_widget(register_widget, info_chunks[1]);
}
/// Fetch real CPU register data from the system
fn get_cpu_register_info(&self) -> Vec<Line<'static>> {
let mut register_info = vec![Line::from(vec![Span::raw("CPU Registers:")])];
match self.system.lock() {
Ok(system) => {
// Try to find Intel 4004 CPU and get register data
let cpu_data = system.get_cpu_register_data();
if let Some(cpu) = cpu_data {
let fmt = &self.config.display_format;
register_info.push(Line::from(vec![
Span::styled("PC: ", Style::default().fg(Color::Yellow)),
Span::raw(fmt.format_address(cpu.program_counter)),
]));
register_info.push(Line::from(vec![
Span::styled("ACC: ", Style::default().fg(Color::Yellow)),
Span::raw(fmt.format_nibble(cpu.accumulator)),
]));
register_info.push(Line::from(vec![
Span::styled("CY: ", Style::default().fg(Color::Yellow)),
Span::raw(if cpu.carry { "1" } else { "0" }),
]));
register_info.push(Line::from(vec![
Span::styled("SP: ", Style::default().fg(Color::Yellow)),
Span::raw(format!("{}", cpu.stack_pointer)),
]));
// Index registers
let mut reg_line = String::from("Index: ");
for (i, val) in cpu.index_registers.iter().enumerate() {
if i > 0 {
reg_line.push_str(", ");
}
reg_line.push_str(&format!("R{}={}", i, fmt.format_nibble(*val)));
}
register_info.push(Line::from(vec![Span::raw(reg_line)]));
} else {
register_info.push(Line::from(vec![Span::raw("No Intel 4004 CPU found")]));
}
}
Err(_) => {
register_info.push(Line::from(vec![Span::raw("Unable to access CPU data")]));
}
}
register_info
}
fn draw_ram_contents(&self, f: &mut Frame, area: Rect) {
let mut ram_info = vec![Line::from(vec![Span::raw("RAM Contents:")])];
// Try to get actual RAM data from the system
match self.system.lock() {
Ok(_system) => {
// This is a simplified version - in a real implementation,
// we would need to access the actual RAM components
// For now, show a more informative placeholder
ram_info.push(Line::from(vec![Span::raw("Reading RAM contents...")]));
if self.config.show_ram {
match self.system.lock() {
Ok(system) => {
let ram_data = system.get_ram_data(self.config.max_ram_rows);
// Show some sample memory ranges
for bank in 0..4 {
let mut bank_data = format!("Bank {}: [", bank);
for i in 0..20 {
if i > 0 && i % 4 == 0 {
bank_data.push(' ');
if ram_data.is_empty() {
ram_info.push(Line::from(vec![Span::raw("No RAM chips found")]));
} else {
let fmt = &self.config.display_format;
for (chip_name, banks) in ram_data {
// Clone chip_name to avoid lifetime issues
let chip_name = chip_name.clone();
ram_info.push(Line::from(vec![Span::styled(
chip_name,
Style::default().fg(Color::Cyan),
)]));
for (bank_idx, data) in banks.iter().enumerate() {
let mut bank_line = format!(" Bank {}: [", bank_idx);
for (i, nibble) in data.iter().enumerate() {
if i > 0 && i % 4 == 0 {
bank_line.push(' ');
}
bank_line.push_str(&fmt.format_nibble(*nibble));
}
bank_line.push(']');
ram_info.push(Line::from(vec![Span::raw(bank_line)]));
}
}
bank_data.push_str("00");
}
bank_data.push(']');
ram_info.push(Line::from(vec![Span::raw(bank_data)]));
}
Err(_) => {
ram_info.push(Line::from(vec![Span::raw("Unable to access RAM data")]));
}
}
Err(_) => {
ram_info.push(Line::from(vec![Span::raw("Unable to access RAM data")]));
}
} else {
ram_info.push(Line::from(vec![Span::raw("RAM display disabled")]));
}
let ram_widget = Paragraph::new(ram_info)

View file

@ -45,11 +45,9 @@ impl GuiApp {
/// ```rust,no_run
/// use rusty_emu::gui::GuiApp;
///
/// let app = GuiApp::new(&eframe::CreationContext {
/// egui_ctx: Default::default(),
/// integration_info: Default::default(),
/// storage: None,
/// });
/// // The CreationContext is provided by eframe when the app is launched
/// // Use new_for_test() for unit testing without eframe infrastructure
/// let app = GuiApp::new_for_test();
/// ```
pub fn new(_cc: &eframe::CreationContext<'_>) -> Self {
let gui_state = state::GuiState::new();
@ -97,11 +95,13 @@ impl GuiApp {
/// ```rust,no_run
/// use std::sync::{Arc, Mutex};
/// use rusty_emu::gui::GuiApp;
/// use rusty_emu::system_config::{ConfigurableSystem, SystemFactory};
/// use rusty_emu::system_config::SystemFactory;
///
/// let mut app = GuiApp::new(&creation_context);
/// let mut app = GuiApp::new_for_test();
/// let factory = SystemFactory::new();
/// let system = Arc::new(Mutex::new(factory.create_from_json("config.json")?));
/// let system = Arc::new(Mutex::new(
/// factory.create_from_json("configs/mcs4_basic.json").expect("Failed to create system")
/// ));
/// app.set_system(system);
/// ```
pub fn set_system(&mut self, system: Arc<Mutex<ConfigurableSystem>>) {
@ -142,6 +142,21 @@ impl eframe::App for GuiApp {
// Request repaint for smooth real-time updates (60 FPS)
ctx.request_repaint();
// Handle CtrlQ keyboard shortcut to close the application
let ctrl_q_pressed = ctx.input(|i| i.modifiers.ctrl && i.key_pressed(egui::Key::Q));
if ctrl_q_pressed {
// Stop the system if available
if let Some(system) = self.get_system() {
if let Ok(mut sys) = system.lock() {
sys.stop();
}
}
// Close the GUI window
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
return;
}
// Update system state if available - non-blocking operation
if let Some(system) = self.get_system() {
self.gui_state.update_from_system(&system);
@ -178,8 +193,10 @@ impl GuiApp {
// Main GUI sections - organized for optimal workflow
self.components
.render_control_panel(ui, &mut self.gui_state);
self.components.render_memory_viewer(ui, &self.gui_state);
self.components.render_register_viewer(ui, &self.gui_state);
self.components
.render_memory_viewer(ui, &mut self.gui_state);
self.components
.render_register_viewer(ui, &mut self.gui_state);
self.components.render_status_bar(ui, &self.gui_state);
});
}
@ -207,13 +224,13 @@ impl GuiApp {
/// ```rust,no_run
/// use std::sync::{Arc, Mutex};
/// use rusty_emu::gui::run_gui;
/// use rusty_emu::system_config::{ConfigurableSystem, SystemFactory};
/// use rusty_emu::system_config::SystemFactory;
///
/// // Create system (optional)
/// let factory = SystemFactory::new();
/// let system = Some(Arc::new(Mutex::new(
/// factory.create_from_json("configs/mcs4_basic.json")?
/// )));
/// let system = factory.create_from_json("configs/mcs4_basic.json").ok().map(|s| {
/// Arc::new(Mutex::new(s))
/// });
///
/// // Launch GUI - this will block until the GUI window is closed
/// if let Err(e) = run_gui(system) {

View file

@ -22,6 +22,8 @@ pub struct GuiState {
pub component_states: ComponentStates,
/// Memory state
pub memory_state: MemoryState,
/// ROM state
pub rom_state: RomState,
/// CPU register state
pub register_state: RegisterState,
/// Last error message
@ -54,6 +56,18 @@ pub struct MemoryState {
pub ram_contents: Vec<[u8; 4]>, // 4 banks of RAM data
pub selected_bank: usize,
pub selected_address: usize,
/// Display mode: true for hexadecimal, false for decimal
pub show_hex: bool,
}
/// ROM state for display
#[derive(Debug, Clone)]
pub struct RomState {
pub rom_contents: Vec<u8>,
pub selected_address: usize,
pub chip_name: String,
/// Display mode: true for hexadecimal, false for decimal
pub show_hex: bool,
}
/// CPU register state for display
@ -62,8 +76,10 @@ pub struct RegisterState {
pub accumulator: u8,
pub carry_flag: bool,
pub program_counter: u16,
pub index_registers: [u8; 16], // 16 index registers (R0-R15)
pub index_registers: [u8; 16], // 16 index registers (R0-R15) for Intel 4004
pub stack_pointer: u8,
/// Currently selected index register for detailed view (0-15)
pub selected_register: usize,
}
impl GuiState {
@ -84,6 +100,13 @@ impl GuiState {
ram_contents: vec![[0; 4]; 4], // Initialize 4 banks with 4 bytes each
selected_bank: 0,
selected_address: 0,
show_hex: true,
},
rom_state: RomState {
rom_contents: vec![0; 256], // Initialize with 256 bytes
selected_address: 0,
chip_name: String::new(),
show_hex: true,
},
register_state: RegisterState {
accumulator: 0,
@ -91,6 +114,7 @@ impl GuiState {
program_counter: 0,
index_registers: [0; 16],
stack_pointer: 0,
selected_register: 0,
},
last_error: None,
}
@ -110,6 +134,15 @@ impl GuiState {
// Update component states
self.update_component_states(&system_guard);
// Update CPU register state from real data
self.update_register_state(&system_guard);
// Update RAM state from real data
self.update_ram_state(&system_guard);
// Update ROM state from real data
self.update_rom_state(&system_guard);
// Update cycle count (simulate for now)
if self.system_running {
self.cycle_count += 1;
@ -146,6 +179,50 @@ impl GuiState {
});
}
/// Update CPU register state from real component data
fn update_register_state(&mut self, system: &ConfigurableSystem) {
if let Some(cpu_data) = system.get_cpu_register_data() {
self.register_state.accumulator = cpu_data.accumulator;
self.register_state.carry_flag = cpu_data.carry;
self.register_state.program_counter = cpu_data.program_counter;
self.register_state.stack_pointer = cpu_data.stack_pointer;
self.register_state
.index_registers
.copy_from_slice(&cpu_data.index_registers);
}
}
/// Update RAM state from real component data
fn update_ram_state(&mut self, system: &ConfigurableSystem) {
let ram_data = system.get_ram_data(4); // Get up to 4 rows per bank
if !ram_data.is_empty() {
// Take the first RAM chip's data
let (_, banks) = &ram_data[0];
// Convert bank data to our display format
self.memory_state.ram_contents.clear();
for bank in banks {
let mut row = [0u8; 4];
for (i, nibble) in bank.iter().take(4).enumerate() {
row[i] = *nibble;
}
self.memory_state.ram_contents.push(row);
}
}
}
/// Update ROM state from real component data
fn update_rom_state(&mut self, system: &ConfigurableSystem) {
let rom_data = system.get_rom_data(256); // Get up to 256 bytes
if !rom_data.is_empty() {
let (chip_name, data) = &rom_data[0];
self.rom_state.chip_name = chip_name.clone();
self.rom_state.rom_contents = data.clone();
}
}
/// Set an error message
pub fn set_error(&mut self, error: String) {
self.last_error = Some(error);

View file

@ -488,7 +488,7 @@ fn run_system_demo(system: ConfigurableSystem) {
let timeout = Duration::from_secs(10); // 10 second timeout
loop {
thread::sleep(Duration::from_millis(500));
thread::sleep(Duration::from_millis(100));
// Check for timeout
if start_time.elapsed() >= timeout {

View file

@ -50,10 +50,29 @@
//! ```
use crate::component::Component;
use crate::components::cpu::intel_4004::Intel4004;
use crate::components::memory::intel_4001::Intel4001;
use crate::components::memory::intel_4002::Intel4002;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
/// CPU register data for display purposes
#[derive(Debug, Clone, Default)]
pub struct CpuRegisterData {
pub program_counter: u16,
pub accumulator: u8,
pub carry: bool,
pub stack_pointer: u8,
pub index_registers: [u8; 16], // Intel 4004 has 16 index registers (R0-R15)
}
/// RAM data for display purposes (chip name -> banks of nibbles)
pub type RamData = Vec<(String, Vec<Vec<u8>>)>;
/// ROM data for display purposes (chip name -> bytes)
pub type RomData = Vec<(String, Vec<u8>)>;
/// JSON-based system configuration structures
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SystemConfig {
@ -616,6 +635,95 @@ impl ConfigurableSystem {
println!("DEBUG: Program loading completed");
Ok(())
}
/// Get CPU register data from Intel 4004 CPU
/// Returns: CpuRegisterData if Intel 4004 CPU is found, None otherwise
pub fn get_cpu_register_data(&self) -> Option<CpuRegisterData> {
// Look for Intel 4004 CPU components
for (_name, component) in &self.components {
if let Ok(comp) = component.lock() {
// Try to downcast to Intel4004
if let Some(cpu) = comp.as_any().downcast_ref::<Intel4004>() {
let mut index_registers = [0u8; 16];
for i in 0..16 {
index_registers[i] = cpu.get_register(i as u8).unwrap_or(0);
}
return Some(CpuRegisterData {
program_counter: cpu.get_program_counter(),
accumulator: cpu.get_accumulator(),
carry: cpu.get_carry(),
stack_pointer: cpu.get_stack_pointer(),
index_registers,
});
}
}
}
None
}
/// Get RAM data from Intel 4002 RAM chips
/// Parameters: max_rows - Maximum number of rows to return per bank
/// Returns: Vector of (chip_name, banks) where each bank is a vector of nibbles
pub fn get_ram_data(&self, max_rows: usize) -> RamData {
let mut ram_data = Vec::new();
for (name, component) in &self.components {
if let Ok(comp) = component.lock() {
// Try to downcast to Intel4002
if let Some(ram) = comp.as_any().downcast_ref::<Intel4002>() {
let mut banks = Vec::new();
// Intel 4002 has 4 banks of 16 4-bit words each (64 nibbles total per chip)
// But actually it has 4 main memory registers, each with 16 4-bit characters
// and 4 status characters = 4 x 20 = 80 nibbles
for bank in 0..4 {
let mut bank_data = Vec::new();
// Read up to max_rows * 4 nibbles per bank
// The read_ram_direct takes a single address parameter
for addr in 0..(max_rows * 4).min(20) {
// Calculate the combined address for this bank and offset
let full_addr = (bank * 20 + addr) as u8;
// Use read_ram_direct to get the nibble at this address
let value = ram.read_ram_direct(full_addr).unwrap_or(0);
bank_data.push(value);
}
banks.push(bank_data);
}
ram_data.push((name.clone(), banks));
}
}
}
ram_data
}
/// Get ROM data from Intel 4001 ROM chips
/// Parameters: max_rows - Maximum number of bytes to return per chip
/// Returns: Vector of (chip_name, bytes)
pub fn get_rom_data(&self, max_rows: usize) -> RomData {
let mut rom_data = Vec::new();
for (name, component) in &self.components {
if let Ok(comp) = component.lock() {
// Try to downcast to Intel4001
if let Some(rom) = comp.as_any().downcast_ref::<Intel4001>() {
let mut data = Vec::new();
// Intel 4001 has 256 bytes of ROM
for addr in 0..max_rows.min(256) {
let value = rom.read_rom(addr as u8).unwrap_or(0);
data.push(value);
}
rom_data.push((name.clone(), data));
}
}
}
rom_data
}
}
#[derive(Debug, Clone)]