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

@ -6,8 +6,12 @@
<show_system_info>true</show_system_info>
<show_registers>true</show_registers>
<show_ram>true</show_ram>
<show_rom>true</show_rom>
<show_cpu_registers>true</show_cpu_registers>
<display_format>hex</display_format>
<ram_banks_per_row>4</ram_banks_per_row>
<max_ram_rows>5</max_ram_rows>
<max_rom_rows>5</max_rom_rows>
</interface>
<display>
@ -16,6 +20,7 @@
<command_bar>white</command_bar>
<system_info>white</system_info>
<ram_contents>white</ram_contents>
<rom_contents>white</rom_contents>
<help_text>white</help_text>
</colors>

589
plans/codebase_analysis.md Normal file
View file

@ -0,0 +1,589 @@
# MCS-4 Emulator Codebase Analysis
## Executive Summary
This document provides a comprehensive analysis of the MCS-4 emulator codebase structure, focusing on GUI, console output, configuration systems, and entry points. The analysis identifies specific locations for implementing ROM/RAM/CPU register display and Ctrl-Q keyboard handling.
---
## 1. GUI Structure Analysis
### 1.1 Framework and Architecture
**Location:** [`src/gui/mod.rs`](src/gui/mod.rs:1-259)
The GUI is built using **eframe/egui** (Rust immediate mode GUI framework):
```rust
use eframe::egui;
```
**Key Structure:**
- [`GuiApp`](src/gui/mod.rs:21-29) - Main GUI application struct
- `system: Option<Arc<Mutex<ConfigurableSystem>>>` - Thread-safe system reference
- `gui_state: state::GuiState` - GUI state management
- `components: components::GuiComponents` - Component container
**Entry Point:** [`run_gui()`](src/gui/mod.rs:234-259) function launches the GUI
### 1.2 State Management
**Location:** [`src/gui/state.rs`](src/gui/state.rs:1-176)
**Key Structures:**
| Structure | Purpose | Location |
|-----------|---------|----------|
| [`GuiState`](src/gui/state.rs:12-29) | Main UI state container | Lines 12-29 |
| [`SystemInfo`](src/gui/state.rs:32-40) | System metadata display | Lines 32-40 |
| [`ComponentStates`](src/gui/state.rs:43-49) | Component running states | Lines 43-49 |
| [`MemoryState`](src/gui/state.rs:52-57) | RAM contents display | Lines 52-57 |
| [`RegisterState`](src/gui/state.rs:60-67) | CPU register display | Lines 60-67 |
**Current State Fields:**
```rust
pub struct MemoryState {
pub ram_contents: Vec<[u8; 4]>, // 4 banks of RAM data
pub selected_bank: usize,
pub selected_address: usize,
}
pub struct RegisterState {
pub accumulator: u8,
pub carry_flag: bool,
pub program_counter: u16,
pub index_registers: [u8; 16], // R0-R15
pub stack_pointer: u8,
}
```
**State Update Method:** [`update_from_system()`](src/gui/state.rs:100-118)
### 1.3 GUI Components
**Location:** [`src/gui/components.rs`](src/gui/components.rs:1-804)
| Component | Purpose | Location |
|-----------|---------|----------|
| [`GuiComponents`](src/gui/components.rs:72-84) | Component container | Lines 72-84 |
| [`ControlPanel`](src/gui/components.rs:180-303) | Start/Stop/Reset buttons | Lines 180-303 |
| [`MemoryViewer`](src/gui/components.rs:305-428) | RAM display | Lines 305-428 |
| [`RegisterViewer`](src/gui/components.rs:430-574) | CPU registers display | Lines 430-574 |
| [`RomLoader`](src/gui/components.rs:576-671) | ROM file loading | Lines 576-671 |
| [`StatusBar`](src/gui/components.rs:673-804) | System status | Lines 673-804 |
**Where to Add ROM Display:**
- Create new `RomViewer` component similar to `MemoryViewer`
- Add to [`GuiComponents`](src/gui/components.rs:72-84) struct
- Render in [`render_gui()`](src/gui/mod.rs:171-185)
### 1.4 Current Display Gaps
**Missing ROM Display:**
- No ROM content viewer exists
- Need to add ROM data retrieval from Intel4001 components
**RAM Display Status:**
- [`MemoryState.ram_contents`](src/gui/state.rs:54) exists but uses placeholder data
- [`update_from_system()`](src/gui/state.rs:100-118) does NOT populate actual RAM data
- Need to add RAM data extraction from Intel4002 components
**CPU Register Status:**
- [`RegisterState`](src/gui/state.rs:60-67) exists with all needed fields
- [`update_from_system()`](src/gui/state.rs:100-118) does NOT populate actual CPU data
- Need to add CPU data extraction from Intel4004 component
---
## 2. Console Output Analysis
### 2.1 Console Interface
**Location:** [`src/gui/console.rs`](src/gui/console.rs:1-515)
**Framework:** ratatui + crossterm (Terminal UI)
**Key Structure:**
```rust
pub struct ConsoleApp {
system: Arc<Mutex<ConfigurableSystem>>,
config: ConsoleConfig,
running: bool,
command_buffer: String,
show_help: bool,
selected_pane: usize,
}
```
### 2.2 Console Configuration
**Location:** [`src/gui/console.rs:35-58`](src/gui/console.rs:35-58)
```rust
pub struct ConsoleConfig {
pub enabled: bool,
pub refresh_rate_ms: u64,
pub show_ram: bool,
pub show_registers: bool,
pub show_system_info: bool,
pub ram_banks_per_row: usize,
pub max_ram_rows: usize,
}
```
**XML Config File:** [`configs/console_config.xml`](configs/console_config.xml:1-45)
### 2.3 Current Console Display
**RAM Display:** [`draw_ram_contents()`](src/gui/console.rs:472-505)
- Currently shows placeholder data (`00` values)
- Does NOT read actual RAM from Intel4002
**CPU Register Display:** [`draw_system_info()`](src/gui/console.rs:401-470)
- Shows placeholder text: "see DEBUG output"
- Does NOT read actual registers from Intel4004
**ROM Display:**
- NOT implemented in console
- Would need new `draw_rom_contents()` method
### 2.4 Input Handling
**Location:** [`handle_key_event()`](src/gui/console.rs:142-202)
Current key bindings:
- `q/Q/Esc` - Quit
- `r/R` - Run/Stop toggle
- `s/S` - Stop
- `h/H` - Help
- `Tab` - Switch panes
- `Enter` - Execute command
**Ctrl-Q NOT currently handled** - Need to add `KeyCode::Char('q')` with Ctrl modifier check
---
## 3. Configuration System Analysis
### 3.1 System Configuration
**Location:** [`src/system_config.rs`](src/system_config.rs:1-629)
**Key Structures:**
| Structure | Purpose | Location |
|-----------|---------|----------|
| [`SystemConfig`](src/system_config.rs:58-67) | JSON system definition | Lines 58-67 |
| [`ComponentConfig`](src/system_config.rs:70-75) | Component configuration enum | Lines 70-75 |
| [`ConfigurableSystem`](src/system_config.rs:362-363) | Runtime system instance | Lines 362-428 |
**Configuration Files:**
- [`configs/mcs4_basic.json`](configs/mcs4_basic.json:1-167) - Basic MCS-4 system
- [`configs/mcs4_max.json`](configs/mcs4_max.json) - Maximum configuration
- [`configs/mcs4_io_demo.json`](configs/mcs4_io_demo.json) - I/O demonstration
### 3.2 Existing Configuration Options
**JSON Configuration Structure:**
```json
{
"name": "IntelMcs4",
"description": "Basic MCS-4 System",
"version": "1.0",
"metadata": {
"cpu_speed": 750000,
"rom_count": 2,
"ram_count": 1
},
"components": { ... },
"connections": { ... }
}
```
### 3.3 Where Display Configuration Fits
**Option 1: Extend ConsoleConfig** in [`src/gui/console.rs:35-58`](src/gui/console.rs:35-58)
```rust
pub struct ConsoleConfig {
// Existing fields...
pub show_rom: bool, // NEW: ROM display toggle
pub rom_address_range: String, // NEW: ROM address range to display
pub display_format: String, // NEW: hex/decimal/ascii
}
```
**Option 2: Extend XML Config** [`configs/console_config.xml`](configs/console_config.xml:1-45)
```xml
<interface>
<!-- Existing options -->
<show_rom>true</show_rom>
<rom_bytes_per_row>16</rom_bytes_per_row>
<display_format>hex</display_format>
</interface>
```
**Option 3: Create New DisplayConfig** structure for unified GUI/Console settings
---
## 4. Main Entry Point Analysis
### 4.1 Application Flow
**Location:** [`src/main.rs`](src/main.rs:1-676)
```mermaid
flowchart TD
A[main] --> B[Parse CLI Args]
B --> C{Mode?}
C -->|--console| D[Launch Console]
C -->|--gui| E[Launch GUI]
C -->|default| F[Traditional Mode]
D --> G[run_console]
E --> H[run_gui]
F --> I[run_system_demo]
```
### 4.2 Mode Selection
**Location:** [`main()`](src/main.rs:102-279)
```rust
// Interface mode selection and launch
if use_console {
// Launch interactive console interface
let console_config = ConsoleConfig::default();
run_console(system_arc, console_config)
} else if use_gui {
// Launch graphical user interface
run_gui(Some(system_arc))
} else {
// Traditional mode
run_system_demo(system)
}
```
### 4.3 System Start/Stop
**ConfigurableSystem Methods:**
- [`run()`](src/system_config.rs:380-428) - Start system
- [`stop()`](src/system_config.rs:538-540) - Stop system
- [`is_running()`](src/system_config.rs:542-544) - Check running state
### 4.4 Keyboard Input Handling
**Console:** [`handle_key_event()`](src/gui/console.rs:142-202)
**GUI:** Uses egui event system - no explicit keyboard handler
**Traditional Mode:** No keyboard handling (Ctrl+C only)
**Where Ctrl-Q Fits:**
| Mode | Location | Implementation |
|------|----------|----------------|
| Console | [`handle_key_event()`](src/gui/console.rs:142) | Add Ctrl-Q detection |
| GUI | [`GuiApp`](src/gui/mod.rs:21) update loop | Add keyboard shortcut |
| Traditional | [`run_system_demo()`](src/main.rs:460) | Add terminal input thread |
---
## 5. Component Data Access Analysis
### 5.1 Intel 4004 CPU
**Location:** [`src/components/cpu/intel_4004.rs`](src/components/cpu/intel_4004.rs:1-1602)
**Register Access Methods:**
| Method | Returns | Location |
|--------|---------|----------|
| [`get_accumulator()`](src/components/cpu/intel_4004.rs:1142-1144) | `u8` | Line 1142 |
| [`get_carry()`](src/components/cpu/intel_4004.rs:1154-1156) | `bool` | Line 1154 |
| [`get_program_counter()`](src/components/cpu/intel_4004.rs:1130-1132) | `u16` | Line 1130 |
| [`get_register(index)`](src/components/cpu/intel_4004.rs:1191-1197) | `Option<u8>` | Line 1191 |
| [`get_stack_pointer()`](src/components/cpu/intel_4004.rs:1160-1162) | `u8` | Line 1160 |
| [`get_cycle_count()`](src/components/cpu/intel_4004.rs:1166-1168) | `u64` | Line 1166 |
### 5.2 Intel 4002 RAM
**Location:** [`src/components/memory/intel_4002.rs`](src/components/memory/intel_4002.rs:1-1140)
**Memory Access Methods:**
| Method | Returns | Location |
|--------|---------|----------|
| [`read_ram_direct(addr)`](src/components/memory/intel_4002.rs:818-824) | `Option<u8>` | Line 818 |
| [`get_io_port(port)`](src/components/memory/intel_4002.rs:842-848) | `Option<u8>` | Line 842 |
| [`get_ram_size()`](src/components/memory/intel_4002.rs:810-813) | `usize` | Line 810 |
### 5.3 Intel 4001 ROM
**Location:** [`src/components/memory/intel_4001.rs`](src/components/memory/intel_4001.rs)
**Expected Methods (need verification):**
- ROM data read/access
- I/O port access
### 5.4 Component Access Pattern
**From ConfigurableSystem:**
```rust
// Access components HashMap
let components = system.get_components();
// Get CPU
if let Some(cpu_component) = components.get("CPU_4004") {
if let Ok(cpu) = cpu_component.lock() {
// Access CPU methods - but need downcast to Intel4004
}
}
```
**Challenge:** Components are stored as `Box<dyn Component>` - need downcasting to access type-specific methods.
---
## 6. Fibonacci Example Analysis
### 6.1 Program Files
**Location:** [`programs/`](programs/)
| File | Size | Purpose |
|------|------|---------|
| [`fibonacci.bin`](programs/fibonacci.bin) | 33 bytes | Basic Fibonacci (RAM only) |
| [`fibonacci_output.bin`](programs/fibonacci_output.bin) | 55 bytes | Fibonacci with output ports |
### 6.2 Expected Behavior
**From [`programs/README.md`](programs/README.md:7-13):**
- Calculates first 8 Fibonacci numbers
- Results stored in RAM locations 0-7
- `fibonacci_output.bin` also outputs to ports for visibility
### 6.3 Where Results Appear
**RAM Locations:** 0x00-0x07 (first 8 Fibonacci numbers: 0, 1, 1, 2, 3, 5, 8, 13)
**Output Ports:** Port values visible during execution with `fibonacci_output.bin`
---
## 7. Implementation Recommendations
### 7.1 ROM/RAM/CPU Display - Console
**File:** [`src/gui/console.rs`](src/gui/console.rs)
**Changes Required:**
1. **Add ROM display method** (new ~50 lines after line 505):
```rust
fn draw_rom_contents(&self, f: &mut Frame, area: Rect) {
// Access ROM components and display contents
}
```
2. **Update RAM display** [`draw_ram_contents()`](src/gui/console.rs:472-505):
- Add actual RAM data retrieval from Intel4002
- Use `read_ram_direct()` method
3. **Update CPU display** [`draw_system_info()`](src/gui/console.rs:401-470):
- Add actual register retrieval from Intel4004
- Use getter methods: `get_accumulator()`, `get_program_counter()`, etc.
4. **Add component downcasting helper** (new method):
```rust
fn get_cpu_state(&self) -> Option<CpuState> { ... }
fn get_ram_state(&self) -> Option<RamState> { ... }
fn get_rom_state(&self) -> Option<RomState> { ... }
```
### 7.2 ROM/RAM/CPU Display - GUI
**File:** [`src/gui/state.rs`](src/gui/state.rs)
**Changes Required:**
1. **Extend `MemoryState`** (line 52-57):
```rust
pub struct MemoryState {
pub ram_contents: Vec<[u8; 4]>,
pub rom_contents: Vec<u8>, // NEW: ROM data
pub rom_address_range: (u16, u16), // NEW: Address range
// ...existing fields
}
```
2. **Update `update_from_system()`** (line 100-118):
- Add actual CPU register retrieval
- Add actual RAM data retrieval
- Add ROM data retrieval
**File:** [`src/gui/components.rs`](src/gui/components.rs)
3. **Add `RomViewer` component** (new ~100 lines after line 428):
- Similar to `MemoryViewer`
- Display ROM contents in hex grid
### 7.3 Display Configuration
**File:** [`src/gui/console.rs`](src/gui/console.rs:35-58)
**Extend `ConsoleConfig`:**
```rust
pub struct ConsoleConfig {
// Existing...
pub show_rom: bool, // NEW
pub display_refresh_rate: u64, // NEW
pub address_format: String, // NEW: hex/decimal
}
```
**File:** [`configs/console_config.xml`](configs/console_config.xml)
**Add display options:**
```xml
<display>
<show_rom>true</show_rom>
<show_rom_addresses>true</show_rom_addresses>
</display>
```
### 7.4 Ctrl-Q Keyboard Handling
**Console - File:** [`src/gui/console.rs`](src/gui/console.rs:142)
**Add in `handle_key_event()`:**
```rust
KeyCode::Char('q') if key.modifiers.contains(KeyModifiers::CONTROL) => {
self.running = false;
if let Ok(mut system) = self.system.lock() {
system.stop();
}
}
```
**GUI - File:** [`src/gui/mod.rs`](src/gui/mod.rs:141)
**Add in `update()` method:**
```rust
// Check for Ctrl-Q shortcut
if ui.input(|i| i.key_pressed(egui::Key::Q) && i.modifiers.ctrl) {
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
}
```
**Traditional Mode - File:** [`src/main.rs`](src/main.rs:460)
**Add terminal input thread in `run_system_demo()`**:
```rust
thread::spawn(move || {
let stdin = std::io::stdin();
for line in stdin.lock().lines() {
if line.unwrap() == "\x11" { // Ctrl-Q
*running.lock().unwrap() = false;
break;
}
}
});
```
---
## 8. Summary of Required Changes
| Task | File | Location | Type |
|------|------|----------|------|
| Console RAM display | [`src/gui/console.rs`](src/gui/console.rs:472) | `draw_ram_contents()` | Modify |
| Console CPU display | [`src/gui/console.rs`](src/gui/console.rs:401) | `draw_system_info()` | Modify |
| Console ROM display | [`src/gui/console.rs`](src/gui/console.rs:505) | New method | Add |
| GUI state update | [`src/gui/state.rs`](src/gui/state.rs:100) | `update_from_system()` | Modify |
| GUI ROM viewer | [`src/gui/components.rs`](src/gui/components.rs:428) | New component | Add |
| Console config | [`src/gui/console.rs`](src/gui/console.rs:35) | `ConsoleConfig` | Extend |
| XML config | [`configs/console_config.xml`](configs/console_config.xml) | Add display options | Extend |
| Console Ctrl-Q | [`src/gui/console.rs`](src/gui/console.rs:142) | `handle_key_event()` | Modify |
| GUI Ctrl-Q | [`src/gui/mod.rs`](src/gui/mod.rs:141) | `update()` | Modify |
| Traditional Ctrl-Q | [`src/main.rs`](src/main.rs:460) | `run_system_demo()` | Modify |
---
## 9. Architecture Diagram
```mermaid
graph TB
subgraph Entry
main[main.rs]
end
subgraph Modes
console[Console Mode]
gui[GUI Mode]
traditional[Traditional Mode]
end
subgraph Console Components
console_app[ConsoleApp]
console_config[ConsoleConfig]
draw_ram[draw_ram_contents]
draw_cpu[draw_system_info]
handle_keys[handle_key_event]
end
subgraph GUI Components
gui_app[GuiApp]
gui_state[GuiState]
gui_components[GuiComponents]
mem_viewer[MemoryViewer]
reg_viewer[RegisterViewer]
end
subgraph System
config_sys[ConfigurableSystem]
components[Component HashMap]
end
subgraph Hardware Components
cpu[Intel4004 CPU]
ram[Intel4002 RAM]
rom[Intel4001 ROM]
end
main --> console
main --> gui
main --> traditional
console --> console_app
console_app --> draw_ram
console_app --> draw_cpu
console_app --> handle_keys
gui --> gui_app
gui_app --> gui_state
gui_app --> gui_components
gui_components --> mem_viewer
gui_components --> reg_viewer
console_app --> config_sys
gui_state --> config_sys
config_sys --> components
components --> cpu
components --> ram
components --> rom
```
---
## 10. Key Code Locations Reference
| Component | File | Key Lines |
|-----------|------|-----------|
| GUI Entry | [`src/gui/mod.rs`](src/gui/mod.rs:234) | 234-259 |
| GUI State | [`src/gui/state.rs`](src/gui/state.rs:12) | 12-176 |
| GUI Components | [`src/gui/components.rs`](src/gui/components.rs:72) | 72-804 |
| Console Entry | [`src/gui/console.rs`](src/gui/console.rs:509) | 509-515 |
| Console Config | [`src/gui/console.rs`](src/gui/console.rs:35) | 35-58 |
| Console Keys | [`src/gui/console.rs`](src/gui/console.rs:142) | 142-202 |
| System Config | [`src/system_config.rs`](src/system_config.rs:58) | 58-629 |
| Main Entry | [`src/main.rs`](src/main.rs:102) | 102-279 |
| CPU Getters | [`src/components/cpu/intel_4004.rs`](src/components/cpu/intel_4004.rs:1130) | 1130-1197 |
| RAM Getters | [`src/components/memory/intel_4002.rs`](src/components/memory/intel_4002.rs:810) | 810-866 |

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,34 +522,102 @@ 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
if self.config.show_ram {
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...")]));
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 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_data.push(' ');
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")]));
}
}
} else {
ram_info.push(Line::from(vec![Span::raw("RAM display disabled")]));
}
let ram_widget = Paragraph::new(ram_info)
.block(Block::default().borders(Borders::ALL).title("RAM Contents"))

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)]

View file

@ -108,7 +108,7 @@ mod gui_system_integration_tests {
// Test that GUI state has proper structure for synchronization
assert_eq!(app.gui_state.memory_state.ram_contents.len(), 4);
assert_eq!(app.gui_state.register_state.index_registers.len(), 16);
assert_eq!(app.gui_state.register_state.index_registers.len(), 16); // Intel 4004 has 16 index registers (R0-R15)
}
#[test]
@ -252,7 +252,7 @@ mod gui_system_integration_tests {
assert_eq!(gui_state.register_state.accumulator, 0);
assert!(!gui_state.register_state.carry_flag);
assert_eq!(gui_state.register_state.program_counter, 0);
assert_eq!(gui_state.register_state.index_registers.len(), 16);
assert_eq!(gui_state.register_state.index_registers.len(), 16); // Intel 4004 has 16 index registers (R0-R15)
assert_eq!(gui_state.register_state.stack_pointer, 0);
}
@ -295,7 +295,7 @@ mod gui_system_integration_tests {
// Test that GUI state is properly initialized for the system
assert_eq!(app.gui_state.memory_state.ram_contents.len(), 4);
assert_eq!(app.gui_state.register_state.index_registers.len(), 16);
assert_eq!(app.gui_state.register_state.index_registers.len(), 16); // Intel 4004 has 16 index registers (R0-R15)
}
#[test]
@ -359,7 +359,7 @@ mod gui_system_integration_tests {
// For now, we verify the state structure remains consistent
assert_eq!(app.gui_state.memory_state.ram_contents.len(), 4);
assert_eq!(app.gui_state.register_state.index_registers.len(), 16);
assert_eq!(app.gui_state.register_state.index_registers.len(), 16); // Intel 4004 has 16 index registers (R0-R15)
// Simulate state change
app.gui_state.cycle_count = i;
@ -565,7 +565,7 @@ mod gui_real_time_tests {
let mut gui_state = rusty_emu::gui::state::GuiState::new();
// Verify register layout matches Intel 4004 structure
assert_eq!(gui_state.register_state.index_registers.len(), 16); // 16 index registers
assert_eq!(gui_state.register_state.index_registers.len(), 16); // Intel 4004 has 16 index registers (R0-R15)
// Test register value bounds
gui_state.register_state.accumulator = 0x0F; // 4-bit value (0-15)

View file

@ -307,10 +307,10 @@ mod gui_integration_tests {
components.render_control_panel(&mut create_test_ui(&ctx), &mut state);
// Test memory viewer rendering
components.render_memory_viewer(&mut create_test_ui(&ctx), &state);
components.render_memory_viewer(&mut create_test_ui(&ctx), &mut state);
// Test register viewer rendering
components.render_register_viewer(&mut create_test_ui(&ctx), &state);
components.render_register_viewer(&mut create_test_ui(&ctx), &mut state);
// Test status bar rendering
components.render_status_bar(&mut create_test_ui(&ctx), &state);

View file

@ -49,42 +49,6 @@ fn wait_for_pin_settlement() {
thread::sleep(Duration::from_nanos(10)); // 10ns settlement time
}
/// Test helper to set control pins for RAM operation
/// RAM requires: SYNC=1, CM=1 (chip_select), CI=1 (io_select for RAM vs ROM)
fn setup_ram_operation(ram: &Intel4002, sync_high: bool, cm_high: bool) {
let sync_pin = ram.get_pin("SYNC").unwrap();
let cm_pin = ram.get_pin("CM").unwrap();
let ci_pin = ram.get_pin("CI").unwrap();
// Set control pins
{
let mut sync_guard = sync_pin.lock().unwrap();
let mut cm_guard = cm_pin.lock().unwrap();
let mut ci_guard = ci_pin.lock().unwrap();
sync_guard.set_driver(
Some("TEST".to_string()),
if sync_high {
PinValue::High
} else {
PinValue::Low
},
);
cm_guard.set_driver(
Some("TEST".to_string()),
if cm_high {
PinValue::High
} else {
PinValue::Low
},
);
// CI must be HIGH for RAM operations (distinguishes RAM from ROM)
ci_guard.set_driver(Some("TEST".to_string()), PinValue::High);
}
wait_for_pin_settlement();
}
/// Test helper to set data bus value
fn set_data_bus(ram: &Intel4002, value: u8) {
let nibble = value & 0x0F; // Only lower 4 bits

View file

@ -2,6 +2,7 @@ use rusty_emu::component::{BaseComponent, Component};
use rusty_emu::components::common::intel_400x::*;
use rusty_emu::components::memory::intel_4001::{Intel4001, IoDirection, IoMode};
use rusty_emu::pin::{Pin, PinValue};
use std::any::Any;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::time::Instant;
@ -45,6 +46,10 @@ impl Component for MockComponent {
fn is_running(&self) -> bool {
false
}
fn as_any(&self) -> &dyn Any {
self
}
}
impl Intel400xAddressHandling for MockComponent {