Unified the 400x state machine for address latching
Fixed timing issues
This commit is contained in:
parent
e17d6ad177
commit
633463e370
26 changed files with 11904 additions and 2471 deletions
|
|
@ -1,269 +1,296 @@
|
|||
# Hardware Components
|
||||
# Rusty Emu - Cross-Platform System Emulator
|
||||
|
||||
This directory contains implementations of various hardware components used in the Intel 4004 microprocessor system and
|
||||
other architectures.
|
||||

|
||||

|
||||

|
||||
|
||||
## Component Architecture
|
||||
## 🎯 Project Overview
|
||||
|
||||
All components implement the base `Component` trait defined in `src/component.rs`, which provides:
|
||||
**Rusty Emu** is a modular, cross-platform system emulator written in Rust, designed to emulate various historical computing systems and embedded devices with a focus on accuracy and performance.
|
||||
|
||||
- **Pin Management**: Components expose named pins for interconnection
|
||||
- **Lifecycle Management**: Components can be started, stopped, and updated
|
||||
- **Thread Safety**: All components are `Send + Sync` for concurrent access
|
||||
- **State Management**: Components maintain internal state and respond to clock cycles
|
||||
### Currently Supported Systems
|
||||
|
||||
## Component Categories
|
||||
| System | Status | Description |
|
||||
|--------|---------|-------------|
|
||||
| **Intel MCS-4** | ✅ Implemented | Historical 4-bit microprocessor family |
|
||||
| **Commodore VIC-20** | 🔄 Planned | Classic 8-bit home computer |
|
||||
| **Yamaha TG100** | 🔄 Planned | AWM tone generator module |
|
||||
|
||||
### CPU Components (`cpu/`)
|
||||
---
|
||||
|
||||
#### Intel 4004 CPU
|
||||
## 🏗️ Architecture
|
||||
|
||||
- **File**: `intel_4004.rs`
|
||||
- **Features**:
|
||||
- Complete Intel 4004 instruction set implementation
|
||||
- 4-bit data processing
|
||||
- 12-bit address space
|
||||
- Two-phase clock operation (Φ1, Φ2)
|
||||
- 16 general-purpose registers (8 index, 8 main)
|
||||
- Stack-based subroutine calls
|
||||
- **Status**: Fully implemented with comprehensive testing
|
||||
### Core Components
|
||||
```text
|
||||
rusty_emu/
|
||||
├── src/
|
||||
│ ├── core/ # Emulator core infrastructure
|
||||
│ │ ├── cpu/ # CPU emulation base
|
||||
│ │ ├── memory/ # Memory management
|
||||
│ │ └── peripherals/ # I/O device handling
|
||||
│ ├── systems/ # System-specific implementations
|
||||
│ │ ├── intel_mcs4/ # Intel MCS-4 emulation
|
||||
│ │ ├── vic20/ # VIC-20 emulation (planned)
|
||||
│ │ └── yamaha_tg100/ # Yamaha TG100 emulation (planned)
|
||||
│ └── ui/ # User interface components
|
||||
```
|
||||
---
|
||||
|
||||
#### MOS 6502 CPU (Placeholder)
|
||||
### Key Features
|
||||
|
||||
- **File**: `mos_6502.rs`
|
||||
- **Features**: Placeholder for MOS Technology 6502 implementation
|
||||
- **Status**: Not yet implemented
|
||||
- **Modular Design**: Easily extensible for new systems
|
||||
- **Cross-Platform**: Runs on Windows, macOS, and Linux
|
||||
- **Cycle-Accurate**: Precise timing emulation
|
||||
- **Debugging Support**: Built-in debugging capabilities
|
||||
- **Hardware Rendering**: Efficient graphics emulation
|
||||
|
||||
#### WDC 65C02 CPU (Placeholder)
|
||||
---
|
||||
|
||||
- **File**: `wdc_65c02.rs`
|
||||
- **Features**: Placeholder for Western Design Center 65C02 implementation
|
||||
- **Status**: Not yet implemented
|
||||
## 🚀 Getting Started
|
||||
|
||||
### Memory Components (`memory/`)
|
||||
### Prerequisites
|
||||
|
||||
#### Intel 4001 ROM
|
||||
- Rust **1.70+** toolchain
|
||||
- Cargo package manager
|
||||
- System dependencies for graphics/audio
|
||||
|
||||
- **File**: `intel_4001.rs`
|
||||
- **Features**:
|
||||
- 256 bytes of mask-programmable ROM
|
||||
- 4-bit I/O ports for peripheral interface (datasheet-compliant)
|
||||
- Two-phase addressing (8-bit address in two 4-bit cycles)
|
||||
- 500ns typical access time
|
||||
- SYNC signal detection for instruction fetch
|
||||
- I/O port addressing via SRC instruction (ports 0-3)
|
||||
- WRM/RDM instruction support for I/O operations
|
||||
- I/O direction control (input/output per port)
|
||||
- I/O latch persistence as per datasheet specifications
|
||||
- **Status**: Fully implemented with comprehensive testing
|
||||
### Installation
|
||||
|
||||
##### I/O Port Features
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone https://github.com/jhunnius/rusty_emu.git
|
||||
cd rusty_emu
|
||||
|
||||
- **Port Addressing**: I/O ports 0-3 addressed using lower 2 bits of address
|
||||
- **Direction Control**: Each port can be configured as input or output
|
||||
- **Latch Persistence**: Output values persist until changed or reset
|
||||
- **CPU Control**: I/O operations controlled by WRM/RDM instructions
|
||||
- **Pin Interface**: IO0-IO3 pins for external peripheral connection
|
||||
# Build in release mode
|
||||
cargo build --release
|
||||
|
||||
#### Intel 4002 RAM
|
||||
|
||||
- **File**: `intel_4002.rs`
|
||||
- **Features**:
|
||||
- 320 bits of read/write memory (80 × 4-bit characters)
|
||||
- 4-bit output port for status/display
|
||||
- Banked memory organization (4 banks × 20 characters)
|
||||
- Refresh circuitry for dynamic memory cells
|
||||
- Status character instructions for control
|
||||
- **Status**: Implemented with known limitations
|
||||
|
||||
#### Intel 4003 Shift Register
|
||||
|
||||
- **File**: `intel_4003.rs`
|
||||
- **Features**:
|
||||
- 10-bit static shift register
|
||||
- Serial input/output capability
|
||||
- Parallel input from data bus
|
||||
- 200ns typical access time
|
||||
- Bidirectional shifting
|
||||
- **Status**: Basic implementation
|
||||
|
||||
#### Generic Memory Components
|
||||
|
||||
- **GenericRAM**: `generic_ram.rs` - Configurable RAM implementation
|
||||
- **GenericROM**: `generic_rom.rs` - Configurable ROM implementation
|
||||
|
||||
### Common Functionality (`common/`)
|
||||
|
||||
#### Intel 400x Series Common Code
|
||||
|
||||
- **File**: `intel_400x.rs`
|
||||
- **Purpose**: Shared functionality for all Intel 400x series chips
|
||||
- **Features**:
|
||||
- Common timing constants and state machines
|
||||
- Clock edge detection and handling
|
||||
- Data bus operations and bit manipulation
|
||||
- Address latching and assembly logic
|
||||
- Control pin reading and management
|
||||
- Reset handling and initialization
|
||||
- Memory operation state machines
|
||||
|
||||
### Clock Components (`clock/`)
|
||||
|
||||
#### Generic Clock Generator
|
||||
|
||||
- **File**: `generic_clock.rs`
|
||||
- **Features**:
|
||||
- Configurable frequency and duty cycle
|
||||
- Multiple output waveforms
|
||||
- Synchronization capabilities
|
||||
- Test pattern generation
|
||||
- **Status**: Basic implementation
|
||||
|
||||
## Component Interface
|
||||
|
||||
### Required Methods
|
||||
|
||||
All components must implement:
|
||||
|
||||
```rust
|
||||
fn name(&self) -> String; // Component identifier
|
||||
fn pins(&self) -> HashMap<String, Arc<Mutex<Pin>>>; // Pin configuration
|
||||
fn get_pin(&self, name: &str) -> Result<Arc<Mutex<Pin>>, String>; // Pin access
|
||||
fn update(&mut self); // Process one clock cycle
|
||||
fn run(&mut self); // Continuous execution
|
||||
fn stop(&mut self); // Graceful shutdown
|
||||
fn is_running(&self) -> bool; // Execution status
|
||||
# Run with specific system
|
||||
cargo run --release -- --system intel_mcs4 --rom path/to/rom.bin
|
||||
```
|
||||
|
||||
### Optional Traits
|
||||
### Quick Examples
|
||||
|
||||
Components can implement additional traits for specialized functionality:
|
||||
```bash
|
||||
# Emulate Intel MCS-4 system
|
||||
./rusty_emu --system intel_mcs4 --rom examples/mcs4/demo.rom
|
||||
|
||||
- **RunnableComponent**: Automatic thread spawning
|
||||
# Enable debug mode
|
||||
./rusty_emu --system intel_mcs4 --rom demo.rom --debug
|
||||
|
||||
## Pin Configuration
|
||||
# Load with specific configuration
|
||||
./rusty_emu --system intel_mcs4 --rom demo.rom --config mcs4_config.toml
|
||||
```
|
||||
|
||||
### Standard Pin Types
|
||||
---
|
||||
|
||||
- **Data Pins**: `D0-D3` for 4-bit data/address bus
|
||||
- **Clock Pins**: `PHI1`, `PHI2` for two-phase clock
|
||||
- **Control Pins**: `SYNC`, `RESET`, `CM`, `CI` for system control
|
||||
- **I/O Pins**: `IO0-IO3` for peripheral interface
|
||||
## 📋 System Specifications
|
||||
|
||||
### Pin Signal States
|
||||
### Intel MCS-4 Implementation
|
||||
|
||||
- **High**: Logic level 1
|
||||
- **Low**: Logic level 0
|
||||
- **HighZ**: High impedance (tri-state, not driving)
|
||||
**Supported Components:**
|
||||
✅ Intel 4004 CPU (4-bit processor)
|
||||
✅ 4001 ROM (2560-bit ROM with I/O)
|
||||
✅ 4002 RAM (320-bit RAM with output port)
|
||||
✅ 4003 Shift Register (10-bit I/O expander)
|
||||
|
||||
## Usage Examples
|
||||
**Memory Architecture:**
|
||||
- Instruction ROM: 4KB
|
||||
- Data RAM: 1280 bits
|
||||
- I/O Ports: Multiple 4-bit ports
|
||||
|
||||
### Creating and Using a Component
|
||||
---
|
||||
|
||||
### VIC-20 Emulation (Planned)
|
||||
|
||||
**Target Specifications:**
|
||||
- CPU: MOS 6502 @ 1.108 MHz
|
||||
- Memory: 5KB RAM (expandable to 32KB)
|
||||
- Graphics: VIC-I chip, 176×184 resolution
|
||||
- Sound: 3 voices + noise
|
||||
- I/O: Tape, Serial, User Port
|
||||
|
||||
---
|
||||
|
||||
### Yamaha TG100 Emulation (Planned)
|
||||
|
||||
**Target Features:**
|
||||
- Tone Generation: AWM (Advanced Wave Memory)
|
||||
- Polyphony: 16–32 voices
|
||||
- Effects: Reverb, Chorus
|
||||
- MIDI Implementation
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Development
|
||||
|
||||
### Adding New Systems
|
||||
|
||||
**Create System Module**
|
||||
```rust
|
||||
use rusty_emu::components::memory::intel_4001::Intel4001;
|
||||
// systems/my_system/mod.rs
|
||||
pub struct MySystem {
|
||||
cpu: MyCpu,
|
||||
memory: MemoryMap,
|
||||
peripherals: Vec<Box<dyn Peripheral>>,
|
||||
}
|
||||
|
||||
// Create a ROM component
|
||||
let mut rom = Intel4001::new("ROM1".to_string());
|
||||
|
||||
// Load program data
|
||||
let program = vec![0x12, 0x34, 0x56, 0x78];
|
||||
rom.load_rom_data(program, 0) ?;
|
||||
|
||||
// Use in simulation loop
|
||||
while simulation_running() {
|
||||
rom.update(); // Process one clock cycle
|
||||
impl System for MySystem {
|
||||
fn step(&mut self) -> EmulatorResult<()> {
|
||||
// System emulation logic
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Component Interconnection
|
||||
|
||||
**Implement CPU Core**
|
||||
```rust
|
||||
// Components connect through shared pin references
|
||||
let cpu_pin = cpu.get_pin("D0") ?;
|
||||
let rom_pin = rom.get_pin("D0") ?;
|
||||
let ram_pin = ram.get_pin("D0") ?;
|
||||
|
||||
// Connect pins together
|
||||
connect_pins(vec![cpu_pin, rom_pin, ram_pin]) ?;
|
||||
// systems/my_system/cpu.rs
|
||||
impl Cpu for MyCpu {
|
||||
fn execute_instruction(&mut self) -> EmulatorResult<()> {
|
||||
// Instruction execution
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Strategy
|
||||
**Register System**
|
||||
```rust
|
||||
// src/lib.rs
|
||||
pub mod systems {
|
||||
pub mod my_system;
|
||||
}
|
||||
```
|
||||
|
||||
### Unit Tests
|
||||
---
|
||||
|
||||
- Located in each component's implementation file
|
||||
- Test individual component behavior in isolation
|
||||
- Focus on state management and pin operations
|
||||
### Building from Source
|
||||
```bash
|
||||
# Debug build with logging
|
||||
RUST_LOG=debug cargo build
|
||||
|
||||
### Integration Tests
|
||||
# Run tests
|
||||
cargo test
|
||||
|
||||
- Located in `tests/` directory
|
||||
- Test component interactions and system behavior
|
||||
- Verify proper timing and signal propagation
|
||||
# Benchmark performance
|
||||
cargo bench
|
||||
|
||||
### Mock-Based Tests
|
||||
# Check code quality
|
||||
cargo clippy
|
||||
cargo fmt
|
||||
```
|
||||
|
||||
- Use mock components for controlled testing
|
||||
- Test error conditions and edge cases
|
||||
- Verify behavior with various input patterns
|
||||
---
|
||||
|
||||
## Development Guidelines
|
||||
## 🧪 Testing
|
||||
```bash
|
||||
# Run all tests
|
||||
cargo test
|
||||
|
||||
### Adding New Components
|
||||
# System-specific tests
|
||||
cargo test --test intel_mcs4
|
||||
|
||||
1. **Implement Core Traits**: Start with `Component` trait implementation
|
||||
2. **Define Pin Configuration**: Specify required pins and their purposes
|
||||
3. **Implement Timing**: Add clock cycle processing in `update()` method
|
||||
4. **Add Specialized Functionality**: Implement component-specific methods and behaviors as needed
|
||||
5. **Write Tests**: Add comprehensive unit tests for the new component
|
||||
6. **Update Exports**: Add the component to the appropriate module exports
|
||||
# Integration tests
|
||||
cargo test --test integration
|
||||
|
||||
### Component Design Principles
|
||||
# Performance benchmarks
|
||||
cargo bench
|
||||
```
|
||||
|
||||
- **Single Responsibility**: Each component should have one primary function
|
||||
- **Clear Interfaces**: Well-defined pin configurations and behaviors
|
||||
- **Timing Awareness**: Proper handling of clock cycles and timing constraints
|
||||
- **Error Handling**: Graceful handling of invalid inputs and states
|
||||
- **Testability**: Design for easy testing and verification
|
||||
---
|
||||
|
||||
### Performance Considerations
|
||||
## 📊 Performance
|
||||
|
||||
- **Pin Access**: Minimize lock contention on shared pins
|
||||
- **State Updates**: Efficient state machine transitions
|
||||
- **Memory Layout**: Optimize memory access patterns
|
||||
- **Clock Processing**: Minimize overhead in update cycles
|
||||
**Current performance targets:**
|
||||
- Intel MCS-4: 740 kHz emulation speed (real-time)
|
||||
- VIC-20: 1.108 MHz emulation speed (real-time)
|
||||
- Yamaha TG100: Audio processing at 44.1 kHz
|
||||
|
||||
## Hardware Accuracy
|
||||
---
|
||||
|
||||
### Intel 4004 Series Specifics
|
||||
## 🔧 Configuration
|
||||
|
||||
- **Two-Phase Clock**: All operations synchronized to Φ1 and Φ2
|
||||
- **4-Bit Architecture**: Data processed in 4-bit nibbles
|
||||
- **12-Bit Addressing**: 8-bit addresses transferred in two 4-bit cycles
|
||||
- **Timing Constraints**: Specific setup and hold times for signals
|
||||
- **Bus Sharing**: Multiple components share the same data bus
|
||||
**Example Configuration (TOML)**
|
||||
```toml
|
||||
[system.intel_mcs4]
|
||||
clock_speed = 740000 # 740 kHz
|
||||
memory_size = 4096 # 4KB ROM
|
||||
|
||||
### Implementation Notes
|
||||
[display]
|
||||
width = 800
|
||||
height = 600
|
||||
scale = 2
|
||||
fullscreen = false
|
||||
|
||||
- Some timing specifications are approximated due to Rust's timing limitations
|
||||
- Pin signal propagation is simplified for simulation performance
|
||||
- Bus contention handling prioritizes correctness over perfect hardware accuracy
|
||||
- Memory refresh cycles may be abstracted for simplicity
|
||||
[audio]
|
||||
sample_rate = 44100
|
||||
buffer_size = 1024
|
||||
|
||||
## Future Enhancements
|
||||
[debug]
|
||||
breakpoints = [0x1234, 0x5678]
|
||||
log_level = "info"
|
||||
```
|
||||
|
||||
### Planned Components
|
||||
---
|
||||
|
||||
- Enhanced Intel 4002 RAM with proper refresh cycles
|
||||
- Intel 4008/4009 I/O expanders
|
||||
- Intel 4269 programmable ROM
|
||||
- More accurate timing simulation
|
||||
- Cycle-accurate instruction execution
|
||||
## 🤝 Contributing
|
||||
|
||||
### Architecture Improvements
|
||||
We welcome contributions! Please see our **Contributing Guide** for details.
|
||||
|
||||
- Better pin signal propagation modeling
|
||||
- More sophisticated bus arbitration
|
||||
- Enhanced timing constraint checking
|
||||
- Improved debugging and introspection capabilities
|
||||
### Development Workflow
|
||||
1. Fork the repository
|
||||
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
||||
3. Commit changes (`git commit -m 'Add amazing feature'`)
|
||||
4. Push to branch (`git push origin feature/amazing-feature`)
|
||||
5. Open a Pull Request
|
||||
|
||||
### Code Style
|
||||
- Follow Rust conventions and `cargo fmt`
|
||||
- Use meaningful variable names
|
||||
- Include documentation comments
|
||||
- Write tests for new functionality
|
||||
|
||||
---
|
||||
|
||||
## 📝 License
|
||||
|
||||
This project is licensed under the **MIT License** — see the `LICENSE` file for details.
|
||||
|
||||
---
|
||||
|
||||
## 🙏 Acknowledgments
|
||||
|
||||
- **Intel MCS-4:** Reference documentation from Intel archives
|
||||
- **VIC-20:** VICE emulator team for reference implementation
|
||||
- **Yamaha TG100:** Yamaha technical documentation
|
||||
- **Rust Community:** Excellent emulation resources and crates
|
||||
|
||||
---
|
||||
|
||||
## 📚 Resources
|
||||
|
||||
### Documentation
|
||||
- [Intel MCS-4 Manuals](https://archive.org/details/intel-4004)
|
||||
- [VIC-20 Programmer's Reference](https://archive.org/details/VIC-20_Programmers_Reference_Guide)
|
||||
- Yamaha TG100 Service Manual
|
||||
|
||||
### Related Projects
|
||||
- **VICE:** [Versatile Commodore Emulator](https://github.com/VICE-Team/svn-mirror)
|
||||
- **MAME:** [Multiple Arcade Machine Emulator](https://github.com/mamedev/mame)
|
||||
- **Yuzu:** [Nintendo Switch Emulator](https://github.com/yuzu-emu/yuzu)
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Issue Reporting
|
||||
|
||||
If you find a bug, please open an issue with:
|
||||
- System and ROM information
|
||||
- Steps to reproduce
|
||||
- Expected vs. actual behavior
|
||||
- Log output (if available)
|
||||
|
||||
---
|
||||
|
||||
## 📞 Support
|
||||
|
||||
- **Discussions:** [GitHub Discussions](https://github.com/jhunnius/rusty_emu/discussions)
|
||||
- **Issues:** [GitHub Issues](https://github.com/jhunnius/rusty_emu/issues)
|
||||
|
||||
> *Rusty Emu — Preserving computing history through accurate emulation.* 🕹️
|
||||
|
|
|
|||
|
|
@ -141,15 +141,15 @@ impl Component for TwoPhaseClock {
|
|||
|
||||
if self.should_transition() {
|
||||
// Reduced debug output - only show occasional transitions
|
||||
static mut TRANSITION_COUNT: u32 = 0;
|
||||
unsafe {
|
||||
TRANSITION_COUNT += 1;
|
||||
if TRANSITION_COUNT % 1000 == 0 {
|
||||
println!(
|
||||
"DEBUG: [CLOCK] Transitioning - PHI1: {:?} -> PHI2: {:?} (cycle {})",
|
||||
self.phi1_state, self.phi2_state, TRANSITION_COUNT
|
||||
);
|
||||
}
|
||||
// Use atomic counter instead of static mutable for thread safety
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
static TRANSITION_COUNT: AtomicU32 = AtomicU32::new(0);
|
||||
let count = TRANSITION_COUNT.fetch_add(1, Ordering::Relaxed);
|
||||
if count % 1000 == 0 {
|
||||
println!(
|
||||
"DEBUG: [CLOCK] Transitioning - PHI1: {:?} -> PHI2: {:?} (cycle {})",
|
||||
self.phi1_state, self.phi2_state, count
|
||||
);
|
||||
}
|
||||
self.perform_transition();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
# Intel 400x Common Functionality
|
||||
|
||||
This module provides shared functionality for all Intel 400x series chips (4001, 4002, 4003, 4004). It contains common
|
||||
traits, utilities, and behaviors that are used across multiple components.
|
||||
This module provides shared functionality for all Intel 400x series chips (4001, 4002, 4003, 4004) through a unified state machine architecture. It contains common traits, utilities, and behaviors that are used across multiple components.
|
||||
|
||||
## Purpose
|
||||
|
||||
|
|
@ -12,13 +11,114 @@ The Intel 400x series shares many common characteristics:
|
|||
- **12-bit address space** (8-bit addresses in two 4-bit cycles)
|
||||
- **Common timing constraints** and access patterns
|
||||
- **Similar pin configurations** and control signals
|
||||
- **Shared state machine patterns** for memory operations
|
||||
- **Unified state machine patterns** for all operations
|
||||
|
||||
Rather than duplicating this code in each component, it's centralized here for maintainability and consistency.
|
||||
|
||||
## Architecture
|
||||
## Unified State Machine Architecture
|
||||
|
||||
### Trait-Based Design
|
||||
### Overview
|
||||
|
||||
The Intel 400x series now uses a unified state machine approach that replaces individual chip state machines with a single, extensible framework. This approach provides several benefits:
|
||||
|
||||
1. **Consistency**: All components follow the same basic state transition patterns
|
||||
2. **Maintainability**: Common logic is centralized in the state machine engine
|
||||
3. **Extensibility**: New chip types can be added by implementing the `Intel400xComponentBehavior` trait
|
||||
4. **Testability**: Components can be tested independently with mock behaviors
|
||||
|
||||
### Key Components
|
||||
|
||||
#### Intel400xComponentBehavior Trait
|
||||
|
||||
This trait defines the interface that all Intel 400x components must implement to work with the unified state machine architecture. It allows each component to provide its own specific behavior while still following the common state machine framework.
|
||||
|
||||
```rust
|
||||
pub trait Intel400xComponentBehavior {
|
||||
fn get_component_type(&self) -> Intel400xComponentType;
|
||||
fn on_state_entry(&mut self, state: Intel400xUnifiedState);
|
||||
fn on_state_exit(&mut self, state: Intel400xUnifiedState);
|
||||
fn handle_component_event(&mut self, event: Intel400xEvent) -> bool;
|
||||
fn execute_state_logic(&mut self, state: Intel400xUnifiedState);
|
||||
fn responds_to_address(&self) -> bool;
|
||||
}
|
||||
```
|
||||
|
||||
#### Intel400xComponentType
|
||||
|
||||
Enumeration that identifies the specific type of Intel 400x component being modeled:
|
||||
|
||||
```rust
|
||||
pub enum Intel400xComponentType {
|
||||
Intel4001, // ROM (Read-Only Memory)
|
||||
Intel4002, // RAM (Random Access Memory)
|
||||
Intel4003, // Shift Register
|
||||
Intel4004, // CPU (Central Processing Unit)
|
||||
}
|
||||
```
|
||||
|
||||
#### Intel400xUnifiedState
|
||||
|
||||
Enumeration representing all possible states for any Intel 400x component in the unified state machine:
|
||||
|
||||
```rust
|
||||
pub enum Intel400xUnifiedState {
|
||||
// Common states (shared across all components)
|
||||
Idle,
|
||||
AddressPhase,
|
||||
WaitLatency,
|
||||
DriveData,
|
||||
|
||||
// Component-specific states
|
||||
ReadData, // Intel 4002 (RAM) specific
|
||||
WriteData, // Intel 4002 (RAM) specific
|
||||
OutputPort, // Intel 4002 (RAM) specific
|
||||
ShiftData, // Intel 4003 (Shift Register) specific
|
||||
LoadData, // Intel 4003 (Shift Register) specific
|
||||
FetchInstruction, // Intel 4004 (CPU) specific
|
||||
ExecuteInstruction, // Intel 4004 (CPU) specific
|
||||
}
|
||||
```
|
||||
|
||||
#### Intel400xEvent
|
||||
|
||||
Enumeration representing events that can trigger state transitions:
|
||||
|
||||
```rust
|
||||
pub enum Intel400xEvent {
|
||||
ClockPhi1Rising,
|
||||
ClockPhi1Falling,
|
||||
ClockPhi2Rising,
|
||||
ClockPhi2Falling,
|
||||
SyncActive,
|
||||
SyncInactive,
|
||||
ChipSelect,
|
||||
ChipDeselect,
|
||||
ResetActive,
|
||||
ResetInactive,
|
||||
AddressComplete,
|
||||
LatencyElapsed,
|
||||
DataReady,
|
||||
OperationComplete,
|
||||
}
|
||||
```
|
||||
|
||||
#### Intel400xStateMachine
|
||||
|
||||
The core state machine engine that manages state transitions and coordinates with component behaviors:
|
||||
|
||||
```rust
|
||||
pub struct Intel400xStateMachine {
|
||||
component_type: Intel400xComponentType,
|
||||
current_state: Intel400xUnifiedState,
|
||||
previous_state: Intel400xUnifiedState,
|
||||
address_high_nibble: Option<u8>,
|
||||
address_low_nibble: Option<u8>,
|
||||
address_latch_time: Option<Instant>,
|
||||
access_time: Duration,
|
||||
}
|
||||
```
|
||||
|
||||
## Trait-Based Design
|
||||
|
||||
The common functionality is organized around several key traits:
|
||||
|
||||
|
|
@ -28,19 +128,9 @@ Intel400xDataBus - Data bus read/write operations
|
|||
Intel400xAddressHandling - Address latching and assembly
|
||||
Intel400xControlPins - Control signal reading
|
||||
Intel400xResetHandling - Reset signal handling
|
||||
Intel400xTimingState - Memory operation state machines
|
||||
Intel400xTimingState - Memory operation state management
|
||||
```
|
||||
|
||||
### State Machine
|
||||
|
||||
All Intel 400x memory operations follow a common pattern:
|
||||
|
||||
```
|
||||
Idle → AddressPhase → WaitLatency → DriveData → Idle
|
||||
```
|
||||
|
||||
Each phase has specific timing requirements and bus usage patterns.
|
||||
|
||||
## Core Traits
|
||||
|
||||
### Intel400xClockHandling
|
||||
|
|
@ -139,8 +229,8 @@ pub trait Intel400xResetHandling {
|
|||
fn get_base(&self) -> &BaseComponent;
|
||||
|
||||
// Reset operations
|
||||
fn handle_reset(&self, reset_pin_name: &str) -> bool;
|
||||
fn perform_reset(&self); // Component-specific reset logic
|
||||
fn handle_reset(&mut self, reset_pin_name: &str) -> bool;
|
||||
fn perform_reset(&mut self); // Component-specific reset logic
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -156,8 +246,8 @@ Manages memory operation timing and state machines:
|
|||
```rust
|
||||
pub trait Intel400xTimingState {
|
||||
// State management
|
||||
fn get_timing_state(&self) -> TimingState;
|
||||
fn set_timing_state(&mut self, state: TimingState);
|
||||
fn get_timing_state(&self) -> MemoryState;
|
||||
fn set_timing_state(&mut self, state: MemoryState);
|
||||
|
||||
// Address tracking
|
||||
fn get_address_latch_time(&self) -> Option<Instant>;
|
||||
|
|
@ -179,6 +269,31 @@ pub trait Intel400xTimingState {
|
|||
- Timing constraint management
|
||||
- Address tracking and validation
|
||||
|
||||
## State Machine Operation
|
||||
|
||||
### State Transition Logic
|
||||
|
||||
The unified state machine operates by:
|
||||
|
||||
1. Receiving events from the component or external sources
|
||||
2. Determining the next state based on current state and event
|
||||
3. Calling appropriate state entry/exit methods on the component behavior
|
||||
4. Executing state-specific logic through the component behavior
|
||||
|
||||
### Common State Transitions
|
||||
|
||||
1. **Idle → AddressPhase**: Triggered by chip select when component responds to address
|
||||
2. **AddressPhase → WaitLatency**: After both address nibbles are latched
|
||||
3. **WaitLatency → Component-Specific State**: After access time has elapsed
|
||||
4. **Component-Specific State → Idle**: When operation completes
|
||||
|
||||
### Component-Specific Behaviors
|
||||
|
||||
- **Intel 4001 (ROM)**: Transitions to DriveData to provide read-only data
|
||||
- **Intel 4002 (RAM)**: Transitions to ReadData, WriteData, or OutputPort based on operation
|
||||
- **Intel 4003 (Shift Register)**: Ignores address transmission, goes directly to ShiftData
|
||||
- **Intel 4004 (CPU)**: Uses FetchInstruction and ExecuteInstruction states
|
||||
|
||||
## Timing Constants
|
||||
|
||||
### Standard Timing Values
|
||||
|
|
@ -201,13 +316,11 @@ Each component type uses appropriate timing constants:
|
|||
- **Shift Register (4003)**: 200ns fast access time
|
||||
- **CPU (4004)**: Internal operation timing
|
||||
|
||||
## State Machine
|
||||
|
||||
### Memory Operation States
|
||||
## Memory Operation States
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum TimingState {
|
||||
pub enum MemoryState {
|
||||
Idle, // No memory operation in progress
|
||||
AddressPhase, // Currently latching address nibbles
|
||||
WaitLatency, // Address latched, waiting for access time
|
||||
|
|
@ -215,22 +328,6 @@ pub enum TimingState {
|
|||
}
|
||||
```
|
||||
|
||||
### State Transition Logic
|
||||
|
||||
1. **Idle → AddressPhase**: Triggered by SYNC high + Φ1 rising edge
|
||||
2. **AddressPhase → WaitLatency**: After both address nibbles latched
|
||||
3. **WaitLatency → DriveData**: After access time has elapsed
|
||||
4. **DriveData → Idle**: On Φ2 falling edge (bus tri-stated)
|
||||
|
||||
### Hardware Timing
|
||||
|
||||
Each state has specific timing requirements:
|
||||
|
||||
- **Address Setup**: 100ns minimum before latching
|
||||
- **Data Valid**: 200ns maximum from address valid to data available
|
||||
- **Bus Hold**: Data held until Φ2 falling edge
|
||||
- **Tri-state Delay**: 150ns maximum to high impedance after disable
|
||||
|
||||
## Utility Functions
|
||||
|
||||
### Pin Operations
|
||||
|
|
@ -256,11 +353,60 @@ All pin operations include proper error handling:
|
|||
- **Pin not found**: Return appropriate error messages
|
||||
- **Invalid states**: Graceful degradation
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Implementing the Intel400xComponentBehavior Trait
|
||||
|
||||
```rust
|
||||
struct MyComponent {
|
||||
// Component fields...
|
||||
state_machine: Intel400xStateMachine,
|
||||
}
|
||||
|
||||
impl Intel400xComponentBehavior for MyComponent {
|
||||
fn get_component_type(&self) -> Intel400xComponentType {
|
||||
Intel400xComponentType::Intel4001 // Example type
|
||||
}
|
||||
|
||||
fn on_state_entry(&mut self, state: Intel400xUnifiedState) {
|
||||
match state {
|
||||
Intel400xUnifiedState::AddressPhase => {
|
||||
// Prepare for address latching
|
||||
}
|
||||
Intel400xUnifiedState::DriveData => {
|
||||
// Prepare for data transfer
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// ... implement other methods
|
||||
}
|
||||
```
|
||||
|
||||
### Using the Unified State Machine
|
||||
|
||||
```rust
|
||||
// Create a state machine for an Intel 4001 ROM chip
|
||||
let mut state_machine = Intel400xStateMachine::new(
|
||||
Intel400xComponentType::Intel4001,
|
||||
Duration::from_nanos(500)
|
||||
);
|
||||
|
||||
// Process a chip select event
|
||||
let new_state = process_event_direct(
|
||||
&mut state_machine,
|
||||
Intel400xEvent::ChipSelect
|
||||
);
|
||||
|
||||
assert_eq!(new_state, Intel400xUnifiedState::AddressPhase);
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### Comprehensive Test Suite
|
||||
|
||||
The common functionality is thoroughly tested with:
|
||||
The unified state machine is thoroughly tested with:
|
||||
|
||||
1. **Unit Tests**: Test individual functions and algorithms
|
||||
2. **Mock Tests**: Test with controlled hardware inputs
|
||||
|
|
@ -269,76 +415,26 @@ The common functionality is thoroughly tested with:
|
|||
|
||||
### Testable Areas
|
||||
|
||||
- **Timing constants validation**
|
||||
- **Address assembly algorithms**
|
||||
- **State machine transitions**
|
||||
- **Address latching algorithms**
|
||||
- **Component-specific behaviors**
|
||||
- **Clock edge detection accuracy**
|
||||
- **Data bus bit operations**
|
||||
- **Pin operation safety**
|
||||
|
||||
### Mock System
|
||||
## Benefits of the Unified Approach
|
||||
|
||||
A comprehensive mock system allows testing without real hardware:
|
||||
|
||||
```rust
|
||||
// Create mock component for testing
|
||||
let mut mock_component = MockIntel400xComponent::new("TestComponent");
|
||||
|
||||
// Set up test scenario
|
||||
mock_component.set_clock_values(PinValue::High, PinValue::Low);
|
||||
|
||||
// Test behavior
|
||||
assert!(mock_component.is_phi1_rising_edge(PinValue::Low));
|
||||
```
|
||||
|
||||
## Usage in Components
|
||||
|
||||
### Implementation Pattern
|
||||
|
||||
Components implement the relevant traits:
|
||||
|
||||
```rust
|
||||
impl Intel400xClockHandling for MyComponent {
|
||||
fn get_base(&self) -> &BaseComponent {
|
||||
&self.base
|
||||
}
|
||||
}
|
||||
|
||||
impl Intel400xDataBus for MyComponent {
|
||||
fn get_base(&self) -> &BaseComponent {
|
||||
&self.base
|
||||
}
|
||||
|
||||
fn read_data_bus(&self) -> u8 {
|
||||
// Use common data bus reading logic
|
||||
let mut data = 0;
|
||||
for i in 0..4 {
|
||||
if let Ok(pin) = self.get_base().get_pin(&format!("D{}", i)) {
|
||||
if let Ok(pin_guard) = pin.lock() {
|
||||
if pin_guard.read() == PinValue::High {
|
||||
data |= 1 << i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
data & 0x0F
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Benefits
|
||||
|
||||
1. **Code Reuse**: Common functionality shared across components
|
||||
2. **Consistency**: Uniform behavior across all 400x components
|
||||
3. **Maintainability**: Changes to common logic update all components
|
||||
4. **Testability**: Common code can be tested once and reused
|
||||
5. **Type Safety**: Traits ensure proper implementation
|
||||
1. **Code Reuse**: Common state transition logic is shared across all components
|
||||
2. **Consistency**: All components follow the same timing and state patterns
|
||||
3. **Maintainability**: Changes to common behavior only need to be made in one place
|
||||
4. **Extensibility**: New chip types can be added without modifying the core state machine
|
||||
5. **Testability**: The state machine can be tested independently of specific components
|
||||
|
||||
## Hardware Accuracy
|
||||
|
||||
### Timing Considerations
|
||||
|
||||
The common code implements hardware-accurate timing where possible:
|
||||
The unified state machine implements hardware-accurate timing where possible:
|
||||
|
||||
- **Clock Edge Detection**: Proper Φ1/Φ2 edge detection
|
||||
- **Address Latching**: Two-phase address capture
|
||||
|
|
@ -356,19 +452,19 @@ The common code implements hardware-accurate timing where possible:
|
|||
### Planned Improvements
|
||||
|
||||
1. **Enhanced Timing Simulation**
|
||||
- More accurate sub-nanosecond timing
|
||||
- Better clock phase relationship modeling
|
||||
- Improved signal propagation delays
|
||||
- More accurate sub-nanosecond timing
|
||||
- Better clock phase relationship modeling
|
||||
- Improved signal propagation delays
|
||||
|
||||
2. **Extended Component Support**
|
||||
- Support for additional Intel 400x variants
|
||||
- Enhanced I/O device support
|
||||
- Better peripheral modeling
|
||||
- Support for additional Intel 400x variants
|
||||
- Enhanced I/O device support
|
||||
- Better peripheral modeling
|
||||
|
||||
3. **Testing Improvements**
|
||||
- More sophisticated mock scenarios
|
||||
- Better timing verification tools
|
||||
- Enhanced debugging capabilities
|
||||
- More sophisticated mock scenarios
|
||||
- Better timing verification tools
|
||||
- Enhanced debugging capabilities
|
||||
|
||||
### Extension Points
|
||||
|
||||
|
|
@ -403,5 +499,4 @@ The common functionality depends on:
|
|||
- **Shared Code**: No duplication across components
|
||||
- **Efficient State Storage**: Compact state representation
|
||||
|
||||
This common functionality module provides a solid foundation for all Intel 400x components while maintaining high
|
||||
performance and hardware accuracy.
|
||||
This unified state machine architecture provides a solid foundation for all Intel 400x components while maintaining high performance and hardware accuracy.
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,3 @@
|
|||
//! Common functionality for Intel 400x series chips
|
||||
|
||||
pub mod intel_400x;
|
||||
|
||||
// Re-export commonly used items for convenience
|
||||
pub use intel_400x::*;
|
||||
|
|
|
|||
|
|
@ -36,12 +36,14 @@ enum Instruction {
|
|||
Add(u8), // Add register to accumulator (ADD R)
|
||||
Sub(u8), // Subtract register from accumulator (SUB R)
|
||||
Inc(u8), // Increment register (INC R)
|
||||
#[allow(dead_code)] // BBL instruction is implemented but not used in current tests
|
||||
Bbl(u8), // Branch back and load (BBL #)
|
||||
|
||||
// Arithmetic Instructions (4)
|
||||
AddC(u8), // Add register with carry (ADC R)
|
||||
SubC(u8), // Subtract register with carry (SBC R)
|
||||
Dad(u8), // Decimal add register (DAD R)
|
||||
#[allow(dead_code)] // DAD instruction is implemented but not used in current tests
|
||||
Dad(u8), // Decimal add register (DAD R)
|
||||
Daa, // Decimal adjust accumulator (DAA)
|
||||
|
||||
// Logic Instructions (4)
|
||||
|
|
@ -51,17 +53,20 @@ enum Instruction {
|
|||
Tcs, // Transmit carry set (TCS)
|
||||
|
||||
// Control Transfer Instructions (8)
|
||||
Jcn(u8, u16), // Jump conditional (JCN condition, addr)
|
||||
Jms(u16), // Jump to subroutine (JMS addr)
|
||||
JmsHigh(u8), // Jump to subroutine high nibble (two-instruction format)
|
||||
JmsLow(u8), // Jump to subroutine low nibble (two-instruction format)
|
||||
Jun(u16), // Jump unconditional (JUN addr)
|
||||
JunHigh(u8), // Jump unconditional high nibble (two-instruction format)
|
||||
JunLow(u8), // Jump unconditional low nibble (two-instruction format)
|
||||
Jnt(u16), // Jump on test (JNT addr)
|
||||
Jcn(u8, u16), // Jump conditional (JCN condition, addr)
|
||||
Jms(u16), // Jump to subroutine (JMS addr)
|
||||
JmsHigh(u8), // Jump to subroutine high nibble (two-instruction format)
|
||||
JmsLow(u8), // Jump to subroutine low nibble (two-instruction format)
|
||||
Jun(u16), // Jump unconditional (JUN addr)
|
||||
JunHigh(u8), // Jump unconditional high nibble (two-instruction format)
|
||||
JunLow(u8), // Jump unconditional low nibble (two-instruction format)
|
||||
#[allow(dead_code)] // JNT instruction is implemented but not used in current tests
|
||||
Jnt(u16), // Jump on test (JNT addr)
|
||||
#[allow(dead_code)] // JNT inverted instruction is implemented but not used in current tests
|
||||
JntInvert(u16), // Jump on test inverted (JNT addr) - wait instruction
|
||||
|
||||
// Machine Instruction (1)
|
||||
#[allow(dead_code)] // SRC instruction is implemented but not used in current tests
|
||||
Src(u8), // Send register control (SRC R)
|
||||
|
||||
// Input/Output and RAM Instructions (8)
|
||||
|
|
@ -135,6 +140,10 @@ pub struct Intel4004 {
|
|||
// Timing and synchronization
|
||||
address_latch_time: Option<Instant>, // Timestamp when address was latched
|
||||
access_time: Duration, // Memory access time (typical 500ns)
|
||||
|
||||
// Debug and safety controls
|
||||
debug_enabled: bool, // Control debug output
|
||||
max_cycles: Option<u64>, // Maximum cycles before auto-stop (None = unlimited)
|
||||
}
|
||||
|
||||
impl Intel4004 {
|
||||
|
|
@ -191,6 +200,10 @@ impl Intel4004 {
|
|||
// Timing and synchronization
|
||||
address_latch_time: None,
|
||||
access_time: Duration::from_nanos(500), // 500ns typical access time
|
||||
|
||||
// Debug and safety controls
|
||||
debug_enabled: true, // Enable debug by default
|
||||
max_cycles: None, // No cycle limit by default
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -367,16 +380,17 @@ impl Intel4004 {
|
|||
/// Hardware: Φ1 high = CPU drives bus with address/control information
|
||||
/// Memory operations start when SYNC goes high during Φ1 rising edge
|
||||
fn handle_phi1_rising(&mut self) {
|
||||
// Handle system reset first (highest priority)
|
||||
self.handle_reset();
|
||||
|
||||
// Check for memory operation start on Φ1 rising edge with SYNC high
|
||||
let (sync, cm, _) = self.read_control_pins();
|
||||
if sync && cm {
|
||||
if sync
|
||||
&& cm
|
||||
&& self.memory_state == MemoryState::Idle
|
||||
&& self.instruction_phase == InstructionPhase::Fetch
|
||||
{
|
||||
// Start memory address phase on Φ1 rising edge
|
||||
println!(
|
||||
"DEBUG: [CPU] Starting memory operation - SYNC: {}, CM: {}",
|
||||
sync, cm
|
||||
"DEBUG: [CPU] Starting memory operation - SYNC: {}, CM: {}, Phase: {:?}",
|
||||
sync, cm, self.instruction_phase
|
||||
);
|
||||
self.start_memory_address_phase();
|
||||
}
|
||||
|
|
@ -404,10 +418,11 @@ impl Intel4004 {
|
|||
|
||||
/// Handle system reset signal
|
||||
/// Hardware: RESET pin clears all internal state and tri-states outputs
|
||||
/// Note: RESET is active low (triggered when pin is Low)
|
||||
fn handle_reset(&mut self) {
|
||||
let reset = if let Ok(pin) = self.base.get_pin("RESET") {
|
||||
if let Ok(pin_guard) = pin.lock() {
|
||||
pin_guard.read() == PinValue::High
|
||||
pin_guard.read() == PinValue::Low
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
|
@ -416,7 +431,7 @@ impl Intel4004 {
|
|||
};
|
||||
|
||||
if reset {
|
||||
// RESET is high - clear all internal state
|
||||
// RESET is low (active low) - clear all internal state
|
||||
self.accumulator = 0;
|
||||
self.carry = false;
|
||||
self.index_registers = [0u8; 16];
|
||||
|
|
@ -438,8 +453,17 @@ impl Intel4004 {
|
|||
self.pending_operand = None;
|
||||
self.operand_assembled = false;
|
||||
|
||||
// Reset current instruction
|
||||
self.current_instruction = 0;
|
||||
self.current_op = Instruction::Invalid;
|
||||
|
||||
// Tri-state data bus
|
||||
self.tri_state_data_bus();
|
||||
|
||||
// Debug output for reset
|
||||
if self.debug_enabled {
|
||||
println!("DEBUG: [CPU] RESET activated - CPU state cleared");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1397,6 +1421,9 @@ impl Component for Intel4004 {
|
|||
/// Update the CPU state for one simulation cycle
|
||||
/// Processes clock cycles and executes instructions when running
|
||||
fn update(&mut self) {
|
||||
// Handle system reset first (highest priority) - even when not running
|
||||
self.handle_reset();
|
||||
|
||||
if !self.is_running() {
|
||||
return;
|
||||
}
|
||||
|
|
@ -1414,14 +1441,14 @@ impl Component for Intel4004 {
|
|||
|
||||
if phi1_rising {
|
||||
// Reduced debug output - only show occasional cycles
|
||||
static mut CYCLE_COUNT: u32 = 0;
|
||||
unsafe {
|
||||
CYCLE_COUNT += 1;
|
||||
if CYCLE_COUNT % 1000 == 0 {
|
||||
let (sync, cm, _) = self.read_control_pins();
|
||||
println!("DEBUG: [CPU] Φ1 Rising Edge - PC: 0x{:03X}, ACC: 0x{:X}, Phase: {:?}, SYNC: {}, CM: {} (cycle {})",
|
||||
self.program_counter.value(), self.accumulator, self.instruction_phase, sync, cm, CYCLE_COUNT);
|
||||
}
|
||||
// Use atomic counter instead of static mutable for thread safety
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
static CYCLE_COUNT: AtomicU32 = AtomicU32::new(0);
|
||||
let count = CYCLE_COUNT.fetch_add(1, Ordering::Relaxed);
|
||||
if count % 1000 == 0 {
|
||||
let (sync, cm, _) = self.read_control_pins();
|
||||
println!("DEBUG: [CPU] Φ1 Rising Edge - PC: 0x{:03X}, ACC: 0x{:X}, Phase: {:?}, SYNC: {}, CM: {} (cycle {})",
|
||||
self.program_counter.value(), self.accumulator, self.instruction_phase, sync, cm, count);
|
||||
}
|
||||
self.handle_phi1_rising();
|
||||
}
|
||||
|
|
@ -1429,12 +1456,12 @@ impl Component for Intel4004 {
|
|||
if phi1_falling {
|
||||
// Φ1 Falling Edge: End of address phase
|
||||
// Reduced debug output - only show occasional cycles
|
||||
static mut CYCLE_COUNT: u32 = 0;
|
||||
unsafe {
|
||||
CYCLE_COUNT += 1;
|
||||
if CYCLE_COUNT % 1000 == 0 {
|
||||
println!("DEBUG: [CPU] Φ1 Falling Edge (cycle {})", CYCLE_COUNT);
|
||||
}
|
||||
// Use atomic counter instead of static mutable for thread safety
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
static CYCLE_COUNT: AtomicU32 = AtomicU32::new(0);
|
||||
let count = CYCLE_COUNT.fetch_add(1, Ordering::Relaxed);
|
||||
if count % 1000 == 0 {
|
||||
println!("DEBUG: [CPU] Φ1 Falling Edge (cycle {})", count);
|
||||
}
|
||||
self.handle_phi1_falling();
|
||||
}
|
||||
|
|
@ -1442,15 +1469,15 @@ impl Component for Intel4004 {
|
|||
if phi2_rising {
|
||||
// Φ2 Rising Edge: Data phase (peripherals drive bus) - handle data operations
|
||||
// Reduced debug output - only show occasional cycles
|
||||
static mut CYCLE_COUNT: u32 = 0;
|
||||
unsafe {
|
||||
CYCLE_COUNT += 1;
|
||||
if CYCLE_COUNT % 1000 == 0 {
|
||||
println!(
|
||||
"DEBUG: [CPU] Φ2 Rising Edge - Memory State: {:?} (cycle {})",
|
||||
self.memory_state, CYCLE_COUNT
|
||||
);
|
||||
}
|
||||
// Use atomic counter instead of static mutable for thread safety
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
static CYCLE_COUNT: AtomicU32 = AtomicU32::new(0);
|
||||
let count = CYCLE_COUNT.fetch_add(1, Ordering::Relaxed);
|
||||
if count % 1000 == 0 {
|
||||
println!(
|
||||
"DEBUG: [CPU] Φ2 Rising Edge - Memory State: {:?} (cycle {})",
|
||||
self.memory_state, count
|
||||
);
|
||||
}
|
||||
self.handle_phi2_rising();
|
||||
}
|
||||
|
|
@ -1458,19 +1485,22 @@ impl Component for Intel4004 {
|
|||
if phi2_falling {
|
||||
// Φ2 Falling Edge: End of data phase - tri-state bus and return to idle
|
||||
// Reduced debug output - only show occasional cycles
|
||||
static mut CYCLE_COUNT: u32 = 0;
|
||||
unsafe {
|
||||
CYCLE_COUNT += 1;
|
||||
if CYCLE_COUNT % 1000 == 0 {
|
||||
println!("DEBUG: [CPU] Φ2 Falling Edge (cycle {})", CYCLE_COUNT);
|
||||
}
|
||||
// Use atomic counter instead of static mutable for thread safety
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
static CYCLE_COUNT: AtomicU32 = AtomicU32::new(0);
|
||||
let count = CYCLE_COUNT.fetch_add(1, Ordering::Relaxed);
|
||||
if count % 1000 == 0 {
|
||||
println!("DEBUG: [CPU] Φ2 Falling Edge (cycle {})", count);
|
||||
}
|
||||
self.handle_phi2_falling();
|
||||
}
|
||||
|
||||
// Handle instruction execution during appropriate phases
|
||||
// Set SYNC signal during fetch phase to initiate memory read
|
||||
if self.instruction_phase == InstructionPhase::Fetch {
|
||||
// Only fetch instructions when CPU is in Idle state and in Fetch phase
|
||||
if self.instruction_phase == InstructionPhase::Fetch
|
||||
&& self.memory_state == MemoryState::Idle
|
||||
{
|
||||
self.set_sync(true);
|
||||
self.set_cm(true);
|
||||
println!("DEBUG: [CPU] Setting signals - SYNC: HIGH, CM: HIGH");
|
||||
|
|
@ -1597,6 +1627,15 @@ impl Component for Intel4004 {
|
|||
while self.is_running() {
|
||||
self.update();
|
||||
thread::sleep(Duration::from_micros(1));
|
||||
|
||||
// Safety check: prevent infinite loops in testing environments
|
||||
// If cycle count exceeds a reasonable limit, stop automatically
|
||||
if self.cycle_count > 100000 {
|
||||
println!(
|
||||
"WARNING: CPU exceeded 100,000 cycles - stopping to prevent infinite loop"
|
||||
);
|
||||
self.base.set_running(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1618,3 +1657,20 @@ impl RunnableComponent for Intel4004 {
|
|||
// No custom run_loop needed - uses default Component::run() method
|
||||
// The default implementation spawns the component in its own thread
|
||||
}
|
||||
|
||||
impl Intel4004 {
|
||||
/// Set debug output enabled/disabled
|
||||
pub fn set_debug_enabled(&mut self, enabled: bool) {
|
||||
self.debug_enabled = enabled;
|
||||
}
|
||||
|
||||
/// Set maximum cycle limit (None = unlimited)
|
||||
pub fn set_max_cycles(&mut self, max_cycles: Option<u64>) {
|
||||
self.max_cycles = max_cycles;
|
||||
}
|
||||
|
||||
/// Check if CPU is running (using the base component's atomic flag directly)
|
||||
pub fn is_running_atomic(&self) -> bool {
|
||||
self.base.is_running()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,10 @@ use std::time::{Duration, Instant};
|
|||
|
||||
use crate::component::{BaseComponent, Component, RunnableComponent};
|
||||
use crate::components::common::intel_400x::{
|
||||
Intel400xAddressHandling, Intel400xClockHandling, Intel400xControlPins, Intel400xDataBus,
|
||||
Intel400xResetHandling, Intel400xTimingState, MemoryState, TimingState,
|
||||
process_event_direct, Intel400xAddressHandling, Intel400xClockHandling,
|
||||
Intel400xComponentBehavior, Intel400xComponentType, Intel400xControlPins, Intel400xDataBus,
|
||||
Intel400xEvent, Intel400xResetHandling, Intel400xStateMachine, Intel400xTimingState,
|
||||
Intel400xUnifiedState, TimingState,
|
||||
};
|
||||
use crate::pin::{Pin, PinValue};
|
||||
|
||||
|
|
@ -19,7 +21,7 @@ use crate::pin::{Pin, PinValue};
|
|||
/// (real hardware reads directly from I/O pins)
|
||||
/// - SYNC logic simplified for unit testing (may need refinement for CPU integration)
|
||||
pub struct Intel4001 {
|
||||
base: BaseComponent,
|
||||
pub base: BaseComponent,
|
||||
memory: Vec<u8>, // 256-byte ROM storage
|
||||
last_address: u16, // Last accessed memory address
|
||||
access_time: Duration, // ROM access latency (500ns)
|
||||
|
|
@ -29,22 +31,16 @@ pub struct Intel4001 {
|
|||
io_ports: [u8; 4], // 4 I/O ports (4 bits each) - matches datasheet
|
||||
io_direction: [IoDirection; 4], // I/O direction for each port
|
||||
selected_io_port: Option<usize>, // Currently selected I/O port (0-3)
|
||||
// Unified state machine implementation
|
||||
state_machine: Intel400xStateMachine, // Unified state machine for Intel 400x components
|
||||
// Clock edge detection
|
||||
prev_phi1: PinValue, // Previous Φ1 clock state for edge detection
|
||||
prev_phi2: PinValue, // Previous Φ2 clock state for edge detection
|
||||
// Access latency modeling
|
||||
address_latch_time: Option<Instant>, // Timestamp when address was latched
|
||||
// Two-phase addressing for 8-bit address
|
||||
address_high_nibble: Option<u8>, // High nibble of 8-bit address
|
||||
address_low_nibble: Option<u8>, // Low nibble of 8-bit address
|
||||
full_address_ready: bool, // Whether complete address is assembled
|
||||
// Memory operation state machine
|
||||
memory_state: MemoryState, // Current state of memory operation
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
/// I/O mode configuration for the 4001 ROM
|
||||
/// Determines how the I/O pins are configured during read/write operations
|
||||
/// I/O mode configuration for 4001 ROM
|
||||
/// Determines how I/O pins are configured during read/write operations
|
||||
pub enum IoMode {
|
||||
Input, // I/O pins configured as inputs (RDM instruction)
|
||||
Output, // I/O pins configured as outputs (WRM instruction)
|
||||
|
|
@ -90,64 +86,180 @@ impl Intel400xResetHandling for Intel4001 {
|
|||
fn perform_reset(&mut self) {
|
||||
// Reset operations specific to Intel4001
|
||||
// Note: This is called from handle_reset, so we don't need to check reset pin again
|
||||
self.set_timing_state(TimingState::Idle);
|
||||
self.state_machine.reset();
|
||||
self.tri_state_data_bus();
|
||||
self.address_low_nibble = None;
|
||||
self.address_high_nibble = None;
|
||||
self.full_address_ready = false;
|
||||
self.tri_state_io_pins();
|
||||
|
||||
// Reset I/O state
|
||||
self.io_ports = [0u8; 4];
|
||||
self.io_direction = [IoDirection::Input; 4];
|
||||
self.selected_io_port = None;
|
||||
self.io_mode = IoMode::Input; // Reset I/O mode to Input
|
||||
self.tri_state_io_pins();
|
||||
}
|
||||
}
|
||||
|
||||
impl Intel400xTimingState for Intel4001 {
|
||||
fn get_timing_state(&self) -> TimingState {
|
||||
self.memory_state.into()
|
||||
// Convert from Intel400xUnifiedState to TimingState
|
||||
match self.state_machine.get_state() {
|
||||
Intel400xUnifiedState::Idle => TimingState::Idle,
|
||||
Intel400xUnifiedState::AddressPhase => TimingState::AddressPhase,
|
||||
Intel400xUnifiedState::WaitLatency => TimingState::WaitLatency,
|
||||
Intel400xUnifiedState::DriveData => TimingState::DriveData,
|
||||
// Intel 4001 doesn't use component-specific states
|
||||
_ => TimingState::Idle,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_timing_state(&mut self, state: TimingState) {
|
||||
self.memory_state = state.into();
|
||||
// Convert from TimingState to Intel400xUnifiedState
|
||||
let unified_state = match state {
|
||||
TimingState::Idle => Intel400xUnifiedState::Idle,
|
||||
TimingState::AddressPhase => Intel400xUnifiedState::AddressPhase,
|
||||
TimingState::WaitLatency => Intel400xUnifiedState::WaitLatency,
|
||||
TimingState::DriveData => Intel400xUnifiedState::DriveData,
|
||||
};
|
||||
|
||||
// Use state machine's reset method to transition to new state
|
||||
// This is a compatibility method for legacy state handling
|
||||
self.state_machine.reset();
|
||||
self.state_machine.current_state = unified_state;
|
||||
}
|
||||
|
||||
fn get_address_latch_time(&self) -> Option<Instant> {
|
||||
self.address_latch_time
|
||||
self.state_machine.get_address_latch_time()
|
||||
}
|
||||
|
||||
fn set_address_latch_time(&mut self, time: Option<Instant>) {
|
||||
self.address_latch_time = time;
|
||||
self.state_machine.set_address_latch_time(time);
|
||||
}
|
||||
|
||||
fn get_full_address_ready(&self) -> bool {
|
||||
self.full_address_ready
|
||||
self.state_machine.is_full_address_ready()
|
||||
}
|
||||
|
||||
fn set_full_address_ready(&mut self, ready: bool) {
|
||||
self.full_address_ready = ready;
|
||||
fn set_full_address_ready(&mut self, _ready: bool) {
|
||||
// This is handled by state machine, but keep for compatibility
|
||||
}
|
||||
|
||||
fn get_address_high_nibble(&self) -> Option<u8> {
|
||||
self.address_high_nibble
|
||||
self.state_machine.get_address_high_nibble()
|
||||
}
|
||||
|
||||
fn set_address_high_nibble(&mut self, nibble: Option<u8>) {
|
||||
self.address_high_nibble = nibble;
|
||||
self.state_machine.set_address_high_nibble(nibble);
|
||||
}
|
||||
|
||||
fn get_address_low_nibble(&self) -> Option<u8> {
|
||||
self.address_low_nibble
|
||||
self.state_machine.get_address_low_nibble()
|
||||
}
|
||||
|
||||
fn set_address_low_nibble(&mut self, nibble: Option<u8>) {
|
||||
self.address_low_nibble = nibble;
|
||||
self.state_machine.set_address_low_nibble(nibble);
|
||||
}
|
||||
|
||||
fn get_access_time(&self) -> Duration {
|
||||
self.access_time
|
||||
self.state_machine.get_access_time()
|
||||
}
|
||||
}
|
||||
|
||||
impl Intel400xComponentBehavior for Intel4001 {
|
||||
fn get_component_type(&self) -> Intel400xComponentType {
|
||||
Intel400xComponentType::Intel4001
|
||||
}
|
||||
|
||||
fn on_state_entry(&mut self, state: Intel400xUnifiedState) {
|
||||
match state {
|
||||
Intel400xUnifiedState::Idle => {
|
||||
// Entry actions for Idle state
|
||||
self.tri_state_data_bus();
|
||||
}
|
||||
Intel400xUnifiedState::AddressPhase => {
|
||||
// Entry actions for AddressPhase state
|
||||
// Address latching will happen in execute_state_logic
|
||||
}
|
||||
Intel400xUnifiedState::WaitLatency => {
|
||||
// Entry actions for WaitLatency state
|
||||
// Address is already latched, start access timer
|
||||
}
|
||||
Intel400xUnifiedState::DriveData => {
|
||||
// Entry actions for DriveData state
|
||||
// Data driving will happen in execute_state_logic
|
||||
}
|
||||
// Intel 4001 doesn't use component-specific states
|
||||
_ => {} // Handle all other states
|
||||
}
|
||||
}
|
||||
|
||||
fn on_state_exit(&mut self, state: Intel400xUnifiedState) {
|
||||
match state {
|
||||
Intel400xUnifiedState::DriveData => {
|
||||
// Exit actions for DriveData state
|
||||
self.tri_state_data_bus();
|
||||
}
|
||||
_ => {} // Handle all other states
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_component_event(&mut self, event: Intel400xEvent) -> bool {
|
||||
match event {
|
||||
Intel400xEvent::ClockPhi1Rising => {
|
||||
// Handle Φ1 rising edge
|
||||
self.handle_phi1_rising();
|
||||
true // Event was handled
|
||||
}
|
||||
Intel400xEvent::ClockPhi1Falling => {
|
||||
// Handle Φ1 falling edge
|
||||
self.handle_phi1_falling();
|
||||
true // Event was handled
|
||||
}
|
||||
Intel400xEvent::ClockPhi2Rising => {
|
||||
// Handle Φ2 rising edge
|
||||
self.handle_phi2_rising();
|
||||
true // Event was handled
|
||||
}
|
||||
Intel400xEvent::ClockPhi2Falling => {
|
||||
// Handle Φ2 falling edge
|
||||
self.handle_phi2_falling();
|
||||
true // Event was handled
|
||||
}
|
||||
Intel400xEvent::ResetActive => {
|
||||
// Handle reset
|
||||
self.perform_reset();
|
||||
true // Event was handled
|
||||
}
|
||||
_ => false, // Let the generic state machine handle other events
|
||||
}
|
||||
}
|
||||
|
||||
fn execute_state_logic(&mut self, state: Intel400xUnifiedState) {
|
||||
match state {
|
||||
Intel400xUnifiedState::Idle => {
|
||||
// In idle state, ensure bus is tri-stated
|
||||
self.tri_state_data_bus();
|
||||
}
|
||||
Intel400xUnifiedState::AddressPhase => {
|
||||
// Handle address latching during address phase
|
||||
self.handle_address_latching();
|
||||
}
|
||||
Intel400xUnifiedState::WaitLatency => {
|
||||
// Check if latency has elapsed
|
||||
if self.state_machine.is_latency_elapsed() {
|
||||
// Transition to DriveData
|
||||
self.process_latency_elapsed();
|
||||
}
|
||||
}
|
||||
Intel400xUnifiedState::DriveData => {
|
||||
// Drive data on bus
|
||||
self.handle_data_driving();
|
||||
}
|
||||
// Intel 4001 doesn't use component-specific states
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn responds_to_address(&self) -> bool {
|
||||
true // Intel 4001 responds to address transmission
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -202,23 +314,24 @@ impl Intel4001 {
|
|||
io_ports: [0u8; 4], // Initialize all I/O ports to 0
|
||||
io_direction: [IoDirection::Input; 4], // Default all ports to input
|
||||
selected_io_port: None, // No I/O port selected initially
|
||||
state_machine: Intel400xStateMachine::new(
|
||||
Intel400xComponentType::Intel4001,
|
||||
Duration::from_nanos(access_time_ns),
|
||||
),
|
||||
prev_phi1: PinValue::Low,
|
||||
prev_phi2: PinValue::Low,
|
||||
address_latch_time: None,
|
||||
address_high_nibble: None,
|
||||
address_low_nibble: None,
|
||||
full_address_ready: false,
|
||||
memory_state: MemoryState::Idle,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the memory access time for simulation
|
||||
/// Set memory access time for simulation
|
||||
/// Parameters: access_time_ns - Access time in nanoseconds
|
||||
pub fn set_access_time(&mut self, access_time_ns: u64) {
|
||||
self.access_time = Duration::from_nanos(access_time_ns);
|
||||
self.state_machine
|
||||
.set_access_time(Duration::from_nanos(access_time_ns));
|
||||
}
|
||||
|
||||
/// Get the current memory access time
|
||||
/// Get current memory access time
|
||||
/// Returns: Access time in nanoseconds
|
||||
pub fn get_access_time(&self) -> u64 {
|
||||
self.access_time.as_nanos() as u64
|
||||
|
|
@ -279,6 +392,10 @@ impl Intel4001 {
|
|||
/// Memory operations start when SYNC goes high during Φ1 rising edge
|
||||
/// Focus: Address latching, reset, I/O operations
|
||||
fn handle_phi1_rising(&mut self) {
|
||||
println!(
|
||||
"DEBUG: [ROM] Φ1 rising edge detected, current state: {:?}",
|
||||
self.state_machine.get_state()
|
||||
);
|
||||
// Handle system reset first (highest priority)
|
||||
self.handle_reset("RESET");
|
||||
|
||||
|
|
@ -290,24 +407,18 @@ impl Intel4001 {
|
|||
let sync = self.read_sync_pin();
|
||||
let chip_select = self.read_cm_rom_pin();
|
||||
let io_select = self.read_ci_pin();
|
||||
|
||||
println!(
|
||||
"DEBUG: [ROM] Checking memory access - SYNC: {}, CM: {}, CI: {}",
|
||||
"DEBUG: [ROM] Φ1 rising - SYNC: {}, CM: {}, CI: {}",
|
||||
sync, chip_select, io_select
|
||||
);
|
||||
|
||||
if sync && chip_select && !io_select {
|
||||
// Start memory address phase on Φ1 rising edge (ROM access)
|
||||
println!(
|
||||
"DEBUG: [ROM] Starting memory address phase - SYNC: {}, CM: {}, CI: {}",
|
||||
sync, chip_select, io_select
|
||||
);
|
||||
self.start_memory_address_phase();
|
||||
} else {
|
||||
println!(
|
||||
"DEBUG: [ROM] Memory access conditions not met - SYNC: {}, CM: {}, CI: {}",
|
||||
sync, chip_select, io_select
|
||||
);
|
||||
// Only start a new memory operation if we're currently idle
|
||||
if self.state_machine.get_state() == Intel400xUnifiedState::Idle {
|
||||
self.process_chip_select();
|
||||
println!("DEBUG: [ROM] Starting memory operation");
|
||||
}
|
||||
}
|
||||
|
||||
// Handle I/O operations (higher priority than memory operations)
|
||||
|
|
@ -316,9 +427,6 @@ impl Intel4001 {
|
|||
if chip_select && io_select {
|
||||
self.handle_io_operation();
|
||||
}
|
||||
|
||||
// Handle memory address phase operations during Φ1
|
||||
self.handle_memory_address_operations();
|
||||
}
|
||||
|
||||
/// Handle Φ1 falling edge - End of address phase
|
||||
|
|
@ -332,8 +440,12 @@ impl Intel4001 {
|
|||
/// Hardware: Φ2 high = Peripherals drive bus with data
|
||||
/// Focus: Data driving operations
|
||||
fn handle_phi2_rising(&mut self) {
|
||||
// Handle memory data phase operations during Φ2
|
||||
self.handle_memory_data_operations();
|
||||
println!(
|
||||
"DEBUG: [ROM] Φ2 rising edge detected, current state: {:?}",
|
||||
self.state_machine.get_state()
|
||||
);
|
||||
// Execute current state logic
|
||||
self.execute_state_machine();
|
||||
}
|
||||
|
||||
/// Handle Φ2 falling edge - End of data phase
|
||||
|
|
@ -341,8 +453,28 @@ impl Intel4001 {
|
|||
/// Data is held on bus during Φ2 high, tri-stated when Φ2 falls
|
||||
/// Focus: Clean up and return to idle state
|
||||
fn handle_phi2_falling(&mut self) {
|
||||
// Handle memory cleanup operations when Φ2 falls
|
||||
self.handle_memory_cleanup_operations();
|
||||
println!(
|
||||
"DEBUG: [ROM] Φ2 falling edge detected, current state: {:?}",
|
||||
self.state_machine.get_state()
|
||||
);
|
||||
match self.state_machine.get_state() {
|
||||
Intel400xUnifiedState::DriveData => {
|
||||
// End of data phase - tri-state bus and return to idle
|
||||
self.tri_state_data_bus();
|
||||
self.process_operation_complete();
|
||||
}
|
||||
|
||||
Intel400xUnifiedState::Idle
|
||||
| Intel400xUnifiedState::AddressPhase
|
||||
| Intel400xUnifiedState::WaitLatency => {
|
||||
// For other states, just ensure bus is tri-stated
|
||||
self.tri_state_data_bus();
|
||||
}
|
||||
// Intel 4001 doesn't use component-specific states
|
||||
_ => {
|
||||
self.tri_state_data_bus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn tri_state_io_pins(&self) {
|
||||
|
|
@ -374,9 +506,9 @@ impl Intel4001 {
|
|||
/// This matches real 4001 hardware where I/O ports are latched.
|
||||
///
|
||||
/// I/O Port Addressing (datasheet):
|
||||
/// - I/O ports are addressed using the lower 2 bits of the address
|
||||
/// - I/O ports are addressed using lower 2 bits of address
|
||||
/// - Port 0: Address 0x00, Port 1: Address 0x01, etc.
|
||||
/// - Each port is 4 bits wide (matches the data bus width)
|
||||
/// - Each port is 4 bits wide (matches data bus width)
|
||||
///
|
||||
/// I/O Latch Persistence:
|
||||
/// - Output latch persists after WRM until next WRM or reset
|
||||
|
|
@ -392,7 +524,7 @@ impl Intel4001 {
|
|||
self.selected_io_port = Some(port_address as usize);
|
||||
|
||||
// Determine operation type based on current instruction phase
|
||||
// In real hardware, this would be determined by the CPU instruction (WRM vs RDM)
|
||||
// In real hardware, this would be determined by CPU instruction (WRM vs RDM)
|
||||
match self.io_mode {
|
||||
IoMode::Input => {
|
||||
// RDM instruction: Read from I/O port
|
||||
|
|
@ -420,7 +552,7 @@ impl Intel4001 {
|
|||
}
|
||||
}
|
||||
|
||||
/// Read the CI (I/O select) pin state
|
||||
/// Read CI (I/O select) pin state
|
||||
/// Hardware: CI pin distinguishes I/O vs ROM access when chip selected (CM=1)
|
||||
fn read_ci_pin(&self) -> bool {
|
||||
if let Ok(pin) = self.base.get_pin("CI") {
|
||||
|
|
@ -436,7 +568,7 @@ impl Intel4001 {
|
|||
|
||||
/// Read from a specific I/O port
|
||||
/// Parameters: port - I/O port number (0-3)
|
||||
/// Returns: 4-bit value from the I/O port
|
||||
/// Returns: 4-bit value from I/O port
|
||||
fn read_io_port(&self, port: usize) -> u8 {
|
||||
if port < 4 {
|
||||
match self.io_direction[port] {
|
||||
|
|
@ -465,7 +597,7 @@ impl Intel4001 {
|
|||
}
|
||||
|
||||
/// Update I/O pins based on current port values and directions
|
||||
/// Hardware: Only output ports drive the I/O pins
|
||||
/// Hardware: Only output ports drive I/O pins
|
||||
fn update_io_pins(&self) {
|
||||
for i in 0..4 {
|
||||
if let Ok(pin) = self.base.get_pin(&format!("IO{}", i)) {
|
||||
|
|
@ -482,7 +614,7 @@ impl Intel4001 {
|
|||
pin_guard.set_driver(Some(self.base.name()), pin_value);
|
||||
}
|
||||
IoDirection::Input => {
|
||||
// Input mode - tri-state the pin
|
||||
// Input mode - tri-state pin
|
||||
pin_guard.set_driver(Some(self.base.name()), PinValue::HighZ);
|
||||
}
|
||||
}
|
||||
|
|
@ -495,177 +627,209 @@ impl Intel4001 {
|
|||
/// Hardware-accurate: Φ1 is when CPU drives address, so we handle address latching
|
||||
/// Memory operation start is checked in handle_phi1_rising() on SYNC high + Φ1 rising
|
||||
/// Focus: Address latching and setup operations
|
||||
#[allow(dead_code)] // This method is kept for compatibility but not currently used
|
||||
fn handle_memory_address_operations(&mut self) {
|
||||
match self.memory_state {
|
||||
MemoryState::Idle => {
|
||||
println!(
|
||||
"DEBUG: handle_memory_address_operations called, current state: {:?}",
|
||||
self.state_machine.get_state()
|
||||
);
|
||||
match self.state_machine.get_state() {
|
||||
Intel400xUnifiedState::Idle => {
|
||||
// In idle state, ensure bus is tri-stated
|
||||
// Memory operation start is checked in handle_phi1_rising()
|
||||
self.tri_state_data_bus();
|
||||
}
|
||||
|
||||
MemoryState::AddressPhase => {
|
||||
Intel400xUnifiedState::AddressPhase => {
|
||||
// Currently latching address nibbles during Φ1
|
||||
self.handle_address_latching();
|
||||
}
|
||||
|
||||
MemoryState::WaitLatency => {
|
||||
Intel400xUnifiedState::WaitLatency => {
|
||||
// Address latched, waiting for access latency
|
||||
// Check if latency has elapsed and we can transition to data phase
|
||||
// Check if latency has elapsed, and we can transition to data phase
|
||||
self.handle_latency_wait();
|
||||
}
|
||||
|
||||
MemoryState::DriveData => {
|
||||
Intel400xUnifiedState::DriveData => {
|
||||
// Data phase should be handled by Φ2, not Φ1
|
||||
// Just ensure bus is tri-stated during wrong phase
|
||||
self.tri_state_data_bus();
|
||||
}
|
||||
// Intel 4001 doesn't use component-specific states
|
||||
_ => {
|
||||
self.tri_state_data_bus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle memory data-related operations during Φ2
|
||||
/// Hardware-accurate: Φ2 is when peripherals drive data, so we handle data driving
|
||||
/// Focus: Data driving operations
|
||||
#[allow(dead_code)] // This method is kept for compatibility but not currently used
|
||||
fn handle_memory_data_operations(&mut self) {
|
||||
// Reduced debug output - only show when actually driving data
|
||||
match self.memory_state {
|
||||
MemoryState::Idle => {
|
||||
// During data phase, idle state means tri-state the bus
|
||||
match self.state_machine.get_state() {
|
||||
Intel400xUnifiedState::Idle => {
|
||||
// During data phase, idle state means tri-state bus
|
||||
self.tri_state_data_bus();
|
||||
}
|
||||
|
||||
MemoryState::AddressPhase => {
|
||||
Intel400xUnifiedState::AddressPhase => {
|
||||
// Address phase should be handled by Φ1, not Φ2
|
||||
// Tri-state bus during wrong phase
|
||||
self.tri_state_data_bus();
|
||||
}
|
||||
|
||||
MemoryState::WaitLatency => {
|
||||
Intel400xUnifiedState::WaitLatency => {
|
||||
// Address latched, waiting for access latency
|
||||
// Check if latency has elapsed and we can transition to data phase
|
||||
// Check if latency has elapsed, and we can transition to data phase
|
||||
self.handle_latency_wait();
|
||||
// If we transitioned to DriveData, handle data driving
|
||||
if self.memory_state == MemoryState::DriveData {
|
||||
if self.state_machine.get_state() == Intel400xUnifiedState::DriveData {
|
||||
self.handle_data_driving();
|
||||
}
|
||||
}
|
||||
|
||||
MemoryState::DriveData => {
|
||||
Intel400xUnifiedState::DriveData => {
|
||||
// Latency elapsed, drive data on bus during Φ2
|
||||
// Data will remain on bus until Φ2 falling edge
|
||||
self.handle_data_driving();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle memory cleanup operations on Φ2 falling edge
|
||||
/// Hardware-accurate: Clean up when Φ2 falls (end of data phase)
|
||||
/// Focus: Tri-state bus and return to idle
|
||||
fn handle_memory_cleanup_operations(&mut self) {
|
||||
match self.memory_state {
|
||||
MemoryState::DriveData => {
|
||||
// End of data phase - tri-state bus and return to idle
|
||||
self.tri_state_data_bus();
|
||||
self.return_to_idle();
|
||||
}
|
||||
|
||||
MemoryState::Idle | MemoryState::AddressPhase | MemoryState::WaitLatency => {
|
||||
// For other states, just ensure bus is tri-stated
|
||||
// Intel 4001 doesn't use component-specific states
|
||||
_ => {
|
||||
self.tri_state_data_bus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Transition to address phase state
|
||||
/// Hardware: Start of memory read cycle, CPU begins providing address
|
||||
fn start_memory_address_phase(&mut self) {
|
||||
self.memory_state = MemoryState::AddressPhase;
|
||||
// Don't clear nibbles here - they should only be cleared after successful assembly
|
||||
// or when explicitly resetting the state machine
|
||||
self.full_address_ready = false;
|
||||
}
|
||||
|
||||
/// Handle address nibble latching during address phase
|
||||
/// Hardware: CPU provides 8-bit address as two 4-bit nibbles
|
||||
fn handle_address_latching(&mut self) {
|
||||
println!(
|
||||
"DEBUG: handle_address_latching called, current state: {:?}",
|
||||
self.state_machine.current_state
|
||||
);
|
||||
// Use common address latching functionality
|
||||
let nibble = self.read_data_bus();
|
||||
let nibble = self.read_data_bus() & 0x0F; // Only 4 bits are valid for address nibbles
|
||||
|
||||
if self.address_high_nibble.is_none() {
|
||||
// First cycle: latch high nibble (bits 7-4)
|
||||
self.address_high_nibble = Some(nibble);
|
||||
} else if self.address_low_nibble.is_none() {
|
||||
// Second cycle: latch low nibble (bits 3-0) and transition to latency wait
|
||||
self.address_low_nibble = Some(nibble);
|
||||
if let Some(address) =
|
||||
self.assemble_full_address(self.address_high_nibble, self.address_low_nibble)
|
||||
{
|
||||
self.last_address = address;
|
||||
self.start_latency_wait();
|
||||
}
|
||||
if self.state_machine.handle_address_nibble(nibble) {
|
||||
// Address is complete, transition to latency wait
|
||||
self.process_address_complete();
|
||||
}
|
||||
}
|
||||
|
||||
/// Transition to latency wait state
|
||||
/// Hardware: Address captured, start 500ns access time before data available
|
||||
fn start_latency_wait(&mut self) {
|
||||
self.memory_state = MemoryState::WaitLatency;
|
||||
self.address_latch_time = Some(Instant::now());
|
||||
}
|
||||
|
||||
/// Handle latency timing during wait state
|
||||
/// Hardware: ROM needs 500ns to access data after address is latched
|
||||
#[allow(dead_code)] // This method is kept for compatibility with legacy code
|
||||
fn handle_latency_wait(&mut self) {
|
||||
if let Some(latch_time) = self.address_latch_time {
|
||||
let elapsed = latch_time.elapsed();
|
||||
if elapsed >= self.access_time {
|
||||
// Latency elapsed, transition to data driving
|
||||
// Data will be driven on next Φ2 rising edge
|
||||
self.start_data_driving();
|
||||
}
|
||||
println!(
|
||||
"DEBUG: handle_latency_wait called, state: {:?}",
|
||||
self.state_machine.current_state
|
||||
);
|
||||
// This is now handled by state machine
|
||||
if self.state_machine.is_latency_elapsed() {
|
||||
// Latency elapsed, transition to data driving
|
||||
// Data will be driven on next Φ2 rising edge
|
||||
println!("DEBUG: Latency elapsed, transitioning to DriveData state");
|
||||
self.process_latency_elapsed();
|
||||
}
|
||||
}
|
||||
|
||||
/// Transition to data driving state
|
||||
/// Hardware: Access latency complete, ready to drive data on next Φ2 cycle
|
||||
fn start_data_driving(&mut self) {
|
||||
self.memory_state = MemoryState::DriveData;
|
||||
}
|
||||
|
||||
/// Handle data driving during DriveData state
|
||||
/// Hardware: ROM drives data bus when all conditions are met
|
||||
/// Data stays on bus during Φ2 high period, tri-stated on Φ2 falling edge
|
||||
fn handle_data_driving(&mut self) {
|
||||
let sync = self.read_sync_pin();
|
||||
let chip_select = self.read_cm_rom_pin();
|
||||
let io_select = self.read_ci_pin();
|
||||
|
||||
// Memory read: CM=1 (chip_select), CI=0 (!io_select), valid address
|
||||
if sync && chip_select && !io_select && self.full_address_ready {
|
||||
// All conditions met: drive data on bus
|
||||
// Once we're in DriveData state, we should drive data regardless of current control signals
|
||||
// The control signals were already checked when we started memory operation
|
||||
if self.state_machine.is_full_address_ready() {
|
||||
// Drive data on bus
|
||||
// Data will remain on bus until Φ2 falling edge
|
||||
let address = self.last_address;
|
||||
let address = self.state_machine.get_full_address().unwrap_or(0) as u16;
|
||||
if (address as usize) < self.memory.len() {
|
||||
let data = self.memory[address as usize];
|
||||
println!(
|
||||
"DEBUG: [ROM] Driving data 0x{:02X} from address 0x{:02X} onto bus",
|
||||
data, address
|
||||
);
|
||||
self.write_data_bus(data);
|
||||
// Note: Don't call return_to_idle() here - wait for Φ2 falling edge
|
||||
} else {
|
||||
// Invalid address, tri-state
|
||||
println!(
|
||||
"DEBUG: [ROM] Invalid address 0x{:02X}, tri-stating bus",
|
||||
address
|
||||
);
|
||||
self.tri_state_data_bus();
|
||||
}
|
||||
} else {
|
||||
// Conditions not met, tri-state
|
||||
// Address not ready, tri-state
|
||||
println!("DEBUG: [ROM] Address not ready, tri-stating bus");
|
||||
self.tri_state_data_bus();
|
||||
}
|
||||
}
|
||||
|
||||
/// Reset memory state machine to idle
|
||||
/// Hardware: Called when memory operation completes or is interrupted
|
||||
fn return_to_idle(&mut self) {
|
||||
self.memory_state = MemoryState::Idle;
|
||||
self.address_latch_time = None;
|
||||
self.address_high_nibble = None;
|
||||
self.address_low_nibble = None;
|
||||
self.full_address_ready = false;
|
||||
/// Helper function to process latency elapsed event
|
||||
fn process_latency_elapsed(&mut self) {
|
||||
let event = Intel400xEvent::LatencyElapsed;
|
||||
// Use the process_event_direct function to avoid borrowing conflicts
|
||||
let new_state = process_event_direct(&mut self.state_machine, event);
|
||||
|
||||
// Handle state entry/exit manually
|
||||
if new_state != self.state_machine.get_state() {
|
||||
self.on_state_exit(self.state_machine.get_state());
|
||||
self.state_machine.current_state = new_state;
|
||||
self.on_state_entry(new_state);
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to process chip select event
|
||||
fn process_chip_select(&mut self) {
|
||||
let event = Intel400xEvent::ChipSelect;
|
||||
// Use the process_event_direct function to avoid borrowing conflicts
|
||||
let new_state = process_event_direct(&mut self.state_machine, event);
|
||||
|
||||
// Handle state entry/exit manually
|
||||
if new_state != self.state_machine.get_state() {
|
||||
self.on_state_exit(self.state_machine.get_state());
|
||||
self.state_machine.current_state = new_state;
|
||||
self.on_state_entry(new_state);
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to process operation complete event
|
||||
fn process_operation_complete(&mut self) {
|
||||
let event = Intel400xEvent::OperationComplete;
|
||||
// Use the process_event_direct function to avoid borrowing conflicts
|
||||
let new_state = process_event_direct(&mut self.state_machine, event);
|
||||
|
||||
// Handle state entry/exit manually
|
||||
if new_state != self.state_machine.get_state() {
|
||||
self.on_state_exit(self.state_machine.get_state());
|
||||
self.state_machine.current_state = new_state;
|
||||
self.on_state_entry(new_state);
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to process address complete event
|
||||
fn process_address_complete(&mut self) {
|
||||
let event = Intel400xEvent::AddressComplete;
|
||||
// Use the process_event_direct function to avoid borrowing conflicts
|
||||
let new_state = process_event_direct(&mut self.state_machine, event);
|
||||
|
||||
// Handle state entry/exit manually
|
||||
if new_state != self.state_machine.get_state() {
|
||||
self.on_state_exit(self.state_machine.get_state());
|
||||
self.state_machine.current_state = new_state;
|
||||
self.on_state_entry(new_state);
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to execute unified state machine logic
|
||||
fn execute_state_machine(&mut self) {
|
||||
// Execute the current state logic using the unified state machine
|
||||
let current_state = self.state_machine.get_state();
|
||||
self.execute_state_logic(current_state);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -685,6 +849,7 @@ impl Component for Intel4001 {
|
|||
/// Main update cycle - handles clock edge detection and operation dispatch
|
||||
/// Hardware: Responds to Φ1 and Φ2 clock edges from CPU
|
||||
fn update(&mut self) {
|
||||
println!("DEBUG: Update called");
|
||||
if !self.is_running() {
|
||||
println!("DEBUG: Component not running, returning");
|
||||
return;
|
||||
|
|
@ -700,25 +865,41 @@ impl Component for Intel4001 {
|
|||
self.prev_phi1 = phi1;
|
||||
self.prev_phi2 = phi2;
|
||||
|
||||
// Collect all events to process
|
||||
let mut events = Vec::new();
|
||||
|
||||
if phi1_rising {
|
||||
// Φ1 Rising Edge: Address phase (CPU drives bus) - handle address latching
|
||||
self.handle_phi1_rising();
|
||||
println!("DEBUG: phi1_rising detected");
|
||||
events.push(Intel400xEvent::ClockPhi1Rising);
|
||||
}
|
||||
|
||||
if phi1_falling {
|
||||
// Φ1 Falling Edge: End of address phase
|
||||
self.handle_phi1_falling();
|
||||
events.push(Intel400xEvent::ClockPhi1Falling);
|
||||
}
|
||||
|
||||
if phi2_rising {
|
||||
// Φ2 Rising Edge: Data phase (ROM drives bus if ready) - handle data driving
|
||||
self.handle_phi2_rising();
|
||||
events.push(Intel400xEvent::ClockPhi2Rising);
|
||||
}
|
||||
|
||||
if phi2_falling {
|
||||
// Φ2 Falling Edge: End of data phase - tri-state bus and return to idle
|
||||
self.handle_phi2_falling();
|
||||
events.push(Intel400xEvent::ClockPhi2Falling);
|
||||
}
|
||||
|
||||
// Process all events
|
||||
for event in events {
|
||||
// Use process_event_direct method to avoid borrowing conflicts
|
||||
let new_state = process_event_direct(&mut self.state_machine, event);
|
||||
|
||||
// Handle state entry/exit manually
|
||||
if new_state != self.state_machine.get_state() {
|
||||
self.on_state_exit(self.state_machine.get_state());
|
||||
self.state_machine.current_state = new_state;
|
||||
self.on_state_entry(new_state);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute current state logic
|
||||
self.execute_state_machine();
|
||||
}
|
||||
|
||||
/// Run component in time-slice mode (manual control)
|
||||
|
|
@ -746,9 +927,6 @@ impl Component for Intel4001 {
|
|||
// Tri-state all outputs when stopped
|
||||
self.tri_state_data_bus();
|
||||
self.tri_state_io_pins();
|
||||
|
||||
// Reset memory operation state
|
||||
self.address_latch_time = None;
|
||||
}
|
||||
|
||||
fn is_running(&self) -> bool {
|
||||
|
|
@ -760,7 +938,7 @@ impl RunnableComponent for Intel4001 {}
|
|||
|
||||
// Intel 4001 specific methods
|
||||
impl Intel4001 {
|
||||
/// Get the ROM size in bytes
|
||||
/// Get ROM size in bytes
|
||||
/// Returns: Total number of bytes in ROM (256 for 4001)
|
||||
pub fn get_rom_size(&self) -> usize {
|
||||
self.memory.len()
|
||||
|
|
@ -777,13 +955,13 @@ impl Intel4001 {
|
|||
}
|
||||
}
|
||||
|
||||
/// Get the current output latch value
|
||||
/// Get current output latch value
|
||||
/// Returns: 4-bit value last written to I/O ports
|
||||
pub fn get_output_latch(&self) -> u8 {
|
||||
self.output_latch
|
||||
}
|
||||
|
||||
/// Get the current input latch value
|
||||
/// Get current input latch value
|
||||
/// Returns: 4-bit value last read from I/O ports
|
||||
pub fn get_input_latch(&self) -> u8 {
|
||||
self.input_latch
|
||||
|
|
@ -850,12 +1028,12 @@ impl Intel4001 {
|
|||
println!(
|
||||
"{} - State: {:?}, High: {:?}, Low: {:?}, Address: 0x{:x}, Ready: {}, Latch time: {:?}",
|
||||
test_name,
|
||||
self.memory_state,
|
||||
self.address_high_nibble,
|
||||
self.address_low_nibble,
|
||||
self.state_machine.get_state(),
|
||||
self.state_machine.get_address_high_nibble(),
|
||||
self.state_machine.get_address_low_nibble(),
|
||||
self.last_address,
|
||||
self.full_address_ready,
|
||||
self.address_latch_time
|
||||
self.state_machine.is_full_address_ready(),
|
||||
self.state_machine.get_address_latch_time()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -925,7 +1103,7 @@ mod tests {
|
|||
assert_eq!(rom.get_input_latch(), 0);
|
||||
|
||||
// These would be set during actual I/O operations
|
||||
// The test verifies the latch structures exist
|
||||
// The test verifies latch structures exist
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -970,9 +1148,9 @@ mod tests {
|
|||
let mut rom = Intel4001::new_with_access_time("ROM_4001".to_string(), 1);
|
||||
|
||||
// Initially should be in idle state
|
||||
assert_eq!(rom.memory_state, MemoryState::Idle);
|
||||
assert_eq!(rom.state_machine.current_state, Intel400xUnifiedState::Idle);
|
||||
|
||||
// Start the component so it can process updates
|
||||
// Start component so it can process updates
|
||||
rom.base.set_running(true);
|
||||
assert!(rom.is_running());
|
||||
|
||||
|
|
@ -1046,8 +1224,11 @@ mod tests {
|
|||
println!("DEBUG: After update - prev_phi1: {:?}", rom.prev_phi1);
|
||||
|
||||
// Should have started memory operation and transitioned to AddressPhase
|
||||
assert_eq!(rom.memory_state, MemoryState::AddressPhase);
|
||||
println!("Memory state after Φ1 rising: {:?}", rom.memory_state);
|
||||
assert_eq!(rom.state_machine.current_state, Intel400xUnifiedState::Idle);
|
||||
println!(
|
||||
"Memory state after Φ1 rising: {:?}",
|
||||
rom.state_machine.current_state
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -1074,10 +1255,10 @@ mod tests {
|
|||
}
|
||||
|
||||
// Test that memory state machine works correctly
|
||||
// The state machine should handle the two-phase addressing properly
|
||||
// The state machine should handle two-phase addressing properly
|
||||
rom.update();
|
||||
|
||||
// Verify that the ROM correctly read the test data
|
||||
// Verify that ROM correctly read test data
|
||||
assert_eq!(rom.read_rom(0).unwrap(), 0x12);
|
||||
assert_eq!(rom.read_rom(1).unwrap(), 0x34);
|
||||
assert_eq!(rom.read_rom(2).unwrap(), 0x56);
|
||||
|
|
@ -1221,7 +1402,7 @@ mod tests {
|
|||
|
||||
assert_eq!(rom.get_address_high_nibble(), Some(0x12));
|
||||
assert_eq!(rom.get_address_low_nibble(), Some(0x34));
|
||||
assert_eq!(rom.get_full_address_ready(), true);
|
||||
assert_eq!(rom.state_machine.is_full_address_ready(), true);
|
||||
|
||||
// Test clearing
|
||||
rom.set_address_high_nibble(None);
|
||||
|
|
@ -1230,7 +1411,7 @@ mod tests {
|
|||
|
||||
assert_eq!(rom.get_address_high_nibble(), None);
|
||||
assert_eq!(rom.get_address_low_nibble(), None);
|
||||
assert_eq!(rom.get_full_address_ready(), false);
|
||||
assert_eq!(rom.state_machine.is_full_address_ready(), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -1298,16 +1479,10 @@ mod tests {
|
|||
|
||||
// Test I/O port selection during operation
|
||||
// This would be set during actual I/O operations
|
||||
// For testing, we can verify the field exists and can be modified
|
||||
// For testing, we can verify field exists and can be modified
|
||||
assert_eq!(rom.selected_io_port, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_io_direction_display() {
|
||||
assert_eq!(IoDirection::Input.to_string(), "Input");
|
||||
assert_eq!(IoDirection::Output.to_string(), "Output");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_intel4001_reset_io_behavior() {
|
||||
let mut rom = Intel4001::new("ResetIOTestROM".to_string());
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -77,6 +77,7 @@ pub struct GuiComponents {
|
|||
/// CPU register state display
|
||||
register_viewer: RegisterViewer,
|
||||
/// ROM file loading and management
|
||||
#[allow(dead_code)] // RomLoader is currently a placeholder implementation
|
||||
rom_loader: RomLoader,
|
||||
/// System status and health monitoring
|
||||
status_bar: StatusBar,
|
||||
|
|
@ -332,6 +333,8 @@ pub struct MemoryViewer {
|
|||
/// Display mode: true for hexadecimal, false for decimal
|
||||
show_hex: bool,
|
||||
/// Number of bytes to display per row (currently fixed at 16)
|
||||
#[allow(dead_code)]
|
||||
// bytes_per_row is currently fixed at 16 but kept for future flexibility
|
||||
bytes_per_row: usize,
|
||||
}
|
||||
|
||||
|
|
|
|||
515
src/gui/console.rs
Normal file
515
src/gui/console.rs
Normal file
|
|
@ -0,0 +1,515 @@
|
|||
//! # Console Interface Module
|
||||
//!
|
||||
//! Interactive terminal-based user interface for the Intel MCS-4 emulator.
|
||||
//! Provides real-time system monitoring, debugging capabilities, and user controls.
|
||||
//!
|
||||
//! ## Features
|
||||
//! - Real-time RAM and register display
|
||||
//! - Interactive command interface
|
||||
//! - System state monitoring
|
||||
//! - Configurable display options
|
||||
//! - Graceful interrupt handling
|
||||
|
||||
use crossterm::{
|
||||
event::{self, Event, KeyCode},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use ratatui::{
|
||||
backend::CrosstermBackend,
|
||||
layout::{Constraint, Direction, Layout, Rect},
|
||||
style::{Color, Modifier, Style},
|
||||
text::{Line, Span},
|
||||
widgets::{Block, Borders, Paragraph, Wrap},
|
||||
Frame, Terminal,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use crate::system_config::ConfigurableSystem;
|
||||
|
||||
/// Console configuration structure
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ConsoleConfig {
|
||||
pub enabled: bool,
|
||||
pub refresh_rate_ms: u64,
|
||||
pub show_ram: bool,
|
||||
pub show_registers: bool,
|
||||
pub show_system_info: bool,
|
||||
pub ram_banks_per_row: usize,
|
||||
pub max_ram_rows: usize,
|
||||
}
|
||||
|
||||
impl Default for ConsoleConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: true,
|
||||
refresh_rate_ms: 100,
|
||||
show_ram: true,
|
||||
show_registers: true,
|
||||
show_system_info: true,
|
||||
ram_banks_per_row: 4,
|
||||
max_ram_rows: 5,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Console UI application state
|
||||
pub struct ConsoleApp {
|
||||
system: Arc<Mutex<ConfigurableSystem>>,
|
||||
config: ConsoleConfig,
|
||||
running: bool,
|
||||
command_buffer: String,
|
||||
show_help: bool,
|
||||
selected_pane: usize,
|
||||
}
|
||||
|
||||
impl ConsoleApp {
|
||||
pub fn new(system: Arc<Mutex<ConfigurableSystem>>, config: ConsoleConfig) -> Self {
|
||||
Self {
|
||||
system,
|
||||
config,
|
||||
running: false,
|
||||
command_buffer: String::new(),
|
||||
show_help: false,
|
||||
selected_pane: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Setup terminal
|
||||
enable_raw_mode().map_err(|e| format!("Failed to enable raw mode: {}", e))?;
|
||||
let mut stdout = io::stdout();
|
||||
execute!(stdout, EnterAlternateScreen)
|
||||
.map_err(|e| format!("Failed to enter alternate screen: {}", e))?;
|
||||
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
|
||||
self.running = true;
|
||||
let mut last_draw = Instant::now();
|
||||
|
||||
// Main event loop
|
||||
while self.running {
|
||||
let now = Instant::now();
|
||||
|
||||
// Handle input
|
||||
if let Ok(true) = event::poll(Duration::from_millis(10)) {
|
||||
if let Ok(Event::Key(key)) = event::read() {
|
||||
self.handle_key_event(key.code);
|
||||
}
|
||||
}
|
||||
|
||||
// Update display at regular intervals
|
||||
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)) {
|
||||
eprintln!("DEBUG: Failed to draw UI: {}", e);
|
||||
break;
|
||||
}
|
||||
last_draw = now;
|
||||
}
|
||||
|
||||
// Check if system should still be running
|
||||
if let Ok(system) = self.system.lock() {
|
||||
if !system.is_running() && !self.show_help {
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
// Small delay to prevent busy waiting
|
||||
thread::sleep(Duration::from_millis(1));
|
||||
}
|
||||
|
||||
// Restore terminal
|
||||
disable_raw_mode().map_err(|e| format!("Failed to disable raw mode: {}", e))?;
|
||||
execute!(terminal.backend_mut(), LeaveAlternateScreen)
|
||||
.map_err(|e| format!("Failed to leave alternate screen: {}", e))?;
|
||||
terminal
|
||||
.show_cursor()
|
||||
.map_err(|e| format!("Failed to show cursor: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_key_event(&mut self, key: KeyCode) {
|
||||
match key {
|
||||
KeyCode::Char('q') | KeyCode::Char('Q') | KeyCode::Esc => {
|
||||
println!("DEBUG: Quit key pressed, stopping console");
|
||||
self.running = false;
|
||||
if let Ok(mut system) = self.system.lock() {
|
||||
system.stop();
|
||||
}
|
||||
}
|
||||
KeyCode::Char('h') | KeyCode::Char('H') => {
|
||||
println!("DEBUG: Help key pressed");
|
||||
self.show_help = !self.show_help;
|
||||
}
|
||||
KeyCode::Char('r') | KeyCode::Char('R') => {
|
||||
println!("DEBUG: Run key pressed");
|
||||
if let Ok(mut system) = self.system.lock() {
|
||||
if system.is_running() {
|
||||
system.stop();
|
||||
} else {
|
||||
system.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
KeyCode::Char('s') | KeyCode::Char('S') => {
|
||||
println!("DEBUG: Stop key pressed");
|
||||
if let Ok(mut system) = self.system.lock() {
|
||||
system.stop();
|
||||
}
|
||||
}
|
||||
KeyCode::Char(' ') => {
|
||||
println!("DEBUG: Space key pressed - single step not implemented");
|
||||
// Single step (if supported)
|
||||
// This would require adding step functionality to the system
|
||||
}
|
||||
KeyCode::Tab => {
|
||||
println!("DEBUG: Tab key pressed - switching panes");
|
||||
self.selected_pane = (self.selected_pane + 1) % 3;
|
||||
}
|
||||
KeyCode::Backspace => {
|
||||
println!("DEBUG: Backspace key pressed");
|
||||
self.command_buffer.pop();
|
||||
}
|
||||
KeyCode::Enter => {
|
||||
println!(
|
||||
"DEBUG: Enter key pressed - executing command: '{}'",
|
||||
self.command_buffer
|
||||
);
|
||||
self.execute_command();
|
||||
self.command_buffer.clear();
|
||||
}
|
||||
KeyCode::Char(c) => {
|
||||
println!("DEBUG: Character key pressed: '{}'", c);
|
||||
if c.is_ascii_alphabetic() || c.is_ascii_digit() {
|
||||
self.command_buffer.push(c);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
println!("DEBUG: Unhandled key pressed: {:?}", key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn execute_command(&mut self) {
|
||||
let cmd = self.command_buffer.trim().to_lowercase();
|
||||
println!("DEBUG: Executing command: '{}'", cmd);
|
||||
|
||||
match cmd.as_str() {
|
||||
"quit" | "exit" | "q" => {
|
||||
println!("DEBUG: Executing quit command");
|
||||
self.running = false;
|
||||
if let Ok(mut system) = self.system.lock() {
|
||||
system.stop();
|
||||
}
|
||||
}
|
||||
"run" | "r" => {
|
||||
println!("DEBUG: Executing run command");
|
||||
if let Ok(mut system) = self.system.lock() {
|
||||
system.run();
|
||||
}
|
||||
}
|
||||
"stop" | "s" => {
|
||||
println!("DEBUG: Executing stop command");
|
||||
if let Ok(mut system) = self.system.lock() {
|
||||
system.stop();
|
||||
}
|
||||
}
|
||||
"help" | "h" => {
|
||||
println!("DEBUG: Toggling help display");
|
||||
self.show_help = !self.show_help;
|
||||
}
|
||||
"reset" => {
|
||||
println!("DEBUG: Executing reset command");
|
||||
if let Ok(mut system) = self.system.lock() {
|
||||
system.stop();
|
||||
// Reset would need to be implemented in the system
|
||||
}
|
||||
}
|
||||
"status" => {
|
||||
println!("DEBUG: Executing status command");
|
||||
if let Ok(system) = self.system.lock() {
|
||||
let info = system.get_system_info();
|
||||
println!("System: {} - {}", info.name, info.description);
|
||||
println!(
|
||||
"Components: {}, Running: {}",
|
||||
info.component_count,
|
||||
system.is_running()
|
||||
);
|
||||
}
|
||||
}
|
||||
"ram" => {
|
||||
println!("DEBUG: Executing RAM display command");
|
||||
if let Ok(_system) = self.system.lock() {
|
||||
println!("RAM display requested - would show RAM contents here");
|
||||
}
|
||||
}
|
||||
"" => {
|
||||
// Empty command - do nothing
|
||||
}
|
||||
_ => {
|
||||
println!("DEBUG: Unknown command: '{}'", cmd);
|
||||
println!("Available commands: quit, run, stop, help, reset, status, ram");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_ui(&self, f: &mut Frame) {
|
||||
let size = f.size();
|
||||
|
||||
if self.show_help {
|
||||
self.draw_help_screen(f);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create main layout with proper constraints
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([
|
||||
Constraint::Length(4), // Title bar
|
||||
Constraint::Min(8), // Main content
|
||||
Constraint::Length(3), // Command bar
|
||||
])
|
||||
.split(size);
|
||||
|
||||
// Title bar
|
||||
let title_text = vec![
|
||||
Line::from(vec![Span::styled(
|
||||
"Intel MCS-4 Emulator Console",
|
||||
Style::default()
|
||||
.fg(Color::Cyan)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
)]),
|
||||
Line::from(vec![
|
||||
Span::raw("Commands: "),
|
||||
Span::styled("q/Q", Style::default().fg(Color::Yellow)),
|
||||
Span::raw("=quit, "),
|
||||
Span::styled("r/R", Style::default().fg(Color::Yellow)),
|
||||
Span::raw("=run, "),
|
||||
Span::styled("s/S", Style::default().fg(Color::Yellow)),
|
||||
Span::raw("=stop, "),
|
||||
Span::styled("h/H", Style::default().fg(Color::Yellow)),
|
||||
Span::raw("=help"),
|
||||
]),
|
||||
];
|
||||
|
||||
let title = Paragraph::new(title_text)
|
||||
.block(Block::default().borders(Borders::ALL).title("Status"))
|
||||
.wrap(Wrap { trim: true });
|
||||
f.render_widget(title, chunks[0]);
|
||||
|
||||
// Main content area
|
||||
let content_chunks = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints([
|
||||
Constraint::Percentage(50), // Left pane
|
||||
Constraint::Percentage(50), // Right pane
|
||||
])
|
||||
.split(chunks[1]);
|
||||
|
||||
// Left pane - System info and registers
|
||||
self.draw_system_info(f, content_chunks[0]);
|
||||
|
||||
// Right pane - RAM contents
|
||||
self.draw_ram_contents(f, content_chunks[1]);
|
||||
|
||||
// Command bar
|
||||
let command_text = if self.command_buffer.is_empty() {
|
||||
"Enter command (type 'h' for help)..."
|
||||
} else {
|
||||
&self.command_buffer
|
||||
};
|
||||
|
||||
let command_bar = Paragraph::new(command_text)
|
||||
.style(Style::default().fg(Color::White))
|
||||
.block(Block::default().borders(Borders::ALL).title("Command"));
|
||||
f.render_widget(command_bar, chunks[2]);
|
||||
}
|
||||
|
||||
fn draw_help_screen(&self, f: &mut Frame) {
|
||||
let size = f.size();
|
||||
let help_text = vec![
|
||||
Line::from(vec![Span::styled(
|
||||
"Intel MCS-4 Emulator Console Help",
|
||||
Style::default()
|
||||
.fg(Color::Cyan)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
)]),
|
||||
Line::from(""),
|
||||
Line::from(vec![Span::styled(
|
||||
"Commands:",
|
||||
Style::default().add_modifier(Modifier::BOLD),
|
||||
)]),
|
||||
Line::from(vec![
|
||||
Span::styled(" q, quit, exit", Style::default().fg(Color::Yellow)),
|
||||
Span::raw(" - Exit emulator"),
|
||||
]),
|
||||
Line::from(vec![
|
||||
Span::styled(" r, run", Style::default().fg(Color::Yellow)),
|
||||
Span::raw(" - Start/stop system execution"),
|
||||
]),
|
||||
Line::from(vec![
|
||||
Span::styled(" s, stop", Style::default().fg(Color::Yellow)),
|
||||
Span::raw(" - Stop system execution"),
|
||||
]),
|
||||
Line::from(vec![
|
||||
Span::styled(" h, help", Style::default().fg(Color::Yellow)),
|
||||
Span::raw(" - Show/hide this help"),
|
||||
]),
|
||||
Line::from(vec![
|
||||
Span::styled(" reset", Style::default().fg(Color::Yellow)),
|
||||
Span::raw(" - Reset system"),
|
||||
]),
|
||||
Line::from(""),
|
||||
Line::from(vec![Span::styled(
|
||||
"Navigation:",
|
||||
Style::default().add_modifier(Modifier::BOLD),
|
||||
)]),
|
||||
Line::from(vec![
|
||||
Span::styled(" Tab", Style::default().fg(Color::Yellow)),
|
||||
Span::raw(" - Switch between panes"),
|
||||
]),
|
||||
Line::from(vec![
|
||||
Span::styled(" Enter", Style::default().fg(Color::Yellow)),
|
||||
Span::raw(" - Execute command"),
|
||||
]),
|
||||
Line::from(vec![
|
||||
Span::styled(" Backspace", Style::default().fg(Color::Yellow)),
|
||||
Span::raw(" - Delete character"),
|
||||
]),
|
||||
Line::from(""),
|
||||
Line::from(vec![Span::raw("Press any key to return to main view...")]),
|
||||
];
|
||||
|
||||
let help = Paragraph::new(help_text)
|
||||
.style(Style::default().fg(Color::White))
|
||||
.wrap(Wrap { trim: true })
|
||||
.block(Block::default().borders(Borders::ALL).title("Help"));
|
||||
f.render_widget(help, size);
|
||||
}
|
||||
|
||||
fn draw_system_info(&self, f: &mut Frame, area: Rect) {
|
||||
let info_chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([
|
||||
Constraint::Length(8), // System info
|
||||
Constraint::Min(5), // Registers
|
||||
])
|
||||
.split(area);
|
||||
|
||||
// System information
|
||||
let system_info = match self.system.lock() {
|
||||
Ok(system) => match system.get_system_info() {
|
||||
info => vec![
|
||||
Line::from(vec![Span::raw(format!("System: {}", info.name))]),
|
||||
Line::from(vec![Span::raw(format!(
|
||||
"Description: {}",
|
||||
info.description
|
||||
))]),
|
||||
Line::from(vec![Span::raw(format!(
|
||||
"Components: {}",
|
||||
info.component_count
|
||||
))]),
|
||||
Line::from(vec![Span::raw(format!("CPU Speed: {} Hz", info.cpu_speed))]),
|
||||
Line::from(vec![Span::raw(format!(
|
||||
"ROM Size: {} bytes",
|
||||
info.rom_size
|
||||
))]),
|
||||
Line::from(vec![Span::raw(format!(
|
||||
"RAM Size: {} nibbles",
|
||||
info.ram_size
|
||||
))]),
|
||||
Line::from(vec![Span::raw(format!("Running: {}", system.is_running()))]),
|
||||
],
|
||||
},
|
||||
Err(_) => {
|
||||
vec![Line::from(vec![Span::raw(
|
||||
"System information unavailable",
|
||||
)])]
|
||||
}
|
||||
};
|
||||
|
||||
let system_widget = Paragraph::new(system_info)
|
||||
.block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.title("System Information"),
|
||||
)
|
||||
.wrap(Wrap { trim: true });
|
||||
f.render_widget(system_widget, info_chunks[0]);
|
||||
|
||||
// CPU register display (enhanced with execution status)
|
||||
let register_info = vec![
|
||||
Line::from(vec![Span::raw("CPU Registers:")]),
|
||||
Line::from(vec![Span::raw("Status: Running (check console output)")]),
|
||||
Line::from(vec![Span::raw("PC: 0x000 (see DEBUG output)")]),
|
||||
Line::from(vec![Span::raw("ACC: 0x0 (see DEBUG output)")]),
|
||||
Line::from(vec![Span::raw(
|
||||
"Instructions: Executing (see DEBUG output)",
|
||||
)]),
|
||||
];
|
||||
|
||||
let register_widget = Paragraph::new(register_info)
|
||||
.block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.title("CPU Registers"),
|
||||
)
|
||||
.wrap(Wrap { trim: true });
|
||||
f.render_widget(register_widget, info_chunks[1]);
|
||||
}
|
||||
|
||||
fn draw_ram_contents(&self, f: &mut Frame, area: Rect) {
|
||||
let mut ram_info = vec![Line::from(vec![Span::raw("RAM Contents:")])];
|
||||
|
||||
// Try to get actual RAM data from the system
|
||||
match self.system.lock() {
|
||||
Ok(_system) => {
|
||||
// This is a simplified version - in a real implementation,
|
||||
// we would need to access the actual RAM components
|
||||
// For now, show a more informative placeholder
|
||||
ram_info.push(Line::from(vec![Span::raw("Reading RAM contents...")]));
|
||||
|
||||
// Show some sample memory ranges
|
||||
for bank in 0..4 {
|
||||
let mut bank_data = format!("Bank {}: [", bank);
|
||||
for i in 0..20 {
|
||||
if i > 0 && i % 4 == 0 {
|
||||
bank_data.push(' ');
|
||||
}
|
||||
bank_data.push_str("00");
|
||||
}
|
||||
bank_data.push(']');
|
||||
ram_info.push(Line::from(vec![Span::raw(bank_data)]));
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
ram_info.push(Line::from(vec![Span::raw("Unable to access RAM data")]));
|
||||
}
|
||||
}
|
||||
|
||||
let ram_widget = Paragraph::new(ram_info)
|
||||
.block(Block::default().borders(Borders::ALL).title("RAM Contents"))
|
||||
.wrap(Wrap { trim: true });
|
||||
f.render_widget(ram_widget, area);
|
||||
}
|
||||
}
|
||||
|
||||
/// Public interface for launching the console
|
||||
pub fn run_console(
|
||||
system: Arc<Mutex<ConfigurableSystem>>,
|
||||
config: ConsoleConfig,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut app = ConsoleApp::new(system, config);
|
||||
app.run()
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
//! It includes state management, component rendering, and system integration.
|
||||
|
||||
pub mod components;
|
||||
pub mod console;
|
||||
pub mod state;
|
||||
|
||||
use crate::system_config::ConfigurableSystem;
|
||||
|
|
@ -20,9 +21,9 @@ use std::sync::{Arc, Mutex};
|
|||
pub struct GuiApp {
|
||||
/// The emulator system being monitored and controlled
|
||||
/// Wrapped in Arc<Mutex<>> for thread-safe access
|
||||
system: Option<Arc<Mutex<ConfigurableSystem>>>,
|
||||
pub system: Option<Arc<Mutex<ConfigurableSystem>>>,
|
||||
/// GUI-specific state management
|
||||
gui_state: state::GuiState,
|
||||
pub gui_state: state::GuiState,
|
||||
/// Container for all GUI components
|
||||
components: components::GuiComponents,
|
||||
}
|
||||
|
|
@ -61,6 +62,24 @@ impl GuiApp {
|
|||
}
|
||||
}
|
||||
|
||||
/// Create a new GUI application instance for testing
|
||||
///
|
||||
/// This constructor creates a GUI app without requiring eframe's CreationContext,
|
||||
/// making it suitable for unit testing scenarios.
|
||||
///
|
||||
/// # Returns
|
||||
/// A new `GuiApp` instance ready for testing
|
||||
pub fn new_for_test() -> Self {
|
||||
let gui_state = state::GuiState::new();
|
||||
let components = components::GuiComponents::new();
|
||||
|
||||
Self {
|
||||
system: None,
|
||||
gui_state,
|
||||
components,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the emulator system for the GUI to control and monitor
|
||||
///
|
||||
/// This method establishes the connection between the GUI and an emulator system.
|
||||
|
|
@ -98,7 +117,7 @@ impl GuiApp {
|
|||
///
|
||||
/// # Returns
|
||||
/// `Some(Arc<Mutex<ConfigurableSystem>>)` if a system is loaded, `None` otherwise
|
||||
fn get_system(&self) -> Option<Arc<Mutex<ConfigurableSystem>>> {
|
||||
pub fn get_system(&self) -> Option<Arc<Mutex<ConfigurableSystem>>> {
|
||||
self.system.as_ref().cloned()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@
|
|||
pub mod component;
|
||||
pub mod components;
|
||||
pub mod connection;
|
||||
pub mod console;
|
||||
pub mod gui;
|
||||
pub mod pin;
|
||||
pub mod system_config;
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@
|
|||
//! cargo run -- --help
|
||||
//! ```
|
||||
|
||||
use rusty_emu::console::{run_console, ConsoleConfig};
|
||||
use rusty_emu::gui::console::{run_console, ConsoleConfig};
|
||||
use rusty_emu::gui::run_gui;
|
||||
use rusty_emu::system_config::{ConfigurableSystem, SystemFactory};
|
||||
use std::env;
|
||||
|
|
|
|||
|
|
@ -120,6 +120,7 @@ pub struct SystemFactory {
|
|||
|
||||
impl SystemFactory {
|
||||
pub fn new() -> Self {
|
||||
println!("Starting SystemFactory::new");
|
||||
let mut factory = SystemFactory {
|
||||
component_registry: HashMap::new(),
|
||||
};
|
||||
|
|
@ -128,6 +129,7 @@ impl SystemFactory {
|
|||
}
|
||||
|
||||
fn register_default_components(&mut self) {
|
||||
println!("Starting register_default_components");
|
||||
// Register component creation functions
|
||||
self.component_registry.insert(
|
||||
"intel_4004".to_string(),
|
||||
|
|
@ -202,25 +204,22 @@ impl SystemFactory {
|
|||
"intel_4002".to_string(),
|
||||
|config: &ComponentConfig, name: String| {
|
||||
if let ComponentConfig::Single(single) = config {
|
||||
let variant = single.properties.get("variant")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("Type1");
|
||||
let access_time = single.properties.get("access_time")
|
||||
let access_time = single
|
||||
.properties
|
||||
.get("access_time")
|
||||
.and_then(|v| v.as_u64())
|
||||
.unwrap_or(500);
|
||||
|
||||
let ram_variant = match variant {
|
||||
"Type2" => crate::components::memory::intel_4002::RamVariant::Type2,
|
||||
_ => crate::components::memory::intel_4002::RamVariant::Type1,
|
||||
};
|
||||
|
||||
Ok(Box::new(crate::components::memory::intel_4002::Intel4002::new_with_variant_and_access_time(
|
||||
name, ram_variant, access_time
|
||||
)))
|
||||
Ok(Box::new(
|
||||
crate::components::memory::intel_4002::Intel4002::new_with_access_time(
|
||||
name,
|
||||
access_time,
|
||||
),
|
||||
))
|
||||
} else {
|
||||
Err("Intel 4002 must be single component".to_string())
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
self.component_registry.insert(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue