feat: migrate to parking_lot mutex and add system control architecture
This commit is contained in:
parent
14060e15e5
commit
54144d10e9
29 changed files with 2387 additions and 1130 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
|
@ -1,6 +1,6 @@
|
||||||
# This file is automatically @generated by Cargo.
|
# This file is automatically @generated by Cargo.
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 4
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ab_glyph"
|
name = "ab_glyph"
|
||||||
|
|
@ -2503,6 +2503,7 @@ dependencies = [
|
||||||
"eframe",
|
"eframe",
|
||||||
"egui",
|
"egui",
|
||||||
"gtk",
|
"gtk",
|
||||||
|
"parking_lot",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
"raw-window-handle 0.5.2",
|
"raw-window-handle 0.5.2",
|
||||||
"rfd",
|
"rfd",
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ egui = "0.24"
|
||||||
eframe = { version = "0.24", default-features = false, features = ["default_fonts", "glow", "persistence"] }
|
eframe = { version = "0.24", default-features = false, features = ["default_fonts", "glow", "persistence"] }
|
||||||
rfd = "0.14"
|
rfd = "0.14"
|
||||||
raw-window-handle = "0.5"
|
raw-window-handle = "0.5"
|
||||||
|
parking_lot = "0.12"
|
||||||
|
|
||||||
# Force compatible versions for Rust 1.81.0
|
# Force compatible versions for Rust 1.81.0
|
||||||
[dependencies.tokio]
|
[dependencies.tokio]
|
||||||
|
|
|
||||||
296
plans/observer_pattern_and_ui_layout.md
Normal file
296
plans/observer_pattern_and_ui_layout.md
Normal file
|
|
@ -0,0 +1,296 @@
|
||||||
|
# Observer Pattern and UI Layout Configuration Implementation Plan
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This plan implements two key improvements to the rusty_emu project:
|
||||||
|
|
||||||
|
1. **Observer Pattern**: Replace inefficient polling with event-driven state updates
|
||||||
|
2. **UI Layout Configuration**: Enable configurable GUI layouts from JSON config files
|
||||||
|
|
||||||
|
## Current Issues
|
||||||
|
|
||||||
|
### Performance Issues
|
||||||
|
- Console polls system state at 100ms intervals (inefficient)
|
||||||
|
- GUI uses `try_lock()` but still has potential contention
|
||||||
|
- No way to configure GUI layout from JSON config
|
||||||
|
|
||||||
|
### Architecture Limitations
|
||||||
|
- Tight coupling between components and UI
|
||||||
|
- No standardized way to notify UI of state changes
|
||||||
|
- Hardcoded UI layouts
|
||||||
|
|
||||||
|
## Solution Architecture
|
||||||
|
|
||||||
|
### Observer Pattern Implementation
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
A[SystemObserver Trait] --> B[CPU Observer]
|
||||||
|
A --> C[RAM Observer]
|
||||||
|
A --> D[ROM Observer]
|
||||||
|
|
||||||
|
B --> E[ConfigurableSystem]
|
||||||
|
C --> E
|
||||||
|
D --> E
|
||||||
|
|
||||||
|
E --> F[Console UI]
|
||||||
|
E --> G[GUI Interface]
|
||||||
|
|
||||||
|
F --> H[Event-driven Updates]
|
||||||
|
G --> H
|
||||||
|
```
|
||||||
|
|
||||||
|
### UI Layout Configuration
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph LR
|
||||||
|
A[SystemConfig JSON] --> B[UI Layout Section]
|
||||||
|
B --> C[Component Positions]
|
||||||
|
B --> D[Component Sizes]
|
||||||
|
B --> E[Display Options]
|
||||||
|
|
||||||
|
C --> F[GUI Renderer]
|
||||||
|
D --> F
|
||||||
|
E --> F
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation Details
|
||||||
|
|
||||||
|
### 1. SystemObserver Trait (Fine-grained with Filtering)
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum ObserverFilter {
|
||||||
|
All,
|
||||||
|
CpuOnly,
|
||||||
|
MemoryOnly,
|
||||||
|
StateChangesOnly,
|
||||||
|
RegisterChangesOnly,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait SystemObserver: Send + Sync {
|
||||||
|
fn get_filter(&self) -> ObserverFilter { return ObserverFilter::All; }
|
||||||
|
|
||||||
|
fn on_state_change(&self, component: &str, old_state: ComponentState, new_state: ComponentState);
|
||||||
|
fn on_ram_change(&self, chip: &str, bank: usize, address: u8, old_value: u8, new_value: u8);
|
||||||
|
fn on_rom_change(&self, chip: &str, address: u16, old_value: u8, new_value: u8);
|
||||||
|
fn on_cpu_register_change(&self, cpu: &str, register: &str, old_value: u8, new_value: u8);
|
||||||
|
fn on_cpu_pc_change(&self, cpu: &str, old_pc: u16, new_pc: u16);
|
||||||
|
fn on_cpu_instruction_executed(&self, cpu: &str, instruction: u8, pc: u16);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Enhanced SystemConfig
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct SystemConfig {
|
||||||
|
pub name: String,
|
||||||
|
pub description: String,
|
||||||
|
pub version: String,
|
||||||
|
pub metadata: HashMap<String, serde_json::Value>,
|
||||||
|
pub components: HashMap<String, ComponentConfig>,
|
||||||
|
pub connections: HashMap<String, ConnectionConfig>,
|
||||||
|
pub layout: Option<LayoutConfig>,
|
||||||
|
// NEW: UI Layout configuration
|
||||||
|
pub ui_layout: Option<UiLayoutConfig>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct UiLayoutConfig {
|
||||||
|
pub console: Option<ConsoleLayoutConfig>,
|
||||||
|
pub gui: Option<GuiLayoutConfig>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct GuiLayoutConfig {
|
||||||
|
pub components: HashMap<String, ComponentLayoutConfig>,
|
||||||
|
pub grid_size: Option<[usize; 2]>,
|
||||||
|
pub refresh_rate_ms: Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct ComponentLayoutConfig {
|
||||||
|
pub position: [usize; 2],
|
||||||
|
pub size: [usize; 2],
|
||||||
|
pub visible: Option<bool>,
|
||||||
|
pub display_format: Option<String>,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Observer Registration in ConfigurableSystem
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct ConfigurableSystem {
|
||||||
|
config: SystemConfig,
|
||||||
|
components: HashMap<String, Arc<Mutex<Box<dyn Component>>>>,
|
||||||
|
is_running: Arc<Mutex<bool>>,
|
||||||
|
component_handles: Arc<Mutex<Option<ComponentHandles>>>,
|
||||||
|
// NEW: Observer registry
|
||||||
|
observers: Arc<Mutex<Vec<Box<dyn SystemObserver>>>>,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Component Integration
|
||||||
|
|
||||||
|
Each component will notify observers of state changes:
|
||||||
|
|
||||||
|
- **Intel4004 CPU**: Register changes, PC changes, instruction execution
|
||||||
|
- **Intel4001 ROM**: Memory reads, address changes
|
||||||
|
- **Intel4002 RAM**: Memory reads/writes, I/O changes
|
||||||
|
|
||||||
|
### 5. UI Updates
|
||||||
|
|
||||||
|
#### Console Updates (Performance Target: 50% CPU reduction, sub-10ms responsiveness)
|
||||||
|
- Replace 100ms polling with event-driven updates using observer pattern
|
||||||
|
- Observer receives notifications and updates display immediately
|
||||||
|
- Implement filtering to reduce unnecessary UI updates
|
||||||
|
- Target: Reduce CPU usage by 50% compared to current polling
|
||||||
|
- Target: Update responsiveness to under 10ms for state changes
|
||||||
|
|
||||||
|
#### GUI Updates
|
||||||
|
- Use layout configuration from JSON for component positioning/sizing
|
||||||
|
- Position components according to config (basic implementation)
|
||||||
|
- Show/hide components based on config visibility settings
|
||||||
|
- Use observer pattern for real-time updates
|
||||||
|
- Support configurable refresh rates from UI layout config
|
||||||
|
|
||||||
|
## Configuration Examples
|
||||||
|
|
||||||
|
### Enhanced JSON Config
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "IntelMcs4",
|
||||||
|
"description": "Basic MCS-4 System with CPU, Clock, 2 ROMs, and 1 RAM",
|
||||||
|
"version": "1.0",
|
||||||
|
"metadata": {
|
||||||
|
"author": "RustyEmu",
|
||||||
|
"created": "2024-01-01",
|
||||||
|
"cpu_speed": 750000,
|
||||||
|
"architecture": "mcs4",
|
||||||
|
"rom_count": 2,
|
||||||
|
"ram_count": 1
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"CPU_4004": {
|
||||||
|
"single": {
|
||||||
|
"component_type": "intel_4004",
|
||||||
|
"name": "CPU_4004",
|
||||||
|
"properties": {
|
||||||
|
"clock_speed": 750000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ... other components
|
||||||
|
},
|
||||||
|
"connections": {
|
||||||
|
// ... existing connections
|
||||||
|
},
|
||||||
|
"layout": {
|
||||||
|
"grid_size": [10, 10],
|
||||||
|
"positions": {
|
||||||
|
"cpu": [1, 1],
|
||||||
|
"clock": [0, 0],
|
||||||
|
"rom1": [2, 0],
|
||||||
|
"rom2": [3, 0],
|
||||||
|
"ram": [2, 2]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ui_layout": {
|
||||||
|
"console": {
|
||||||
|
"refresh_rate_ms": 50,
|
||||||
|
"show_ram": true,
|
||||||
|
"show_registers": true,
|
||||||
|
"show_system_info": true
|
||||||
|
},
|
||||||
|
"gui": {
|
||||||
|
"grid_size": [20, 20],
|
||||||
|
"components": {
|
||||||
|
"CPU_4004": {
|
||||||
|
"position": [1, 1],
|
||||||
|
"size": [20, 10],
|
||||||
|
"visible": true,
|
||||||
|
"display_format": "hex"
|
||||||
|
},
|
||||||
|
"RAM_4002": {
|
||||||
|
"position": [3, 2],
|
||||||
|
"size": [25, 15],
|
||||||
|
"visible": true,
|
||||||
|
"display_format": "hex"
|
||||||
|
},
|
||||||
|
"ROM_4001_1": {
|
||||||
|
"position": [2, 0],
|
||||||
|
"size": [20, 8],
|
||||||
|
"visible": true,
|
||||||
|
"display_format": "hex"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"refresh_rate_ms": 60
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Benefits
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
- **Reduced CPU usage**: Event-driven vs polling
|
||||||
|
- **Better responsiveness**: Immediate updates vs 100ms delays
|
||||||
|
- **Less contention**: Observer pattern reduces lock contention
|
||||||
|
|
||||||
|
### User Experience
|
||||||
|
- **Configurable layouts**: Users can customize UI arrangement
|
||||||
|
- **Persistent settings**: Layouts saved in config files
|
||||||
|
- **Flexible display options**: Show/hide components, change formats
|
||||||
|
|
||||||
|
### Architecture Benefits
|
||||||
|
- **Loose coupling**: Components don't depend on UI
|
||||||
|
- **Extensibility**: Easy to add new observers
|
||||||
|
- **Maintainability**: Clear separation of concerns
|
||||||
|
|
||||||
|
## Implementation Steps
|
||||||
|
|
||||||
|
1. **Add SystemObserver trait** to `system_config.rs`
|
||||||
|
2. **Extend SystemConfig** with UI layout configuration
|
||||||
|
3. **Add observer registration** to `ConfigurableSystem`
|
||||||
|
4. **Implement observer notifications** in Intel4004 CPU
|
||||||
|
5. **Implement observer notifications** in Intel4001 ROM
|
||||||
|
6. **Implement observer notifications** in Intel4002 RAM
|
||||||
|
7. **Update console.rs** to use observer pattern
|
||||||
|
8. **Update GUI** to use observer pattern and layout configuration
|
||||||
|
9. **Add UI layout configuration** to existing JSON configs
|
||||||
|
10. **Test** the observer pattern and UI layout system
|
||||||
|
11. **Update documentation** with new features
|
||||||
|
|
||||||
|
## Testing Strategy
|
||||||
|
|
||||||
|
### Unit Tests
|
||||||
|
- Test observer registration and notification
|
||||||
|
- Test UI layout configuration parsing
|
||||||
|
- Test component state change notifications
|
||||||
|
|
||||||
|
### Integration Tests
|
||||||
|
- Test console with observer pattern
|
||||||
|
- Test GUI with layout configuration
|
||||||
|
- Test end-to-end state change propagation
|
||||||
|
|
||||||
|
### Performance Tests
|
||||||
|
- Measure CPU usage reduction
|
||||||
|
- Test responsiveness improvements
|
||||||
|
- Validate lock contention reduction
|
||||||
|
|
||||||
|
## Backward Compatibility
|
||||||
|
|
||||||
|
- Existing configs without `ui_layout` section will use defaults (console: 100ms, current GUI layout)
|
||||||
|
- Observer pattern is additive - doesn't break existing code, components work without observers
|
||||||
|
- ConfigurableSystem will handle missing `ui_layout` gracefully with sensible defaults
|
||||||
|
- Gradual migration path: existing configs continue to work, new configs get enhanced features
|
||||||
|
- No breaking changes to existing JSON configs - only additive new section
|
||||||
|
|
||||||
|
## Future Enhancements
|
||||||
|
|
||||||
|
- **Multiple observer types**: Different observers for different concerns
|
||||||
|
- **Layout templates**: Predefined layouts for different use cases
|
||||||
|
- **Dynamic layout editing**: Runtime layout modification
|
||||||
|
- **Observer filtering**: Selective notification based on component types
|
||||||
218
plans/system_control_architecture.md
Normal file
218
plans/system_control_architecture.md
Normal file
|
|
@ -0,0 +1,218 @@
|
||||||
|
# System Control Architecture Design
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This document outlines the architecture for implementing enhanced system start/stop/pause controls for the Intel MCS-4 emulator. The design provides centralized system management with fine-grained control over execution state and clock parameters.
|
||||||
|
|
||||||
|
## System Architecture
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TB
|
||||||
|
subgraph "System Controller Layer"
|
||||||
|
SC[SystemController Trait]
|
||||||
|
EC[ExecutionController]
|
||||||
|
SS[SystemState Enum]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph "System Layer"
|
||||||
|
CS[ConfigurableSystem]
|
||||||
|
SF[SystemFactory]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph "Component Layer"
|
||||||
|
CPU[Intel4004]
|
||||||
|
CLK1[GenericClock]
|
||||||
|
CLK2[TwoPhaseClock]
|
||||||
|
ROM[Intel4001]
|
||||||
|
RAM[Intel4002]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph "Interface Layer"
|
||||||
|
GUI[GUI Control Panel]
|
||||||
|
CON[Console Commands]
|
||||||
|
KB[Keyboard Shortcuts]
|
||||||
|
end
|
||||||
|
|
||||||
|
SC --> EC
|
||||||
|
EC --> CS
|
||||||
|
CS --> CPU
|
||||||
|
CS --> CLK1
|
||||||
|
CS --> CLK2
|
||||||
|
CS --> ROM
|
||||||
|
CS --> RAM
|
||||||
|
|
||||||
|
GUI --> SC
|
||||||
|
CON --> SC
|
||||||
|
KB --> SC
|
||||||
|
```
|
||||||
|
|
||||||
|
## Core Components
|
||||||
|
|
||||||
|
### 1. SystemController Trait
|
||||||
|
|
||||||
|
Defines the interface for system-wide control operations:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub trait SystemController {
|
||||||
|
fn start(&mut self) -> Result<(), String>;
|
||||||
|
fn stop(&mut self) -> Result<(), String>;
|
||||||
|
fn pause(&mut self) -> Result<(), String>;
|
||||||
|
fn resume(&mut self) -> Result<(), String>;
|
||||||
|
fn is_running(&self) -> bool;
|
||||||
|
fn is_paused(&self) -> bool;
|
||||||
|
fn set_clock_frequency(&mut self, frequency: f64) -> Result<(), String>;
|
||||||
|
fn set_clock_enabled(&mut self, enabled: bool) -> Result<(), String>;
|
||||||
|
fn get_system_state(&self) -> SystemState;
|
||||||
|
fn get_clock_frequency(&self) -> f64;
|
||||||
|
fn get_clock_enabled(&self) -> bool;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. SystemState Enum
|
||||||
|
|
||||||
|
Tracks the overall system execution state:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub enum SystemState {
|
||||||
|
Stopped,
|
||||||
|
Running,
|
||||||
|
Paused,
|
||||||
|
Error(String),
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. ExecutionController
|
||||||
|
|
||||||
|
Manages system lifecycle and coordinates component states:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct ExecutionController {
|
||||||
|
system_state: SystemState,
|
||||||
|
clock_frequency: f64,
|
||||||
|
clock_enabled: bool,
|
||||||
|
components: HashMap<String, Arc<Mutex<Box<dyn Component>>>>,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Component Enhancements
|
||||||
|
|
||||||
|
### Clock Components
|
||||||
|
|
||||||
|
**GenericClock & TwoPhaseClock Enhancements:**
|
||||||
|
- Add `paused` boolean field
|
||||||
|
- Implement `pause()` and `resume()` methods
|
||||||
|
- Add dynamic frequency adjustment with `set_frequency(f64)`
|
||||||
|
- Add `enable()` and `disable()` methods for power control
|
||||||
|
- Update `update()` methods to respect pause state
|
||||||
|
|
||||||
|
### CPU Component
|
||||||
|
|
||||||
|
**Intel4004 Enhancements:**
|
||||||
|
- Add `paused` boolean field
|
||||||
|
- Implement `pause()` to halt instruction execution
|
||||||
|
- Implement `resume()` to continue instruction execution
|
||||||
|
- Update `update()` to check pause state before executing instructions
|
||||||
|
- Maintain clock edge detection during pause for seamless resume
|
||||||
|
|
||||||
|
### Memory Components
|
||||||
|
|
||||||
|
**Intel4001 & Intel4002 Enhancements:**
|
||||||
|
- Add `paused` boolean field
|
||||||
|
- Implement `pause()` and `resume()` methods
|
||||||
|
- Update `update()` to respect pause state
|
||||||
|
- Ensure memory operations complete current cycle before pausing
|
||||||
|
|
||||||
|
## Interface Updates
|
||||||
|
|
||||||
|
### Console Commands
|
||||||
|
|
||||||
|
New console commands:
|
||||||
|
- `pause` / `p` - Pause system execution
|
||||||
|
- `resume` - Resume system execution
|
||||||
|
- `clock <freq>` - Set clock frequency (e.g., `clock 500000`)
|
||||||
|
- `clock on` / `clock off` - Enable/disable clock
|
||||||
|
- `speed +` / `speed -` - Increase/decrease clock frequency by 50%
|
||||||
|
|
||||||
|
### GUI Controls
|
||||||
|
|
||||||
|
New GUI control panel:
|
||||||
|
- System state indicator (Stopped/Running/Paused)
|
||||||
|
- Start/Stop/Pause/Resume buttons
|
||||||
|
- Clock frequency slider (100Hz - 10MHz range)
|
||||||
|
- Clock enable/disable toggle
|
||||||
|
- Real-time frequency display
|
||||||
|
- Keyboard shortcuts (Space=pause/resume, +/- for frequency)
|
||||||
|
|
||||||
|
## Configuration Schema
|
||||||
|
|
||||||
|
Extended JSON configuration with clock control:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"clock_control": {
|
||||||
|
"initial_frequency": 750000,
|
||||||
|
"min_frequency": 1000,
|
||||||
|
"max_frequency": 10000000,
|
||||||
|
"frequency_step": 50000,
|
||||||
|
"auto_start": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation Strategy
|
||||||
|
|
||||||
|
### Phase 1: Core Infrastructure
|
||||||
|
1. Implement SystemController trait
|
||||||
|
2. Create SystemState enum
|
||||||
|
3. Build ExecutionController
|
||||||
|
4. Update ConfigurableSystem
|
||||||
|
|
||||||
|
### Phase 2: Component Updates
|
||||||
|
1. Update clock components with pause/resume
|
||||||
|
2. Update CPU with execution control
|
||||||
|
3. Update memory components
|
||||||
|
4. Add dynamic frequency adjustment
|
||||||
|
|
||||||
|
### Phase 3: Interface Integration
|
||||||
|
1. Update console with new commands
|
||||||
|
2. Enhance GUI with control panel
|
||||||
|
3. Add keyboard shortcuts
|
||||||
|
4. Update configuration handling
|
||||||
|
|
||||||
|
### Phase 4: Testing & Documentation
|
||||||
|
1. Create comprehensive test suite
|
||||||
|
2. Update all documentation
|
||||||
|
3. Verify backwards compatibility
|
||||||
|
|
||||||
|
## Key Design Decisions
|
||||||
|
|
||||||
|
### Pause Behavior
|
||||||
|
- Complete system freeze including clock signals
|
||||||
|
- Components maintain state for seamless resume
|
||||||
|
- Clock edges are tracked but not propagated during pause
|
||||||
|
|
||||||
|
### Frequency Adjustment
|
||||||
|
- Dynamic adjustment during runtime
|
||||||
|
- Smooth transitions without glitches
|
||||||
|
- Validation against component limits
|
||||||
|
|
||||||
|
### State Management
|
||||||
|
- Centralized state tracking
|
||||||
|
- Thread-safe state updates
|
||||||
|
- Consistent state across all components
|
||||||
|
|
||||||
|
## Benefits
|
||||||
|
|
||||||
|
1. **Enhanced Control**: Fine-grained system management
|
||||||
|
2. **Dynamic Adjustment**: Runtime frequency changes
|
||||||
|
3. **Better Debugging**: Pause/resume for analysis
|
||||||
|
4. **User Experience**: Intuitive controls in both interfaces
|
||||||
|
5. **Extensibility**: Clean architecture for future enhancements
|
||||||
|
|
||||||
|
## Compatibility
|
||||||
|
|
||||||
|
- Maintains existing Component trait
|
||||||
|
- Backwards compatible with current configurations
|
||||||
|
- No breaking changes to existing APIs
|
||||||
|
- Optional features with sensible defaults
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
|
use parking_lot::Mutex;
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::{
|
use std::sync::{
|
||||||
atomic::{AtomicBool, Ordering},
|
atomic::{AtomicBool, Ordering},
|
||||||
Arc, Mutex,
|
Arc,
|
||||||
};
|
};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
@ -42,10 +43,21 @@ pub trait Component: Send + Sync {
|
||||||
/// Returns: true if component is running, false otherwise
|
/// Returns: true if component is running, false otherwise
|
||||||
fn is_running(&self) -> bool;
|
fn is_running(&self) -> bool;
|
||||||
|
|
||||||
|
/// Set the component's running state
|
||||||
|
/// This is used by the system controller to manage component lifecycle
|
||||||
|
/// without requiring the blocking run() method
|
||||||
|
/// Parameters: running - New running state
|
||||||
|
fn set_running(&mut self, running: bool);
|
||||||
|
|
||||||
/// Get a reference to this component as Any for downcasting
|
/// Get a reference to this component as Any for downcasting
|
||||||
/// This allows runtime type inspection to access component-specific methods
|
/// This allows runtime type inspection to access component-specific methods
|
||||||
/// Returns: A reference to self as &dyn Any
|
/// Returns: A reference to self as &dyn Any
|
||||||
fn as_any(&self) -> &dyn Any;
|
fn as_any(&self) -> &dyn Any;
|
||||||
|
|
||||||
|
/// Get a mutable reference to this component as Any for downcasting
|
||||||
|
/// This allows runtime type inspection to access component-specific methods mutably
|
||||||
|
/// Returns: A mutable reference to self as &mut dyn Any
|
||||||
|
fn as_any_mut(&mut self) -> &mut dyn Any;
|
||||||
}
|
}
|
||||||
/// Extended trait for components that can be run in their own threads
|
/// Extended trait for components that can be run in their own threads
|
||||||
/// Provides automatic thread spawning functionality for components
|
/// Provides automatic thread spawning functionality for components
|
||||||
|
|
@ -164,9 +176,17 @@ impl Component for BaseComponent {
|
||||||
self.running.load(Ordering::SeqCst)
|
self.running.load(Ordering::SeqCst)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_running(&mut self, running: bool) {
|
||||||
|
self.running.store(running, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
|
||||||
fn as_any(&self) -> &dyn Any {
|
fn as_any(&self) -> &dyn Any {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RunnableComponent for BaseComponent {}
|
impl RunnableComponent for BaseComponent {}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
|
use parking_lot::Mutex;
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::Arc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
|
@ -16,6 +17,8 @@ pub struct GenericClock {
|
||||||
high_time: Duration,
|
high_time: Duration,
|
||||||
low_time: Duration,
|
low_time: Duration,
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
|
paused: bool, // Pause state for system control
|
||||||
|
pause_start: Option<Instant>, // When pause started, to track pause duration
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GenericClock {
|
impl GenericClock {
|
||||||
|
|
@ -32,12 +35,27 @@ impl GenericClock {
|
||||||
high_time: Duration::from_secs_f64(0.5 / frequency), // Will be recalculated in set_duty_cycle
|
high_time: Duration::from_secs_f64(0.5 / frequency), // Will be recalculated in set_duty_cycle
|
||||||
low_time: Duration::from_secs_f64(0.5 / frequency), // Will be recalculated in set_duty_cycle
|
low_time: Duration::from_secs_f64(0.5 / frequency), // Will be recalculated in set_duty_cycle
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
paused: false,
|
||||||
|
pause_start: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
clock.set_duty_cycle(0.5); // Initialize timing
|
clock.set_duty_cycle(0.5); // Initialize timing
|
||||||
clock
|
clock
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the clock frequency dynamically
|
||||||
|
pub fn set_frequency(&mut self, frequency: f64) {
|
||||||
|
if frequency > 0.0 {
|
||||||
|
self.frequency = frequency;
|
||||||
|
self.update_timing();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the current clock frequency
|
||||||
|
pub fn get_frequency(&self) -> f64 {
|
||||||
|
self.frequency
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_duty_cycle(&mut self, duty_cycle: f64) {
|
pub fn set_duty_cycle(&mut self, duty_cycle: f64) {
|
||||||
self.duty_cycle = duty_cycle.clamp(0.1, 0.9); // Keep within reasonable bounds
|
self.duty_cycle = duty_cycle.clamp(0.1, 0.9); // Keep within reasonable bounds
|
||||||
self.update_timing();
|
self.update_timing();
|
||||||
|
|
@ -56,6 +74,30 @@ impl GenericClock {
|
||||||
self.set_clock_output(PinValue::Low);
|
self.set_clock_output(PinValue::Low);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Pause the clock - freeze output state
|
||||||
|
pub fn pause(&mut self) {
|
||||||
|
self.paused = true;
|
||||||
|
self.pause_start = Some(Instant::now());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resume the clock from paused state
|
||||||
|
/// Maintains phase consistency by adjusting last_transition to account for pause duration
|
||||||
|
pub fn resume(&mut self) {
|
||||||
|
if let Some(pause_start) = self.pause_start {
|
||||||
|
// Calculate how long we were paused
|
||||||
|
let pause_duration = pause_start.elapsed();
|
||||||
|
// Adjust last_transition forward by the pause duration to maintain phase
|
||||||
|
self.last_transition += pause_duration;
|
||||||
|
}
|
||||||
|
self.paused = false;
|
||||||
|
self.pause_start = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if the clock is paused
|
||||||
|
pub fn is_paused(&self) -> bool {
|
||||||
|
self.paused
|
||||||
|
}
|
||||||
|
|
||||||
fn frequency_to_duration(frequency: f64) -> Duration {
|
fn frequency_to_duration(frequency: f64) -> Duration {
|
||||||
if frequency <= 0.0 {
|
if frequency <= 0.0 {
|
||||||
Duration::from_secs(0)
|
Duration::from_secs(0)
|
||||||
|
|
@ -72,18 +114,16 @@ impl GenericClock {
|
||||||
|
|
||||||
fn set_clock_output(&self, value: PinValue) {
|
fn set_clock_output(&self, value: PinValue) {
|
||||||
if let Ok(clock_pin) = self.base.get_pin("CLK") {
|
if let Ok(clock_pin) = self.base.get_pin("CLK") {
|
||||||
if let Ok(mut pin_guard) = clock_pin.lock() {
|
let mut pin_guard = clock_pin.lock();
|
||||||
pin_guard.set_driver(Some(self.base.get_name().to_string()), value);
|
pin_guard.set_driver(Some(self.base.get_name().to_string()), value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn read_enable_pin(&self) -> bool {
|
fn read_enable_pin(&self) -> bool {
|
||||||
if let Ok(enable_pin) = self.base.get_pin("ENABLE") {
|
if let Ok(enable_pin) = self.base.get_pin("ENABLE") {
|
||||||
if let Ok(pin_guard) = enable_pin.lock() {
|
let pin_guard = enable_pin.lock();
|
||||||
return pin_guard.read() == PinValue::High;
|
return pin_guard.read() == PinValue::High;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
true // Enabled by default if pin doesn't exist or can't be read
|
true // Enabled by default if pin doesn't exist or can't be read
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -124,6 +164,11 @@ impl Component for GenericClock {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self) {
|
fn update(&mut self) {
|
||||||
|
// Check pause state first - don't update if paused
|
||||||
|
if self.paused {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Read external enable control
|
// Read external enable control
|
||||||
let external_enable = self.read_enable_pin();
|
let external_enable = self.read_enable_pin();
|
||||||
let should_be_enabled = self.enabled && external_enable;
|
let should_be_enabled = self.enabled && external_enable;
|
||||||
|
|
@ -173,9 +218,17 @@ impl Component for GenericClock {
|
||||||
self.base.is_running()
|
self.base.is_running()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_running(&mut self, running: bool) {
|
||||||
|
self.base.set_running(running);
|
||||||
|
}
|
||||||
|
|
||||||
fn as_any(&self) -> &dyn Any {
|
fn as_any(&self) -> &dyn Any {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Advanced clock features
|
// Advanced clock features
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
|
use parking_lot::Mutex;
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::Arc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
|
@ -14,6 +15,9 @@ pub struct TwoPhaseClock {
|
||||||
last_transition: Instant,
|
last_transition: Instant,
|
||||||
phase_time: Duration,
|
phase_time: Duration,
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
|
paused: bool, // Pause state for system control
|
||||||
|
pause_start: Option<Instant>, // When pause started, to track pause duration
|
||||||
|
frequency: f64, // Store frequency for dynamic adjustment
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TwoPhaseClock {
|
impl TwoPhaseClock {
|
||||||
|
|
@ -34,9 +38,25 @@ impl TwoPhaseClock {
|
||||||
last_transition: Instant::now(),
|
last_transition: Instant::now(),
|
||||||
phase_time,
|
phase_time,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
paused: false,
|
||||||
|
pause_start: None,
|
||||||
|
frequency,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the clock frequency dynamically
|
||||||
|
pub fn set_frequency(&mut self, frequency: f64) {
|
||||||
|
if frequency > 0.0 {
|
||||||
|
self.frequency = frequency;
|
||||||
|
self.phase_time = Duration::from_secs_f64(1.0 / frequency / 2.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the current clock frequency
|
||||||
|
pub fn get_frequency(&self) -> f64 {
|
||||||
|
self.frequency
|
||||||
|
}
|
||||||
|
|
||||||
pub fn enable(&mut self) {
|
pub fn enable(&mut self) {
|
||||||
self.enabled = true;
|
self.enabled = true;
|
||||||
self.phi1_state = PinValue::High;
|
self.phi1_state = PinValue::High;
|
||||||
|
|
@ -55,18 +75,41 @@ impl TwoPhaseClock {
|
||||||
self.update_outputs();
|
self.update_outputs();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Pause the clock - freeze output state
|
||||||
|
pub fn pause(&mut self) {
|
||||||
|
self.paused = true;
|
||||||
|
self.pause_start = Some(Instant::now());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resume the clock from paused state
|
||||||
|
/// Maintains phase consistency by adjusting last_transition to account for pause duration
|
||||||
|
pub fn resume(&mut self) {
|
||||||
|
if let Some(pause_start) = self.pause_start {
|
||||||
|
// Calculate how long we were paused
|
||||||
|
let pause_duration = pause_start.elapsed();
|
||||||
|
// Adjust last_transition forward by the pause duration to maintain phase
|
||||||
|
self.last_transition += pause_duration;
|
||||||
|
}
|
||||||
|
self.paused = false;
|
||||||
|
self.pause_start = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if the clock is paused
|
||||||
|
pub fn is_paused(&self) -> bool {
|
||||||
|
self.paused
|
||||||
|
}
|
||||||
|
|
||||||
fn update_outputs(&self) {
|
fn update_outputs(&self) {
|
||||||
// Set CLK output (for compatibility)
|
// Set CLK output (for compatibility)
|
||||||
if let Ok(clk_pin) = self.base.get_pin("CLK") {
|
if let Ok(clk_pin) = self.base.get_pin("CLK") {
|
||||||
if let Ok(mut pin_guard) = clk_pin.lock() {
|
let mut pin_guard = clk_pin.lock();
|
||||||
// CLK follows PHI1 for compatibility
|
// CLK follows PHI1 for compatibility
|
||||||
pin_guard.set_driver(Some(self.base.get_name().to_string()), self.phi1_state);
|
pin_guard.set_driver(Some(self.base.get_name().to_string()), self.phi1_state);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Set PHI1 output
|
// Set PHI1 output
|
||||||
if let Ok(phi1_pin) = self.base.get_pin("PHI1") {
|
if let Ok(phi1_pin) = self.base.get_pin("PHI1") {
|
||||||
if let Ok(mut pin_guard) = phi1_pin.lock() {
|
let mut pin_guard = phi1_pin.lock();
|
||||||
pin_guard.set_driver(Some(self.base.get_name().to_string()), self.phi1_state);
|
pin_guard.set_driver(Some(self.base.get_name().to_string()), self.phi1_state);
|
||||||
|
|
||||||
// Check if pin has connections and trigger propagation
|
// Check if pin has connections and trigger propagation
|
||||||
|
|
@ -76,11 +119,10 @@ impl TwoPhaseClock {
|
||||||
pin_guard.propagate();
|
pin_guard.propagate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Set PHI2 output
|
// Set PHI2 output
|
||||||
if let Ok(phi2_pin) = self.base.get_pin("PHI2") {
|
if let Ok(phi2_pin) = self.base.get_pin("PHI2") {
|
||||||
if let Ok(mut pin_guard) = phi2_pin.lock() {
|
let mut pin_guard = phi2_pin.lock();
|
||||||
pin_guard.set_driver(Some(self.base.get_name().to_string()), self.phi2_state);
|
pin_guard.set_driver(Some(self.base.get_name().to_string()), self.phi2_state);
|
||||||
|
|
||||||
// Check if pin has connections and trigger propagation
|
// Check if pin has connections and trigger propagation
|
||||||
|
|
@ -90,7 +132,6 @@ impl TwoPhaseClock {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn should_transition(&self) -> bool {
|
fn should_transition(&self) -> bool {
|
||||||
self.last_transition.elapsed() >= self.phase_time
|
self.last_transition.elapsed() >= self.phase_time
|
||||||
|
|
@ -134,6 +175,11 @@ impl Component for TwoPhaseClock {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self) {
|
fn update(&mut self) {
|
||||||
|
// Check pause state first - don't update if paused
|
||||||
|
if self.paused {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if !self.enabled {
|
if !self.enabled {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -174,7 +220,15 @@ impl Component for TwoPhaseClock {
|
||||||
self.base.is_running()
|
self.base.is_running()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_running(&mut self, running: bool) {
|
||||||
|
self.base.set_running(running);
|
||||||
|
}
|
||||||
|
|
||||||
fn as_any(&self) -> &dyn Any {
|
fn as_any(&self) -> &dyn Any {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use std::sync::{Arc, Mutex};
|
use parking_lot::Mutex;
|
||||||
|
use std::sync::Arc;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use crate::component::{BaseComponent, Component};
|
use crate::component::{BaseComponent, Component};
|
||||||
|
|
@ -81,14 +82,11 @@ pub trait Intel400xClockHandling {
|
||||||
/// Get a clock pin value safely
|
/// Get a clock pin value safely
|
||||||
fn get_clock_pin(&self, name: &str) -> PinValue {
|
fn get_clock_pin(&self, name: &str) -> PinValue {
|
||||||
if let Ok(pin) = self.get_base().get_pin(name) {
|
if let Ok(pin) = self.get_base().get_pin(name) {
|
||||||
if let Ok(pin_guard) = pin.lock() {
|
let pin_guard = pin.lock();
|
||||||
pin_guard.read_immediate()
|
pin_guard.read_immediate()
|
||||||
} else {
|
} else {
|
||||||
PinValue::Low
|
PinValue::Low
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
PinValue::Low
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check for Φ1 rising edge
|
/// Check for Φ1 rising edge
|
||||||
|
|
@ -134,13 +132,12 @@ pub trait Intel400xDataBus {
|
||||||
|
|
||||||
for i in 0..4 {
|
for i in 0..4 {
|
||||||
if let Ok(pin) = self.get_base().get_pin(&format!("D{}", i)) {
|
if let Ok(pin) = self.get_base().get_pin(&format!("D{}", i)) {
|
||||||
if let Ok(pin_guard) = pin.lock() {
|
let pin_guard = pin.lock();
|
||||||
if pin_guard.read_immediate() == PinValue::High {
|
if pin_guard.read_immediate() == PinValue::High {
|
||||||
data |= 1 << i;
|
data |= 1 << i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
data & 0x0F
|
data & 0x0F
|
||||||
}
|
}
|
||||||
|
|
@ -152,7 +149,7 @@ pub trait Intel400xDataBus {
|
||||||
|
|
||||||
for i in 0..4 {
|
for i in 0..4 {
|
||||||
if let Ok(pin) = self.get_base().get_pin(&format!("D{}", i)) {
|
if let Ok(pin) = self.get_base().get_pin(&format!("D{}", i)) {
|
||||||
if let Ok(mut pin_guard) = pin.lock() {
|
let mut pin_guard = pin.lock();
|
||||||
let bit_value = (nibble >> i) & 1;
|
let bit_value = (nibble >> i) & 1;
|
||||||
let pin_value = if bit_value == 1 {
|
let pin_value = if bit_value == 1 {
|
||||||
PinValue::High
|
PinValue::High
|
||||||
|
|
@ -166,13 +163,12 @@ pub trait Intel400xDataBus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Set data bus to high-impedance state to avoid bus contention
|
/// Set data bus to high-impedance state to avoid bus contention
|
||||||
fn tri_state_data_bus(&self) {
|
fn tri_state_data_bus(&self) {
|
||||||
for i in 0..4 {
|
for i in 0..4 {
|
||||||
if let Ok(pin) = self.get_base().get_pin(&format!("D{}", i)) {
|
if let Ok(pin) = self.get_base().get_pin(&format!("D{}", i)) {
|
||||||
if let Ok(mut pin_guard) = pin.lock() {
|
let mut pin_guard = pin.lock();
|
||||||
pin_guard.set_driver(
|
pin_guard.set_driver(
|
||||||
Some(format!("{}_DATA", self.get_base().get_name())),
|
Some(format!("{}_DATA", self.get_base().get_name())),
|
||||||
PinValue::HighZ,
|
PinValue::HighZ,
|
||||||
|
|
@ -181,7 +177,6 @@ pub trait Intel400xDataBus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Common address handling functionality
|
/// Common address handling functionality
|
||||||
pub trait Intel400xAddressHandling {
|
pub trait Intel400xAddressHandling {
|
||||||
|
|
@ -256,55 +251,43 @@ pub trait Intel400xControlPins {
|
||||||
/// Read SYNC pin state
|
/// Read SYNC pin state
|
||||||
fn read_sync_pin(&self) -> bool {
|
fn read_sync_pin(&self) -> bool {
|
||||||
if let Ok(pin) = self.get_base().get_pin("SYNC") {
|
if let Ok(pin) = self.get_base().get_pin("SYNC") {
|
||||||
if let Ok(pin_guard) = pin.lock() {
|
let pin_guard = pin.lock();
|
||||||
pin_guard.read_immediate() == PinValue::High
|
pin_guard.read_immediate() == PinValue::High
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read CM-ROM pin state
|
/// Read CM-ROM pin state
|
||||||
fn read_cm_rom_pin(&self) -> bool {
|
fn read_cm_rom_pin(&self) -> bool {
|
||||||
// Note: CM pin name varies by component
|
// Note: CM pin name varies by component
|
||||||
if let Ok(pin) = self.get_base().get_pin("CM") {
|
if let Ok(pin) = self.get_base().get_pin("CM") {
|
||||||
if let Ok(pin_guard) = pin.lock() {
|
let pin_guard = pin.lock();
|
||||||
pin_guard.read() == PinValue::High
|
pin_guard.read() == PinValue::High
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read the CI (I/O select) pin state
|
/// Read the CI (I/O select) pin state
|
||||||
fn read_ci_pin(&self) -> bool {
|
fn read_ci_pin(&self) -> bool {
|
||||||
if let Ok(pin) = self.get_base().get_pin("CI") {
|
if let Ok(pin) = self.get_base().get_pin("CI") {
|
||||||
if let Ok(pin_guard) = pin.lock() {
|
let pin_guard = pin.lock();
|
||||||
pin_guard.read() == PinValue::High
|
pin_guard.read() == PinValue::High
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read RESET pin state
|
/// Read RESET pin state
|
||||||
/// Hardware: RESET is active LOW (pin LOW = reset active)
|
/// Hardware: RESET is active LOW (pin LOW = reset active)
|
||||||
fn read_reset_pin(&self) -> bool {
|
fn read_reset_pin(&self) -> bool {
|
||||||
if let Ok(pin) = self.get_base().get_pin("RESET") {
|
if let Ok(pin) = self.get_base().get_pin("RESET") {
|
||||||
if let Ok(pin_guard) = pin.lock() {
|
let pin_guard = pin.lock();
|
||||||
pin_guard.read() == PinValue::Low // Active LOW: LOW means reset is active
|
pin_guard.read() == PinValue::Low // Active LOW: LOW means reset is active
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -317,13 +300,10 @@ pub trait Intel400xResetHandling {
|
||||||
/// Note: RESET is active LOW (pin LOW = reset active)
|
/// Note: RESET is active LOW (pin LOW = reset active)
|
||||||
fn handle_reset(&mut self, reset_pin_name: &str) -> bool {
|
fn handle_reset(&mut self, reset_pin_name: &str) -> bool {
|
||||||
let reset = if let Ok(pin) = self.get_base().get_pin(reset_pin_name) {
|
let reset = if let Ok(pin) = self.get_base().get_pin(reset_pin_name) {
|
||||||
if let Ok(pin_guard) = pin.lock() {
|
let pin_guard = pin.lock();
|
||||||
pin_guard.read() == PinValue::Low // Active LOW: LOW means reset is active
|
pin_guard.read() == PinValue::Low // Active LOW: LOW means reset is active
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if reset {
|
if reset {
|
||||||
|
|
@ -1330,38 +1310,28 @@ pub mod utils {
|
||||||
|
|
||||||
/// Check if a pin value represents a logical high
|
/// Check if a pin value represents a logical high
|
||||||
pub fn is_pin_high(pin: &Arc<Mutex<Pin>>) -> bool {
|
pub fn is_pin_high(pin: &Arc<Mutex<Pin>>) -> bool {
|
||||||
if let Ok(pin_guard) = pin.lock() {
|
let pin_guard = pin.lock();
|
||||||
pin_guard.read() == PinValue::High
|
pin_guard.read() == PinValue::High
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if a pin value represents high impedance
|
/// Check if a pin value represents high impedance
|
||||||
pub fn is_pin_high_z(pin: &Arc<Mutex<Pin>>) -> bool {
|
pub fn is_pin_high_z(pin: &Arc<Mutex<Pin>>) -> bool {
|
||||||
if let Ok(pin_guard) = pin.lock() {
|
let pin_guard = pin.lock();
|
||||||
pin_guard.read() == PinValue::HighZ
|
pin_guard.read() == PinValue::HighZ
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Safely read a pin value with fallback
|
/// Safely read a pin value with fallback
|
||||||
pub fn read_pin_safe(pin: &Arc<Mutex<Pin>>, default: PinValue) -> PinValue {
|
pub fn read_pin_safe(pin: &Arc<Mutex<Pin>>, _default: PinValue) -> PinValue {
|
||||||
if let Ok(pin_guard) = pin.lock() {
|
let pin_guard = pin.lock();
|
||||||
pin_guard.read()
|
pin_guard.read()
|
||||||
} else {
|
|
||||||
default
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Safely set a pin driver with error handling
|
/// Safely set a pin driver with error handling
|
||||||
pub fn set_pin_driver_safe(pin: &Arc<Mutex<Pin>>, driver_name: String, value: PinValue) {
|
pub fn set_pin_driver_safe(pin: &Arc<Mutex<Pin>>, driver_name: String, value: PinValue) {
|
||||||
if let Ok(mut pin_guard) = pin.lock() {
|
let mut pin_guard = pin.lock();
|
||||||
pin_guard.set_driver(Some(driver_name), value);
|
pin_guard.set_driver(Some(driver_name), value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Re-export commonly used items for convenience
|
// Re-export commonly used items for convenience
|
||||||
pub use utils::*;
|
pub use utils::*;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
|
use parking_lot::Mutex;
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::Arc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
|
@ -262,13 +263,12 @@ impl Intel4004 {
|
||||||
|
|
||||||
for i in 0..4 {
|
for i in 0..4 {
|
||||||
if let Ok(pin) = self.base.get_pin(&format!("D{}", i)) {
|
if let Ok(pin) = self.base.get_pin(&format!("D{}", i)) {
|
||||||
if let Ok(pin_guard) = pin.lock() {
|
let pin_guard = pin.lock();
|
||||||
if pin_guard.read() == PinValue::High {
|
if pin_guard.read() == PinValue::High {
|
||||||
data |= 1 << i;
|
data |= 1 << i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
data & 0x0F
|
data & 0x0F
|
||||||
}
|
}
|
||||||
|
|
@ -280,7 +280,7 @@ impl Intel4004 {
|
||||||
|
|
||||||
for i in 0..4 {
|
for i in 0..4 {
|
||||||
if let Ok(pin) = self.base.get_pin(&format!("D{}", i)) {
|
if let Ok(pin) = self.base.get_pin(&format!("D{}", i)) {
|
||||||
if let Ok(mut pin_guard) = pin.lock() {
|
let mut pin_guard = pin.lock();
|
||||||
let bit_value = (nibble >> i) & 1;
|
let bit_value = (nibble >> i) & 1;
|
||||||
let pin_value = if bit_value == 1 {
|
let pin_value = if bit_value == 1 {
|
||||||
PinValue::High
|
PinValue::High
|
||||||
|
|
@ -291,17 +291,14 @@ impl Intel4004 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Set data bus to high-impedance state to avoid bus contention
|
/// Set data bus to high-impedance state to avoid bus contention
|
||||||
/// CRITICAL: Must be called whenever CPU is not actively driving valid data
|
/// CRITICAL: Must be called whenever CPU is not actively driving valid data
|
||||||
fn tri_state_data_bus(&self) {
|
fn tri_state_data_bus(&self) {
|
||||||
for i in 0..4 {
|
for i in 0..4 {
|
||||||
if let Ok(pin) = self.base.get_pin(&format!("D{}", i)) {
|
if let Ok(pin) = self.base.get_pin(&format!("D{}", i)) {
|
||||||
if let Ok(mut pin_guard) = pin.lock() {
|
let mut pin_guard = pin.lock();
|
||||||
pin_guard
|
pin_guard.set_driver(Some(self.base.get_name().parse().unwrap()), PinValue::HighZ);
|
||||||
.set_driver(Some(self.base.get_name().parse().unwrap()), PinValue::HighZ);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -310,55 +307,44 @@ impl Intel4004 {
|
||||||
/// Parameters: high - true for high voltage, false for low voltage
|
/// Parameters: high - true for high voltage, false for low voltage
|
||||||
fn set_sync(&self, high: bool) {
|
fn set_sync(&self, high: bool) {
|
||||||
if let Ok(pin) = self.base.get_pin("SYNC") {
|
if let Ok(pin) = self.base.get_pin("SYNC") {
|
||||||
if let Ok(mut pin_guard) = pin.lock() {
|
let mut pin_guard = pin.lock();
|
||||||
let value = if high { PinValue::High } else { PinValue::Low };
|
let value = if high { PinValue::High } else { PinValue::Low };
|
||||||
pin_guard.set_driver(Some(self.base.get_name().parse().unwrap()), value);
|
pin_guard.set_driver(Some(self.base.get_name().parse().unwrap()), value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the CM (Chip Select) pin state
|
/// Set the CM (Chip Select) pin state
|
||||||
/// Parameters: high - true for high voltage, false for low voltage
|
/// Parameters: high - true for high voltage, false for low voltage
|
||||||
fn set_cm(&self, high: bool) {
|
fn set_cm(&self, high: bool) {
|
||||||
if let Ok(pin) = self.base.get_pin("CM") {
|
if let Ok(pin) = self.base.get_pin("CM") {
|
||||||
if let Ok(mut pin_guard) = pin.lock() {
|
let mut pin_guard = pin.lock();
|
||||||
let value = if high { PinValue::High } else { PinValue::Low };
|
let value = if high { PinValue::High } else { PinValue::Low };
|
||||||
pin_guard.set_driver(Some(self.base.get_name().parse().unwrap()), value);
|
pin_guard.set_driver(Some(self.base.get_name().parse().unwrap()), value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Read the state of all control pins
|
/// Read the state of all control pins
|
||||||
/// Returns: (sync, cm, test) - State of control signals
|
/// Returns: (sync, cm, test) - State of control signals
|
||||||
fn read_control_pins(&self) -> (bool, bool, bool) {
|
fn read_control_pins(&self) -> (bool, bool, bool) {
|
||||||
let sync = if let Ok(pin) = self.base.get_pin("SYNC") {
|
let sync = if let Ok(pin) = self.base.get_pin("SYNC") {
|
||||||
if let Ok(pin_guard) = pin.lock() {
|
let pin_guard = pin.lock();
|
||||||
pin_guard.read() == PinValue::High
|
pin_guard.read() == PinValue::High
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let cm = if let Ok(pin) = self.base.get_pin("CM") {
|
let cm = if let Ok(pin) = self.base.get_pin("CM") {
|
||||||
if let Ok(pin_guard) = pin.lock() {
|
let pin_guard = pin.lock();
|
||||||
pin_guard.read() == PinValue::High
|
pin_guard.read() == PinValue::High
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let test = if let Ok(pin) = self.base.get_pin("TEST") {
|
let test = if let Ok(pin) = self.base.get_pin("TEST") {
|
||||||
if let Ok(pin_guard) = pin.lock() {
|
let pin_guard = pin.lock();
|
||||||
pin_guard.read() == PinValue::High
|
pin_guard.read() == PinValue::High
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
};
|
||||||
|
|
||||||
(sync, cm, test)
|
(sync, cm, test)
|
||||||
|
|
@ -369,23 +355,17 @@ impl Intel4004 {
|
||||||
/// Hardware: Intel 4004 provides two-phase clock for synchronization
|
/// Hardware: Intel 4004 provides two-phase clock for synchronization
|
||||||
fn read_clock_pins(&self) -> (PinValue, PinValue) {
|
fn read_clock_pins(&self) -> (PinValue, PinValue) {
|
||||||
let phi1 = if let Ok(pin) = self.base.get_pin("PHI1") {
|
let phi1 = if let Ok(pin) = self.base.get_pin("PHI1") {
|
||||||
if let Ok(pin_guard) = pin.lock() {
|
let pin_guard = pin.lock();
|
||||||
pin_guard.read()
|
pin_guard.read()
|
||||||
} else {
|
} else {
|
||||||
PinValue::Low
|
PinValue::Low
|
||||||
}
|
|
||||||
} else {
|
|
||||||
PinValue::Low
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let phi2 = if let Ok(pin) = self.base.get_pin("PHI2") {
|
let phi2 = if let Ok(pin) = self.base.get_pin("PHI2") {
|
||||||
if let Ok(pin_guard) = pin.lock() {
|
let pin_guard = pin.lock();
|
||||||
pin_guard.read()
|
pin_guard.read()
|
||||||
} else {
|
} else {
|
||||||
PinValue::Low
|
PinValue::Low
|
||||||
}
|
|
||||||
} else {
|
|
||||||
PinValue::Low
|
|
||||||
};
|
};
|
||||||
|
|
||||||
(phi1, phi2)
|
(phi1, phi2)
|
||||||
|
|
@ -442,13 +422,10 @@ impl Intel4004 {
|
||||||
/// Note: RESET is active low (triggered when pin is Low)
|
/// Note: RESET is active low (triggered when pin is Low)
|
||||||
fn handle_reset(&mut self) {
|
fn handle_reset(&mut self) {
|
||||||
let reset = if let Ok(pin) = self.base.get_pin("RESET") {
|
let reset = if let Ok(pin) = self.base.get_pin("RESET") {
|
||||||
if let Ok(pin_guard) = pin.lock() {
|
let pin_guard = pin.lock();
|
||||||
pin_guard.read() == PinValue::Low
|
pin_guard.read() == PinValue::Low
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if reset {
|
if reset {
|
||||||
|
|
@ -1579,9 +1556,17 @@ impl Component for Intel4004 {
|
||||||
self.base.is_running()
|
self.base.is_running()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_running(&mut self, running: bool) {
|
||||||
|
self.base.set_running(running);
|
||||||
|
}
|
||||||
|
|
||||||
fn as_any(&self) -> &dyn Any {
|
fn as_any(&self) -> &dyn Any {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RunnableComponent for Intel4004 {
|
impl RunnableComponent for Intel4004 {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
|
use parking_lot::Mutex;
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::Arc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
|
@ -73,7 +74,7 @@ impl MOS6502 {
|
||||||
fn set_address_bus(&self, address: u16) {
|
fn set_address_bus(&self, address: u16) {
|
||||||
for i in 0..16 {
|
for i in 0..16 {
|
||||||
if let Ok(pin) = self.base.get_pin(&format!("A{}", i)) {
|
if let Ok(pin) = self.base.get_pin(&format!("A{}", i)) {
|
||||||
if let Ok(mut pin_guard) = pin.lock() {
|
let mut pin_guard = pin.lock();
|
||||||
let bit_value = (address >> i) & 1;
|
let bit_value = (address >> i) & 1;
|
||||||
let pin_value = if bit_value == 1 {
|
let pin_value = if bit_value == 1 {
|
||||||
PinValue::High
|
PinValue::High
|
||||||
|
|
@ -84,12 +85,11 @@ impl MOS6502 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn set_data_bus(&self, data: u8) {
|
fn set_data_bus(&self, data: u8) {
|
||||||
for i in 0..8 {
|
for i in 0..8 {
|
||||||
if let Ok(pin) = self.base.get_pin(&format!("D{}", i)) {
|
if let Ok(pin) = self.base.get_pin(&format!("D{}", i)) {
|
||||||
if let Ok(mut pin_guard) = pin.lock() {
|
let mut pin_guard = pin.lock();
|
||||||
let bit_value = (data >> i) & 1;
|
let bit_value = (data >> i) & 1;
|
||||||
let pin_value = if bit_value == 1 {
|
let pin_value = if bit_value == 1 {
|
||||||
PinValue::High
|
PinValue::High
|
||||||
|
|
@ -100,72 +100,57 @@ impl MOS6502 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn set_rw_pin(&self, read: bool) {
|
fn set_rw_pin(&self, read: bool) {
|
||||||
if let Ok(pin) = self.base.get_pin("RW") {
|
if let Ok(pin) = self.base.get_pin("RW") {
|
||||||
if let Ok(mut pin_guard) = pin.lock() {
|
let mut pin_guard = pin.lock();
|
||||||
let pin_value = if read { PinValue::High } else { PinValue::Low };
|
let pin_value = if read { PinValue::High } else { PinValue::Low };
|
||||||
pin_guard.set_driver(Some(self.base.get_name().parse().unwrap()), pin_value);
|
pin_guard.set_driver(Some(self.base.get_name().parse().unwrap()), pin_value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn _read_data_bus(&self) -> u8 {
|
fn _read_data_bus(&self) -> u8 {
|
||||||
let mut data = 0;
|
let mut data = 0;
|
||||||
|
|
||||||
for i in 0..8 {
|
for i in 0..8 {
|
||||||
if let Ok(pin) = self.base.get_pin(&format!("D{}", i)) {
|
if let Ok(pin) = self.base.get_pin(&format!("D{}", i)) {
|
||||||
if let Ok(pin_guard) = pin.lock() {
|
let pin_guard = pin.lock();
|
||||||
if pin_guard.read() == PinValue::High {
|
if pin_guard.read() == PinValue::High {
|
||||||
data |= 1 << i;
|
data |= 1 << i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
data
|
data
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn read_control_pins(&self) -> (bool, bool, bool, bool) {
|
pub(crate) fn read_control_pins(&self) -> (bool, bool, bool, bool) {
|
||||||
let irq = if let Ok(pin) = self.base.get_pin("IRQ") {
|
let irq = if let Ok(pin) = self.base.get_pin("IRQ") {
|
||||||
if let Ok(pin_guard) = pin.lock() {
|
let pin_guard = pin.lock();
|
||||||
pin_guard.read() == PinValue::Low // IRQ is active low
|
pin_guard.read() == PinValue::Low // IRQ is active low
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let nmi = if let Ok(pin) = self.base.get_pin("NMI") {
|
let nmi = if let Ok(pin) = self.base.get_pin("NMI") {
|
||||||
if let Ok(pin_guard) = pin.lock() {
|
let pin_guard = pin.lock();
|
||||||
pin_guard.read() == PinValue::Low // NMI is active low
|
pin_guard.read() == PinValue::Low // NMI is active low
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let reset = if let Ok(pin) = self.base.get_pin("RES") {
|
let reset = if let Ok(pin) = self.base.get_pin("RES") {
|
||||||
if let Ok(pin_guard) = pin.lock() {
|
let pin_guard = pin.lock();
|
||||||
pin_guard.read() == PinValue::Low // RESET is active low
|
pin_guard.read() == PinValue::Low // RESET is active low
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let rdy = if let Ok(pin) = self.base.get_pin("RDY") {
|
let rdy = if let Ok(pin) = self.base.get_pin("RDY") {
|
||||||
if let Ok(pin_guard) = pin.lock() {
|
let pin_guard = pin.lock();
|
||||||
pin_guard.read() == PinValue::High // RDY is active high
|
pin_guard.read() == PinValue::High // RDY is active high
|
||||||
} else {
|
} else {
|
||||||
true // Default to ready if pin doesn't exist
|
true // Default to ready if pin doesn't exist
|
||||||
}
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
};
|
};
|
||||||
|
|
||||||
(irq, nmi, reset, rdy)
|
(irq, nmi, reset, rdy)
|
||||||
|
|
@ -233,9 +218,17 @@ impl Component for MOS6502 {
|
||||||
self.is_running
|
self.is_running
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_running(&mut self, running: bool) {
|
||||||
|
self.is_running = running;
|
||||||
|
}
|
||||||
|
|
||||||
fn as_any(&self) -> &dyn Any {
|
fn as_any(&self) -> &dyn Any {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6502-specific methods
|
// 6502-specific methods
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
|
use parking_lot::Mutex;
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::component::Component;
|
use crate::component::Component;
|
||||||
use crate::components::cpu::MOS6502;
|
use crate::components::cpu::MOS6502;
|
||||||
|
|
@ -109,9 +110,17 @@ impl Component for WDC65C02 {
|
||||||
self.base.is_running()
|
self.base.is_running()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_running(&mut self, running: bool) {
|
||||||
|
self.base.set_running(running);
|
||||||
|
}
|
||||||
|
|
||||||
fn as_any(&self) -> &dyn Any {
|
fn as_any(&self) -> &dyn Any {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 65C02-specific enhancements
|
// 65C02-specific enhancements
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
|
use parking_lot::Mutex;
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::Arc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
|
@ -382,13 +383,12 @@ impl Intel4001 {
|
||||||
|
|
||||||
for i in 0..4 {
|
for i in 0..4 {
|
||||||
if let Ok(pin) = self.base.get_pin(&format!("IO{}", i)) {
|
if let Ok(pin) = self.base.get_pin(&format!("IO{}", i)) {
|
||||||
if let Ok(pin_guard) = pin.lock() {
|
let pin_guard = pin.lock();
|
||||||
if pin_guard.read() == PinValue::High {
|
if pin_guard.read() == PinValue::High {
|
||||||
data |= 1 << i;
|
data |= 1 << i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
data
|
data
|
||||||
}
|
}
|
||||||
|
|
@ -499,12 +499,11 @@ impl Intel4001 {
|
||||||
fn tri_state_io_pins(&self) {
|
fn tri_state_io_pins(&self) {
|
||||||
for i in 0..4 {
|
for i in 0..4 {
|
||||||
if let Ok(pin) = self.base.get_pin(&format!("IO{}", i)) {
|
if let Ok(pin) = self.base.get_pin(&format!("IO{}", i)) {
|
||||||
if let Ok(mut pin_guard) = pin.lock() {
|
let mut pin_guard = pin.lock();
|
||||||
pin_guard.set_driver(Some(self.base.name()), PinValue::HighZ);
|
pin_guard.set_driver(Some(self.base.name()), PinValue::HighZ);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Set I/O mode for WRM/RDM instruction handling
|
/// Set I/O mode for WRM/RDM instruction handling
|
||||||
/// Parameters: mode - I/O mode (Input for RDM, Output for WRM)
|
/// Parameters: mode - I/O mode (Input for RDM, Output for WRM)
|
||||||
|
|
@ -575,14 +574,11 @@ impl Intel4001 {
|
||||||
/// Hardware: CI pin distinguishes I/O vs ROM access when chip selected (CM=1)
|
/// Hardware: CI pin distinguishes I/O vs ROM access when chip selected (CM=1)
|
||||||
fn read_ci_pin(&self) -> bool {
|
fn read_ci_pin(&self) -> bool {
|
||||||
if let Ok(pin) = self.base.get_pin("CI") {
|
if let Ok(pin) = self.base.get_pin("CI") {
|
||||||
if let Ok(pin_guard) = pin.lock() {
|
let pin_guard = pin.lock();
|
||||||
pin_guard.read() == PinValue::High
|
pin_guard.read() == PinValue::High
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read from a specific I/O port
|
/// Read from a specific I/O port
|
||||||
|
|
@ -620,7 +616,7 @@ impl Intel4001 {
|
||||||
fn update_io_pins(&self) {
|
fn update_io_pins(&self) {
|
||||||
for i in 0..4 {
|
for i in 0..4 {
|
||||||
if let Ok(pin) = self.base.get_pin(&format!("IO{}", i)) {
|
if let Ok(pin) = self.base.get_pin(&format!("IO{}", i)) {
|
||||||
if let Ok(mut pin_guard) = pin.lock() {
|
let mut pin_guard = pin.lock();
|
||||||
match self.io_direction[i] {
|
match self.io_direction[i] {
|
||||||
IoDirection::Output => {
|
IoDirection::Output => {
|
||||||
// Drive pin with output port value
|
// Drive pin with output port value
|
||||||
|
|
@ -640,7 +636,6 @@ impl Intel4001 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Handle memory address-related operations during Φ1
|
/// Handle memory address-related operations during Φ1
|
||||||
/// Hardware-accurate: Φ1 is when CPU drives address, so we handle address latching
|
/// Hardware-accurate: Φ1 is when CPU drives address, so we handle address latching
|
||||||
|
|
@ -926,9 +921,17 @@ impl Component for Intel4001 {
|
||||||
self.base.is_running()
|
self.base.is_running()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_running(&mut self, running: bool) {
|
||||||
|
self.base.set_running(running);
|
||||||
|
}
|
||||||
|
|
||||||
fn as_any(&self) -> &dyn Any {
|
fn as_any(&self) -> &dyn Any {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RunnableComponent for Intel4001 {}
|
impl RunnableComponent for Intel4001 {}
|
||||||
|
|
@ -1159,15 +1162,15 @@ mod tests {
|
||||||
|
|
||||||
// Initialize PHI1 to Low first
|
// Initialize PHI1 to Low first
|
||||||
{
|
{
|
||||||
let mut phi1_guard = phi1_pin.lock().unwrap();
|
let mut phi1_guard = phi1_pin.lock();
|
||||||
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up memory read operation: SYNC=1, CM=1, CI=0 (memory access)
|
// Set up memory read operation: SYNC=1, CM=1, CI=0 (memory access)
|
||||||
{
|
{
|
||||||
let mut sync_guard = sync_pin.lock().unwrap();
|
let mut sync_guard = sync_pin.lock();
|
||||||
let mut cm_guard = cm_pin.lock().unwrap();
|
let mut cm_guard = cm_pin.lock();
|
||||||
let mut ci_guard = ci_pin.lock().unwrap();
|
let mut ci_guard = ci_pin.lock();
|
||||||
sync_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
sync_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
||||||
cm_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
cm_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
||||||
ci_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Memory access
|
ci_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Memory access
|
||||||
|
|
@ -1175,7 +1178,7 @@ mod tests {
|
||||||
|
|
||||||
// Set Φ1 high (creating rising edge)
|
// Set Φ1 high (creating rising edge)
|
||||||
{
|
{
|
||||||
let mut phi1_guard = phi1_pin.lock().unwrap();
|
let mut phi1_guard = phi1_pin.lock();
|
||||||
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1224,9 +1227,9 @@ mod tests {
|
||||||
|
|
||||||
// Set up memory read operation: SYNC=1, chip_select=1, io_select=0 (memory access)
|
// Set up memory read operation: SYNC=1, chip_select=1, io_select=0 (memory access)
|
||||||
{
|
{
|
||||||
let mut sync_guard = sync_pin.lock().unwrap();
|
let mut sync_guard = sync_pin.lock();
|
||||||
let mut cm_guard = cm_pin.lock().unwrap();
|
let mut cm_guard = cm_pin.lock();
|
||||||
let mut ci_guard = ci_pin.lock().unwrap();
|
let mut ci_guard = ci_pin.lock();
|
||||||
sync_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
sync_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
||||||
cm_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
cm_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
||||||
ci_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Memory access, not I/O
|
ci_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Memory access, not I/O
|
||||||
|
|
@ -1259,9 +1262,9 @@ mod tests {
|
||||||
|
|
||||||
// Set memory operation active
|
// Set memory operation active
|
||||||
{
|
{
|
||||||
let mut sync_guard = sync_pin.lock().unwrap();
|
let mut sync_guard = sync_pin.lock();
|
||||||
let mut cm_guard = cm_pin.lock().unwrap();
|
let mut cm_guard = cm_pin.lock();
|
||||||
let mut ci_guard = ci_pin.lock().unwrap();
|
let mut ci_guard = ci_pin.lock();
|
||||||
sync_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
sync_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
||||||
cm_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
cm_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
||||||
ci_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Memory access, not I/O
|
ci_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Memory access, not I/O
|
||||||
|
|
@ -1272,7 +1275,7 @@ mod tests {
|
||||||
|
|
||||||
// Now assert RESET
|
// Now assert RESET
|
||||||
{
|
{
|
||||||
let mut reset_guard = reset_pin.lock().unwrap();
|
let mut reset_guard = reset_pin.lock();
|
||||||
reset_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
reset_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1290,10 +1293,10 @@ mod tests {
|
||||||
let d3_pin = rom.get_pin("D3").unwrap();
|
let d3_pin = rom.get_pin("D3").unwrap();
|
||||||
|
|
||||||
// After reset, all pins should be HighZ (tri-stated)
|
// After reset, all pins should be HighZ (tri-stated)
|
||||||
assert_eq!(d0_pin.lock().unwrap().read(), PinValue::HighZ);
|
assert_eq!(d0_pin.lock().read(), PinValue::HighZ);
|
||||||
assert_eq!(d1_pin.lock().unwrap().read(), PinValue::HighZ);
|
assert_eq!(d1_pin.lock().read(), PinValue::HighZ);
|
||||||
assert_eq!(d2_pin.lock().unwrap().read(), PinValue::HighZ);
|
assert_eq!(d2_pin.lock().read(), PinValue::HighZ);
|
||||||
assert_eq!(d3_pin.lock().unwrap().read(), PinValue::HighZ);
|
assert_eq!(d3_pin.lock().read(), PinValue::HighZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -1310,19 +1313,19 @@ mod tests {
|
||||||
let d3_pin = rom.get_pin("D3").unwrap();
|
let d3_pin = rom.get_pin("D3").unwrap();
|
||||||
|
|
||||||
// Initially, no operation - should be tri-stated
|
// Initially, no operation - should be tri-stated
|
||||||
assert_eq!(d0_pin.lock().unwrap().read(), PinValue::HighZ);
|
assert_eq!(d0_pin.lock().read(), PinValue::HighZ);
|
||||||
assert_eq!(d1_pin.lock().unwrap().read(), PinValue::HighZ);
|
assert_eq!(d1_pin.lock().read(), PinValue::HighZ);
|
||||||
assert_eq!(d2_pin.lock().unwrap().read(), PinValue::HighZ);
|
assert_eq!(d2_pin.lock().read(), PinValue::HighZ);
|
||||||
assert_eq!(d3_pin.lock().unwrap().read(), PinValue::HighZ);
|
assert_eq!(d3_pin.lock().read(), PinValue::HighZ);
|
||||||
|
|
||||||
// Set up memory operation but don't complete address phase
|
// Set up memory operation but don't complete address phase
|
||||||
let sync_pin = rom.get_pin("SYNC").unwrap();
|
let sync_pin = rom.get_pin("SYNC").unwrap();
|
||||||
let cm_pin = rom.get_pin("CM").unwrap();
|
let cm_pin = rom.get_pin("CM").unwrap();
|
||||||
let ci_pin = rom.get_pin("CI").unwrap();
|
let ci_pin = rom.get_pin("CI").unwrap();
|
||||||
|
|
||||||
let mut sync_guard = sync_pin.lock().unwrap();
|
let mut sync_guard = sync_pin.lock();
|
||||||
let mut cm_guard = cm_pin.lock().unwrap();
|
let mut cm_guard = cm_pin.lock();
|
||||||
let mut ci_guard = ci_pin.lock().unwrap();
|
let mut ci_guard = ci_pin.lock();
|
||||||
sync_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
sync_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
||||||
cm_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
cm_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
||||||
ci_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Memory access, not I/O
|
ci_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Memory access, not I/O
|
||||||
|
|
@ -1331,20 +1334,20 @@ mod tests {
|
||||||
rom.update();
|
rom.update();
|
||||||
|
|
||||||
// Should still be tri-stated during address phase
|
// Should still be tri-stated during address phase
|
||||||
assert_eq!(d0_pin.lock().unwrap().read(), PinValue::HighZ);
|
assert_eq!(d0_pin.lock().read(), PinValue::HighZ);
|
||||||
assert_eq!(d1_pin.lock().unwrap().read(), PinValue::HighZ);
|
assert_eq!(d1_pin.lock().read(), PinValue::HighZ);
|
||||||
assert_eq!(d2_pin.lock().unwrap().read(), PinValue::HighZ);
|
assert_eq!(d2_pin.lock().read(), PinValue::HighZ);
|
||||||
assert_eq!(d3_pin.lock().unwrap().read(), PinValue::HighZ);
|
assert_eq!(d3_pin.lock().read(), PinValue::HighZ);
|
||||||
|
|
||||||
// Complete address phase but don't wait for latency
|
// Complete address phase but don't wait for latency
|
||||||
// Should still be tri-stated
|
// Should still be tri-stated
|
||||||
rom.update();
|
rom.update();
|
||||||
|
|
||||||
// Should still be tri-stated (waiting for latency)
|
// Should still be tri-stated (waiting for latency)
|
||||||
assert_eq!(d0_pin.lock().unwrap().read(), PinValue::HighZ);
|
assert_eq!(d0_pin.lock().read(), PinValue::HighZ);
|
||||||
assert_eq!(d1_pin.lock().unwrap().read(), PinValue::HighZ);
|
assert_eq!(d1_pin.lock().read(), PinValue::HighZ);
|
||||||
assert_eq!(d2_pin.lock().unwrap().read(), PinValue::HighZ);
|
assert_eq!(d2_pin.lock().read(), PinValue::HighZ);
|
||||||
assert_eq!(d3_pin.lock().unwrap().read(), PinValue::HighZ);
|
assert_eq!(d3_pin.lock().read(), PinValue::HighZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
|
use parking_lot::Mutex;
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::Arc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
|
@ -407,11 +408,9 @@ impl Intel4002 {
|
||||||
fn tri_state_data_bus(&self) {
|
fn tri_state_data_bus(&self) {
|
||||||
for i in 0..4 {
|
for i in 0..4 {
|
||||||
if let Ok(pin) = self.base.get_pin(&format!("D{}", i)) {
|
if let Ok(pin) = self.base.get_pin(&format!("D{}", i)) {
|
||||||
if let Ok(mut pin_guard) = pin.lock() {
|
let mut pin_guard = pin.lock();
|
||||||
// Use the same driver name as write_data_bus() to properly tri-state
|
// Use the same driver name as write_data_bus() to properly tri-state
|
||||||
pin_guard
|
pin_guard.set_driver(Some(format!("{}_DATA", self.base.name())), PinValue::HighZ);
|
||||||
.set_driver(Some(format!("{}_DATA", self.base.name())), PinValue::HighZ);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -420,12 +419,11 @@ impl Intel4002 {
|
||||||
fn tri_state_status_pins(&self) {
|
fn tri_state_status_pins(&self) {
|
||||||
for i in 0..4 {
|
for i in 0..4 {
|
||||||
if let Ok(pin) = self.base.get_pin(&format!("S{}", i)) {
|
if let Ok(pin) = self.base.get_pin(&format!("S{}", i)) {
|
||||||
if let Ok(mut pin_guard) = pin.lock() {
|
let mut pin_guard = pin.lock();
|
||||||
pin_guard.set_driver(Some(self.base.name()), PinValue::HighZ);
|
pin_guard.set_driver(Some(self.base.name()), PinValue::HighZ);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Handle Φ1 rising edge - Address and control phase
|
/// Handle Φ1 rising edge - Address and control phase
|
||||||
/// Hardware: Φ1 high = CPU drives bus with address/control information
|
/// Hardware: Φ1 high = CPU drives bus with address/control information
|
||||||
|
|
@ -749,9 +747,17 @@ impl Component for Intel4002 {
|
||||||
self.base.is_running()
|
self.base.is_running()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_running(&mut self, running: bool) {
|
||||||
|
self.base.set_running(running);
|
||||||
|
}
|
||||||
|
|
||||||
fn as_any(&self) -> &dyn Any {
|
fn as_any(&self) -> &dyn Any {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RunnableComponent for Intel4002 {}
|
impl RunnableComponent for Intel4002 {}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
|
use parking_lot::Mutex;
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::Arc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
|
@ -337,12 +338,11 @@ impl Intel4003 {
|
||||||
fn tri_state_data_bus(&self) {
|
fn tri_state_data_bus(&self) {
|
||||||
for i in 0..4 {
|
for i in 0..4 {
|
||||||
if let Ok(pin) = self.base.get_pin(&format!("D{}", i)) {
|
if let Ok(pin) = self.base.get_pin(&format!("D{}", i)) {
|
||||||
if let Ok(mut pin_guard) = pin.lock() {
|
let mut pin_guard = pin.lock();
|
||||||
pin_guard.set_driver(Some(self.base.name()), PinValue::HighZ);
|
pin_guard.set_driver(Some(self.base.name()), PinValue::HighZ);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Handle Φ1 rising edge - Address and control phase
|
/// Handle Φ1 rising edge - Address and control phase
|
||||||
/// Hardware: Φ1 high = CPU drives bus with address/control information
|
/// Hardware: Φ1 high = CPU drives bus with address/control information
|
||||||
|
|
@ -621,9 +621,17 @@ impl Component for Intel4003 {
|
||||||
self.base.is_running()
|
self.base.is_running()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_running(&mut self, running: bool) {
|
||||||
|
self.base.set_running(running);
|
||||||
|
}
|
||||||
|
|
||||||
fn as_any(&self) -> &dyn Any {
|
fn as_any(&self) -> &dyn Any {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RunnableComponent for Intel4003 {}
|
impl RunnableComponent for Intel4003 {}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::pin::Pin;
|
use crate::pin::Pin;
|
||||||
|
use parking_lot::Mutex;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// Manages electrical connections between pins
|
/// Manages electrical connections between pins
|
||||||
pub struct ConnectionManager {
|
pub struct ConnectionManager {
|
||||||
|
|
@ -31,32 +32,24 @@ impl ConnectionManager {
|
||||||
pin2: Arc<Mutex<Pin>>,
|
pin2: Arc<Mutex<Pin>>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let pin1_name = {
|
let pin1_name = {
|
||||||
let p1 = pin1
|
let p1 = pin1.lock();
|
||||||
.lock()
|
|
||||||
.map_err(|e| format!("Failed to lock pin1: {}", e))?;
|
|
||||||
p1.name().to_string()
|
p1.name().to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
let pin2_name = {
|
let pin2_name = {
|
||||||
let p2 = pin2
|
let p2 = pin2.lock();
|
||||||
.lock()
|
|
||||||
.map_err(|e| format!("Failed to lock pin2: {}", e))?;
|
|
||||||
p2.name().to_string()
|
p2.name().to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Connect pin1 to pin2
|
// Connect pin1 to pin2
|
||||||
{
|
{
|
||||||
let mut p1 = pin1
|
let mut p1 = pin1.lock();
|
||||||
.lock()
|
|
||||||
.map_err(|e| format!("Failed to lock pin1: {}", e))?;
|
|
||||||
p1.connect_to(pin2.clone());
|
p1.connect_to(pin2.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect pin2 to pin1 (bidirectional)
|
// Connect pin2 to pin1 (bidirectional)
|
||||||
{
|
{
|
||||||
let mut p2 = pin2
|
let mut p2 = pin2.lock();
|
||||||
.lock()
|
|
||||||
.map_err(|e| format!("Failed to lock pin2: {}", e))?;
|
|
||||||
p2.connect_to(pin1.clone());
|
p2.connect_to(pin1.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -96,32 +89,24 @@ impl ConnectionManager {
|
||||||
pin2: &Arc<Mutex<Pin>>,
|
pin2: &Arc<Mutex<Pin>>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let pin1_name = {
|
let pin1_name = {
|
||||||
let p1 = pin1
|
let p1 = pin1.lock();
|
||||||
.lock()
|
|
||||||
.map_err(|e| format!("Failed to lock pin1: {}", e))?;
|
|
||||||
p1.name().to_string()
|
p1.name().to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
let pin2_name = {
|
let pin2_name = {
|
||||||
let p2 = pin2
|
let p2 = pin2.lock();
|
||||||
.lock()
|
|
||||||
.map_err(|e| format!("Failed to lock pin2: {}", e))?;
|
|
||||||
p2.name().to_string()
|
p2.name().to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Disconnect pin1 from pin2
|
// Disconnect pin1 from pin2
|
||||||
{
|
{
|
||||||
let mut p1 = pin1
|
let mut p1 = pin1.lock();
|
||||||
.lock()
|
|
||||||
.map_err(|e| format!("Failed to lock pin1: {}", e))?;
|
|
||||||
p1.disconnect_from_pin(pin2);
|
p1.disconnect_from_pin(pin2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disconnect pin2 from pin1
|
// Disconnect pin2 from pin1
|
||||||
{
|
{
|
||||||
let mut p2 = pin2
|
let mut p2 = pin2.lock();
|
||||||
.lock()
|
|
||||||
.map_err(|e| format!("Failed to lock pin2: {}", e))?;
|
|
||||||
p2.disconnect_from_pin(pin1);
|
p2.disconnect_from_pin(pin1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -153,9 +138,7 @@ impl ConnectionManager {
|
||||||
/// Disconnect all pins from a given pin
|
/// Disconnect all pins from a given pin
|
||||||
pub fn disconnect_all(&mut self, pin: &Arc<Mutex<Pin>>) -> Result<(), String> {
|
pub fn disconnect_all(&mut self, pin: &Arc<Mutex<Pin>>) -> Result<(), String> {
|
||||||
let pin_name = {
|
let pin_name = {
|
||||||
let p = pin
|
let p = pin.lock();
|
||||||
.lock()
|
|
||||||
.map_err(|e| format!("Failed to lock pin: {}", e))?;
|
|
||||||
p.name().to_string()
|
p.name().to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -182,9 +165,7 @@ impl ConnectionManager {
|
||||||
|
|
||||||
// Clear the pin's internal connections
|
// Clear the pin's internal connections
|
||||||
{
|
{
|
||||||
let mut p = pin
|
let mut p = pin.lock();
|
||||||
.lock()
|
|
||||||
.map_err(|e| format!("Failed to lock pin: {}", e))?;
|
|
||||||
p.clear_connections();
|
p.clear_connections();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -252,8 +233,8 @@ mod tests {
|
||||||
assert!(connect_pins(pin1.clone(), pin2.clone()).is_ok());
|
assert!(connect_pins(pin1.clone(), pin2.clone()).is_ok());
|
||||||
|
|
||||||
// Verify connection
|
// Verify connection
|
||||||
let p1 = pin1.lock().unwrap();
|
let p1 = pin1.lock();
|
||||||
let p2 = pin2.lock().unwrap();
|
let p2 = pin2.lock();
|
||||||
|
|
||||||
assert_eq!(p1.get_connection_count(), 1);
|
assert_eq!(p1.get_connection_count(), 1);
|
||||||
assert_eq!(p2.get_connection_count(), 1);
|
assert_eq!(p2.get_connection_count(), 1);
|
||||||
|
|
@ -271,8 +252,8 @@ mod tests {
|
||||||
|
|
||||||
manager.disconnect_pins(&pin1, &pin2).unwrap();
|
manager.disconnect_pins(&pin1, &pin2).unwrap();
|
||||||
|
|
||||||
let p1 = pin1.lock().unwrap();
|
let p1 = pin1.lock();
|
||||||
let p2 = pin2.lock().unwrap();
|
let p2 = pin2.lock();
|
||||||
|
|
||||||
assert_eq!(p1.get_connection_count(), 0);
|
assert_eq!(p1.get_connection_count(), 0);
|
||||||
assert_eq!(p2.get_connection_count(), 0);
|
assert_eq!(p2.get_connection_count(), 0);
|
||||||
|
|
@ -290,7 +271,7 @@ mod tests {
|
||||||
|
|
||||||
// Verify all pins are connected to each other
|
// Verify all pins are connected to each other
|
||||||
for i in 0..pins.len() {
|
for i in 0..pins.len() {
|
||||||
let pin = pins[i].lock().unwrap();
|
let pin = pins[i].lock();
|
||||||
assert_eq!(pin.get_connection_count(), pins.len() - 1);
|
assert_eq!(pin.get_connection_count(), pins.len() - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -307,7 +288,7 @@ mod tests {
|
||||||
|
|
||||||
manager.disconnect_all(&pin1).unwrap();
|
manager.disconnect_all(&pin1).unwrap();
|
||||||
|
|
||||||
let p1 = pin1.lock().unwrap();
|
let p1 = pin1.lock();
|
||||||
assert_eq!(p1.get_connection_count(), 0);
|
assert_eq!(p1.get_connection_count(), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,10 @@
|
||||||
//! - Recovery options are provided where applicable
|
//! - Recovery options are provided where applicable
|
||||||
|
|
||||||
use super::state::GuiState;
|
use super::state::GuiState;
|
||||||
|
use crate::system_config::{ConfigurableSystem, SystemController};
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// Container for all GUI components
|
/// Container for all GUI components
|
||||||
///
|
///
|
||||||
|
|
@ -120,8 +123,14 @@ impl GuiComponents {
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
/// * `ui` - egui UI context for rendering
|
/// * `ui` - egui UI context for rendering
|
||||||
/// * `state` - Mutable reference to GUI state for control operations
|
/// * `state` - Mutable reference to GUI state for control operations
|
||||||
pub fn render_control_panel(&mut self, ui: &mut egui::Ui, state: &mut GuiState) {
|
/// * `system` - Optional reference to the emulator system for actual control
|
||||||
self.control_panel.render(ui, state);
|
pub fn render_control_panel(
|
||||||
|
&mut self,
|
||||||
|
ui: &mut egui::Ui,
|
||||||
|
state: &mut GuiState,
|
||||||
|
system: Option<&Arc<Mutex<ConfigurableSystem>>>,
|
||||||
|
) {
|
||||||
|
self.control_panel.render(ui, state, system);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Render the memory viewer component
|
/// Render the memory viewer component
|
||||||
|
|
@ -233,7 +242,12 @@ impl ControlPanel {
|
||||||
/// │ [Load ROM] [Start System] [Stop] [Reset] [Close] │
|
/// │ [Load ROM] [Start System] [Stop] [Reset] [Close] │
|
||||||
/// └─────────────────────────────────────────────────┘
|
/// └─────────────────────────────────────────────────┘
|
||||||
/// ```
|
/// ```
|
||||||
pub fn render(&mut self, ui: &mut egui::Ui, state: &mut GuiState) {
|
pub fn render(
|
||||||
|
&mut self,
|
||||||
|
ui: &mut egui::Ui,
|
||||||
|
state: &mut GuiState,
|
||||||
|
system: Option<&Arc<Mutex<ConfigurableSystem>>>,
|
||||||
|
) {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
// Section header
|
// Section header
|
||||||
ui.heading("System Control");
|
ui.heading("System Control");
|
||||||
|
|
@ -247,22 +261,28 @@ impl ControlPanel {
|
||||||
// Start/Stop system button (context-sensitive)
|
// Start/Stop system button (context-sensitive)
|
||||||
if ui.button(&self.start_button_text).clicked() {
|
if ui.button(&self.start_button_text).clicked() {
|
||||||
if !state.system_running {
|
if !state.system_running {
|
||||||
self.start_system(state);
|
self.start_system(state, system);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop system button (only enabled when running)
|
// Stop system button (only enabled when running)
|
||||||
if ui.button("Stop System").clicked() && state.system_running {
|
if ui.button("Stop System").clicked() && state.system_running {
|
||||||
self.stop_system(state);
|
self.stop_system(state, system);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset system button
|
// Reset system button
|
||||||
if ui.button("Reset System").clicked() {
|
if ui.button("Reset System").clicked() {
|
||||||
self.reset_system(state);
|
self.reset_system(state, system);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Application close button
|
// Application close button
|
||||||
if ui.button("Close").clicked() {
|
if ui.button("Close").clicked() {
|
||||||
|
// Stop the system before closing if available
|
||||||
|
if let Some(sys) = system {
|
||||||
|
let mut sys_guard = sys.lock();
|
||||||
|
// Use the SystemController trait method explicitly
|
||||||
|
let _ = SystemController::stop(&mut *sys_guard);
|
||||||
|
}
|
||||||
// Close application - handled by eframe
|
// Close application - handled by eframe
|
||||||
ui.ctx().send_viewport_cmd(egui::ViewportCommand::Close);
|
ui.ctx().send_viewport_cmd(egui::ViewportCommand::Close);
|
||||||
}
|
}
|
||||||
|
|
@ -278,13 +298,38 @@ impl ControlPanel {
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
/// * `state` - Mutable reference to GUI state
|
/// * `state` - Mutable reference to GUI state
|
||||||
fn start_system(&mut self, state: &mut GuiState) {
|
/// * `system` - Optional reference to the emulator system
|
||||||
|
fn start_system(
|
||||||
|
&mut self,
|
||||||
|
state: &mut GuiState,
|
||||||
|
system: Option<&Arc<Mutex<ConfigurableSystem>>>,
|
||||||
|
) {
|
||||||
|
// Actually start the system if available
|
||||||
|
if let Some(sys) = system {
|
||||||
|
let mut sys_guard = sys.lock();
|
||||||
|
// Use the SystemController trait method explicitly
|
||||||
|
let result = SystemController::start(&mut *sys_guard);
|
||||||
|
match result {
|
||||||
|
Ok(()) => {
|
||||||
state.system_running = true;
|
state.system_running = true;
|
||||||
self.start_button_text = "System Running...".to_string();
|
self.start_button_text = "System Running...".to_string();
|
||||||
self.stop_button_enabled = true;
|
self.stop_button_enabled = true;
|
||||||
self.reset_button_enabled = true;
|
self.reset_button_enabled = true;
|
||||||
state.clear_error();
|
state.clear_error();
|
||||||
}
|
}
|
||||||
|
Err(e) => {
|
||||||
|
state.set_error(format!("Failed to start system: {}", e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No system available - just update UI state
|
||||||
|
state.system_running = true;
|
||||||
|
self.start_button_text = "System Running...".to_string();
|
||||||
|
self.stop_button_enabled = true;
|
||||||
|
self.reset_button_enabled = true;
|
||||||
|
state.clear_error();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Stop the emulator system
|
/// Stop the emulator system
|
||||||
///
|
///
|
||||||
|
|
@ -293,12 +338,36 @@ impl ControlPanel {
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
/// * `state` - Mutable reference to GUI state
|
/// * `state` - Mutable reference to GUI state
|
||||||
fn stop_system(&mut self, state: &mut GuiState) {
|
/// * `system` - Optional reference to the emulator system
|
||||||
|
fn stop_system(
|
||||||
|
&mut self,
|
||||||
|
state: &mut GuiState,
|
||||||
|
system: Option<&Arc<Mutex<ConfigurableSystem>>>,
|
||||||
|
) {
|
||||||
|
// Actually stop the system if available
|
||||||
|
if let Some(sys) = system {
|
||||||
|
let mut sys_guard = sys.lock();
|
||||||
|
// Use the SystemController trait method explicitly
|
||||||
|
let result = SystemController::stop(&mut *sys_guard);
|
||||||
|
match result {
|
||||||
|
Ok(()) => {
|
||||||
state.system_running = false;
|
state.system_running = false;
|
||||||
self.start_button_text = "Start System".to_string();
|
self.start_button_text = "Start System".to_string();
|
||||||
self.stop_button_enabled = false;
|
self.stop_button_enabled = false;
|
||||||
state.clear_error();
|
state.clear_error();
|
||||||
}
|
}
|
||||||
|
Err(e) => {
|
||||||
|
state.set_error(format!("Failed to stop system: {}", e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No system available - just update UI state
|
||||||
|
state.system_running = false;
|
||||||
|
self.start_button_text = "Start System".to_string();
|
||||||
|
self.stop_button_enabled = false;
|
||||||
|
state.clear_error();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Reset the emulator system
|
/// Reset the emulator system
|
||||||
///
|
///
|
||||||
|
|
@ -307,7 +376,19 @@ impl ControlPanel {
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
/// * `state` - Mutable reference to GUI state
|
/// * `state` - Mutable reference to GUI state
|
||||||
fn reset_system(&mut self, state: &mut GuiState) {
|
/// * `system` - Optional reference to the emulator system
|
||||||
|
fn reset_system(
|
||||||
|
&mut self,
|
||||||
|
state: &mut GuiState,
|
||||||
|
system: Option<&Arc<Mutex<ConfigurableSystem>>>,
|
||||||
|
) {
|
||||||
|
// Stop the system first if available
|
||||||
|
if let Some(sys) = system {
|
||||||
|
let mut sys_guard = sys.lock();
|
||||||
|
// Use the SystemController trait method explicitly
|
||||||
|
let _ = SystemController::stop(&mut *sys_guard);
|
||||||
|
}
|
||||||
|
// Reset UI state
|
||||||
state.system_running = false;
|
state.system_running = false;
|
||||||
state.cycle_count = 0;
|
state.cycle_count = 0;
|
||||||
self.start_button_text = "Start System".to_string();
|
self.start_button_text = "Start System".to_string();
|
||||||
|
|
@ -785,7 +866,7 @@ impl RomLoader {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display currently selected file
|
// Display currently selected file
|
||||||
if let Some(ref file) = self.selected_file.clone() {
|
if let Some(ref file) = self.selected_file {
|
||||||
ui.label(format!("Selected: {}", file));
|
ui.label(format!("Selected: {}", file));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ use crossterm::{
|
||||||
execute,
|
execute,
|
||||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||||
};
|
};
|
||||||
|
use parking_lot::Mutex;
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
backend::CrosstermBackend,
|
backend::CrosstermBackend,
|
||||||
layout::{Constraint, Direction, Layout, Rect},
|
layout::{Constraint, Direction, Layout, Rect},
|
||||||
|
|
@ -25,11 +26,11 @@ use ratatui::{
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::Arc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use crate::system_config::ConfigurableSystem;
|
use crate::system_config::{ConfigurableSystem, SystemController, SystemState};
|
||||||
|
|
||||||
/// Display format for memory and register values
|
/// Display format for memory and register values
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||||
|
|
@ -89,7 +90,7 @@ impl Default for ConsoleConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
refresh_rate_ms: 100,
|
refresh_rate_ms: 10, // 100 Hz update rate (10ms interval)
|
||||||
show_ram: true,
|
show_ram: true,
|
||||||
show_registers: true,
|
show_registers: true,
|
||||||
show_system_info: true,
|
show_system_info: true,
|
||||||
|
|
@ -111,6 +112,10 @@ pub struct ConsoleApp {
|
||||||
command_buffer: String,
|
command_buffer: String,
|
||||||
show_help: bool,
|
show_help: bool,
|
||||||
selected_pane: usize,
|
selected_pane: usize,
|
||||||
|
/// Status message to display to the user (e.g., errors, feedback)
|
||||||
|
status_message: Option<String>,
|
||||||
|
/// When the status message was set, for auto-clearing
|
||||||
|
status_message_time: Option<Instant>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConsoleApp {
|
impl ConsoleApp {
|
||||||
|
|
@ -122,18 +127,76 @@ impl ConsoleApp {
|
||||||
command_buffer: String::new(),
|
command_buffer: String::new(),
|
||||||
show_help: false,
|
show_help: false,
|
||||||
selected_pane: 0,
|
selected_pane: 0,
|
||||||
|
status_message: None,
|
||||||
|
status_message_time: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a status message to display to the user
|
||||||
|
fn set_status(&mut self, message: String) {
|
||||||
|
self.status_message = Some(message);
|
||||||
|
self.status_message_time = Some(Instant::now());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clear the status message if it has been displayed long enough (3 seconds)
|
||||||
|
fn maybe_clear_status(&mut self) {
|
||||||
|
if let Some(time) = self.status_message_time {
|
||||||
|
if time.elapsed().as_secs() >= 3 {
|
||||||
|
self.status_message = None;
|
||||||
|
self.status_message_time = None;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
pub fn run(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
// Setup terminal
|
// Set up panic hook to ensure terminal cleanup on panic
|
||||||
enable_raw_mode().map_err(|e| format!("Failed to enable raw mode: {}", e))?;
|
// Note: We use a custom cleanup hook that runs before the default panic handler
|
||||||
|
std::panic::set_hook(Box::new(|panic_info| {
|
||||||
|
// Try to restore terminal state before printing panic
|
||||||
|
let _ = disable_raw_mode();
|
||||||
|
let _ = execute!(io::stdout(), LeaveAlternateScreen);
|
||||||
|
// Print the panic info after cleanup
|
||||||
|
eprintln!("\npanic occurred: {}", panic_info);
|
||||||
|
}));
|
||||||
|
|
||||||
|
let result = self.run_inner();
|
||||||
|
|
||||||
|
// Restore default panic hook after normal completion
|
||||||
|
let _ = std::panic::take_hook();
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_inner(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
// Setup terminal with explicit error handling
|
||||||
|
enable_raw_mode().map_err(|e| {
|
||||||
|
eprintln!("ERROR: Failed to enable raw mode: {}", e);
|
||||||
|
eprintln!("This may occur if the terminal doesn't support raw mode.");
|
||||||
|
format!("Failed to enable raw mode: {}", e)
|
||||||
|
})?;
|
||||||
|
|
||||||
let mut stdout = io::stdout();
|
let mut stdout = io::stdout();
|
||||||
execute!(stdout, EnterAlternateScreen)
|
execute!(stdout, EnterAlternateScreen).map_err(|e| {
|
||||||
.map_err(|e| format!("Failed to enter alternate screen: {}", e))?;
|
let _ = disable_raw_mode();
|
||||||
|
eprintln!("ERROR: Failed to enter alternate screen: {}", e);
|
||||||
|
eprintln!("The console requires an alternate screen buffer.");
|
||||||
|
format!("Failed to enter alternate screen: {}", e)
|
||||||
|
})?;
|
||||||
|
|
||||||
let backend = CrosstermBackend::new(stdout);
|
let backend = CrosstermBackend::new(stdout);
|
||||||
let mut terminal = Terminal::new(backend)?;
|
let mut terminal = Terminal::new(backend).map_err(|e| {
|
||||||
|
let _ = disable_raw_mode();
|
||||||
|
let _ = execute!(io::stdout(), LeaveAlternateScreen);
|
||||||
|
eprintln!("ERROR: Failed to create terminal backend: {}", e);
|
||||||
|
format!("Failed to create terminal backend: {}", e)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Clear the terminal initially to ensure a fresh start
|
||||||
|
terminal.clear().map_err(|e| {
|
||||||
|
let _ = disable_raw_mode();
|
||||||
|
let _ = execute!(io::stdout(), LeaveAlternateScreen);
|
||||||
|
format!("Failed to clear terminal: {}", e)
|
||||||
|
})?;
|
||||||
|
|
||||||
self.running = true;
|
self.running = true;
|
||||||
let mut last_draw = Instant::now();
|
let mut last_draw = Instant::now();
|
||||||
|
|
@ -142,40 +205,63 @@ impl ConsoleApp {
|
||||||
while self.running {
|
while self.running {
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
|
|
||||||
// Handle input with longer poll interval to reduce CPU usage
|
// Handle input with poll interval to reduce CPU usage
|
||||||
if let Ok(true) = event::poll(Duration::from_millis(50)) {
|
match event::poll(Duration::from_millis(50)) {
|
||||||
if let Ok(Event::Key(key)) = event::read() {
|
Ok(true) => {
|
||||||
|
match event::read() {
|
||||||
|
Ok(Event::Key(key)) => {
|
||||||
self.handle_key_event(key.code, key.modifiers);
|
self.handle_key_event(key.code, key.modifiers);
|
||||||
}
|
}
|
||||||
|
Ok(Event::Resize(_, _)) => {
|
||||||
|
// Terminal was resized, force a redraw
|
||||||
|
if let Err(e) = terminal.draw(|f| self.draw_ui(f)) {
|
||||||
|
eprintln!("Terminal draw error after resize: {}", e);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(_) => {
|
||||||
|
// Other events (mouse, etc.) - ignore
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Event read error: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(false) => {
|
||||||
|
// No event available, continue
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Event poll error: {}", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update display at regular intervals
|
// Update display at regular intervals
|
||||||
if now.duration_since(last_draw) >= Duration::from_millis(self.config.refresh_rate_ms) {
|
if now.duration_since(last_draw) >= Duration::from_millis(self.config.refresh_rate_ms) {
|
||||||
if let Err(e) = terminal.draw(|f| self.draw_ui(f)) {
|
match terminal.draw(|f| self.draw_ui(f)) {
|
||||||
eprintln!("DEBUG: Failed to draw UI: {}", e);
|
Ok(_) => {
|
||||||
break;
|
|
||||||
}
|
|
||||||
last_draw = now;
|
last_draw = now;
|
||||||
}
|
}
|
||||||
|
Err(e) => {
|
||||||
// Check if system should still be running using try_lock to avoid blocking
|
eprintln!("Terminal draw error: {}", e);
|
||||||
match self.system.try_lock() {
|
// Try to recover by clearing and redrawing
|
||||||
Ok(system) => {
|
if let Err(clear_err) = terminal.clear() {
|
||||||
if !system.is_running() && !self.show_help {
|
eprintln!("Failed to clear terminal: {}", clear_err);
|
||||||
// System stopped, show final state briefly
|
|
||||||
if let Err(e) = terminal.draw(|f| self.draw_ui(f)) {
|
|
||||||
eprintln!("DEBUG: Failed to draw final UI: {}", e);
|
|
||||||
}
|
|
||||||
thread::sleep(Duration::from_millis(500));
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(std::sync::TryLockError::WouldBlock) => {
|
|
||||||
// System is busy, continue running - this is expected
|
|
||||||
}
|
}
|
||||||
Err(std::sync::TryLockError::Poisoned(_)) => {
|
}
|
||||||
// System mutex is poisoned, exit gracefully
|
|
||||||
eprintln!("DEBUG: System mutex poisoned, exiting");
|
// Check if system should still be running using blocking lock
|
||||||
|
// GUI can wait for simulation - this ensures we always get the data
|
||||||
|
{
|
||||||
|
let system = self.system.lock();
|
||||||
|
let state = system.get_system_state();
|
||||||
|
if state == SystemState::Stopped && !self.show_help {
|
||||||
|
// System stopped, show final state briefly
|
||||||
|
drop(system); // Release lock before draw
|
||||||
|
let _ = terminal.draw(|f| self.draw_ui(f));
|
||||||
|
thread::sleep(Duration::from_millis(500));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -184,13 +270,30 @@ impl ConsoleApp {
|
||||||
thread::sleep(Duration::from_millis(10));
|
thread::sleep(Duration::from_millis(10));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore terminal
|
// Restore terminal - always attempt cleanup even if errors occurred
|
||||||
disable_raw_mode().map_err(|e| format!("Failed to disable raw mode: {}", e))?;
|
let cleanup_result = self.cleanup_terminal(&mut terminal);
|
||||||
execute!(terminal.backend_mut(), LeaveAlternateScreen)
|
|
||||||
.map_err(|e| format!("Failed to leave alternate screen: {}", e))?;
|
cleanup_result
|
||||||
terminal
|
}
|
||||||
.show_cursor()
|
|
||||||
.map_err(|e| format!("Failed to show cursor: {}", e))?;
|
fn cleanup_terminal(
|
||||||
|
&self,
|
||||||
|
terminal: &mut Terminal<CrosstermBackend<io::Stdout>>,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
// Disable raw mode first
|
||||||
|
if let Err(e) = disable_raw_mode() {
|
||||||
|
eprintln!("Warning: Failed to disable raw mode: {}", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Leave alternate screen
|
||||||
|
if let Err(e) = execute!(terminal.backend_mut(), LeaveAlternateScreen) {
|
||||||
|
eprintln!("Warning: Failed to leave alternate screen: {}", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show cursor
|
||||||
|
if let Err(e) = terminal.show_cursor() {
|
||||||
|
eprintln!("Warning: Failed to show cursor: {}", e);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -200,138 +303,215 @@ impl ConsoleApp {
|
||||||
if key == KeyCode::Char('q') || key == KeyCode::Char('Q') {
|
if key == KeyCode::Char('q') || key == KeyCode::Char('Q') {
|
||||||
if modifiers.contains(KeyModifiers::CONTROL) {
|
if modifiers.contains(KeyModifiers::CONTROL) {
|
||||||
self.running = false;
|
self.running = false;
|
||||||
if let Ok(mut system) = self.system.lock() {
|
let mut system = self.system.lock();
|
||||||
system.stop();
|
system.stop();
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match key {
|
match key {
|
||||||
KeyCode::Char('q') | KeyCode::Char('Q') | KeyCode::Esc => {
|
KeyCode::Char('q') | KeyCode::Char('Q') | KeyCode::Esc => {
|
||||||
println!("DEBUG: Quit key pressed, stopping console");
|
|
||||||
self.running = false;
|
self.running = false;
|
||||||
if let Ok(mut system) = self.system.lock() {
|
let mut system = self.system.lock();
|
||||||
system.stop();
|
system.stop();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
KeyCode::Char('h') | KeyCode::Char('H') => {
|
KeyCode::Char('h') | KeyCode::Char('H') => {
|
||||||
println!("DEBUG: Help key pressed");
|
|
||||||
self.show_help = !self.show_help;
|
self.show_help = !self.show_help;
|
||||||
}
|
}
|
||||||
KeyCode::Char('r') | KeyCode::Char('R') => {
|
KeyCode::Char('r') | KeyCode::Char('R') => {
|
||||||
println!("DEBUG: Run key pressed");
|
let mut system = self.system.lock();
|
||||||
if let Ok(mut system) = self.system.lock() {
|
|
||||||
if system.is_running() {
|
if system.is_running() {
|
||||||
system.stop();
|
let _ = system.stop();
|
||||||
} else {
|
} else {
|
||||||
system.run();
|
let _ = system.start();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
KeyCode::Char('s') | KeyCode::Char('S') => {
|
KeyCode::Char('s') | KeyCode::Char('S') => {
|
||||||
println!("DEBUG: Stop key pressed");
|
let mut system = self.system.lock();
|
||||||
if let Ok(mut system) = self.system.lock() {
|
|
||||||
system.stop();
|
system.stop();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
KeyCode::Char(' ') => {
|
KeyCode::Char(' ') => {
|
||||||
println!("DEBUG: Space key pressed - single step not implemented");
|
// Space = pause/resume toggle
|
||||||
// Single step (if supported)
|
let mut system = self.system.lock();
|
||||||
// This would require adding step functionality to the system
|
let state = system.get_system_state();
|
||||||
|
match state {
|
||||||
|
SystemState::Running => {
|
||||||
|
let _ = system.pause();
|
||||||
|
}
|
||||||
|
SystemState::Paused => {
|
||||||
|
let _ = system.resume();
|
||||||
|
}
|
||||||
|
SystemState::Stopped => {
|
||||||
|
let _ = system.start();
|
||||||
|
}
|
||||||
|
SystemState::Error => {
|
||||||
|
// Try to recover by stopping and starting
|
||||||
|
system.stop();
|
||||||
|
let _ = system.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::Char('+') | KeyCode::Char('=') => {
|
||||||
|
// Increase clock frequency by 20%
|
||||||
|
let status_msg = {
|
||||||
|
let mut system = self.system.lock();
|
||||||
|
let current = system.get_clock_frequency();
|
||||||
|
let new_freq = current * 1.2;
|
||||||
|
match system.set_clock_frequency(new_freq) {
|
||||||
|
Ok(_) => Some(format!("Frequency: {:.0} Hz", new_freq)),
|
||||||
|
Err(e) => Some(format!("Error: {}", e)),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Some(msg) = status_msg {
|
||||||
|
self.set_status(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::Char('-') | KeyCode::Char('_') => {
|
||||||
|
// Decrease clock frequency by 20%
|
||||||
|
let status_msg = {
|
||||||
|
let mut system = self.system.lock();
|
||||||
|
let current = system.get_clock_frequency();
|
||||||
|
let new_freq = current / 1.2;
|
||||||
|
match system.set_clock_frequency(new_freq) {
|
||||||
|
Ok(_) => Some(format!("Frequency: {:.0} Hz", new_freq)),
|
||||||
|
Err(e) => Some(format!("Error: {}", e)),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Some(msg) = status_msg {
|
||||||
|
self.set_status(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::Char('p') | KeyCode::Char('P') => {
|
||||||
|
// Pause command
|
||||||
|
let mut system = self.system.lock();
|
||||||
|
let _ = system.pause();
|
||||||
|
}
|
||||||
|
KeyCode::Char('c') | KeyCode::Char('C') => {
|
||||||
|
// Resume (continue) command
|
||||||
|
let mut system = self.system.lock();
|
||||||
|
let _ = system.resume();
|
||||||
}
|
}
|
||||||
KeyCode::Tab => {
|
KeyCode::Tab => {
|
||||||
println!("DEBUG: Tab key pressed - switching panes");
|
|
||||||
self.selected_pane = (self.selected_pane + 1) % 3;
|
self.selected_pane = (self.selected_pane + 1) % 3;
|
||||||
}
|
}
|
||||||
KeyCode::Backspace => {
|
KeyCode::Backspace => {
|
||||||
println!("DEBUG: Backspace key pressed");
|
|
||||||
self.command_buffer.pop();
|
self.command_buffer.pop();
|
||||||
}
|
}
|
||||||
KeyCode::Enter => {
|
KeyCode::Enter => {
|
||||||
println!(
|
|
||||||
"DEBUG: Enter key pressed - executing command: '{}'",
|
|
||||||
self.command_buffer
|
|
||||||
);
|
|
||||||
self.execute_command();
|
self.execute_command();
|
||||||
self.command_buffer.clear();
|
self.command_buffer.clear();
|
||||||
}
|
}
|
||||||
KeyCode::Char(c) => {
|
KeyCode::Char(c) => {
|
||||||
println!("DEBUG: Character key pressed: '{}'", c);
|
if c.is_ascii_alphabetic() || c.is_ascii_digit() || c == '.' || c == ' ' {
|
||||||
if c.is_ascii_alphabetic() || c.is_ascii_digit() {
|
|
||||||
self.command_buffer.push(c);
|
self.command_buffer.push(c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {}
|
||||||
println!("DEBUG: Unhandled key pressed: {:?}", key);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn execute_command(&mut self) {
|
fn execute_command(&mut self) {
|
||||||
let cmd = self.command_buffer.trim().to_lowercase();
|
let cmd = self.command_buffer.trim().to_lowercase();
|
||||||
println!("DEBUG: Executing command: '{}'", cmd);
|
let parts: Vec<&str> = cmd.split_whitespace().collect();
|
||||||
|
|
||||||
match cmd.as_str() {
|
if parts.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
match parts[0] {
|
||||||
"quit" | "exit" | "q" => {
|
"quit" | "exit" | "q" => {
|
||||||
println!("DEBUG: Executing quit command");
|
|
||||||
self.running = false;
|
self.running = false;
|
||||||
if let Ok(mut system) = self.system.lock() {
|
let mut system = self.system.lock();
|
||||||
system.stop();
|
system.stop();
|
||||||
}
|
}
|
||||||
}
|
"run" | "r" | "start" => {
|
||||||
"run" | "r" => {
|
let mut system = self.system.lock();
|
||||||
println!("DEBUG: Executing run command");
|
let _ = system.start();
|
||||||
if let Ok(mut system) = self.system.lock() {
|
|
||||||
system.run();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
"stop" | "s" => {
|
"stop" | "s" => {
|
||||||
println!("DEBUG: Executing stop command");
|
let mut system = self.system.lock();
|
||||||
if let Ok(mut system) = self.system.lock() {
|
let _ = system.stop();
|
||||||
system.stop();
|
|
||||||
}
|
}
|
||||||
|
"pause" | "p" => {
|
||||||
|
let mut system = self.system.lock();
|
||||||
|
let _ = system.pause();
|
||||||
|
}
|
||||||
|
"resume" | "c" | "continue" => {
|
||||||
|
let mut system = self.system.lock();
|
||||||
|
let _ = system.resume();
|
||||||
}
|
}
|
||||||
"help" | "h" => {
|
"help" | "h" => {
|
||||||
println!("DEBUG: Toggling help display");
|
|
||||||
self.show_help = !self.show_help;
|
self.show_help = !self.show_help;
|
||||||
}
|
}
|
||||||
"reset" => {
|
"reset" => {
|
||||||
println!("DEBUG: Executing reset command");
|
let mut system = self.system.lock();
|
||||||
if let Ok(mut system) = self.system.lock() {
|
let _ = system.stop();
|
||||||
system.stop();
|
|
||||||
// Reset would need to be implemented in the system
|
// Reset would need to be implemented in the system
|
||||||
}
|
}
|
||||||
}
|
|
||||||
"status" => {
|
"status" => {
|
||||||
println!("DEBUG: Executing status command");
|
let system = self.system.lock();
|
||||||
if let Ok(system) = self.system.lock() {
|
|
||||||
let info = system.get_system_info();
|
let info = system.get_system_info();
|
||||||
|
let state = system.get_system_state();
|
||||||
|
let freq = system.get_clock_frequency();
|
||||||
println!("System: {} - {}", info.name, info.description);
|
println!("System: {} - {}", info.name, info.description);
|
||||||
println!(
|
println!("Components: {}, State: {:?}", info.component_count, state);
|
||||||
"Components: {}, Running: {}",
|
println!("Clock Frequency: {:.2} Hz", freq);
|
||||||
info.component_count,
|
}
|
||||||
system.is_running()
|
"freq" | "frequency" => {
|
||||||
);
|
if parts.len() > 1 {
|
||||||
|
if let Ok(freq) = parts[1].parse::<f64>() {
|
||||||
|
let mut system = self.system.lock();
|
||||||
|
match system.set_clock_frequency(freq) {
|
||||||
|
Ok(_) => println!("Clock frequency set to {:.2} Hz", freq),
|
||||||
|
Err(e) => println!("Error setting frequency: {}", e),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println!("Invalid frequency value: {}", parts[1]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let system = self.system.lock();
|
||||||
|
let freq = system.get_clock_frequency();
|
||||||
|
println!("Current clock frequency: {:.2} Hz", freq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"clock" => {
|
||||||
|
if parts.len() > 1 {
|
||||||
|
match parts[1] {
|
||||||
|
"on" | "enable" => {
|
||||||
|
let mut system = self.system.lock();
|
||||||
|
let _ = system.set_clock_enabled(true);
|
||||||
|
}
|
||||||
|
"off" | "disable" => {
|
||||||
|
let mut system = self.system.lock();
|
||||||
|
let _ = system.set_clock_enabled(false);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
println!("Usage: clock [on|off]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let system = self.system.lock();
|
||||||
|
let enabled = system.is_clock_enabled();
|
||||||
|
println!("Clock enabled: {}", enabled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"ram" => {
|
"ram" => {
|
||||||
println!("DEBUG: Executing RAM display command");
|
// RAM display is always shown in the UI
|
||||||
if let Ok(_system) = self.system.lock() {
|
|
||||||
println!("RAM display requested - would show RAM contents here");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
"" => {
|
"" => {
|
||||||
// Empty command - do nothing
|
// Empty command - do nothing
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
println!("DEBUG: Unknown command: '{}'", cmd);
|
println!("Unknown command: '{}'", cmd);
|
||||||
println!("Available commands: quit, run, stop, help, reset, status, ram");
|
println!("Available commands: quit, run, stop, pause, resume, help, reset, status, freq, clock");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_ui(&self, f: &mut Frame) {
|
fn draw_ui(&mut self, f: &mut Frame) {
|
||||||
|
// Clear old status messages first
|
||||||
|
self.maybe_clear_status();
|
||||||
|
|
||||||
let size = f.size();
|
let size = f.size();
|
||||||
|
|
||||||
if self.show_help {
|
if self.show_help {
|
||||||
|
|
@ -358,14 +538,16 @@ impl ConsoleApp {
|
||||||
.add_modifier(Modifier::BOLD),
|
.add_modifier(Modifier::BOLD),
|
||||||
)]),
|
)]),
|
||||||
Line::from(vec![
|
Line::from(vec![
|
||||||
Span::raw("Commands: "),
|
Span::raw("Keys: "),
|
||||||
Span::styled("q/Q", Style::default().fg(Color::Yellow)),
|
Span::styled("Space", Style::default().fg(Color::Yellow)),
|
||||||
Span::raw("=quit, "),
|
Span::raw("=pause/resume, "),
|
||||||
Span::styled("r/R", Style::default().fg(Color::Yellow)),
|
Span::styled("+/-", Style::default().fg(Color::Yellow)),
|
||||||
|
Span::raw("=freq, "),
|
||||||
|
Span::styled("r", Style::default().fg(Color::Yellow)),
|
||||||
Span::raw("=run, "),
|
Span::raw("=run, "),
|
||||||
Span::styled("s/S", Style::default().fg(Color::Yellow)),
|
Span::styled("s", Style::default().fg(Color::Yellow)),
|
||||||
Span::raw("=stop, "),
|
Span::raw("=stop, "),
|
||||||
Span::styled("h/H", Style::default().fg(Color::Yellow)),
|
Span::styled("h", Style::default().fg(Color::Yellow)),
|
||||||
Span::raw("=help"),
|
Span::raw("=help"),
|
||||||
]),
|
]),
|
||||||
];
|
];
|
||||||
|
|
@ -390,15 +572,23 @@ impl ConsoleApp {
|
||||||
// Right pane - RAM contents
|
// Right pane - RAM contents
|
||||||
self.draw_ram_contents(f, content_chunks[1]);
|
self.draw_ram_contents(f, content_chunks[1]);
|
||||||
|
|
||||||
// Command bar
|
// Command bar - show status message or command buffer
|
||||||
let command_text = if self.command_buffer.is_empty() {
|
let command_text = if let Some(ref msg) = self.status_message {
|
||||||
|
msg.as_str()
|
||||||
|
} else if self.command_buffer.is_empty() {
|
||||||
"Enter command (type 'h' for help)..."
|
"Enter command (type 'h' for help)..."
|
||||||
} else {
|
} else {
|
||||||
&self.command_buffer
|
&self.command_buffer
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let command_style = if self.status_message.is_some() {
|
||||||
|
Style::default().fg(Color::Yellow)
|
||||||
|
} else {
|
||||||
|
Style::default().fg(Color::White)
|
||||||
|
};
|
||||||
|
|
||||||
let command_bar = Paragraph::new(command_text)
|
let command_bar = Paragraph::new(command_text)
|
||||||
.style(Style::default().fg(Color::White))
|
.style(command_style)
|
||||||
.block(Block::default().borders(Borders::ALL).title("Command"));
|
.block(Block::default().borders(Borders::ALL).title("Command"));
|
||||||
f.render_widget(command_bar, chunks[2]);
|
f.render_widget(command_bar, chunks[2]);
|
||||||
}
|
}
|
||||||
|
|
@ -414,7 +604,7 @@ impl ConsoleApp {
|
||||||
)]),
|
)]),
|
||||||
Line::from(""),
|
Line::from(""),
|
||||||
Line::from(vec![Span::styled(
|
Line::from(vec![Span::styled(
|
||||||
"Commands:",
|
"System Control Commands:",
|
||||||
Style::default().add_modifier(Modifier::BOLD),
|
Style::default().add_modifier(Modifier::BOLD),
|
||||||
)]),
|
)]),
|
||||||
Line::from(vec![
|
Line::from(vec![
|
||||||
|
|
@ -422,22 +612,64 @@ impl ConsoleApp {
|
||||||
Span::raw(" - Exit emulator"),
|
Span::raw(" - Exit emulator"),
|
||||||
]),
|
]),
|
||||||
Line::from(vec![
|
Line::from(vec![
|
||||||
Span::styled(" r, run", Style::default().fg(Color::Yellow)),
|
Span::styled(" r, run, start", Style::default().fg(Color::Yellow)),
|
||||||
Span::raw(" - Start/stop system execution"),
|
Span::raw(" - Start system execution"),
|
||||||
]),
|
]),
|
||||||
Line::from(vec![
|
Line::from(vec![
|
||||||
Span::styled(" s, stop", Style::default().fg(Color::Yellow)),
|
Span::styled(" s, stop", Style::default().fg(Color::Yellow)),
|
||||||
Span::raw(" - Stop system execution"),
|
Span::raw(" - Stop system execution"),
|
||||||
]),
|
]),
|
||||||
Line::from(vec![
|
Line::from(vec![
|
||||||
Span::styled(" h, help", Style::default().fg(Color::Yellow)),
|
Span::styled(" p, pause", Style::default().fg(Color::Yellow)),
|
||||||
Span::raw(" - Show/hide this help"),
|
Span::raw(" - Pause system execution"),
|
||||||
|
]),
|
||||||
|
Line::from(vec![
|
||||||
|
Span::styled(" c, resume, continue", Style::default().fg(Color::Yellow)),
|
||||||
|
Span::raw(" - Resume from pause"),
|
||||||
]),
|
]),
|
||||||
Line::from(vec![
|
Line::from(vec![
|
||||||
Span::styled(" reset", Style::default().fg(Color::Yellow)),
|
Span::styled(" reset", Style::default().fg(Color::Yellow)),
|
||||||
Span::raw(" - Reset system"),
|
Span::raw(" - Reset system"),
|
||||||
]),
|
]),
|
||||||
Line::from(""),
|
Line::from(""),
|
||||||
|
Line::from(vec![Span::styled(
|
||||||
|
"Clock Control Commands:",
|
||||||
|
Style::default().add_modifier(Modifier::BOLD),
|
||||||
|
)]),
|
||||||
|
Line::from(vec![
|
||||||
|
Span::styled(" freq [Hz]", Style::default().fg(Color::Yellow)),
|
||||||
|
Span::raw(" - Get/set clock frequency"),
|
||||||
|
]),
|
||||||
|
Line::from(vec![
|
||||||
|
Span::styled(" clock [on|off]", Style::default().fg(Color::Yellow)),
|
||||||
|
Span::raw(" - Enable/disable clock"),
|
||||||
|
]),
|
||||||
|
Line::from(""),
|
||||||
|
Line::from(vec![Span::styled(
|
||||||
|
"Keyboard Shortcuts:",
|
||||||
|
Style::default().add_modifier(Modifier::BOLD),
|
||||||
|
)]),
|
||||||
|
Line::from(vec![
|
||||||
|
Span::styled(" Space", Style::default().fg(Color::Yellow)),
|
||||||
|
Span::raw(" - Toggle pause/resume"),
|
||||||
|
]),
|
||||||
|
Line::from(vec![
|
||||||
|
Span::styled(" +/=", Style::default().fg(Color::Yellow)),
|
||||||
|
Span::raw(" - Increase clock frequency by 20%"),
|
||||||
|
]),
|
||||||
|
Line::from(vec![
|
||||||
|
Span::styled(" -", Style::default().fg(Color::Yellow)),
|
||||||
|
Span::raw(" - Decrease clock frequency by 20%"),
|
||||||
|
]),
|
||||||
|
Line::from(vec![
|
||||||
|
Span::styled(" p", Style::default().fg(Color::Yellow)),
|
||||||
|
Span::raw(" - Pause system"),
|
||||||
|
]),
|
||||||
|
Line::from(vec![
|
||||||
|
Span::styled(" c", Style::default().fg(Color::Yellow)),
|
||||||
|
Span::raw(" - Resume (continue) system"),
|
||||||
|
]),
|
||||||
|
Line::from(""),
|
||||||
Line::from(vec![Span::styled(
|
Line::from(vec![Span::styled(
|
||||||
"Navigation:",
|
"Navigation:",
|
||||||
Style::default().add_modifier(Modifier::BOLD),
|
Style::default().add_modifier(Modifier::BOLD),
|
||||||
|
|
@ -454,6 +686,10 @@ impl ConsoleApp {
|
||||||
Span::styled(" Backspace", Style::default().fg(Color::Yellow)),
|
Span::styled(" Backspace", Style::default().fg(Color::Yellow)),
|
||||||
Span::raw(" - Delete character"),
|
Span::raw(" - Delete character"),
|
||||||
]),
|
]),
|
||||||
|
Line::from(vec![
|
||||||
|
Span::styled(" h, help", Style::default().fg(Color::Yellow)),
|
||||||
|
Span::raw(" - Show/hide this help"),
|
||||||
|
]),
|
||||||
Line::from(""),
|
Line::from(""),
|
||||||
Line::from(vec![Span::raw("Press any key to return to main view...")]),
|
Line::from(vec![Span::raw("Press any key to return to main view...")]),
|
||||||
];
|
];
|
||||||
|
|
@ -469,16 +705,27 @@ impl ConsoleApp {
|
||||||
let info_chunks = Layout::default()
|
let info_chunks = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.constraints([
|
.constraints([
|
||||||
Constraint::Length(8), // System info
|
Constraint::Length(10), // System info (increased for state display)
|
||||||
Constraint::Min(5), // Registers
|
Constraint::Min(5), // Registers
|
||||||
])
|
])
|
||||||
.split(area);
|
.split(area);
|
||||||
|
|
||||||
// System information - use try_lock to avoid blocking
|
// System information - use blocking lock, GUI can wait
|
||||||
let system_info = match self.system.try_lock() {
|
let system = self.system.lock();
|
||||||
Ok(system) => {
|
|
||||||
let info = system.get_system_info();
|
let info = system.get_system_info();
|
||||||
vec![
|
let state = system.get_system_state();
|
||||||
|
let freq = system.get_clock_frequency();
|
||||||
|
let clock_enabled = system.is_clock_enabled();
|
||||||
|
|
||||||
|
// Color code the state
|
||||||
|
let state_style = match state {
|
||||||
|
SystemState::Running => Style::default().fg(Color::Green),
|
||||||
|
SystemState::Paused => Style::default().fg(Color::Yellow),
|
||||||
|
SystemState::Stopped => Style::default().fg(Color::Red),
|
||||||
|
SystemState::Error => Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),
|
||||||
|
};
|
||||||
|
|
||||||
|
let system_info = vec![
|
||||||
Line::from(vec![Span::raw(format!("System: {}", info.name))]),
|
Line::from(vec![Span::raw(format!("System: {}", info.name))]),
|
||||||
Line::from(vec![Span::raw(format!(
|
Line::from(vec![Span::raw(format!(
|
||||||
"Description: {}",
|
"Description: {}",
|
||||||
|
|
@ -488,6 +735,22 @@ impl ConsoleApp {
|
||||||
"Components: {}",
|
"Components: {}",
|
||||||
info.component_count
|
info.component_count
|
||||||
))]),
|
))]),
|
||||||
|
Line::from(vec![
|
||||||
|
Span::raw("State: "),
|
||||||
|
Span::styled(format!("{:?}", state), state_style),
|
||||||
|
]),
|
||||||
|
Line::from(vec![
|
||||||
|
Span::raw("Clock: "),
|
||||||
|
Span::styled(
|
||||||
|
if clock_enabled { "ON" } else { "OFF" },
|
||||||
|
if clock_enabled {
|
||||||
|
Style::default().fg(Color::Green)
|
||||||
|
} else {
|
||||||
|
Style::default().fg(Color::Red)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Span::raw(format!(" @ {:.2} Hz", freq)),
|
||||||
|
]),
|
||||||
Line::from(vec![Span::raw(format!("CPU Speed: {} Hz", info.cpu_speed))]),
|
Line::from(vec![Span::raw(format!("CPU Speed: {} Hz", info.cpu_speed))]),
|
||||||
Line::from(vec![Span::raw(format!(
|
Line::from(vec![Span::raw(format!(
|
||||||
"ROM Size: {} bytes",
|
"ROM Size: {} bytes",
|
||||||
|
|
@ -497,20 +760,8 @@ impl ConsoleApp {
|
||||||
"RAM Size: {} nibbles",
|
"RAM Size: {} nibbles",
|
||||||
info.ram_size
|
info.ram_size
|
||||||
))]),
|
))]),
|
||||||
Line::from(vec![Span::raw(format!("Running: {}", system.is_running()))]),
|
];
|
||||||
]
|
drop(system); // Release lock before rendering
|
||||||
}
|
|
||||||
Err(std::sync::TryLockError::WouldBlock) => {
|
|
||||||
vec![Line::from(vec![Span::raw(
|
|
||||||
"System busy (emulation running)...",
|
|
||||||
)])]
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
vec![Line::from(vec![Span::raw(
|
|
||||||
"System information unavailable",
|
|
||||||
)])]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let system_widget = Paragraph::new(system_info)
|
let system_widget = Paragraph::new(system_info)
|
||||||
.block(
|
.block(
|
||||||
|
|
@ -542,10 +793,11 @@ impl ConsoleApp {
|
||||||
fn get_cpu_register_info(&self) -> Vec<Line<'static>> {
|
fn get_cpu_register_info(&self) -> Vec<Line<'static>> {
|
||||||
let mut register_info = vec![Line::from(vec![Span::raw("CPU Registers:")])];
|
let mut register_info = vec![Line::from(vec![Span::raw("CPU Registers:")])];
|
||||||
|
|
||||||
match self.system.try_lock() {
|
// Use blocking lock - GUI can wait for simulation
|
||||||
Ok(system) => {
|
let system = self.system.lock();
|
||||||
// Try to find Intel 4004 CPU and get register data
|
// Try to find Intel 4004 CPU and get register data
|
||||||
let cpu_data = system.get_cpu_register_data();
|
let cpu_data = system.get_cpu_register_data();
|
||||||
|
drop(system); // Release lock after getting data
|
||||||
|
|
||||||
if let Some(cpu) = cpu_data {
|
if let Some(cpu) = cpu_data {
|
||||||
let fmt = &self.config.display_format;
|
let fmt = &self.config.display_format;
|
||||||
|
|
@ -582,28 +834,35 @@ impl ConsoleApp {
|
||||||
} else {
|
} else {
|
||||||
register_info.push(Line::from(vec![Span::raw("No Intel 4004 CPU found")]));
|
register_info.push(Line::from(vec![Span::raw("No Intel 4004 CPU found")]));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Err(std::sync::TryLockError::WouldBlock) => {
|
|
||||||
register_info.push(Line::from(vec![Span::raw(
|
|
||||||
"CPU data temporarily unavailable (system busy)...",
|
|
||||||
)]));
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
register_info.push(Line::from(vec![Span::raw("Unable to access CPU data")]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
register_info
|
register_info
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_ram_contents(&self, f: &mut Frame, area: Rect) {
|
fn draw_ram_contents(&self, f: &mut Frame, area: Rect) {
|
||||||
|
// Split area into RAM and ROM sections
|
||||||
|
let memory_chunks = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints([
|
||||||
|
Constraint::Percentage(50), // RAM
|
||||||
|
Constraint::Percentage(50), // ROM
|
||||||
|
])
|
||||||
|
.split(area);
|
||||||
|
|
||||||
|
// Draw RAM contents
|
||||||
|
self.draw_ram_panel(f, memory_chunks[0]);
|
||||||
|
|
||||||
|
// Draw ROM contents
|
||||||
|
self.draw_rom_panel(f, memory_chunks[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_ram_panel(&self, f: &mut Frame, area: Rect) {
|
||||||
let mut ram_info = vec![Line::from(vec![Span::raw("RAM Contents:")])];
|
let mut ram_info = vec![Line::from(vec![Span::raw("RAM Contents:")])];
|
||||||
|
|
||||||
// Try to get actual RAM data from the system using try_lock
|
// Get actual RAM data from the system using blocking lock
|
||||||
if self.config.show_ram {
|
if self.config.show_ram {
|
||||||
match self.system.try_lock() {
|
let system = self.system.lock();
|
||||||
Ok(system) => {
|
|
||||||
let ram_data = system.get_ram_data(self.config.max_ram_rows);
|
let ram_data = system.get_ram_data(self.config.max_ram_rows);
|
||||||
|
drop(system); // Release lock after getting data
|
||||||
|
|
||||||
if ram_data.is_empty() {
|
if ram_data.is_empty() {
|
||||||
ram_info.push(Line::from(vec![Span::raw("No RAM chips found")]));
|
ram_info.push(Line::from(vec![Span::raw("No RAM chips found")]));
|
||||||
|
|
@ -631,16 +890,6 @@ impl ConsoleApp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Err(std::sync::TryLockError::WouldBlock) => {
|
|
||||||
ram_info.push(Line::from(vec![Span::raw(
|
|
||||||
"RAM data temporarily unavailable (system busy)...",
|
|
||||||
)]));
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
ram_info.push(Line::from(vec![Span::raw("Unable to access RAM data")]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
ram_info.push(Line::from(vec![Span::raw("RAM display disabled")]));
|
ram_info.push(Line::from(vec![Span::raw("RAM display disabled")]));
|
||||||
}
|
}
|
||||||
|
|
@ -650,6 +899,59 @@ impl ConsoleApp {
|
||||||
.wrap(Wrap { trim: true });
|
.wrap(Wrap { trim: true });
|
||||||
f.render_widget(ram_widget, area);
|
f.render_widget(ram_widget, area);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn draw_rom_panel(&self, f: &mut Frame, area: Rect) {
|
||||||
|
let mut rom_info = vec![Line::from(vec![Span::raw("ROM Contents:")])];
|
||||||
|
|
||||||
|
// Get actual ROM data from the system using blocking lock
|
||||||
|
if self.config.show_rom {
|
||||||
|
let system = self.system.lock();
|
||||||
|
let rom_data = system.get_rom_data(self.config.max_rom_rows);
|
||||||
|
drop(system); // Release lock after getting data
|
||||||
|
|
||||||
|
if rom_data.is_empty() {
|
||||||
|
rom_info.push(Line::from(vec![Span::raw("No ROM chips found")]));
|
||||||
|
} else {
|
||||||
|
let fmt = &self.config.display_format;
|
||||||
|
|
||||||
|
for (chip_name, data) in rom_data {
|
||||||
|
// Clone chip_name to avoid lifetime issues
|
||||||
|
let chip_name = chip_name.clone();
|
||||||
|
rom_info.push(Line::from(vec![Span::styled(
|
||||||
|
chip_name,
|
||||||
|
Style::default().fg(Color::Cyan),
|
||||||
|
)]));
|
||||||
|
|
||||||
|
// Display ROM data in rows of 8 bytes
|
||||||
|
let mut addr = 0;
|
||||||
|
for chunk in data.chunks(8) {
|
||||||
|
let mut row = format!(" {:03X}: ", addr);
|
||||||
|
for (i, byte) in chunk.iter().enumerate() {
|
||||||
|
if i > 0 {
|
||||||
|
row.push(' ');
|
||||||
|
}
|
||||||
|
row.push_str(&fmt.format_byte(*byte));
|
||||||
|
}
|
||||||
|
rom_info.push(Line::from(vec![Span::raw(row)]));
|
||||||
|
addr += 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show total size
|
||||||
|
rom_info.push(Line::from(vec![Span::styled(
|
||||||
|
format!(" ({} bytes total)", data.len()),
|
||||||
|
Style::default().fg(Color::DarkGray),
|
||||||
|
)]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rom_info.push(Line::from(vec![Span::raw("ROM display disabled")]));
|
||||||
|
}
|
||||||
|
|
||||||
|
let rom_widget = Paragraph::new(rom_info)
|
||||||
|
.block(Block::default().borders(Borders::ALL).title("ROM Contents"))
|
||||||
|
.wrap(Wrap { trim: true });
|
||||||
|
f.render_widget(rom_widget, area);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Public interface for launching the console
|
/// Public interface for launching the console
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,8 @@ pub mod state;
|
||||||
|
|
||||||
use crate::system_config::ConfigurableSystem;
|
use crate::system_config::ConfigurableSystem;
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
use std::sync::{Arc, Mutex};
|
use parking_lot::Mutex;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// Main GUI application structure
|
/// Main GUI application structure
|
||||||
///
|
///
|
||||||
|
|
@ -93,7 +94,8 @@ impl GuiApp {
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```rust,no_run
|
/// ```rust,no_run
|
||||||
/// use std::sync::{Arc, Mutex};
|
/// use parking_lot::Mutex;
|
||||||
|
/// use std::sync::Arc;
|
||||||
/// use rusty_emu::gui::GuiApp;
|
/// use rusty_emu::gui::GuiApp;
|
||||||
/// use rusty_emu::system_config::SystemFactory;
|
/// use rusty_emu::system_config::SystemFactory;
|
||||||
///
|
///
|
||||||
|
|
@ -148,10 +150,9 @@ impl eframe::App for GuiApp {
|
||||||
if ctrl_q_pressed {
|
if ctrl_q_pressed {
|
||||||
// Stop the system if available
|
// Stop the system if available
|
||||||
if let Some(system) = self.get_system() {
|
if let Some(system) = self.get_system() {
|
||||||
if let Ok(mut sys) = system.lock() {
|
let mut sys = system.lock();
|
||||||
sys.stop();
|
sys.stop();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// Close the GUI window
|
// Close the GUI window
|
||||||
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
||||||
return;
|
return;
|
||||||
|
|
@ -192,9 +193,19 @@ impl GuiApp {
|
||||||
|
|
||||||
// Main GUI sections - organized for optimal workflow
|
// Main GUI sections - organized for optimal workflow
|
||||||
self.components
|
self.components
|
||||||
.render_control_panel(ui, &mut self.gui_state);
|
.render_control_panel(ui, &mut self.gui_state, self.system.as_ref());
|
||||||
|
|
||||||
|
// Memory section: RAM and ROM side by side
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.vertical(|ui| {
|
||||||
self.components
|
self.components
|
||||||
.render_memory_viewer(ui, &mut self.gui_state);
|
.render_memory_viewer(ui, &mut self.gui_state);
|
||||||
|
});
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
self.components.render_rom_viewer(ui, &mut self.gui_state);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
self.components
|
self.components
|
||||||
.render_register_viewer(ui, &mut self.gui_state);
|
.render_register_viewer(ui, &mut self.gui_state);
|
||||||
self.components.render_status_bar(ui, &self.gui_state);
|
self.components.render_status_bar(ui, &self.gui_state);
|
||||||
|
|
@ -222,7 +233,8 @@ impl GuiApp {
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```rust,no_run
|
/// ```rust,no_run
|
||||||
/// use std::sync::{Arc, Mutex};
|
/// use parking_lot::Mutex;
|
||||||
|
/// use std::sync::Arc;
|
||||||
/// use rusty_emu::gui::run_gui;
|
/// use rusty_emu::gui::run_gui;
|
||||||
/// use rusty_emu::system_config::SystemFactory;
|
/// use rusty_emu::system_config::SystemFactory;
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,8 @@
|
||||||
//! to emulator state for real-time GUI updates.
|
//! to emulator state for real-time GUI updates.
|
||||||
|
|
||||||
use crate::system_config::ConfigurableSystem;
|
use crate::system_config::ConfigurableSystem;
|
||||||
use std::sync::{Arc, Mutex};
|
use parking_lot::Mutex;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// GUI state structure containing all UI-relevant data
|
/// GUI state structure containing all UI-relevant data
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -122,7 +123,7 @@ impl GuiState {
|
||||||
|
|
||||||
/// Update state from the current system
|
/// Update state from the current system
|
||||||
pub fn update_from_system(&mut self, system: &Arc<Mutex<ConfigurableSystem>>) {
|
pub fn update_from_system(&mut self, system: &Arc<Mutex<ConfigurableSystem>>) {
|
||||||
if let Ok(system_guard) = system.lock() {
|
let system_guard = system.lock();
|
||||||
// Update basic system state
|
// Update basic system state
|
||||||
self.system_running = system_guard.is_running();
|
self.system_running = system_guard.is_running();
|
||||||
|
|
||||||
|
|
@ -148,35 +149,29 @@ impl GuiState {
|
||||||
self.cycle_count += 1;
|
self.cycle_count += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Update component running states
|
/// Update component running states
|
||||||
/// Uses try_lock() to avoid potential deadlocks when multiple threads
|
/// Uses blocking locks for consistent GUI data - GUI can wait for simulation
|
||||||
/// access components in different orders
|
|
||||||
fn update_component_states(&mut self, system: &ConfigurableSystem) {
|
fn update_component_states(&mut self, system: &ConfigurableSystem) {
|
||||||
let components = system.get_components();
|
let components = system.get_components();
|
||||||
|
|
||||||
// Use try_lock() to avoid blocking - if we can't get the lock immediately,
|
// Use blocking locks for consistent data - GUI can wait for simulation
|
||||||
// just assume the component is not running (safe default for GUI display)
|
self.component_states.cpu_running = components
|
||||||
self.component_states.cpu_running = components.get("CPU_4004").map_or(false, |comp| {
|
.get("CPU_4004")
|
||||||
comp.try_lock().map_or(false, |c| c.is_running())
|
.map_or(false, |comp| comp.lock().is_running());
|
||||||
});
|
|
||||||
|
|
||||||
self.component_states.ram_running = components.get("RAM_4002").map_or(false, |comp| {
|
self.component_states.ram_running = components
|
||||||
comp.try_lock().map_or(false, |c| c.is_running())
|
.get("RAM_4002")
|
||||||
});
|
.map_or(false, |comp| comp.lock().is_running());
|
||||||
|
|
||||||
self.component_states.rom_running = components
|
self.component_states.rom_running = components
|
||||||
.get("ROM_4001_1")
|
.get("ROM_4001_1")
|
||||||
.or_else(|| components.get("ROM_4001_2"))
|
.or_else(|| components.get("ROM_4001_2"))
|
||||||
.map_or(false, |comp| {
|
.map_or(false, |comp| comp.lock().is_running());
|
||||||
comp.try_lock().map_or(false, |c| c.is_running())
|
|
||||||
});
|
|
||||||
|
|
||||||
self.component_states.clock_running =
|
self.component_states.clock_running = components
|
||||||
components.get("SYSTEM_CLOCK").map_or(false, |comp| {
|
.get("SYSTEM_CLOCK")
|
||||||
comp.try_lock().map_or(false, |c| c.is_running())
|
.map_or(false, |comp| comp.lock().is_running());
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update CPU register state from real component data
|
/// Update CPU register state from real component data
|
||||||
|
|
|
||||||
43
src/main.rs
43
src/main.rs
|
|
@ -68,13 +68,14 @@
|
||||||
//! cargo run -- --help
|
//! cargo run -- --help
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
|
use parking_lot::Mutex;
|
||||||
use rusty_emu::gui::console::{run_console, ConsoleConfig};
|
use rusty_emu::gui::console::{run_console, ConsoleConfig};
|
||||||
use rusty_emu::gui::run_gui;
|
use rusty_emu::gui::run_gui;
|
||||||
use rusty_emu::system_config::{ConfigurableSystem, SystemFactory};
|
use rusty_emu::system_config::{ConfigurableSystem, SystemFactory};
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::process;
|
use std::process;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::Arc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
|
@ -208,7 +209,8 @@ fn main() {
|
||||||
|
|
||||||
// Start the emulator system using start() which doesn't block
|
// Start the emulator system using start() which doesn't block
|
||||||
// This allows the console interface to access the system while emulation runs
|
// This allows the console interface to access the system while emulation runs
|
||||||
if let Ok(mut system) = system_arc.lock() {
|
{
|
||||||
|
let mut system = system_arc.lock();
|
||||||
println!("DEBUG: Starting system for console mode (non-blocking)");
|
println!("DEBUG: Starting system for console mode (non-blocking)");
|
||||||
system.start();
|
system.start();
|
||||||
}
|
}
|
||||||
|
|
@ -228,7 +230,8 @@ fn main() {
|
||||||
|
|
||||||
// Start the emulator system using start() which doesn't block
|
// Start the emulator system using start() which doesn't block
|
||||||
// This allows the GUI to remain responsive while emulation runs
|
// This allows the GUI to remain responsive while emulation runs
|
||||||
if let Ok(mut system) = system_arc.lock() {
|
{
|
||||||
|
let mut system = system_arc.lock();
|
||||||
println!("DEBUG: Starting system for GUI mode (non-blocking)");
|
println!("DEBUG: Starting system for GUI mode (non-blocking)");
|
||||||
system.start();
|
system.start();
|
||||||
}
|
}
|
||||||
|
|
@ -459,13 +462,12 @@ fn run_system_demo(system: ConfigurableSystem) {
|
||||||
println!("CPU Speed: {} Hz", info.cpu_speed);
|
println!("CPU Speed: {} Hz", info.cpu_speed);
|
||||||
|
|
||||||
// Run system in a separate thread
|
// Run system in a separate thread
|
||||||
let system_arc = std::sync::Arc::new(std::sync::Mutex::new(system));
|
let system_arc = Arc::new(Mutex::new(system));
|
||||||
let system_clone = system_arc.clone();
|
let system_clone = system_arc.clone();
|
||||||
|
|
||||||
let handle = thread::spawn(move || {
|
let handle = thread::spawn(move || {
|
||||||
if let Ok(mut system) = system_clone.lock() {
|
let mut system = system_clone.lock();
|
||||||
system.run();
|
system.run();
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Start monitoring in a separate thread
|
// Start monitoring in a separate thread
|
||||||
|
|
@ -487,16 +489,18 @@ fn run_system_demo(system: ConfigurableSystem) {
|
||||||
// Check for timeout
|
// Check for timeout
|
||||||
if start_time.elapsed() >= timeout {
|
if start_time.elapsed() >= timeout {
|
||||||
println!("\nSimulation timed out after 10 seconds - stopping system");
|
println!("\nSimulation timed out after 10 seconds - stopping system");
|
||||||
if let Ok(mut system) = system_arc.lock() {
|
{
|
||||||
|
let mut system = system_arc.lock();
|
||||||
system.stop();
|
system.stop();
|
||||||
}
|
}
|
||||||
*running.lock().unwrap() = false;
|
*running.lock() = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(system) = system_arc.lock() {
|
{
|
||||||
|
let system = system_arc.lock();
|
||||||
if !system.is_running() {
|
if !system.is_running() {
|
||||||
*running.lock().unwrap() = false;
|
*running.lock() = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -529,10 +533,8 @@ fn monitor_system_state(system_arc: Arc<Mutex<ConfigurableSystem>>, running_arc:
|
||||||
thread::sleep(Duration::from_millis(100)); // Reasonable interval for console monitoring
|
thread::sleep(Duration::from_millis(100)); // Reasonable interval for console monitoring
|
||||||
|
|
||||||
// Check if we should still be running (fast check)
|
// Check if we should still be running (fast check)
|
||||||
let should_continue = match running_arc.lock() {
|
// parking_lot::Mutex::lock() returns MutexGuard directly (no Result)
|
||||||
Ok(running_val) => *running_val,
|
let should_continue = *running_arc.lock();
|
||||||
Err(_) => true, // Continue if lock is poisoned
|
|
||||||
};
|
|
||||||
|
|
||||||
if !should_continue {
|
if !should_continue {
|
||||||
break;
|
break;
|
||||||
|
|
@ -542,12 +544,13 @@ fn monitor_system_state(system_arc: Arc<Mutex<ConfigurableSystem>>, running_arc:
|
||||||
|
|
||||||
// High-frequency monitoring: try_lock for immediate availability
|
// High-frequency monitoring: try_lock for immediate availability
|
||||||
// This matches MCS-4's 11µs cycle timing without blocking emulation
|
// This matches MCS-4's 11µs cycle timing without blocking emulation
|
||||||
|
// parking_lot::Mutex::try_lock() returns Option<MutexGuard>
|
||||||
match system_arc.try_lock() {
|
match system_arc.try_lock() {
|
||||||
Ok(system) => {
|
Some(system) => {
|
||||||
// Got the lock immediately - show detailed state (rare but possible)
|
// Got the lock immediately - show detailed state (rare but possible)
|
||||||
display_detailed_system_state(&system, cycle);
|
display_detailed_system_state(&system, cycle);
|
||||||
}
|
}
|
||||||
Err(_) => {
|
None => {
|
||||||
// System is locked by emulation - this is normal and expected
|
// System is locked by emulation - this is normal and expected
|
||||||
// Show basic state without trying to acquire locks
|
// Show basic state without trying to acquire locks
|
||||||
display_basic_system_state(cycle);
|
display_basic_system_state(cycle);
|
||||||
|
|
@ -573,7 +576,7 @@ fn display_detailed_system_state(system: &ConfigurableSystem, cycle: u32) {
|
||||||
|
|
||||||
// CPU State Section
|
// CPU State Section
|
||||||
if let Some(cpu_component) = system.get_components().get("CPU_4004") {
|
if let Some(cpu_component) = system.get_components().get("CPU_4004") {
|
||||||
if let Ok(cpu) = cpu_component.lock() {
|
let cpu = cpu_component.lock();
|
||||||
println!("│ CPU STATE: │");
|
println!("│ CPU STATE: │");
|
||||||
println!(
|
println!(
|
||||||
"│ Status: {} │",
|
"│ Status: {} │",
|
||||||
|
|
@ -587,14 +590,13 @@ fn display_detailed_system_state(system: &ConfigurableSystem, cycle: u32) {
|
||||||
"│ Component: {} │",
|
"│ Component: {} │",
|
||||||
cpu.name()
|
cpu.name()
|
||||||
);
|
);
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
println!("│ CPU_4004 component not found │");
|
println!("│ CPU_4004 component not found │");
|
||||||
}
|
}
|
||||||
|
|
||||||
// RAM Section
|
// RAM Section
|
||||||
if let Some(ram_component) = system.get_components().get("RAM_4002") {
|
if let Some(ram_component) = system.get_components().get("RAM_4002") {
|
||||||
if let Ok(ram) = ram_component.lock() {
|
let ram = ram_component.lock();
|
||||||
println!(
|
println!(
|
||||||
"│ RAM_4002: {} ({}) │",
|
"│ RAM_4002: {} ({}) │",
|
||||||
ram.name(),
|
ram.name(),
|
||||||
|
|
@ -605,13 +607,12 @@ fn display_detailed_system_state(system: &ConfigurableSystem, cycle: u32) {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Component Status Summary
|
// Component Status Summary
|
||||||
let running_count = system
|
let running_count = system
|
||||||
.get_components()
|
.get_components()
|
||||||
.values()
|
.values()
|
||||||
.filter(|comp| comp.lock().map_or(false, |c| c.is_running()))
|
.filter(|comp| comp.lock().is_running())
|
||||||
.count();
|
.count();
|
||||||
println!("│ │");
|
println!("│ │");
|
||||||
println!(
|
println!(
|
||||||
|
|
|
||||||
14
src/pin.rs
14
src/pin.rs
|
|
@ -1,6 +1,7 @@
|
||||||
|
use parking_lot::Mutex;
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::Arc;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
|
@ -188,13 +189,12 @@ impl Pin {
|
||||||
};
|
};
|
||||||
|
|
||||||
for connected_pin in &self.connected_pins {
|
for connected_pin in &self.connected_pins {
|
||||||
if let Ok(mut pin) = connected_pin.lock() {
|
let mut pin = connected_pin.lock();
|
||||||
// Copy all our drivers with their strengths
|
// Copy all our drivers with their strengths
|
||||||
for (driver_name, (value, strength)) in &self.drivers {
|
for (driver_name, (value, strength)) in &self.drivers {
|
||||||
pin.set_driver_with_strength(Some(driver_name.clone()), *value, *strength);
|
pin.set_driver_with_strength(Some(driver_name.clone()), *value, *strength);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Guard will be dropped here, resetting is_propagating to false
|
// Guard will be dropped here, resetting is_propagating to false
|
||||||
}
|
}
|
||||||
|
|
@ -303,8 +303,6 @@ impl std::fmt::Display for Pin {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use std::sync::Arc;
|
|
||||||
use std::sync::Mutex;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_pin_creation() {
|
fn test_pin_creation() {
|
||||||
|
|
@ -359,13 +357,13 @@ mod tests {
|
||||||
|
|
||||||
// Connect pins
|
// Connect pins
|
||||||
{
|
{
|
||||||
let mut p1 = pin1.lock().unwrap();
|
let mut p1 = pin1.lock();
|
||||||
p1.connect_to(pin2.clone());
|
p1.connect_to(pin2.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drive pin1
|
// Drive pin1
|
||||||
{
|
{
|
||||||
let mut p1 = pin1.lock().unwrap();
|
let mut p1 = pin1.lock();
|
||||||
p1.set_driver(Some("test".to_string()), PinValue::High);
|
p1.set_driver(Some("test".to_string()), PinValue::High);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -373,7 +371,7 @@ mod tests {
|
||||||
std::thread::sleep(Duration::from_millis(1));
|
std::thread::sleep(Duration::from_millis(1));
|
||||||
|
|
||||||
// Check pin2
|
// Check pin2
|
||||||
let p2 = pin2.lock().unwrap();
|
let p2 = pin2.lock();
|
||||||
assert_eq!(p2.read(), PinValue::High);
|
assert_eq!(p2.read(), PinValue::High);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,9 +53,11 @@ use crate::component::Component;
|
||||||
use crate::components::cpu::intel_4004::Intel4004;
|
use crate::components::cpu::intel_4004::Intel4004;
|
||||||
use crate::components::memory::intel_4001::Intel4001;
|
use crate::components::memory::intel_4001::Intel4001;
|
||||||
use crate::components::memory::intel_4002::Intel4002;
|
use crate::components::memory::intel_4002::Intel4002;
|
||||||
|
use parking_lot::Mutex;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
/// CPU register data for display purposes
|
/// CPU register data for display purposes
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
|
|
@ -73,6 +75,74 @@ pub type RamData = Vec<(String, Vec<Vec<u8>>)>;
|
||||||
/// ROM data for display purposes (chip name -> bytes)
|
/// ROM data for display purposes (chip name -> bytes)
|
||||||
pub type RomData = Vec<(String, Vec<u8>)>;
|
pub type RomData = Vec<(String, Vec<u8>)>;
|
||||||
|
|
||||||
|
/// Maximum allowed clock frequency (10 MHz)
|
||||||
|
pub const MAX_CLOCK_FREQUENCY: f64 = 10_000_000.0;
|
||||||
|
|
||||||
|
/// Minimum allowed clock frequency (1 Hz)
|
||||||
|
pub const MIN_CLOCK_FREQUENCY: f64 = 1.0;
|
||||||
|
|
||||||
|
/// System execution state
|
||||||
|
/// Tracks the overall state of the system for control and monitoring
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub enum SystemState {
|
||||||
|
/// System is stopped - no components are running
|
||||||
|
Stopped,
|
||||||
|
/// System is running normally
|
||||||
|
Running,
|
||||||
|
/// System is paused - components are frozen but can be resumed
|
||||||
|
Paused,
|
||||||
|
/// System encountered an error
|
||||||
|
Error,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for SystemState {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
SystemState::Stopped => write!(f, "Stopped"),
|
||||||
|
SystemState::Running => write!(f, "Running"),
|
||||||
|
SystemState::Paused => write!(f, "Paused"),
|
||||||
|
SystemState::Error => write!(f, "Error"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// System controller trait for comprehensive system management
|
||||||
|
/// Provides start/stop/pause/resume functionality and clock control
|
||||||
|
pub trait SystemController {
|
||||||
|
/// Start the system from a stopped or paused state
|
||||||
|
fn start(&mut self) -> Result<(), String>;
|
||||||
|
|
||||||
|
/// Stop the system completely
|
||||||
|
fn stop(&mut self) -> Result<(), String>;
|
||||||
|
|
||||||
|
/// Pause the system - freeze all components
|
||||||
|
fn pause(&mut self) -> Result<(), String>;
|
||||||
|
|
||||||
|
/// Resume the system from a paused state
|
||||||
|
fn resume(&mut self) -> Result<(), String>;
|
||||||
|
|
||||||
|
/// Check if the system is currently running
|
||||||
|
fn is_running(&self) -> bool;
|
||||||
|
|
||||||
|
/// Check if the system is paused
|
||||||
|
fn is_paused(&self) -> bool;
|
||||||
|
|
||||||
|
/// Get the current system state
|
||||||
|
fn get_system_state(&self) -> SystemState;
|
||||||
|
|
||||||
|
/// Set the clock frequency for all clock components
|
||||||
|
fn set_clock_frequency(&mut self, frequency: f64) -> Result<(), String>;
|
||||||
|
|
||||||
|
/// Get the current clock frequency
|
||||||
|
fn get_clock_frequency(&self) -> f64;
|
||||||
|
|
||||||
|
/// Enable or disable the clock
|
||||||
|
fn set_clock_enabled(&mut self, enabled: bool) -> Result<(), String>;
|
||||||
|
|
||||||
|
/// Check if the clock is enabled
|
||||||
|
fn is_clock_enabled(&self) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
/// JSON-based system configuration structures
|
/// JSON-based system configuration structures
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct SystemConfig {
|
pub struct SystemConfig {
|
||||||
|
|
@ -348,7 +418,7 @@ impl SystemFactory {
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let source_pin = {
|
let source_pin = {
|
||||||
let component = source_component.lock().unwrap();
|
let component = source_component.lock();
|
||||||
component
|
component
|
||||||
.get_pin(&connection_config.source.pin)
|
.get_pin(&connection_config.source.pin)
|
||||||
.map_err(|e| format!("Failed to get source pin: {}", e))?
|
.map_err(|e| format!("Failed to get source pin: {}", e))?
|
||||||
|
|
@ -361,14 +431,14 @@ impl SystemFactory {
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let target_pin = {
|
let target_pin = {
|
||||||
let component = target_component.lock().unwrap();
|
let component = target_component.lock();
|
||||||
component
|
component
|
||||||
.get_pin(&target_ref.pin)
|
.get_pin(&target_ref.pin)
|
||||||
.map_err(|e| format!("Failed to get target pin: {}", e))?
|
.map_err(|e| format!("Failed to get target pin: {}", e))?
|
||||||
};
|
};
|
||||||
|
|
||||||
// Connect the pins - source connects to target for proper signal flow
|
// Connect the pins - source connects to target for proper signal flow
|
||||||
let mut source_pin_guard = source_pin.lock().unwrap();
|
let mut source_pin_guard = source_pin.lock();
|
||||||
source_pin_guard.connect_to(target_pin.clone());
|
source_pin_guard.connect_to(target_pin.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -385,6 +455,9 @@ pub struct ConfigurableSystem {
|
||||||
config: SystemConfig,
|
config: SystemConfig,
|
||||||
components: HashMap<String, Arc<Mutex<Box<dyn Component>>>>,
|
components: HashMap<String, Arc<Mutex<Box<dyn Component>>>>,
|
||||||
is_running: Arc<Mutex<bool>>,
|
is_running: Arc<Mutex<bool>>,
|
||||||
|
is_paused: Arc<Mutex<bool>>,
|
||||||
|
clock_frequency: Arc<Mutex<f64>>,
|
||||||
|
clock_enabled: Arc<Mutex<bool>>,
|
||||||
component_handles: Arc<Mutex<Option<ComponentHandles>>>,
|
component_handles: Arc<Mutex<Option<ComponentHandles>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -393,10 +466,20 @@ impl ConfigurableSystem {
|
||||||
config: SystemConfig,
|
config: SystemConfig,
|
||||||
components: HashMap<String, Arc<Mutex<Box<dyn Component>>>>,
|
components: HashMap<String, Arc<Mutex<Box<dyn Component>>>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
// Extract initial clock frequency from metadata
|
||||||
|
let initial_frequency = config
|
||||||
|
.metadata
|
||||||
|
.get("cpu_speed")
|
||||||
|
.and_then(|v| v.as_f64())
|
||||||
|
.unwrap_or(750000.0);
|
||||||
|
|
||||||
ConfigurableSystem {
|
ConfigurableSystem {
|
||||||
config,
|
config,
|
||||||
components,
|
components,
|
||||||
is_running: Arc::new(Mutex::new(false)),
|
is_running: Arc::new(Mutex::new(false)),
|
||||||
|
is_paused: Arc::new(Mutex::new(false)),
|
||||||
|
clock_frequency: Arc::new(Mutex::new(initial_frequency)),
|
||||||
|
clock_enabled: Arc::new(Mutex::new(true)),
|
||||||
component_handles: Arc::new(Mutex::new(None)),
|
component_handles: Arc::new(Mutex::new(None)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -407,7 +490,7 @@ impl ConfigurableSystem {
|
||||||
/// allowing the caller to retain access to the system via the mutex.
|
/// allowing the caller to retain access to the system via the mutex.
|
||||||
/// The system will continue running in the background until `stop()` is called.
|
/// The system will continue running in the background until `stop()` is called.
|
||||||
pub fn start(&mut self) {
|
pub fn start(&mut self) {
|
||||||
let mut is_running = self.is_running.lock().unwrap();
|
let mut is_running = self.is_running.lock();
|
||||||
if *is_running {
|
if *is_running {
|
||||||
println!("System is already running");
|
println!("System is already running");
|
||||||
return;
|
return;
|
||||||
|
|
@ -420,31 +503,80 @@ impl ConfigurableSystem {
|
||||||
|
|
||||||
let mut handles = vec![];
|
let mut handles = vec![];
|
||||||
let is_running_clone = Arc::clone(&self.is_running);
|
let is_running_clone = Arc::clone(&self.is_running);
|
||||||
|
let is_paused_clone = Arc::clone(&self.is_paused);
|
||||||
|
|
||||||
|
let clock_frequency_clone = Arc::clone(&self.clock_frequency);
|
||||||
|
|
||||||
for (name, component) in &self.components {
|
for (name, component) in &self.components {
|
||||||
let comp_clone = Arc::clone(component);
|
let comp_clone = Arc::clone(component);
|
||||||
let name_clone = name.clone();
|
|
||||||
let running_clone = Arc::clone(&is_running_clone);
|
let running_clone = Arc::clone(&is_running_clone);
|
||||||
|
let paused_clone = Arc::clone(&is_paused_clone);
|
||||||
|
let frequency_clone = Arc::clone(&clock_frequency_clone);
|
||||||
|
|
||||||
let handle = std::thread::spawn(move || {
|
let handle = std::thread::spawn(move || {
|
||||||
println!("DEBUG: Starting component thread: {}", name_clone);
|
// Run loop that doesn't hold the lock continuously
|
||||||
if let Ok(mut comp) = comp_clone.lock() {
|
// This allows other threads (like cleanup) to access the component
|
||||||
println!("DEBUG: Running component: {}", name_clone);
|
loop {
|
||||||
comp.run();
|
// Check system-level running flag first
|
||||||
}
|
let should_continue = *running_clone.lock();
|
||||||
println!("DEBUG: Component {} stopped", name_clone);
|
|
||||||
|
|
||||||
// Signal that system should stop when any component stops
|
if !should_continue {
|
||||||
if let Ok(mut running) = running_clone.lock() {
|
break;
|
||||||
*running = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for pause state - skip update but keep thread alive
|
||||||
|
let is_paused = *paused_clone.lock();
|
||||||
|
|
||||||
|
if is_paused {
|
||||||
|
// While paused, sleep longer to reduce CPU usage
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use immediate try_lock() for component threads
|
||||||
|
// This ensures simulation threads never wait for GUI
|
||||||
|
// GUI uses blocking locks and updates at 100 Hz
|
||||||
|
if let Some(mut comp) = comp_clone.try_lock() {
|
||||||
|
// Ensure component is in running state before calling update()
|
||||||
|
// This is needed because many components check is_running() at the start
|
||||||
|
// of update() and return early if false
|
||||||
|
if !comp.is_running() {
|
||||||
|
comp.set_running(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call update() for one simulation cycle
|
||||||
|
comp.update();
|
||||||
|
}
|
||||||
|
// If lock is held by GUI, skip this cycle and try again immediately
|
||||||
|
// This prioritizes simulation performance over GUI updates
|
||||||
|
|
||||||
|
// Adaptive sleep based on clock frequency
|
||||||
|
// For high-frequency clocks (>100kHz), use yield instead of sleep
|
||||||
|
// For lower frequencies, calculate appropriate sleep duration
|
||||||
|
let frequency = *frequency_clone.lock();
|
||||||
|
|
||||||
|
if frequency > 100_000.0 {
|
||||||
|
// High frequency: use yield for better responsiveness
|
||||||
|
std::thread::yield_now();
|
||||||
|
} else {
|
||||||
|
// Lower frequency: calculate sleep based on period
|
||||||
|
// Sleep for 1/10 of the clock period to balance responsiveness and CPU usage
|
||||||
|
let sleep_micros =
|
||||||
|
(1_000_000.0 / frequency / 10.0).min(1000.0).max(1.0) as u64;
|
||||||
|
std::thread::sleep(std::time::Duration::from_micros(sleep_micros));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop the component after loop exits
|
||||||
|
comp_clone.lock().stop();
|
||||||
});
|
});
|
||||||
|
|
||||||
handles.push((name.clone(), handle));
|
handles.push((name.clone(), handle));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store handles for later cleanup
|
// Store handles for later cleanup
|
||||||
if let Ok(mut stored_handles) = self.component_handles.lock() {
|
{
|
||||||
|
let mut stored_handles = self.component_handles.lock();
|
||||||
*stored_handles = Some(handles);
|
*stored_handles = Some(handles);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -469,22 +601,32 @@ impl ConfigurableSystem {
|
||||||
|
|
||||||
/// Clean up component threads and stop all components.
|
/// Clean up component threads and stop all components.
|
||||||
fn cleanup(&mut self) {
|
fn cleanup(&mut self) {
|
||||||
// Signal stop
|
// Signal stop first - this tells all component threads to stop
|
||||||
if let Ok(mut is_running) = self.is_running.lock() {
|
{
|
||||||
|
let mut is_running = self.is_running.lock();
|
||||||
*is_running = false;
|
*is_running = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop all components
|
// Stop all components using try_lock_for to avoid deadlock
|
||||||
|
// If a component is busy (its thread holds the lock), wait briefly
|
||||||
|
// - the thread will exit when it checks the is_running flag
|
||||||
println!("\nStopping system components...");
|
println!("\nStopping system components...");
|
||||||
for (name, component) in &self.components {
|
for (name, component) in &self.components {
|
||||||
if let Ok(mut comp) = component.lock() {
|
if let Some(mut comp) = component.try_lock_for(Duration::from_millis(100)) {
|
||||||
comp.stop();
|
comp.stop();
|
||||||
println!("Stopped component: {}", name);
|
println!("Stopped component: {}", name);
|
||||||
|
} else {
|
||||||
|
// Component is busy running - it will stop when is_running becomes false
|
||||||
|
println!("Component {} is busy, will stop via is_running flag", name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for threads
|
// Wait for threads to finish
|
||||||
if let Ok(mut handles_guard) = self.component_handles.lock() {
|
// Note: join() blocks indefinitely - threads should respond to is_running flag
|
||||||
|
if let Some(mut handles_guard) = self
|
||||||
|
.component_handles
|
||||||
|
.try_lock_for(Duration::from_millis(100))
|
||||||
|
{
|
||||||
if let Some(handles) = handles_guard.take() {
|
if let Some(handles) = handles_guard.take() {
|
||||||
for (name, handle) in handles {
|
for (name, handle) in handles {
|
||||||
match handle.join() {
|
match handle.join() {
|
||||||
|
|
@ -511,9 +653,7 @@ impl ConfigurableSystem {
|
||||||
std::thread::sleep(std::time::Duration::from_millis(500));
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
|
|
||||||
// Check if system should stop
|
// Check if system should stop
|
||||||
let should_continue = self.is_running.lock()
|
let should_continue = *self.is_running.lock();
|
||||||
.map(|guard| *guard)
|
|
||||||
.unwrap_or(false);
|
|
||||||
|
|
||||||
if !should_continue {
|
if !should_continue {
|
||||||
println!("\nSystem stop requested - terminating monitoring");
|
println!("\nSystem stop requested - terminating monitoring");
|
||||||
|
|
@ -545,7 +685,7 @@ impl ConfigurableSystem {
|
||||||
|
|
||||||
// Display RAM contents prominently
|
// Display RAM contents prominently
|
||||||
if let Some(ram_component) = self.components.get("RAM_4002") {
|
if let Some(ram_component) = self.components.get("RAM_4002") {
|
||||||
if let Ok(ram) = ram_component.lock() {
|
let ram = ram_component.lock();
|
||||||
println!("│ RAM Contents (Intel 4002 - 320 bits / 80 x 4-bit words): │");
|
println!("│ RAM Contents (Intel 4002 - 320 bits / 80 x 4-bit words): │");
|
||||||
|
|
||||||
// Display RAM in a grid format for better readability
|
// Display RAM in a grid format for better readability
|
||||||
|
|
@ -553,16 +693,13 @@ impl ConfigurableSystem {
|
||||||
print!("│ Bank {:2}: ", bank);
|
print!("│ Bank {:2}: ", bank);
|
||||||
for addr in 0..4 {
|
for addr in 0..4 {
|
||||||
let value = if let Ok(pin) = ram.get_pin(&format!("D{}", addr)) {
|
let value = if let Ok(pin) = ram.get_pin(&format!("D{}", addr)) {
|
||||||
if let Ok(pin_guard) = pin.lock() {
|
let pin_guard = pin.lock();
|
||||||
// Read actual pin value if possible
|
// Read actual pin value if possible
|
||||||
match pin_guard.read() {
|
match pin_guard.read() {
|
||||||
crate::pin::PinValue::High => "1",
|
crate::pin::PinValue::High => "1",
|
||||||
crate::pin::PinValue::Low => "0",
|
crate::pin::PinValue::Low => "0",
|
||||||
crate::pin::PinValue::HighZ => "Z",
|
crate::pin::PinValue::HighZ => "Z",
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
"L"
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
"?"
|
"?"
|
||||||
};
|
};
|
||||||
|
|
@ -570,14 +707,13 @@ impl ConfigurableSystem {
|
||||||
}
|
}
|
||||||
println!(" │");
|
println!(" │");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
println!("│ RAM_4002 component not found │");
|
println!("│ RAM_4002 component not found │");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display CPU state
|
// Display CPU state
|
||||||
if let Some(cpu_component) = self.components.get("CPU_4004") {
|
if let Some(cpu_component) = self.components.get("CPU_4004") {
|
||||||
if let Ok(_cpu) = cpu_component.lock() {
|
let _cpu = cpu_component.lock();
|
||||||
println!("│ CPU State: │");
|
println!("│ CPU State: │");
|
||||||
println!(
|
println!(
|
||||||
"│ Accumulator: 0x{:X} Carry: {} PC: 0x{:03X} │",
|
"│ Accumulator: 0x{:X} Carry: {} PC: 0x{:03X} │",
|
||||||
|
|
@ -586,20 +722,18 @@ impl ConfigurableSystem {
|
||||||
0
|
0
|
||||||
); // Placeholder - would need CPU-specific method
|
); // Placeholder - would need CPU-specific method
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Display clock state
|
// Display clock state
|
||||||
if let Some(clock_component) = self.components.get("SYSTEM_CLOCK") {
|
if let Some(clock_component) = self.components.get("SYSTEM_CLOCK") {
|
||||||
if let Ok(_clock) = clock_component.lock() {
|
let _clock = clock_component.lock();
|
||||||
println!("│ Clock: Running │");
|
println!("│ Clock: Running │");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Component status summary
|
// Component status summary
|
||||||
let running_count = self
|
let running_count = self
|
||||||
.components
|
.components
|
||||||
.values()
|
.values()
|
||||||
.filter(|comp| comp.lock().map_or(false, |c| c.is_running()))
|
.filter(|comp| comp.lock().is_running())
|
||||||
.count();
|
.count();
|
||||||
println!(
|
println!(
|
||||||
"│ Components: {}/{} running │",
|
"│ Components: {}/{} running │",
|
||||||
|
|
@ -611,7 +745,8 @@ impl ConfigurableSystem {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stop(&mut self) {
|
pub fn stop(&mut self) {
|
||||||
if let Ok(mut is_running) = self.is_running.lock() {
|
{
|
||||||
|
let mut is_running = self.is_running.lock();
|
||||||
*is_running = false;
|
*is_running = false;
|
||||||
}
|
}
|
||||||
// Also cleanup threads
|
// Also cleanup threads
|
||||||
|
|
@ -619,9 +754,7 @@ impl ConfigurableSystem {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_running(&self) -> bool {
|
pub fn is_running(&self) -> bool {
|
||||||
self.is_running.lock()
|
*self.is_running.lock()
|
||||||
.map(|guard| *guard)
|
|
||||||
.unwrap_or(false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get access to components for monitoring purposes
|
/// Get access to components for monitoring purposes
|
||||||
|
|
@ -664,48 +797,54 @@ impl ConfigurableSystem {
|
||||||
/// Returns: Ok(()) on success, Err(String) on failure
|
/// Returns: Ok(()) on success, Err(String) on failure
|
||||||
pub fn load_program_data(&mut self, program_data: &[u8]) -> Result<(), String> {
|
pub fn load_program_data(&mut self, program_data: &[u8]) -> Result<(), String> {
|
||||||
println!(
|
println!(
|
||||||
"DEBUG: Loading {} bytes of program data into ROM components",
|
"Loading {} bytes of program data into ROM components",
|
||||||
program_data.len()
|
program_data.len()
|
||||||
);
|
);
|
||||||
|
|
||||||
// Load program data into ROM_4001_1 (first 256 bytes)
|
// Load program data into ROM_4001_1 (first 256 bytes)
|
||||||
if let Some(rom1_component) = self.components.get_mut("ROM_4001_1") {
|
if let Some(rom1_component) = self.components.get("ROM_4001_1") {
|
||||||
if let Ok(_rom1) = rom1_component.lock() {
|
let mut rom1 = rom1_component.lock();
|
||||||
|
if let Some(rom) = rom1.as_any_mut().downcast_mut::<Intel4001>() {
|
||||||
let rom1_data = &program_data[..program_data.len().min(256)];
|
let rom1_data = &program_data[..program_data.len().min(256)];
|
||||||
println!("DEBUG: Loading {} bytes into ROM_4001_1", rom1_data.len());
|
rom.load_rom_data(rom1_data.to_vec(), 0)
|
||||||
println!("DEBUG: Program data: {:02X?}", rom1_data);
|
.map_err(|e| format!("Failed to load ROM_4001_1: {}", e))?;
|
||||||
// TODO: Load data using Intel4001's load_rom_data method when downcast is working
|
println!("Loaded {} bytes into ROM_4001_1", rom1_data.len());
|
||||||
println!("DEBUG: Program data should be loaded into ROM_4001_1");
|
} else {
|
||||||
|
return Err("ROM_4001_1 is not an Intel4001 component".to_string());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
println!("DEBUG: Warning - ROM_4001_1 component not found");
|
println!("Warning - ROM_4001_1 component not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load remaining data into ROM_4001_2 if program is larger than 256 bytes
|
// Load remaining data into ROM_4001_2 if program is larger than 256 bytes
|
||||||
if program_data.len() > 256 {
|
if program_data.len() > 256 {
|
||||||
if let Some(rom2_component) = self.components.get_mut("ROM_4001_2") {
|
if let Some(rom2_component) = self.components.get("ROM_4001_2") {
|
||||||
if let Ok(_rom2) = rom2_component.lock() {
|
let mut rom2 = rom2_component.lock();
|
||||||
|
if let Some(rom) = rom2.as_any_mut().downcast_mut::<Intel4001>() {
|
||||||
let rom2_data = &program_data[256..program_data.len().min(512)];
|
let rom2_data = &program_data[256..program_data.len().min(512)];
|
||||||
println!("DEBUG: Loading {} bytes into ROM_4001_2", rom2_data.len());
|
rom.load_rom_data(rom2_data.to_vec(), 0)
|
||||||
// TODO: Load data using Intel4001's load_rom_data method when available
|
.map_err(|e| format!("Failed to load ROM_4001_2: {}", e))?;
|
||||||
|
println!("Loaded {} bytes into ROM_4001_2", rom2_data.len());
|
||||||
|
} else {
|
||||||
|
return Err("ROM_4001_2 is not an Intel4001 component".to_string());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
println!("DEBUG: Warning - ROM_4001_2 component not found");
|
println!("Warning - ROM_4001_2 component not found");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("DEBUG: Program loading completed");
|
println!("Program loading completed");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get CPU register data from Intel 4004 CPU
|
/// Get CPU register data from Intel 4004 CPU
|
||||||
/// Returns: CpuRegisterData if Intel 4004 CPU is found, None otherwise
|
/// Returns: CpuRegisterData if Intel 4004 CPU is found, None otherwise
|
||||||
/// Uses try_lock() to avoid blocking when component is busy
|
/// Uses blocking lock - GUI can wait for simulation to release lock
|
||||||
pub fn get_cpu_register_data(&self) -> Option<CpuRegisterData> {
|
pub fn get_cpu_register_data(&self) -> Option<CpuRegisterData> {
|
||||||
// Look for Intel 4004 CPU components
|
// Look for Intel 4004 CPU components
|
||||||
for (_name, component) in &self.components {
|
for (_name, component) in &self.components {
|
||||||
match component.try_lock() {
|
// Use blocking lock - GUI can wait for simulation
|
||||||
Ok(comp) => {
|
let comp = component.lock();
|
||||||
// Try to downcast to Intel4004
|
// Try to downcast to Intel4004
|
||||||
if let Some(cpu) = comp.as_any().downcast_ref::<Intel4004>() {
|
if let Some(cpu) = comp.as_any().downcast_ref::<Intel4004>() {
|
||||||
let mut index_registers = [0u8; 16];
|
let mut index_registers = [0u8; 16];
|
||||||
|
|
@ -722,29 +861,19 @@ impl ConfigurableSystem {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(std::sync::TryLockError::WouldBlock) => {
|
|
||||||
// Component is busy, continue to next component
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Err(std::sync::TryLockError::Poisoned(_)) => {
|
|
||||||
// Handle poisoned mutex - continue to next component
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get RAM data from Intel 4002 RAM chips
|
/// Get RAM data from Intel 4002 RAM chips
|
||||||
/// Parameters: max_rows - Maximum number of rows to return per bank
|
/// 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
|
/// Returns: Vector of (chip_name, banks) where each bank is a vector of nibbles
|
||||||
/// Uses try_lock() to avoid blocking when component is busy
|
/// Uses blocking lock - GUI can wait for simulation to release lock
|
||||||
pub fn get_ram_data(&self, max_rows: usize) -> RamData {
|
pub fn get_ram_data(&self, max_rows: usize) -> RamData {
|
||||||
let mut ram_data = Vec::new();
|
let mut ram_data = Vec::new();
|
||||||
|
|
||||||
for (name, component) in &self.components {
|
for (_name, component) in &self.components {
|
||||||
match component.try_lock() {
|
// Use blocking lock - GUI can wait for simulation
|
||||||
Ok(comp) => {
|
let comp = component.lock();
|
||||||
// Try to downcast to Intel4002
|
// Try to downcast to Intel4002
|
||||||
if let Some(ram) = comp.as_any().downcast_ref::<Intel4002>() {
|
if let Some(ram) = comp.as_any().downcast_ref::<Intel4002>() {
|
||||||
let mut banks = Vec::new();
|
let mut banks = Vec::new();
|
||||||
|
|
@ -766,17 +895,7 @@ impl ConfigurableSystem {
|
||||||
banks.push(bank_data);
|
banks.push(bank_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
ram_data.push((name.clone(), banks));
|
ram_data.push((_name.clone(), banks));
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(std::sync::TryLockError::WouldBlock) => {
|
|
||||||
// Component is busy, skip to next component
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Err(std::sync::TryLockError::Poisoned(_)) => {
|
|
||||||
// Handle poisoned mutex - skip to next component
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -786,13 +905,13 @@ impl ConfigurableSystem {
|
||||||
/// Get ROM data from Intel 4001 ROM chips
|
/// Get ROM data from Intel 4001 ROM chips
|
||||||
/// Parameters: max_rows - Maximum number of bytes to return per chip
|
/// Parameters: max_rows - Maximum number of bytes to return per chip
|
||||||
/// Returns: Vector of (chip_name, bytes)
|
/// Returns: Vector of (chip_name, bytes)
|
||||||
/// Uses try_lock() to avoid blocking when component is busy
|
/// Uses blocking lock - GUI can wait for simulation to release lock
|
||||||
pub fn get_rom_data(&self, max_rows: usize) -> RomData {
|
pub fn get_rom_data(&self, max_rows: usize) -> RomData {
|
||||||
let mut rom_data = Vec::new();
|
let mut rom_data = Vec::new();
|
||||||
|
|
||||||
for (name, component) in &self.components {
|
for (name, component) in &self.components {
|
||||||
match component.try_lock() {
|
// Use blocking lock - GUI can wait for simulation
|
||||||
Ok(comp) => {
|
let comp = component.lock();
|
||||||
// Try to downcast to Intel4001
|
// Try to downcast to Intel4001
|
||||||
if let Some(rom) = comp.as_any().downcast_ref::<Intel4001>() {
|
if let Some(rom) = comp.as_any().downcast_ref::<Intel4001>() {
|
||||||
let mut data = Vec::new();
|
let mut data = Vec::new();
|
||||||
|
|
@ -806,18 +925,252 @@ impl ConfigurableSystem {
|
||||||
rom_data.push((name.clone(), data));
|
rom_data.push((name.clone(), data));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(std::sync::TryLockError::WouldBlock) => {
|
|
||||||
// Component is busy, skip to next component
|
rom_data
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Err(std::sync::TryLockError::Poisoned(_)) => {
|
|
||||||
// Handle poisoned mutex - skip to next component
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rom_data
|
// Implement SystemController trait for ConfigurableSystem
|
||||||
|
impl SystemController for ConfigurableSystem {
|
||||||
|
fn start(&mut self) -> Result<(), String> {
|
||||||
|
let is_paused = *self.is_paused.lock();
|
||||||
|
|
||||||
|
if is_paused {
|
||||||
|
// Resume from pause
|
||||||
|
return self.resume();
|
||||||
|
}
|
||||||
|
|
||||||
|
let is_running = *self.is_running.lock();
|
||||||
|
if is_running {
|
||||||
|
return Err("System is already running".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the inherent method explicitly to avoid infinite recursion
|
||||||
|
ConfigurableSystem::start(self);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stop(&mut self) -> Result<(), String> {
|
||||||
|
let is_running = *self.is_running.lock();
|
||||||
|
if !is_running {
|
||||||
|
return Err("System is not running".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset pause state when stopping
|
||||||
|
{
|
||||||
|
let mut paused = self.is_paused.lock();
|
||||||
|
*paused = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the inherent method explicitly to avoid infinite recursion
|
||||||
|
ConfigurableSystem::stop(self);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pause(&mut self) -> Result<(), String> {
|
||||||
|
let is_running = *self.is_running.lock();
|
||||||
|
if !is_running {
|
||||||
|
return Err("System is not running".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let is_paused = *self.is_paused.lock();
|
||||||
|
if is_paused {
|
||||||
|
return Err("System is already paused".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set pause state
|
||||||
|
{
|
||||||
|
let mut paused = self.is_paused.lock();
|
||||||
|
*paused = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Propagate pause to ALL components (clocks, CPU, memory)
|
||||||
|
// The system-level is_paused flag handles the thread-level pause,
|
||||||
|
// but we also propagate to components for component-specific pause behavior
|
||||||
|
for (name, component) in &self.components {
|
||||||
|
let mut comp = component.lock();
|
||||||
|
// Try to downcast to GenericClock
|
||||||
|
if let Some(clock) =
|
||||||
|
comp.as_any_mut()
|
||||||
|
.downcast_mut::<crate::components::clock::generic_clock::GenericClock>()
|
||||||
|
{
|
||||||
|
clock.pause();
|
||||||
|
println!("Clock {} paused", name);
|
||||||
|
}
|
||||||
|
// Try to downcast to TwoPhaseClock
|
||||||
|
else if let Some(clock) =
|
||||||
|
comp.as_any_mut()
|
||||||
|
.downcast_mut::<crate::components::clock::two_phase_clock::TwoPhaseClock>()
|
||||||
|
{
|
||||||
|
clock.pause();
|
||||||
|
println!("Clock {} paused", name);
|
||||||
|
}
|
||||||
|
// Note: CPU and memory components don't have explicit pause methods,
|
||||||
|
// but they are effectively paused by the system-level is_paused flag
|
||||||
|
// which prevents their update() from being called in the thread loop
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("System paused - all components frozen");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resume(&mut self) -> Result<(), String> {
|
||||||
|
let is_paused = *self.is_paused.lock();
|
||||||
|
if !is_paused {
|
||||||
|
return Err("System is not paused".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear pause state
|
||||||
|
{
|
||||||
|
let mut paused = self.is_paused.lock();
|
||||||
|
*paused = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Propagate resume to ALL components (clocks, CPU, memory)
|
||||||
|
for (name, component) in &self.components {
|
||||||
|
let mut comp = component.lock();
|
||||||
|
// Try to downcast to GenericClock
|
||||||
|
if let Some(clock) =
|
||||||
|
comp.as_any_mut()
|
||||||
|
.downcast_mut::<crate::components::clock::generic_clock::GenericClock>()
|
||||||
|
{
|
||||||
|
clock.resume();
|
||||||
|
println!("Clock {} resumed", name);
|
||||||
|
}
|
||||||
|
// Try to downcast to TwoPhaseClock
|
||||||
|
else if let Some(clock) =
|
||||||
|
comp.as_any_mut()
|
||||||
|
.downcast_mut::<crate::components::clock::two_phase_clock::TwoPhaseClock>()
|
||||||
|
{
|
||||||
|
clock.resume();
|
||||||
|
println!("Clock {} resumed", name);
|
||||||
|
}
|
||||||
|
// Note: CPU and memory components resume automatically via the
|
||||||
|
// system-level is_paused flag being cleared in the thread loop
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("System resumed - all components running");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_running(&self) -> bool {
|
||||||
|
*self.is_running.lock()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_paused(&self) -> bool {
|
||||||
|
*self.is_paused.lock()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_system_state(&self) -> SystemState {
|
||||||
|
let is_running = *self.is_running.lock();
|
||||||
|
let is_paused = *self.is_paused.lock();
|
||||||
|
|
||||||
|
if !is_running {
|
||||||
|
SystemState::Stopped
|
||||||
|
} else if is_paused {
|
||||||
|
SystemState::Paused
|
||||||
|
} else {
|
||||||
|
SystemState::Running
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_clock_frequency(&mut self, frequency: f64) -> Result<(), String> {
|
||||||
|
if frequency < MIN_CLOCK_FREQUENCY {
|
||||||
|
return Err(format!(
|
||||||
|
"Frequency must be at least {} Hz",
|
||||||
|
MIN_CLOCK_FREQUENCY
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if frequency > MAX_CLOCK_FREQUENCY {
|
||||||
|
return Err(format!(
|
||||||
|
"Frequency must not exceed {} Hz",
|
||||||
|
MAX_CLOCK_FREQUENCY
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update our stored frequency
|
||||||
|
{
|
||||||
|
let mut freq = self.clock_frequency.lock();
|
||||||
|
*freq = frequency;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update all clock components
|
||||||
|
for (name, component) in &self.components {
|
||||||
|
let mut comp = component.lock();
|
||||||
|
// Try to downcast to GenericClock
|
||||||
|
if let Some(clock) =
|
||||||
|
comp.as_any_mut()
|
||||||
|
.downcast_mut::<crate::components::clock::generic_clock::GenericClock>()
|
||||||
|
{
|
||||||
|
clock.set_frequency(frequency);
|
||||||
|
println!("Updated clock frequency for {} to {} Hz", name, frequency);
|
||||||
|
}
|
||||||
|
// Try to downcast to TwoPhaseClock
|
||||||
|
else if let Some(clock) =
|
||||||
|
comp.as_any_mut()
|
||||||
|
.downcast_mut::<crate::components::clock::two_phase_clock::TwoPhaseClock>()
|
||||||
|
{
|
||||||
|
clock.set_frequency(frequency);
|
||||||
|
println!("Updated clock frequency for {} to {} Hz", name, frequency);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_clock_frequency(&self) -> f64 {
|
||||||
|
*self.clock_frequency.lock()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_clock_enabled(&mut self, enabled: bool) -> Result<(), String> {
|
||||||
|
// Update our stored state
|
||||||
|
{
|
||||||
|
let mut clk_enabled = self.clock_enabled.lock();
|
||||||
|
*clk_enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update all clock components
|
||||||
|
for (name, component) in &self.components {
|
||||||
|
let mut comp = component.lock();
|
||||||
|
// Try to downcast to GenericClock
|
||||||
|
if let Some(clock) =
|
||||||
|
comp.as_any_mut()
|
||||||
|
.downcast_mut::<crate::components::clock::generic_clock::GenericClock>()
|
||||||
|
{
|
||||||
|
if enabled {
|
||||||
|
clock.enable();
|
||||||
|
} else {
|
||||||
|
clock.disable();
|
||||||
|
}
|
||||||
|
println!(
|
||||||
|
"Clock {} {}",
|
||||||
|
name,
|
||||||
|
if enabled { "enabled" } else { "disabled" }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Try to downcast to TwoPhaseClock
|
||||||
|
else if let Some(clock) =
|
||||||
|
comp.as_any_mut()
|
||||||
|
.downcast_mut::<crate::components::clock::two_phase_clock::TwoPhaseClock>()
|
||||||
|
{
|
||||||
|
if enabled {
|
||||||
|
clock.enable();
|
||||||
|
} else {
|
||||||
|
clock.disable();
|
||||||
|
}
|
||||||
|
println!(
|
||||||
|
"Clock {} {}",
|
||||||
|
name,
|
||||||
|
if enabled { "enabled" } else { "disabled" }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_clock_enabled(&self) -> bool {
|
||||||
|
*self.clock_enabled.lock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,10 @@
|
||||||
|
|
||||||
#![cfg(test)]
|
#![cfg(test)]
|
||||||
|
|
||||||
|
use parking_lot::Mutex;
|
||||||
use rusty_emu::gui::GuiApp;
|
use rusty_emu::gui::GuiApp;
|
||||||
use rusty_emu::system_config::{ConfigurableSystem, SystemFactory};
|
use rusty_emu::system_config::{ConfigurableSystem, SystemFactory};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::Arc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
|
@ -36,7 +37,8 @@ mod gui_integration_test_utils {
|
||||||
/// Note: This function acquires the lock internally. Do NOT call it while holding the lock.
|
/// Note: This function acquires the lock internally. Do NOT call it while holding the lock.
|
||||||
pub fn simulate_gui_updates(system: &Arc<Mutex<ConfigurableSystem>>) {
|
pub fn simulate_gui_updates(system: &Arc<Mutex<ConfigurableSystem>>) {
|
||||||
// Simulate the kind of updates that would happen in the GUI thread
|
// Simulate the kind of updates that would happen in the GUI thread
|
||||||
if let Ok(system_guard) = system.lock() {
|
let system_guard = system.lock();
|
||||||
|
{
|
||||||
// Simulate some system activity
|
// Simulate some system activity
|
||||||
let _ = system_guard.is_running(); // Access system state
|
let _ = system_guard.is_running(); // Access system state
|
||||||
}
|
}
|
||||||
|
|
@ -48,14 +50,19 @@ mod gui_integration_test_utils {
|
||||||
|
|
||||||
// Spawn a thread that accesses the system
|
// Spawn a thread that accesses the system
|
||||||
let handle = thread::spawn(move || {
|
let handle = thread::spawn(move || {
|
||||||
// Try to acquire the lock - return true if successful (regardless of is_running value)
|
// Try to acquire the lock - return true if successful (parking_lot::lock always succeeds)
|
||||||
system_clone.lock().is_ok()
|
let _guard = system_clone.lock();
|
||||||
|
true
|
||||||
});
|
});
|
||||||
|
|
||||||
// Main thread also accesses the system
|
// Main thread also accesses the system - but release lock before joining to avoid deadlock
|
||||||
let main_thread_result = system.lock().is_ok();
|
{
|
||||||
|
let _guard = system.lock();
|
||||||
|
// Access the system while holding the lock
|
||||||
|
}
|
||||||
|
let main_thread_result = true;
|
||||||
|
|
||||||
// Wait for spawned thread
|
// Wait for spawned thread (lock is released, so no deadlock)
|
||||||
let spawned_thread_result = handle.join().unwrap_or(false);
|
let spawned_thread_result = handle.join().unwrap_or(false);
|
||||||
|
|
||||||
// Both threads should be able to acquire the lock without deadlock
|
// Both threads should be able to acquire the lock without deadlock
|
||||||
|
|
@ -150,7 +157,8 @@ mod gui_system_integration_tests {
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
// Simulate GUI update pattern
|
// Simulate GUI update pattern
|
||||||
for _ in 0..10 {
|
for _ in 0..10 {
|
||||||
if let Ok(_system_guard) = system_clone.lock() {
|
let _system_guard = system_clone.lock();
|
||||||
|
{
|
||||||
// Simulate some processing time
|
// Simulate some processing time
|
||||||
thread::sleep(Duration::from_micros(10));
|
thread::sleep(Duration::from_micros(10));
|
||||||
}
|
}
|
||||||
|
|
@ -196,7 +204,7 @@ mod gui_system_integration_tests {
|
||||||
// Test the polling mechanism that GUI would use
|
// Test the polling mechanism that GUI would use
|
||||||
for i in 0..10 {
|
for i in 0..10 {
|
||||||
{
|
{
|
||||||
let system_guard = system.lock().unwrap();
|
let system_guard = system.lock();
|
||||||
let is_running = system_guard.is_running();
|
let is_running = system_guard.is_running();
|
||||||
|
|
||||||
// Simulate state updates that would happen in GUI
|
// Simulate state updates that would happen in GUI
|
||||||
|
|
@ -221,7 +229,7 @@ mod gui_system_integration_tests {
|
||||||
// In a real implementation, this would test actual memory access
|
// In a real implementation, this would test actual memory access
|
||||||
|
|
||||||
{
|
{
|
||||||
let system_guard = system.lock().unwrap();
|
let system_guard = system.lock();
|
||||||
// Test that system provides memory information
|
// Test that system provides memory information
|
||||||
let _ = system_guard.get_system_info(); // Access system info
|
let _ = system_guard.get_system_info(); // Access system info
|
||||||
} // system_guard is dropped here
|
} // system_guard is dropped here
|
||||||
|
|
@ -241,7 +249,7 @@ mod gui_system_integration_tests {
|
||||||
|
|
||||||
// Test register state structure for GUI integration
|
// Test register state structure for GUI integration
|
||||||
{
|
{
|
||||||
let _system_guard = system.lock().unwrap();
|
let _system_guard = system.lock();
|
||||||
// In a real implementation, this would access actual CPU registers
|
// In a real implementation, this would access actual CPU registers
|
||||||
// For now, we test the GUI state structure
|
// For now, we test the GUI state structure
|
||||||
}
|
}
|
||||||
|
|
@ -262,7 +270,7 @@ mod gui_system_integration_tests {
|
||||||
|
|
||||||
// Test component state monitoring for GUI
|
// Test component state monitoring for GUI
|
||||||
{
|
{
|
||||||
let system_guard = system.lock().unwrap();
|
let system_guard = system.lock();
|
||||||
let components = system_guard.get_components();
|
let components = system_guard.get_components();
|
||||||
|
|
||||||
// Test that components are accessible (GUI would monitor these)
|
// Test that components are accessible (GUI would monitor these)
|
||||||
|
|
@ -434,18 +442,19 @@ mod gui_error_handling_tests {
|
||||||
// Hold the lock for a short time to simulate contention
|
// Hold the lock for a short time to simulate contention
|
||||||
let system_clone = Arc::clone(&system);
|
let system_clone = Arc::clone(&system);
|
||||||
let lock_handle = thread::spawn(move || {
|
let lock_handle = thread::spawn(move || {
|
||||||
if let Ok(_system_guard) = system_clone.lock() {
|
let _system_guard = system_clone.lock();
|
||||||
|
{
|
||||||
thread::sleep(Duration::from_millis(10));
|
thread::sleep(Duration::from_millis(10));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Try to access from main thread (should not deadlock)
|
// Try to access from main thread (should not deadlock)
|
||||||
let access_result = system.try_lock().is_ok();
|
let access_result = system.try_lock().is_some();
|
||||||
|
|
||||||
lock_handle.join().unwrap();
|
lock_handle.join().unwrap();
|
||||||
|
|
||||||
// Should be able to access after the lock is released
|
// Should be able to access after the lock is released
|
||||||
assert!(access_result || system.try_lock().is_ok());
|
assert!(access_result || system.try_lock().is_some());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -481,7 +490,7 @@ mod gui_real_time_tests {
|
||||||
|
|
||||||
for i in 0..20 {
|
for i in 0..20 {
|
||||||
{
|
{
|
||||||
let _system_guard = system.lock().unwrap();
|
let _system_guard = system.lock();
|
||||||
// Simulate GUI state polling
|
// Simulate GUI state polling
|
||||||
update_count += 1;
|
update_count += 1;
|
||||||
|
|
||||||
|
|
@ -535,7 +544,7 @@ mod gui_real_time_tests {
|
||||||
// For now, we test the data structures are compatible
|
// For now, we test the data structures are compatible
|
||||||
|
|
||||||
{
|
{
|
||||||
let _system_guard = system.lock().unwrap();
|
let _system_guard = system.lock();
|
||||||
// Test memory data structure compatibility
|
// Test memory data structure compatibility
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -559,7 +568,7 @@ mod gui_real_time_tests {
|
||||||
let system = create_test_system();
|
let system = create_test_system();
|
||||||
|
|
||||||
{
|
{
|
||||||
let _system_guard = system.lock().unwrap();
|
let _system_guard = system.lock();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut gui_state = rusty_emu::gui::state::GuiState::new();
|
let mut gui_state = rusty_emu::gui::state::GuiState::new();
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,10 @@
|
||||||
#![cfg(test)]
|
#![cfg(test)]
|
||||||
|
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
|
use parking_lot::Mutex;
|
||||||
use rusty_emu::gui::components::*;
|
use rusty_emu::gui::components::*;
|
||||||
use rusty_emu::gui::state::*;
|
use rusty_emu::gui::state::*;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// Test utilities for GUI testing
|
/// Test utilities for GUI testing
|
||||||
mod gui_test_utils {
|
mod gui_test_utils {
|
||||||
|
|
@ -304,7 +305,8 @@ mod gui_integration_tests {
|
||||||
// For now, we verify the components exist and have valid state
|
// For now, we verify the components exist and have valid state
|
||||||
|
|
||||||
// Test control panel rendering (would panic if component is invalid)
|
// Test control panel rendering (would panic if component is invalid)
|
||||||
components.render_control_panel(&mut create_test_ui(&ctx), &mut state);
|
// Note: render_control_panel now takes an optional system parameter
|
||||||
|
components.render_control_panel(&mut create_test_ui(&ctx), &mut state, None);
|
||||||
|
|
||||||
// Test memory viewer rendering
|
// Test memory viewer rendering
|
||||||
components.render_memory_viewer(&mut create_test_ui(&ctx), &mut state);
|
components.render_memory_viewer(&mut create_test_ui(&ctx), &mut state);
|
||||||
|
|
|
||||||
|
|
@ -38,8 +38,8 @@ fn test_intel_4001_state_machine_progression() {
|
||||||
|
|
||||||
// Initialize clock pins to Low
|
// Initialize clock pins to Low
|
||||||
{
|
{
|
||||||
let mut phi1_guard = phi1_pin.lock().unwrap();
|
let mut phi1_guard = phi1_pin.lock();
|
||||||
let mut phi2_guard = phi2_pin.lock().unwrap();
|
let mut phi2_guard = phi2_pin.lock();
|
||||||
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||||
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||||
}
|
}
|
||||||
|
|
@ -47,10 +47,10 @@ fn test_intel_4001_state_machine_progression() {
|
||||||
// Initialize control pins
|
// Initialize control pins
|
||||||
// Note: RESET is active LOW, so set it to High to ensure component is not in reset
|
// Note: RESET is active LOW, so set it to High to ensure component is not in reset
|
||||||
{
|
{
|
||||||
let mut sync_guard = sync_pin.lock().unwrap();
|
let mut sync_guard = sync_pin.lock();
|
||||||
let mut cm_guard = cm_pin.lock().unwrap();
|
let mut cm_guard = cm_pin.lock();
|
||||||
let mut ci_guard = ci_pin.lock().unwrap();
|
let mut ci_guard = ci_pin.lock();
|
||||||
let mut reset_guard = reset_pin.lock().unwrap();
|
let mut reset_guard = reset_pin.lock();
|
||||||
sync_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
sync_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||||
cm_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
cm_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||||
ci_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
ci_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||||
|
|
@ -59,10 +59,10 @@ fn test_intel_4001_state_machine_progression() {
|
||||||
|
|
||||||
// Initialize data pins to Low
|
// Initialize data pins to Low
|
||||||
{
|
{
|
||||||
let mut d0_guard = d0_pin.lock().unwrap();
|
let mut d0_guard = d0_pin.lock();
|
||||||
let mut d1_guard = d1_pin.lock().unwrap();
|
let mut d1_guard = d1_pin.lock();
|
||||||
let mut d2_guard = d2_pin.lock().unwrap();
|
let mut d2_guard = d2_pin.lock();
|
||||||
let mut d3_guard = d3_pin.lock().unwrap();
|
let mut d3_guard = d3_pin.lock();
|
||||||
d0_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
d0_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||||
d1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
d1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||||
d2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
d2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||||
|
|
@ -78,9 +78,9 @@ fn test_intel_4001_state_machine_progression() {
|
||||||
|
|
||||||
// Set up memory read operation: SYNC=1, CM=1, CI=0 (memory access)
|
// Set up memory read operation: SYNC=1, CM=1, CI=0 (memory access)
|
||||||
{
|
{
|
||||||
let mut sync_guard = sync_pin.lock().unwrap();
|
let mut sync_guard = sync_pin.lock();
|
||||||
let mut cm_guard = cm_pin.lock().unwrap();
|
let mut cm_guard = cm_pin.lock();
|
||||||
let mut ci_guard = ci_pin.lock().unwrap();
|
let mut ci_guard = ci_pin.lock();
|
||||||
sync_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
sync_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
||||||
cm_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
cm_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
||||||
ci_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Memory access
|
ci_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Memory access
|
||||||
|
|
@ -88,10 +88,10 @@ fn test_intel_4001_state_machine_progression() {
|
||||||
|
|
||||||
// Set up address high nibble (0x1 for address 0x12)
|
// Set up address high nibble (0x1 for address 0x12)
|
||||||
{
|
{
|
||||||
let mut d0_guard = d0_pin.lock().unwrap();
|
let mut d0_guard = d0_pin.lock();
|
||||||
let mut d1_guard = d1_pin.lock().unwrap();
|
let mut d1_guard = d1_pin.lock();
|
||||||
let mut d2_guard = d2_pin.lock().unwrap();
|
let mut d2_guard = d2_pin.lock();
|
||||||
let mut d3_guard = d3_pin.lock().unwrap();
|
let mut d3_guard = d3_pin.lock();
|
||||||
// 0x1 = 0001 (D0=1, D1=0, D2=0, D3=0)
|
// 0x1 = 0001 (D0=1, D1=0, D2=0, D3=0)
|
||||||
d0_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
d0_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
||||||
d1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
d1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||||
|
|
@ -104,7 +104,7 @@ fn test_intel_4001_state_machine_progression() {
|
||||||
|
|
||||||
// Trigger Φ1 rising edge to start memory operation and latch high nibble
|
// Trigger Φ1 rising edge to start memory operation and latch high nibble
|
||||||
{
|
{
|
||||||
let mut phi1_guard = phi1_pin.lock().unwrap();
|
let mut phi1_guard = phi1_pin.lock();
|
||||||
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -120,7 +120,7 @@ fn test_intel_4001_state_machine_progression() {
|
||||||
|
|
||||||
// Reset Φ1 to Low
|
// Reset Φ1 to Low
|
||||||
{
|
{
|
||||||
let mut phi1_guard = phi1_pin.lock().unwrap();
|
let mut phi1_guard = phi1_pin.lock();
|
||||||
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -129,10 +129,10 @@ fn test_intel_4001_state_machine_progression() {
|
||||||
|
|
||||||
// Set up address low nibble (0x2 for address 0x12)
|
// Set up address low nibble (0x2 for address 0x12)
|
||||||
{
|
{
|
||||||
let mut d0_guard = d0_pin.lock().unwrap();
|
let mut d0_guard = d0_pin.lock();
|
||||||
let mut d1_guard = d1_pin.lock().unwrap();
|
let mut d1_guard = d1_pin.lock();
|
||||||
let mut d2_guard = d2_pin.lock().unwrap();
|
let mut d2_guard = d2_pin.lock();
|
||||||
let mut d3_guard = d3_pin.lock().unwrap();
|
let mut d3_guard = d3_pin.lock();
|
||||||
// 0x2 = 0010 (D0=0, D1=1, D2=0, D3=0)
|
// 0x2 = 0010 (D0=0, D1=1, D2=0, D3=0)
|
||||||
d0_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
d0_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||||
d1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
d1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
||||||
|
|
@ -146,7 +146,7 @@ fn test_intel_4001_state_machine_progression() {
|
||||||
// Trigger Φ1 rising edge again - ROM should stay in AddressPhase
|
// Trigger Φ1 rising edge again - ROM should stay in AddressPhase
|
||||||
// (address nibbles are latched on Φ1 falling edge, not rising)
|
// (address nibbles are latched on Φ1 falling edge, not rising)
|
||||||
{
|
{
|
||||||
let mut phi1_guard = phi1_pin.lock().unwrap();
|
let mut phi1_guard = phi1_pin.lock();
|
||||||
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -166,7 +166,7 @@ fn test_intel_4001_state_machine_progression() {
|
||||||
|
|
||||||
// Reset Φ1 to Low to trigger falling edge - this is when low nibble is latched
|
// Reset Φ1 to Low to trigger falling edge - this is when low nibble is latched
|
||||||
{
|
{
|
||||||
let mut phi1_guard = phi1_pin.lock().unwrap();
|
let mut phi1_guard = phi1_pin.lock();
|
||||||
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -189,10 +189,10 @@ fn test_intel_4001_state_machine_progression() {
|
||||||
|
|
||||||
// Remove test drivers from data pins to allow ROM to drive bus
|
// Remove test drivers from data pins to allow ROM to drive bus
|
||||||
{
|
{
|
||||||
let mut d0_guard = d0_pin.lock().unwrap();
|
let mut d0_guard = d0_pin.lock();
|
||||||
let mut d1_guard = d1_pin.lock().unwrap();
|
let mut d1_guard = d1_pin.lock();
|
||||||
let mut d2_guard = d2_pin.lock().unwrap();
|
let mut d2_guard = d2_pin.lock();
|
||||||
let mut d3_guard = d3_pin.lock().unwrap();
|
let mut d3_guard = d3_pin.lock();
|
||||||
d0_guard.remove_driver("TEST");
|
d0_guard.remove_driver("TEST");
|
||||||
d1_guard.remove_driver("TEST");
|
d1_guard.remove_driver("TEST");
|
||||||
d2_guard.remove_driver("TEST");
|
d2_guard.remove_driver("TEST");
|
||||||
|
|
@ -203,7 +203,7 @@ fn test_intel_4001_state_machine_progression() {
|
||||||
if rom.get_timing_state() == TimingState::WaitLatency {
|
if rom.get_timing_state() == TimingState::WaitLatency {
|
||||||
// Trigger Φ2 rising edge
|
// Trigger Φ2 rising edge
|
||||||
{
|
{
|
||||||
let mut phi2_guard = phi2_pin.lock().unwrap();
|
let mut phi2_guard = phi2_pin.lock();
|
||||||
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -221,14 +221,14 @@ fn test_intel_4001_state_machine_progression() {
|
||||||
// Verify data is being driven on the bus
|
// Verify data is being driven on the bus
|
||||||
// Data 0xAB has low nibble 0xB = 1011 (D0=1, D1=1, D2=0, D3=1)
|
// Data 0xAB has low nibble 0xB = 1011 (D0=1, D1=1, D2=0, D3=1)
|
||||||
// Note: Due to bit ordering, D0 should be High, D1 should be High, D2 should be Low, D3 should be High
|
// Note: Due to bit ordering, D0 should be High, D1 should be High, D2 should be Low, D3 should be High
|
||||||
assert_eq!(d0_pin.lock().unwrap().read(), PinValue::High);
|
assert_eq!(d0_pin.lock().read(), PinValue::High);
|
||||||
assert_eq!(d1_pin.lock().unwrap().read(), PinValue::High);
|
assert_eq!(d1_pin.lock().read(), PinValue::High);
|
||||||
assert_eq!(d2_pin.lock().unwrap().read(), PinValue::Low);
|
assert_eq!(d2_pin.lock().read(), PinValue::Low);
|
||||||
assert_eq!(d3_pin.lock().unwrap().read(), PinValue::High);
|
assert_eq!(d3_pin.lock().read(), PinValue::High);
|
||||||
|
|
||||||
// Reset Φ2 to Low
|
// Reset Φ2 to Low
|
||||||
{
|
{
|
||||||
let mut phi2_guard = phi2_pin.lock().unwrap();
|
let mut phi2_guard = phi2_pin.lock();
|
||||||
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -240,10 +240,10 @@ fn test_intel_4001_state_machine_progression() {
|
||||||
println!("Final state: {:?}", rom.get_timing_state());
|
println!("Final state: {:?}", rom.get_timing_state());
|
||||||
|
|
||||||
// Verify data bus is tri-stated
|
// Verify data bus is tri-stated
|
||||||
assert_eq!(d0_pin.lock().unwrap().read(), PinValue::HighZ);
|
assert_eq!(d0_pin.lock().read(), PinValue::HighZ);
|
||||||
assert_eq!(d1_pin.lock().unwrap().read(), PinValue::HighZ);
|
assert_eq!(d1_pin.lock().read(), PinValue::HighZ);
|
||||||
assert_eq!(d2_pin.lock().unwrap().read(), PinValue::HighZ);
|
assert_eq!(d2_pin.lock().read(), PinValue::HighZ);
|
||||||
assert_eq!(d3_pin.lock().unwrap().read(), PinValue::HighZ);
|
assert_eq!(d3_pin.lock().read(), PinValue::HighZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test to verify that unified state machine doesn't progress without proper control signals
|
/// Test to verify that unified state machine doesn't progress without proper control signals
|
||||||
|
|
@ -261,8 +261,8 @@ fn test_intel_4001_state_machine_no_progression_without_signals() {
|
||||||
|
|
||||||
// Initialize clock pins to Low
|
// Initialize clock pins to Low
|
||||||
{
|
{
|
||||||
let mut phi1_guard = phi1_pin.lock().unwrap();
|
let mut phi1_guard = phi1_pin.lock();
|
||||||
let mut phi2_guard = phi2_pin.lock().unwrap();
|
let mut phi2_guard = phi2_pin.lock();
|
||||||
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||||
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||||
}
|
}
|
||||||
|
|
@ -275,7 +275,7 @@ fn test_intel_4001_state_machine_no_progression_without_signals() {
|
||||||
|
|
||||||
// Trigger clock edges without setting up control signals
|
// Trigger clock edges without setting up control signals
|
||||||
{
|
{
|
||||||
let mut phi1_guard = phi1_pin.lock().unwrap();
|
let mut phi1_guard = phi1_pin.lock();
|
||||||
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -287,7 +287,7 @@ fn test_intel_4001_state_machine_no_progression_without_signals() {
|
||||||
|
|
||||||
// Reset Φ1 to Low
|
// Reset Φ1 to Low
|
||||||
{
|
{
|
||||||
let mut phi1_guard = phi1_pin.lock().unwrap();
|
let mut phi1_guard = phi1_pin.lock();
|
||||||
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -296,7 +296,7 @@ fn test_intel_4001_state_machine_no_progression_without_signals() {
|
||||||
|
|
||||||
// Trigger Φ2 rising edge
|
// Trigger Φ2 rising edge
|
||||||
{
|
{
|
||||||
let mut phi2_guard = phi2_pin.lock().unwrap();
|
let mut phi2_guard = phi2_pin.lock();
|
||||||
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -325,7 +325,7 @@ fn test_intel_4001_state_machine_reset() {
|
||||||
|
|
||||||
// Initialize clock pins to Low
|
// Initialize clock pins to Low
|
||||||
{
|
{
|
||||||
let mut phi1_guard = phi1_pin.lock().unwrap();
|
let mut phi1_guard = phi1_pin.lock();
|
||||||
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -337,9 +337,9 @@ fn test_intel_4001_state_machine_reset() {
|
||||||
|
|
||||||
// Set up memory read operation: SYNC=1, CM=1, CI=0 (memory access)
|
// Set up memory read operation: SYNC=1, CM=1, CI=0 (memory access)
|
||||||
{
|
{
|
||||||
let mut sync_guard = sync_pin.lock().unwrap();
|
let mut sync_guard = sync_pin.lock();
|
||||||
let mut cm_guard = cm_pin.lock().unwrap();
|
let mut cm_guard = cm_pin.lock();
|
||||||
let mut ci_guard = ci_pin.lock().unwrap();
|
let mut ci_guard = ci_pin.lock();
|
||||||
sync_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
sync_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
||||||
cm_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
cm_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
||||||
ci_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Memory access
|
ci_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Memory access
|
||||||
|
|
@ -347,7 +347,7 @@ fn test_intel_4001_state_machine_reset() {
|
||||||
|
|
||||||
// Trigger Φ1 rising edge to start memory operation
|
// Trigger Φ1 rising edge to start memory operation
|
||||||
{
|
{
|
||||||
let mut phi1_guard = phi1_pin.lock().unwrap();
|
let mut phi1_guard = phi1_pin.lock();
|
||||||
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -359,7 +359,7 @@ fn test_intel_4001_state_machine_reset() {
|
||||||
|
|
||||||
// Assert RESET (active LOW - set to Low to trigger reset)
|
// Assert RESET (active LOW - set to Low to trigger reset)
|
||||||
{
|
{
|
||||||
let mut reset_guard = reset_pin.lock().unwrap();
|
let mut reset_guard = reset_pin.lock();
|
||||||
reset_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
reset_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,8 @@ fn create_test_ram() -> Intel4002 {
|
||||||
let phi1_pin = ram.get_pin("PHI1").unwrap();
|
let phi1_pin = ram.get_pin("PHI1").unwrap();
|
||||||
let phi2_pin = ram.get_pin("PHI2").unwrap();
|
let phi2_pin = ram.get_pin("PHI2").unwrap();
|
||||||
{
|
{
|
||||||
let mut phi1_guard = phi1_pin.lock().unwrap();
|
let mut phi1_guard = phi1_pin.lock();
|
||||||
let mut phi2_guard = phi2_pin.lock().unwrap();
|
let mut phi2_guard = phi2_pin.lock();
|
||||||
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||||
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||||
}
|
}
|
||||||
|
|
@ -55,7 +55,7 @@ fn set_data_bus(ram: &Intel4002, value: u8) {
|
||||||
|
|
||||||
for i in 0..4 {
|
for i in 0..4 {
|
||||||
if let Ok(pin) = ram.get_pin(&format!("D{}", i)) {
|
if let Ok(pin) = ram.get_pin(&format!("D{}", i)) {
|
||||||
if let Ok(mut pin_guard) = pin.lock() {
|
if let Some(mut pin_guard) = pin.try_lock() {
|
||||||
let bit_value = (nibble >> i) & 1;
|
let bit_value = (nibble >> i) & 1;
|
||||||
let pin_value = if bit_value == 1 {
|
let pin_value = if bit_value == 1 {
|
||||||
PinValue::High
|
PinValue::High
|
||||||
|
|
@ -74,7 +74,7 @@ fn set_data_bus(ram: &Intel4002, value: u8) {
|
||||||
fn clear_data_bus_drivers(ram: &Intel4002) {
|
fn clear_data_bus_drivers(ram: &Intel4002) {
|
||||||
for i in 0..4 {
|
for i in 0..4 {
|
||||||
if let Ok(pin) = ram.get_pin(&format!("D{}", i)) {
|
if let Ok(pin) = ram.get_pin(&format!("D{}", i)) {
|
||||||
if let Ok(mut pin_guard) = pin.lock() {
|
if let Some(mut pin_guard) = pin.try_lock() {
|
||||||
pin_guard.remove_driver("TEST");
|
pin_guard.remove_driver("TEST");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -90,8 +90,8 @@ fn generate_clock_edge(ram: &mut Intel4002, phi1_high: bool, phi2_high: bool) {
|
||||||
|
|
||||||
// Set clock pins
|
// Set clock pins
|
||||||
{
|
{
|
||||||
let mut phi1_guard = phi1_pin.lock().unwrap();
|
let mut phi1_guard = phi1_pin.lock();
|
||||||
let mut phi2_guard = phi2_pin.lock().unwrap();
|
let mut phi2_guard = phi2_pin.lock();
|
||||||
|
|
||||||
phi1_guard.set_driver(
|
phi1_guard.set_driver(
|
||||||
Some("TEST".to_string()),
|
Some("TEST".to_string()),
|
||||||
|
|
@ -217,16 +217,16 @@ fn test_ram_read_state_progression() {
|
||||||
|
|
||||||
// Read data bus value
|
// Read data bus value
|
||||||
let mut data_value = 0;
|
let mut data_value = 0;
|
||||||
if d0_pin.lock().unwrap().read() == PinValue::High {
|
if d0_pin.lock().read() == PinValue::High {
|
||||||
data_value |= 1 << 0;
|
data_value |= 1 << 0;
|
||||||
}
|
}
|
||||||
if d1_pin.lock().unwrap().read() == PinValue::High {
|
if d1_pin.lock().read() == PinValue::High {
|
||||||
data_value |= 1 << 1;
|
data_value |= 1 << 1;
|
||||||
}
|
}
|
||||||
if d2_pin.lock().unwrap().read() == PinValue::High {
|
if d2_pin.lock().read() == PinValue::High {
|
||||||
data_value |= 1 << 2;
|
data_value |= 1 << 2;
|
||||||
}
|
}
|
||||||
if d3_pin.lock().unwrap().read() == PinValue::High {
|
if d3_pin.lock().read() == PinValue::High {
|
||||||
data_value |= 1 << 3;
|
data_value |= 1 << 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -470,7 +470,7 @@ fn test_reset_functionality() {
|
||||||
// Assert RESET (active low)
|
// Assert RESET (active low)
|
||||||
let reset_pin = ram.get_pin("RESET").unwrap();
|
let reset_pin = ram.get_pin("RESET").unwrap();
|
||||||
{
|
{
|
||||||
let mut reset_guard = reset_pin.lock().unwrap();
|
let mut reset_guard = reset_pin.lock();
|
||||||
reset_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
reset_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||||
}
|
}
|
||||||
wait_for_pin_settlement();
|
wait_for_pin_settlement();
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ use rusty_emu::pin::PinValue;
|
||||||
/// Test helper to set pin value with proper driver management
|
/// Test helper to set pin value with proper driver management
|
||||||
fn set_pin_value(component: &Intel4003, pin_name: &str, value: PinValue, driver: &str) {
|
fn set_pin_value(component: &Intel4003, pin_name: &str, value: PinValue, driver: &str) {
|
||||||
if let Ok(pin) = component.get_pin(pin_name) {
|
if let Ok(pin) = component.get_pin(pin_name) {
|
||||||
if let Ok(mut pin_guard) = pin.lock() {
|
if let Some(mut pin_guard) = pin.try_lock() {
|
||||||
pin_guard.set_driver(Some(driver.to_string()), value);
|
pin_guard.set_driver(Some(driver.to_string()), value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -22,7 +22,7 @@ fn set_pin_value(component: &Intel4003, pin_name: &str, value: PinValue, driver:
|
||||||
/// Test helper to remove pin driver
|
/// Test helper to remove pin driver
|
||||||
fn remove_pin_driver(component: &Intel4003, pin_name: &str) {
|
fn remove_pin_driver(component: &Intel4003, pin_name: &str) {
|
||||||
if let Ok(pin) = component.get_pin(pin_name) {
|
if let Ok(pin) = component.get_pin(pin_name) {
|
||||||
if let Ok(mut pin_guard) = pin.lock() {
|
if let Some(mut pin_guard) = pin.try_lock() {
|
||||||
pin_guard.set_driver(None, PinValue::HighZ);
|
pin_guard.set_driver(None, PinValue::HighZ);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,8 @@
|
||||||
//! This test verifies that the Intel 4004 CPU state machine progresses correctly
|
//! This test verifies that the Intel 4004 CPU state machine progresses correctly
|
||||||
//! through all phases: Idle → AddressPhase → WaitLatency → DriveData → Idle
|
//! through all phases: Idle → AddressPhase → WaitLatency → DriveData → Idle
|
||||||
|
|
||||||
use std::sync::{Arc, Mutex};
|
use parking_lot::Mutex;
|
||||||
|
use std::sync::Arc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
|
@ -46,16 +47,8 @@ impl CpuTestHelper {
|
||||||
let phi2 = Arc::new(Mutex::new(Pin::new("PHI2".to_string())));
|
let phi2 = Arc::new(Mutex::new(Pin::new("PHI2".to_string())));
|
||||||
|
|
||||||
// Connect CPU to test clock
|
// Connect CPU to test clock
|
||||||
cpu.get_pin("PHI1")
|
cpu.get_pin("PHI1").unwrap().lock().connect_to(phi1.clone());
|
||||||
.unwrap()
|
cpu.get_pin("PHI2").unwrap().lock().connect_to(phi2.clone());
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.connect_to(phi1.clone());
|
|
||||||
cpu.get_pin("PHI2")
|
|
||||||
.unwrap()
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.connect_to(phi2.clone());
|
|
||||||
|
|
||||||
CpuTestHelper {
|
CpuTestHelper {
|
||||||
cpu,
|
cpu,
|
||||||
|
|
@ -90,11 +83,9 @@ impl CpuTestHelper {
|
||||||
// Phase 1: Φ1 high, Φ2 low
|
// Phase 1: Φ1 high, Φ2 low
|
||||||
self.phi1
|
self.phi1
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
|
||||||
.set_driver(Some("clock".to_string()), PinValue::High);
|
.set_driver(Some("clock".to_string()), PinValue::High);
|
||||||
self.phi2
|
self.phi2
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
|
||||||
.set_driver(Some("clock".to_string()), PinValue::Low);
|
.set_driver(Some("clock".to_string()), PinValue::Low);
|
||||||
thread::sleep(Duration::from_millis(1));
|
thread::sleep(Duration::from_millis(1));
|
||||||
|
|
||||||
|
|
@ -104,11 +95,9 @@ impl CpuTestHelper {
|
||||||
// Phase 2: Φ1 low, Φ2 high
|
// Phase 2: Φ1 low, Φ2 high
|
||||||
self.phi1
|
self.phi1
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
|
||||||
.set_driver(Some("clock".to_string()), PinValue::Low);
|
.set_driver(Some("clock".to_string()), PinValue::Low);
|
||||||
self.phi2
|
self.phi2
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
|
||||||
.set_driver(Some("clock".to_string()), PinValue::High);
|
.set_driver(Some("clock".to_string()), PinValue::High);
|
||||||
thread::sleep(Duration::from_millis(1));
|
thread::sleep(Duration::from_millis(1));
|
||||||
|
|
||||||
|
|
@ -117,11 +106,9 @@ impl CpuTestHelper {
|
||||||
// Phase 3: Both low
|
// Phase 3: Both low
|
||||||
self.phi1
|
self.phi1
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
|
||||||
.set_driver(Some("clock".to_string()), PinValue::Low);
|
.set_driver(Some("clock".to_string()), PinValue::Low);
|
||||||
self.phi2
|
self.phi2
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
|
||||||
.set_driver(Some("clock".to_string()), PinValue::Low);
|
.set_driver(Some("clock".to_string()), PinValue::Low);
|
||||||
thread::sleep(Duration::from_millis(1));
|
thread::sleep(Duration::from_millis(1));
|
||||||
|
|
||||||
|
|
@ -137,25 +124,15 @@ impl CpuTestHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_control_pins(&self, sync: bool, cm: bool, reset: bool) {
|
fn set_control_pins(&self, sync: bool, cm: bool, reset: bool) {
|
||||||
self.cpu
|
self.cpu.get_pin("SYNC").unwrap().lock().set_driver(
|
||||||
.get_pin("SYNC")
|
|
||||||
.unwrap()
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.set_driver(
|
|
||||||
Some("test".to_string()),
|
Some("test".to_string()),
|
||||||
if sync { PinValue::High } else { PinValue::Low },
|
if sync { PinValue::High } else { PinValue::Low },
|
||||||
);
|
);
|
||||||
self.cpu.get_pin("CM").unwrap().lock().unwrap().set_driver(
|
self.cpu.get_pin("CM").unwrap().lock().set_driver(
|
||||||
Some("test".to_string()),
|
Some("test".to_string()),
|
||||||
if cm { PinValue::High } else { PinValue::Low },
|
if cm { PinValue::High } else { PinValue::Low },
|
||||||
);
|
);
|
||||||
self.cpu
|
self.cpu.get_pin("RESET").unwrap().lock().set_driver(
|
||||||
.get_pin("RESET")
|
|
||||||
.unwrap()
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.set_driver(
|
|
||||||
Some("test".to_string()),
|
Some("test".to_string()),
|
||||||
if reset { PinValue::High } else { PinValue::Low },
|
if reset { PinValue::High } else { PinValue::Low },
|
||||||
);
|
);
|
||||||
|
|
@ -168,7 +145,6 @@ impl CpuTestHelper {
|
||||||
.get_pin(&format!("D{}", i))
|
.get_pin(&format!("D{}", i))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
|
||||||
.set_driver(
|
.set_driver(
|
||||||
Some("test".to_string()),
|
Some("test".to_string()),
|
||||||
if bit_value == 1 {
|
if bit_value == 1 {
|
||||||
|
|
@ -184,26 +160,24 @@ impl CpuTestHelper {
|
||||||
let mut data = 0;
|
let mut data = 0;
|
||||||
for i in 0..4 {
|
for i in 0..4 {
|
||||||
if let Ok(pin) = self.cpu.get_pin(&format!("D{}", i)) {
|
if let Ok(pin) = self.cpu.get_pin(&format!("D{}", i)) {
|
||||||
if let Ok(pin_guard) = pin.lock() {
|
let pin_guard = pin.lock();
|
||||||
if pin_guard.read() == PinValue::High {
|
if pin_guard.read() == PinValue::High {
|
||||||
data |= 1 << i;
|
data |= 1 << i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
data & 0x0F
|
data & 0x0F
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tri_state_data_bus(&self) {
|
fn tri_state_data_bus(&self) {
|
||||||
for i in 0..4 {
|
for i in 0..4 {
|
||||||
if let Ok(pin) = self.cpu.get_pin(&format!("D{}", i)) {
|
if let Ok(pin) = self.cpu.get_pin(&format!("D{}", i)) {
|
||||||
if let Ok(mut pin_guard) = pin.lock() {
|
let mut pin_guard = pin.lock();
|
||||||
pin_guard.set_driver(Some("test".to_string()), PinValue::HighZ);
|
pin_guard.set_driver(Some("test".to_string()), PinValue::HighZ);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper function to run tests with timeout
|
/// Wrapper function to run tests with timeout
|
||||||
#[allow(dead_code)] // This function is kept for future use but not currently used
|
#[allow(dead_code)] // This function is kept for future use but not currently used
|
||||||
|
|
@ -294,7 +268,7 @@ fn test_intel_4004_state_machine_progression() {
|
||||||
for i in 0..4 {
|
for i in 0..4 {
|
||||||
let pin_name = format!("D{}", i);
|
let pin_name = format!("D{}", i);
|
||||||
if let Ok(pin) = helper.cpu.get_pin(&pin_name) {
|
if let Ok(pin) = helper.cpu.get_pin(&pin_name) {
|
||||||
let mut pin_guard = pin.lock().unwrap();
|
let mut pin_guard = pin.lock();
|
||||||
pin_guard.set_driver(Some("test".to_string()), PinValue::High);
|
pin_guard.set_driver(Some("test".to_string()), PinValue::High);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -352,7 +326,6 @@ fn test_intel_4004_reset_functionality() {
|
||||||
.get_pin("RESET")
|
.get_pin("RESET")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
|
||||||
.set_driver(Some("test".to_string()), PinValue::High);
|
.set_driver(Some("test".to_string()), PinValue::High);
|
||||||
helper.cpu.update();
|
helper.cpu.update();
|
||||||
|
|
||||||
|
|
@ -362,7 +335,6 @@ fn test_intel_4004_reset_functionality() {
|
||||||
.get_pin("RESET")
|
.get_pin("RESET")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
|
||||||
.set_driver(Some("test".to_string()), PinValue::Low);
|
.set_driver(Some("test".to_string()), PinValue::Low);
|
||||||
|
|
||||||
// Process the reset signal for multiple cycles to ensure it's applied
|
// Process the reset signal for multiple cycles to ensure it's applied
|
||||||
|
|
@ -378,7 +350,6 @@ fn test_intel_4004_reset_functionality() {
|
||||||
.get_pin("RESET")
|
.get_pin("RESET")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
|
||||||
.set_driver(Some("test".to_string()), PinValue::High);
|
.set_driver(Some("test".to_string()), PinValue::High);
|
||||||
helper.cpu.update();
|
helper.cpu.update();
|
||||||
thread::sleep(Duration::from_millis(1));
|
thread::sleep(Duration::from_millis(1));
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
|
use parking_lot::Mutex;
|
||||||
use rusty_emu::component::{BaseComponent, Component};
|
use rusty_emu::component::{BaseComponent, Component};
|
||||||
use rusty_emu::components::common::intel_400x::*;
|
use rusty_emu::components::common::intel_400x::*;
|
||||||
use rusty_emu::components::memory::intel_4001::{Intel4001, IoDirection, IoMode};
|
use rusty_emu::components::memory::intel_4001::{Intel4001, IoDirection, IoMode};
|
||||||
use rusty_emu::pin::{Pin, PinValue};
|
use rusty_emu::pin::{Pin, PinValue};
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::Arc;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
// Mock component for testing trait implementations
|
// Mock component for testing trait implementations
|
||||||
|
|
@ -47,9 +48,17 @@ impl Component for MockComponent {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_running(&mut self, _running: bool) {
|
||||||
|
// No-op for mock
|
||||||
|
}
|
||||||
|
|
||||||
fn as_any(&self) -> &dyn Any {
|
fn as_any(&self) -> &dyn Any {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Intel400xAddressHandling for MockComponent {
|
impl Intel400xAddressHandling for MockComponent {
|
||||||
|
|
@ -354,57 +363,29 @@ mod tests {
|
||||||
let d3_pin = rom.get_pin("D3").unwrap();
|
let d3_pin = rom.get_pin("D3").unwrap();
|
||||||
|
|
||||||
// Set settlement time to 0 for immediate testing
|
// Set settlement time to 0 for immediate testing
|
||||||
sync_pin
|
sync_pin.lock().set_settlement_time(Duration::from_nanos(0));
|
||||||
.lock()
|
cm_pin.lock().set_settlement_time(Duration::from_nanos(0));
|
||||||
.unwrap()
|
ci_pin.lock().set_settlement_time(Duration::from_nanos(0));
|
||||||
.set_settlement_time(Duration::from_nanos(0));
|
|
||||||
cm_pin
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.set_settlement_time(Duration::from_nanos(0));
|
|
||||||
ci_pin
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.set_settlement_time(Duration::from_nanos(0));
|
|
||||||
reset_pin
|
reset_pin
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
|
||||||
.set_settlement_time(Duration::from_nanos(0));
|
|
||||||
phi1_pin
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.set_settlement_time(Duration::from_nanos(0));
|
|
||||||
phi2_pin
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.set_settlement_time(Duration::from_nanos(0));
|
|
||||||
d0_pin
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.set_settlement_time(Duration::from_nanos(0));
|
|
||||||
d1_pin
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.set_settlement_time(Duration::from_nanos(0));
|
|
||||||
d2_pin
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.set_settlement_time(Duration::from_nanos(0));
|
|
||||||
d3_pin
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.set_settlement_time(Duration::from_nanos(0));
|
.set_settlement_time(Duration::from_nanos(0));
|
||||||
|
phi1_pin.lock().set_settlement_time(Duration::from_nanos(0));
|
||||||
|
phi2_pin.lock().set_settlement_time(Duration::from_nanos(0));
|
||||||
|
d0_pin.lock().set_settlement_time(Duration::from_nanos(0));
|
||||||
|
d1_pin.lock().set_settlement_time(Duration::from_nanos(0));
|
||||||
|
d2_pin.lock().set_settlement_time(Duration::from_nanos(0));
|
||||||
|
d3_pin.lock().set_settlement_time(Duration::from_nanos(0));
|
||||||
|
|
||||||
// Initially, all data pins should be HighZ
|
// Initially, all data pins should be HighZ
|
||||||
assert_eq!(d0_pin.lock().unwrap().read(), PinValue::HighZ);
|
assert_eq!(d0_pin.lock().read(), PinValue::HighZ);
|
||||||
assert_eq!(d1_pin.lock().unwrap().read(), PinValue::HighZ);
|
assert_eq!(d1_pin.lock().read(), PinValue::HighZ);
|
||||||
assert_eq!(d2_pin.lock().unwrap().read(), PinValue::HighZ);
|
assert_eq!(d2_pin.lock().read(), PinValue::HighZ);
|
||||||
assert_eq!(d3_pin.lock().unwrap().read(), PinValue::HighZ);
|
assert_eq!(d3_pin.lock().read(), PinValue::HighZ);
|
||||||
|
|
||||||
// Test reset (RESET is active LOW)
|
// Test reset (RESET is active LOW)
|
||||||
println!("=== Testing Reset ===");
|
println!("=== Testing Reset ===");
|
||||||
{
|
{
|
||||||
let mut reset_guard = reset_pin.lock().unwrap();
|
let mut reset_guard = reset_pin.lock();
|
||||||
reset_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Assert reset
|
reset_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Assert reset
|
||||||
}
|
}
|
||||||
rom.update();
|
rom.update();
|
||||||
|
|
@ -413,7 +394,7 @@ mod tests {
|
||||||
|
|
||||||
// Release reset (set HIGH for normal operation)
|
// Release reset (set HIGH for normal operation)
|
||||||
{
|
{
|
||||||
let mut reset_guard = reset_pin.lock().unwrap();
|
let mut reset_guard = reset_pin.lock();
|
||||||
reset_guard.set_driver(Some("TEST".to_string()), PinValue::High); // Release reset
|
reset_guard.set_driver(Some("TEST".to_string()), PinValue::High); // Release reset
|
||||||
}
|
}
|
||||||
rom.update();
|
rom.update();
|
||||||
|
|
@ -466,11 +447,11 @@ mod tests {
|
||||||
// Pin state snapshots
|
// Pin state snapshots
|
||||||
println!(
|
println!(
|
||||||
"Pin states: SYNC: {:?}, CM: {:?}, CI: {:?}, PHI1: {:?}, PHI2: {:?}",
|
"Pin states: SYNC: {:?}, CM: {:?}, CI: {:?}, PHI1: {:?}, PHI2: {:?}",
|
||||||
sync_pin.lock().unwrap().read(),
|
sync_pin.lock().read(),
|
||||||
cm_pin.lock().unwrap().read(),
|
cm_pin.lock().read(),
|
||||||
ci_pin.lock().unwrap().read(),
|
ci_pin.lock().read(),
|
||||||
phi1_pin.lock().unwrap().read(),
|
phi1_pin.lock().read(),
|
||||||
phi2_pin.lock().unwrap().read()
|
phi2_pin.lock().read()
|
||||||
);
|
);
|
||||||
|
|
||||||
println!("Test completed successfully!");
|
println!("Test completed successfully!");
|
||||||
|
|
@ -493,22 +474,22 @@ mod tests {
|
||||||
// Reset to known state first (RESET is active LOW)
|
// Reset to known state first (RESET is active LOW)
|
||||||
{
|
{
|
||||||
let reset_pin = rom.get_pin("RESET").unwrap();
|
let reset_pin = rom.get_pin("RESET").unwrap();
|
||||||
let mut reset_guard = reset_pin.lock().unwrap();
|
let mut reset_guard = reset_pin.lock();
|
||||||
reset_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Assert reset (active LOW)
|
reset_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Assert reset (active LOW)
|
||||||
}
|
}
|
||||||
rom.update();
|
rom.update();
|
||||||
{
|
{
|
||||||
let reset_pin = rom.get_pin("RESET").unwrap();
|
let reset_pin = rom.get_pin("RESET").unwrap();
|
||||||
let mut reset_guard = reset_pin.lock().unwrap();
|
let mut reset_guard = reset_pin.lock();
|
||||||
reset_guard.set_driver(Some("TEST".to_string()), PinValue::High); // Release reset (HIGH = normal operation)
|
reset_guard.set_driver(Some("TEST".to_string()), PinValue::High); // Release reset (HIGH = normal operation)
|
||||||
}
|
}
|
||||||
rom.update();
|
rom.update();
|
||||||
|
|
||||||
// Set SYNC=1, CM=1, CI=0 for memory read
|
// Set SYNC=1, CM=1, CI=0 for memory read
|
||||||
{
|
{
|
||||||
let mut sync_guard = sync_pin.lock().unwrap();
|
let mut sync_guard = sync_pin.lock();
|
||||||
let mut cm_guard = cm_pin.lock().unwrap();
|
let mut cm_guard = cm_pin.lock();
|
||||||
let mut ci_guard = ci_pin.lock().unwrap();
|
let mut ci_guard = ci_pin.lock();
|
||||||
sync_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
sync_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
||||||
cm_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
cm_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
||||||
ci_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
ci_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||||
|
|
@ -517,14 +498,14 @@ mod tests {
|
||||||
|
|
||||||
// Ensure PHI2 is low initially
|
// Ensure PHI2 is low initially
|
||||||
{
|
{
|
||||||
let mut phi2_guard = phi2_pin.lock().unwrap();
|
let mut phi2_guard = phi2_pin.lock();
|
||||||
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||||
}
|
}
|
||||||
rom.update();
|
rom.update();
|
||||||
|
|
||||||
// Ensure PHI1 is low initially
|
// Ensure PHI1 is low initially
|
||||||
{
|
{
|
||||||
let mut phi1_guard = phi1_pin.lock().unwrap();
|
let mut phi1_guard = phi1_pin.lock();
|
||||||
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||||
}
|
}
|
||||||
rom.update();
|
rom.update();
|
||||||
|
|
@ -534,7 +515,7 @@ mod tests {
|
||||||
|
|
||||||
// PHI1 low to high: Address phase for high nibble
|
// PHI1 low to high: Address phase for high nibble
|
||||||
{
|
{
|
||||||
let mut phi1_guard = phi1_pin.lock().unwrap();
|
let mut phi1_guard = phi1_pin.lock();
|
||||||
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
||||||
}
|
}
|
||||||
rom.update();
|
rom.update();
|
||||||
|
|
@ -546,14 +527,14 @@ mod tests {
|
||||||
|
|
||||||
// PHI1 high to low: Complete address phase for high nibble
|
// PHI1 high to low: Complete address phase for high nibble
|
||||||
{
|
{
|
||||||
let mut phi1_guard = phi1_pin.lock().unwrap();
|
let mut phi1_guard = phi1_pin.lock();
|
||||||
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||||
}
|
}
|
||||||
rom.update();
|
rom.update();
|
||||||
|
|
||||||
// PHI1 low to high again: Address phase for low nibble
|
// PHI1 low to high again: Address phase for low nibble
|
||||||
{
|
{
|
||||||
let mut phi1_guard = phi1_pin.lock().unwrap();
|
let mut phi1_guard = phi1_pin.lock();
|
||||||
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
||||||
}
|
}
|
||||||
rom.update();
|
rom.update();
|
||||||
|
|
@ -564,7 +545,7 @@ mod tests {
|
||||||
|
|
||||||
// PHI1 high to low: Complete address phase
|
// PHI1 high to low: Complete address phase
|
||||||
{
|
{
|
||||||
let mut phi1_guard = phi1_pin.lock().unwrap();
|
let mut phi1_guard = phi1_pin.lock();
|
||||||
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||||
}
|
}
|
||||||
rom.update();
|
rom.update();
|
||||||
|
|
@ -589,14 +570,14 @@ mod tests {
|
||||||
// Release data bus from test control to allow ROM to drive it
|
// Release data bus from test control to allow ROM to drive it
|
||||||
for i in 0..4 {
|
for i in 0..4 {
|
||||||
let pin = rom.get_pin(&format!("D{}", i)).unwrap();
|
let pin = rom.get_pin(&format!("D{}", i)).unwrap();
|
||||||
let mut pin_guard = pin.lock().unwrap();
|
let mut pin_guard = pin.lock();
|
||||||
pin_guard.set_driver(Some("TEST".to_string()), PinValue::HighZ);
|
pin_guard.set_driver(Some("TEST".to_string()), PinValue::HighZ);
|
||||||
}
|
}
|
||||||
rom.update();
|
rom.update();
|
||||||
|
|
||||||
// PHI2 low to high: Data phase
|
// PHI2 low to high: Data phase
|
||||||
{
|
{
|
||||||
let mut phi2_guard = phi2_pin.lock().unwrap();
|
let mut phi2_guard = phi2_pin.lock();
|
||||||
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
||||||
}
|
}
|
||||||
rom.update();
|
rom.update();
|
||||||
|
|
@ -612,17 +593,17 @@ mod tests {
|
||||||
|
|
||||||
// PHI2 high to low: Cleanup
|
// PHI2 high to low: Cleanup
|
||||||
{
|
{
|
||||||
let mut phi2_guard = phi2_pin.lock().unwrap();
|
let mut phi2_guard = phi2_pin.lock();
|
||||||
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||||
}
|
}
|
||||||
rom.update();
|
rom.update();
|
||||||
assert_eq!(rom.get_timing_state(), TimingState::Idle);
|
assert_eq!(rom.get_timing_state(), TimingState::Idle);
|
||||||
|
|
||||||
// Tri-state pins
|
// Tri-state pins
|
||||||
assert_eq!(d0_pin.lock().unwrap().read(), PinValue::HighZ);
|
assert_eq!(d0_pin.lock().read(), PinValue::HighZ);
|
||||||
assert_eq!(d1_pin.lock().unwrap().read(), PinValue::HighZ);
|
assert_eq!(d1_pin.lock().read(), PinValue::HighZ);
|
||||||
assert_eq!(d2_pin.lock().unwrap().read(), PinValue::HighZ);
|
assert_eq!(d2_pin.lock().read(), PinValue::HighZ);
|
||||||
assert_eq!(d3_pin.lock().unwrap().read(), PinValue::HighZ);
|
assert_eq!(d3_pin.lock().read(), PinValue::HighZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -644,46 +625,18 @@ mod tests {
|
||||||
let d3_pin = chip.get_pin("D3").unwrap();
|
let d3_pin = chip.get_pin("D3").unwrap();
|
||||||
|
|
||||||
// Set settlement time to 0 for immediate testing
|
// Set settlement time to 0 for immediate testing
|
||||||
sync_pin
|
sync_pin.lock().set_settlement_time(Duration::from_nanos(0));
|
||||||
.lock()
|
cm_pin.lock().set_settlement_time(Duration::from_nanos(0));
|
||||||
.unwrap()
|
ci_pin.lock().set_settlement_time(Duration::from_nanos(0));
|
||||||
.set_settlement_time(Duration::from_nanos(0));
|
|
||||||
cm_pin
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.set_settlement_time(Duration::from_nanos(0));
|
|
||||||
ci_pin
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.set_settlement_time(Duration::from_nanos(0));
|
|
||||||
reset_pin
|
reset_pin
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
|
||||||
.set_settlement_time(Duration::from_nanos(0));
|
|
||||||
phi1_pin
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.set_settlement_time(Duration::from_nanos(0));
|
|
||||||
phi2_pin
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.set_settlement_time(Duration::from_nanos(0));
|
|
||||||
d0_pin
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.set_settlement_time(Duration::from_nanos(0));
|
|
||||||
d1_pin
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.set_settlement_time(Duration::from_nanos(0));
|
|
||||||
d2_pin
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.set_settlement_time(Duration::from_nanos(0));
|
|
||||||
d3_pin
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.set_settlement_time(Duration::from_nanos(0));
|
.set_settlement_time(Duration::from_nanos(0));
|
||||||
|
phi1_pin.lock().set_settlement_time(Duration::from_nanos(0));
|
||||||
|
phi2_pin.lock().set_settlement_time(Duration::from_nanos(0));
|
||||||
|
d0_pin.lock().set_settlement_time(Duration::from_nanos(0));
|
||||||
|
d1_pin.lock().set_settlement_time(Duration::from_nanos(0));
|
||||||
|
d2_pin.lock().set_settlement_time(Duration::from_nanos(0));
|
||||||
|
d3_pin.lock().set_settlement_time(Duration::from_nanos(0));
|
||||||
|
|
||||||
// Test memory read at minimum timing
|
// Test memory read at minimum timing
|
||||||
// Note: Intel 4004 has a 4-bit data bus, so we can only read 4 bits at a time
|
// Note: Intel 4004 has a 4-bit data bus, so we can only read 4 bits at a time
|
||||||
|
|
@ -861,28 +814,19 @@ mod tests {
|
||||||
// Set settlement times to 0
|
// Set settlement times to 0
|
||||||
test_sync
|
test_sync
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
|
||||||
.set_settlement_time(Duration::from_nanos(0));
|
|
||||||
test_cm
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.set_settlement_time(Duration::from_nanos(0));
|
|
||||||
test_ci
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.set_settlement_time(Duration::from_nanos(0));
|
.set_settlement_time(Duration::from_nanos(0));
|
||||||
|
test_cm.lock().set_settlement_time(Duration::from_nanos(0));
|
||||||
|
test_ci.lock().set_settlement_time(Duration::from_nanos(0));
|
||||||
test_phi1
|
test_phi1
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
|
||||||
.set_settlement_time(Duration::from_nanos(0));
|
.set_settlement_time(Duration::from_nanos(0));
|
||||||
test_phi2
|
test_phi2
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
|
||||||
.set_settlement_time(Duration::from_nanos(0));
|
.set_settlement_time(Duration::from_nanos(0));
|
||||||
|
|
||||||
// Initialize PHI2 to Low to ensure proper edge detection
|
// Initialize PHI2 to Low to ensure proper edge detection
|
||||||
{
|
{
|
||||||
let mut phi2_guard = test_phi2.lock().unwrap();
|
let mut phi2_guard = test_phi2.lock();
|
||||||
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||||
}
|
}
|
||||||
test_chip.update();
|
test_chip.update();
|
||||||
|
|
@ -890,9 +834,9 @@ mod tests {
|
||||||
// Manually step through the state machine to verify timing
|
// Manually step through the state machine to verify timing
|
||||||
// Set control pins for memory read
|
// Set control pins for memory read
|
||||||
{
|
{
|
||||||
let mut sync_guard = test_sync.lock().unwrap();
|
let mut sync_guard = test_sync.lock();
|
||||||
let mut cm_guard = test_cm.lock().unwrap();
|
let mut cm_guard = test_cm.lock();
|
||||||
let mut ci_guard = test_ci.lock().unwrap();
|
let mut ci_guard = test_ci.lock();
|
||||||
sync_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
sync_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
||||||
cm_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
cm_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
||||||
ci_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
ci_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||||
|
|
@ -902,14 +846,14 @@ mod tests {
|
||||||
|
|
||||||
// Ensure PHI1 is low initially
|
// Ensure PHI1 is low initially
|
||||||
{
|
{
|
||||||
let mut phi1_guard = test_phi1.lock().unwrap();
|
let mut phi1_guard = test_phi1.lock();
|
||||||
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||||
}
|
}
|
||||||
test_chip.update();
|
test_chip.update();
|
||||||
|
|
||||||
// Start address phase - first PHI1 cycle for high nibble
|
// Start address phase - first PHI1 cycle for high nibble
|
||||||
{
|
{
|
||||||
let mut phi1_guard = test_phi1.lock().unwrap();
|
let mut phi1_guard = test_phi1.lock();
|
||||||
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
||||||
}
|
}
|
||||||
test_chip.update();
|
test_chip.update();
|
||||||
|
|
@ -921,7 +865,7 @@ mod tests {
|
||||||
|
|
||||||
// PHI1 falling edge - latch high nibble
|
// PHI1 falling edge - latch high nibble
|
||||||
{
|
{
|
||||||
let mut phi1_guard = test_phi1.lock().unwrap();
|
let mut phi1_guard = test_phi1.lock();
|
||||||
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||||
}
|
}
|
||||||
test_chip.update();
|
test_chip.update();
|
||||||
|
|
@ -929,7 +873,7 @@ mod tests {
|
||||||
|
|
||||||
// Second PHI1 cycle for low nibble
|
// Second PHI1 cycle for low nibble
|
||||||
{
|
{
|
||||||
let mut phi1_guard = test_phi1.lock().unwrap();
|
let mut phi1_guard = test_phi1.lock();
|
||||||
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
||||||
}
|
}
|
||||||
test_chip.update();
|
test_chip.update();
|
||||||
|
|
@ -940,7 +884,7 @@ mod tests {
|
||||||
|
|
||||||
// PHI1 falling edge - latch low nibble and complete address
|
// PHI1 falling edge - latch low nibble and complete address
|
||||||
{
|
{
|
||||||
let mut phi1_guard = test_phi1.lock().unwrap();
|
let mut phi1_guard = test_phi1.lock();
|
||||||
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||||
}
|
}
|
||||||
test_chip.update();
|
test_chip.update();
|
||||||
|
|
@ -951,7 +895,7 @@ mod tests {
|
||||||
|
|
||||||
// PHI2 high: Data phase
|
// PHI2 high: Data phase
|
||||||
{
|
{
|
||||||
let mut phi2_guard = test_phi2.lock().unwrap();
|
let mut phi2_guard = test_phi2.lock();
|
||||||
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
||||||
}
|
}
|
||||||
test_chip.update();
|
test_chip.update();
|
||||||
|
|
@ -963,7 +907,7 @@ mod tests {
|
||||||
|
|
||||||
// Complete cycle
|
// Complete cycle
|
||||||
{
|
{
|
||||||
let mut phi2_guard = test_phi2.lock().unwrap();
|
let mut phi2_guard = test_phi2.lock();
|
||||||
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||||
}
|
}
|
||||||
test_chip.update();
|
test_chip.update();
|
||||||
|
|
@ -984,37 +928,28 @@ mod tests {
|
||||||
// Set settlement times to 0
|
// Set settlement times to 0
|
||||||
fast_sync
|
fast_sync
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
|
||||||
.set_settlement_time(Duration::from_nanos(0));
|
|
||||||
fast_cm
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.set_settlement_time(Duration::from_nanos(0));
|
|
||||||
fast_ci
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.set_settlement_time(Duration::from_nanos(0));
|
.set_settlement_time(Duration::from_nanos(0));
|
||||||
|
fast_cm.lock().set_settlement_time(Duration::from_nanos(0));
|
||||||
|
fast_ci.lock().set_settlement_time(Duration::from_nanos(0));
|
||||||
fast_phi1
|
fast_phi1
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
|
||||||
.set_settlement_time(Duration::from_nanos(0));
|
.set_settlement_time(Duration::from_nanos(0));
|
||||||
fast_phi2
|
fast_phi2
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
|
||||||
.set_settlement_time(Duration::from_nanos(0));
|
.set_settlement_time(Duration::from_nanos(0));
|
||||||
|
|
||||||
// Initialize PHI2 to Low to ensure proper edge detection
|
// Initialize PHI2 to Low to ensure proper edge detection
|
||||||
{
|
{
|
||||||
let mut phi2_guard = fast_phi2.lock().unwrap();
|
let mut phi2_guard = fast_phi2.lock();
|
||||||
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||||
}
|
}
|
||||||
fast_chip.update();
|
fast_chip.update();
|
||||||
|
|
||||||
// Set up for memory read
|
// Set up for memory read
|
||||||
{
|
{
|
||||||
let mut sync_guard = fast_sync.lock().unwrap();
|
let mut sync_guard = fast_sync.lock();
|
||||||
let mut cm_guard = fast_cm.lock().unwrap();
|
let mut cm_guard = fast_cm.lock();
|
||||||
let mut ci_guard = fast_ci.lock().unwrap();
|
let mut ci_guard = fast_ci.lock();
|
||||||
sync_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
sync_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
||||||
cm_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
cm_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
||||||
ci_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
ci_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||||
|
|
@ -1023,14 +958,14 @@ mod tests {
|
||||||
|
|
||||||
// Ensure PHI1 is low initially
|
// Ensure PHI1 is low initially
|
||||||
{
|
{
|
||||||
let mut phi1_guard = fast_phi1.lock().unwrap();
|
let mut phi1_guard = fast_phi1.lock();
|
||||||
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||||
}
|
}
|
||||||
fast_chip.update();
|
fast_chip.update();
|
||||||
|
|
||||||
// First PHI1 cycle - latch high nibble
|
// First PHI1 cycle - latch high nibble
|
||||||
{
|
{
|
||||||
let mut phi1_guard = fast_phi1.lock().unwrap();
|
let mut phi1_guard = fast_phi1.lock();
|
||||||
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
||||||
}
|
}
|
||||||
fast_chip.update();
|
fast_chip.update();
|
||||||
|
|
@ -1041,14 +976,14 @@ mod tests {
|
||||||
|
|
||||||
// PHI1 falling edge - latch high nibble
|
// PHI1 falling edge - latch high nibble
|
||||||
{
|
{
|
||||||
let mut phi1_guard = fast_phi1.lock().unwrap();
|
let mut phi1_guard = fast_phi1.lock();
|
||||||
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||||
}
|
}
|
||||||
fast_chip.update();
|
fast_chip.update();
|
||||||
|
|
||||||
// Second PHI1 cycle - latch low nibble
|
// Second PHI1 cycle - latch low nibble
|
||||||
{
|
{
|
||||||
let mut phi1_guard = fast_phi1.lock().unwrap();
|
let mut phi1_guard = fast_phi1.lock();
|
||||||
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
||||||
}
|
}
|
||||||
fast_chip.update();
|
fast_chip.update();
|
||||||
|
|
@ -1059,7 +994,7 @@ mod tests {
|
||||||
|
|
||||||
// PHI1 falling edge - latch low nibble and complete address
|
// PHI1 falling edge - latch low nibble and complete address
|
||||||
{
|
{
|
||||||
let mut phi1_guard = fast_phi1.lock().unwrap();
|
let mut phi1_guard = fast_phi1.lock();
|
||||||
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||||
}
|
}
|
||||||
fast_chip.update();
|
fast_chip.update();
|
||||||
|
|
@ -1075,14 +1010,14 @@ mod tests {
|
||||||
// Release data bus from test control to allow ROM to drive it
|
// Release data bus from test control to allow ROM to drive it
|
||||||
for i in 0..4 {
|
for i in 0..4 {
|
||||||
let pin = fast_chip.get_pin(&format!("D{}", i)).unwrap();
|
let pin = fast_chip.get_pin(&format!("D{}", i)).unwrap();
|
||||||
let mut pin_guard = pin.lock().unwrap();
|
let mut pin_guard = pin.lock();
|
||||||
pin_guard.set_driver(Some("TEST".to_string()), PinValue::HighZ);
|
pin_guard.set_driver(Some("TEST".to_string()), PinValue::HighZ);
|
||||||
}
|
}
|
||||||
fast_chip.update();
|
fast_chip.update();
|
||||||
|
|
||||||
// Try to read data - PHI2 rising edge triggers data driving if latency elapsed
|
// Try to read data - PHI2 rising edge triggers data driving if latency elapsed
|
||||||
{
|
{
|
||||||
let mut phi2_guard = fast_phi2.lock().unwrap();
|
let mut phi2_guard = fast_phi2.lock();
|
||||||
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
||||||
}
|
}
|
||||||
fast_chip.update();
|
fast_chip.update();
|
||||||
|
|
@ -1124,9 +1059,9 @@ mod tests {
|
||||||
|
|
||||||
// Set control pins for memory read
|
// Set control pins for memory read
|
||||||
{
|
{
|
||||||
let mut sync_guard = sync_pin.lock().unwrap();
|
let mut sync_guard = sync_pin.lock();
|
||||||
let mut cm_guard = cm_pin.lock().unwrap();
|
let mut cm_guard = cm_pin.lock();
|
||||||
let mut ci_guard = ci_pin.lock().unwrap();
|
let mut ci_guard = ci_pin.lock();
|
||||||
sync_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
sync_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
||||||
cm_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
cm_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
||||||
ci_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
ci_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||||
|
|
@ -1135,21 +1070,21 @@ mod tests {
|
||||||
|
|
||||||
// Ensure PHI2 is low initially
|
// Ensure PHI2 is low initially
|
||||||
{
|
{
|
||||||
let mut phi2_guard = phi2_pin.lock().unwrap();
|
let mut phi2_guard = phi2_pin.lock();
|
||||||
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||||
}
|
}
|
||||||
chip.update();
|
chip.update();
|
||||||
|
|
||||||
// Ensure PHI1 is low initially
|
// Ensure PHI1 is low initially
|
||||||
{
|
{
|
||||||
let mut phi1_guard = phi1_pin.lock().unwrap();
|
let mut phi1_guard = phi1_pin.lock();
|
||||||
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||||
}
|
}
|
||||||
chip.update();
|
chip.update();
|
||||||
|
|
||||||
// PHI1 low to high: Address phase for high nibble
|
// PHI1 low to high: Address phase for high nibble
|
||||||
{
|
{
|
||||||
let mut phi1_guard = phi1_pin.lock().unwrap();
|
let mut phi1_guard = phi1_pin.lock();
|
||||||
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
||||||
}
|
}
|
||||||
chip.update();
|
chip.update();
|
||||||
|
|
@ -1160,14 +1095,14 @@ mod tests {
|
||||||
|
|
||||||
// PHI1 high to low: Complete address phase for high nibble
|
// PHI1 high to low: Complete address phase for high nibble
|
||||||
{
|
{
|
||||||
let mut phi1_guard = phi1_pin.lock().unwrap();
|
let mut phi1_guard = phi1_pin.lock();
|
||||||
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||||
}
|
}
|
||||||
chip.update();
|
chip.update();
|
||||||
|
|
||||||
// PHI1 low to high again: Address phase for low nibble
|
// PHI1 low to high again: Address phase for low nibble
|
||||||
{
|
{
|
||||||
let mut phi1_guard = phi1_pin.lock().unwrap();
|
let mut phi1_guard = phi1_pin.lock();
|
||||||
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
||||||
}
|
}
|
||||||
chip.update();
|
chip.update();
|
||||||
|
|
@ -1178,7 +1113,7 @@ mod tests {
|
||||||
|
|
||||||
// PHI1 high to low: Complete address phase
|
// PHI1 high to low: Complete address phase
|
||||||
{
|
{
|
||||||
let mut phi1_guard = phi1_pin.lock().unwrap();
|
let mut phi1_guard = phi1_pin.lock();
|
||||||
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||||
}
|
}
|
||||||
chip.update();
|
chip.update();
|
||||||
|
|
@ -1208,14 +1143,14 @@ mod tests {
|
||||||
// Release data bus from test control to allow ROM to drive it
|
// Release data bus from test control to allow ROM to drive it
|
||||||
for i in 0..4 {
|
for i in 0..4 {
|
||||||
let pin = chip.get_pin(&format!("D{}", i)).unwrap();
|
let pin = chip.get_pin(&format!("D{}", i)).unwrap();
|
||||||
let mut pin_guard = pin.lock().unwrap();
|
let mut pin_guard = pin.lock();
|
||||||
pin_guard.set_driver(Some("TEST".to_string()), PinValue::HighZ);
|
pin_guard.set_driver(Some("TEST".to_string()), PinValue::HighZ);
|
||||||
}
|
}
|
||||||
chip.update();
|
chip.update();
|
||||||
|
|
||||||
// PHI2 low to high: Data phase
|
// PHI2 low to high: Data phase
|
||||||
{
|
{
|
||||||
let mut phi2_guard = phi2_pin.lock().unwrap();
|
let mut phi2_guard = phi2_pin.lock();
|
||||||
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
||||||
}
|
}
|
||||||
chip.update();
|
chip.update();
|
||||||
|
|
@ -1230,7 +1165,7 @@ mod tests {
|
||||||
|
|
||||||
// PHI2 high to low: Cleanup
|
// PHI2 high to low: Cleanup
|
||||||
{
|
{
|
||||||
let mut phi2_guard = phi2_pin.lock().unwrap();
|
let mut phi2_guard = phi2_pin.lock();
|
||||||
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||||
}
|
}
|
||||||
chip.update();
|
chip.update();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue