Added I/O demo system

CLean up of unused components and functions
This commit is contained in:
Jan-Peter von Hunnius 2025-09-29 21:42:37 +02:00
parent 77ede95bce
commit 7d06837608
20 changed files with 714 additions and 1924 deletions

View file

@ -1,406 +0,0 @@
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::{Duration, Instant};
use crate::component::{BaseComponent, Component};
use crate::pin::{DriveStrength, Pin, PinValue};
pub struct GenericBus {
base: BaseComponent,
connected_pins: Vec<Arc<Mutex<Pin>>>,
bus_value: PinValue,
last_update: Instant,
settlement_time: Duration,
is_active: bool,
}
impl GenericBus {
pub fn new(name: String) -> Self {
// Bus typically has bidirectional data pins
let pin_names = vec!["DATA"];
let pins = BaseComponent::create_pin_map(&pin_names, &name);
GenericBus {
base: BaseComponent::new(name, pins),
connected_pins: Vec::new(),
bus_value: PinValue::HighZ,
last_update: Instant::now(),
settlement_time: Duration::from_nanos(10),
is_active: true,
}
}
pub fn with_settlement_time(mut self, time: Duration) -> Self {
self.settlement_time = time;
self
}
pub fn connect_pin(&mut self, pin: Arc<Mutex<Pin>>) -> Result<(), String> {
if !self.connected_pins.iter().any(|p| Arc::ptr_eq(p, &pin)) {
self.connected_pins.push(pin);
Ok(())
} else {
Err("Pin already connected to bus".to_string())
}
}
pub fn disconnect_pin(&mut self, pin: &Arc<Mutex<Pin>>) -> Result<(), String> {
let initial_len = self.connected_pins.len();
self.connected_pins.retain(|p| !Arc::ptr_eq(p, pin));
if self.connected_pins.len() < initial_len {
Ok(())
} else {
Err("Pin not found on bus".to_string())
}
}
pub fn get_connected_pins(&self) -> &Vec<Arc<Mutex<Pin>>> {
&self.connected_pins
}
pub fn set_active(&mut self, active: bool) {
self.is_active = active;
if !active {
// Tri-state the bus when deactivated
self.bus_value = PinValue::HighZ;
self.propagate_bus_value();
}
}
pub fn is_active(&self) -> bool {
self.is_active
}
pub fn get_bus_value(&self) -> PinValue {
self.bus_value
}
fn read_bus_state(&self) -> PinValue {
if !self.is_active {
return PinValue::HighZ;
}
if self.connected_pins.is_empty() {
return PinValue::HighZ;
}
// Collect all active drivers from connected pins
let mut drivers = Vec::new();
for pin in &self.connected_pins {
if let Ok(pin_guard) = pin.lock() {
let pin_drivers = pin_guard.get_drivers();
for (_, (value, strength)) in pin_drivers {
if *strength != DriveStrength::HighImpedance && *value != PinValue::HighZ {
drivers.push((*value, *strength));
}
}
}
}
if drivers.is_empty() {
return PinValue::HighZ;
}
// Find the strongest driver
let max_strength = drivers
.iter()
.map(|(_, strength)| *strength)
.max()
.unwrap_or(DriveStrength::HighImpedance);
if max_strength == DriveStrength::HighImpedance {
return PinValue::HighZ;
}
// Get values from strongest drivers
let strong_drivers: Vec<PinValue> = drivers
.iter()
.filter(|(_, strength)| *strength == max_strength)
.map(|(value, _)| *value)
.collect();
// Resolve conflicts: Low dominates
if strong_drivers.iter().any(|v| *v == PinValue::Low) {
PinValue::Low
} else if strong_drivers.iter().any(|v| *v == PinValue::High) {
PinValue::High
} else {
PinValue::HighZ
}
}
fn propagate_bus_value(&self) {
if !self.is_active {
return;
}
for pin in &self.connected_pins {
if let Ok(mut pin_guard) = pin.lock() {
// The bus acts as a driver for connected pins
pin_guard.set_driver_with_strength(
Some(self.base.get_name().to_string()),
self.bus_value,
DriveStrength::Standard,
);
}
}
}
pub fn simulate_bus_contention(&self) -> Result<(), String> {
// Simulate bus contention detection
let mut high_drivers = 0;
let mut low_drivers = 0;
for pin in &self.connected_pins {
if let Ok(pin_guard) = pin.lock() {
let value = pin_guard.read();
match value {
PinValue::High => high_drivers += 1,
PinValue::Low => low_drivers += 1,
PinValue::HighZ => {}
}
}
}
if high_drivers > 0 && low_drivers > 0 {
// Bus contention detected
Err("Bus contention: multiple drivers conflict".to_string())
} else {
Ok(())
}
}
}
impl Component for GenericBus {
fn name(&self) -> String {
self.base.name()
}
fn pins(&self) -> HashMap<String, Arc<Mutex<Pin>>> {
self.base.pins()
}
fn get_pin(&self, name: &str) -> Result<Arc<Mutex<Pin>>, String> {
self.base.get_pin(name)
}
fn update(&mut self) {
if !self.is_active {
return;
}
// Respect settlement timing
if self.last_update.elapsed() < self.settlement_time {
return;
}
let new_bus_value = self.read_bus_state();
if new_bus_value != self.bus_value {
self.bus_value = new_bus_value;
self.propagate_bus_value();
self.last_update = Instant::now();
}
// Check for bus contention (optional - could be expensive)
// if let Err(e) = self.simulate_bus_contention() {
// eprintln!("Bus contention warning: {}", e);
// }
}
fn run(&mut self) {
self.base.set_running(true);
while self.is_running() {
self.update();
thread::sleep(Duration::from_micros(1));
}
}
fn stop(&mut self) {
self.base.set_running(false);
self.set_active(false);
}
fn is_running(&self) -> bool {
self.base.is_running()
}
}
// Active bus implementation for test equipment
pub struct ActiveBus {
base: GenericBus,
test_pattern: Vec<PinValue>,
pattern_index: usize,
pattern_interval: Duration,
last_pattern_update: Instant,
}
impl ActiveBus {
pub fn new(name: String) -> Self {
ActiveBus {
base: GenericBus::new(name),
test_pattern: Vec::new(),
pattern_index: 0,
pattern_interval: Duration::from_millis(1),
last_pattern_update: Instant::now(),
}
}
pub fn set_test_pattern(&mut self, pattern: Vec<PinValue>) {
self.test_pattern = pattern;
self.pattern_index = 0;
}
pub fn set_pattern_interval(&mut self, interval: Duration) {
self.pattern_interval = interval;
}
pub fn drive_pattern(&mut self) {
if self.test_pattern.is_empty() {
return;
}
if self.last_pattern_update.elapsed() >= self.pattern_interval {
if let Some(data_pin) = self.base.get_pin("DATA").ok() {
if let Ok(mut pin_guard) = data_pin.lock() {
let value = self.test_pattern[self.pattern_index];
pin_guard.set_driver_with_strength(
Some(self.base.name() + "_pattern"),
value,
DriveStrength::Strong,
);
}
}
self.pattern_index = (self.pattern_index + 1) % self.test_pattern.len();
self.last_pattern_update = Instant::now();
}
}
}
impl Component for ActiveBus {
fn name(&self) -> String {
self.base.name()
}
fn pins(&self) -> HashMap<String, Arc<Mutex<Pin>>> {
self.base.pins()
}
fn get_pin(&self, name: &str) -> Result<Arc<Mutex<Pin>>, String> {
self.base.get_pin(name)
}
fn update(&mut self) {
self.base.update();
self.drive_pattern();
}
fn run(&mut self) {
self.base.base.set_running(true);
while self.is_running() {
self.update();
thread::sleep(Duration::from_micros(10));
}
}
fn stop(&mut self) {
self.base.stop();
}
fn is_running(&self) -> bool {
self.base.is_running()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bus_creation() {
let bus = GenericBus::new("TEST_BUS".to_string());
assert_eq!(bus.name(), "TEST_BUS");
assert!(!bus.is_running());
assert!(bus.is_active());
}
#[test]
fn test_bus_pin_connection() {
let mut bus = GenericBus::new("TEST_BUS".to_string());
let pin = Arc::new(Mutex::new(Pin::new("TEST_PIN".to_string())));
assert!(bus.connect_pin(pin.clone()).is_ok());
assert_eq!(bus.get_connected_pins().len(), 1);
// Try connecting same pin again
assert!(bus.connect_pin(pin.clone()).is_err());
}
#[test]
fn test_bus_value_propagation() {
let mut bus = GenericBus::new("TEST_BUS".to_string());
let pin1 = Arc::new(Mutex::new(Pin::new("PIN1".to_string())));
let pin2 = Arc::new(Mutex::new(Pin::new("PIN2".to_string())));
bus.connect_pin(pin1.clone()).unwrap();
bus.connect_pin(pin2.clone()).unwrap();
// Drive pin1 high
{
let mut pin_guard = pin1.lock().unwrap();
pin_guard.set_driver(Some("test".to_string()), PinValue::High);
}
// Update bus
bus.update();
// Pin2 should see the high value
let pin2_guard = pin2.lock().unwrap();
assert_eq!(pin2_guard.read(), PinValue::High);
}
#[test]
fn test_bus_contention_detection() {
let mut bus = GenericBus::new("TEST_BUS".to_string());
let pin1 = Arc::new(Mutex::new(Pin::new("PIN1".to_string())));
let pin2 = Arc::new(Mutex::new(Pin::new("PIN2".to_string())));
bus.connect_pin(pin1.clone()).unwrap();
bus.connect_pin(pin2.clone()).unwrap();
// Drive pins to conflicting states
{
let mut pin1_guard = pin1.lock().unwrap();
pin1_guard.set_driver(Some("driver1".to_string()), PinValue::High);
}
{
let mut pin2_guard = pin2.lock().unwrap();
pin2_guard.set_driver(Some("driver2".to_string()), PinValue::Low);
}
// Should detect bus contention
assert!(bus.simulate_bus_contention().is_err());
}
#[test]
fn test_active_bus_pattern() {
let mut bus = ActiveBus::new("ACTIVE_BUS".to_string());
// Give the thread a moment to start
thread::sleep(Duration::from_millis(10));
let pattern = vec![PinValue::High, PinValue::Low, PinValue::High, PinValue::Low];
bus.set_test_pattern(pattern);
// Give the thread a moment to start
thread::sleep(Duration::from_millis(100));
assert!(!bus.is_running());
}
}

View file

@ -47,12 +47,24 @@ All components implement the base `Component` trait defined in `src/component.rs
- **File**: `intel_4001.rs`
- **Features**:
- 256 bytes of mask-programmable ROM
- 4-bit I/O ports for peripheral interface
- 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
##### I/O Port Features
- **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
#### Intel 4002 RAM
- **File**: `intel_4002.rs`
@ -128,8 +140,6 @@ fn is_running(&self) -> bool; // Execution status
Components can implement additional traits for specialized functionality:
- **RunnableComponent**: Automatic thread spawning
- **CPU**: Processor-specific operations
- **Memory**: Storage-specific operations
## Pin Configuration
@ -205,7 +215,7 @@ connect_pins(vec![cpu_pin, rom_pin, ram_pin]) ?;
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 Traits**: Implement CPU, Memory, or other specialized traits as needed
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

View file

@ -12,7 +12,6 @@ pub struct GenericClock {
duty_cycle: f64, // Duty cycle as percentage (0.0 to 1.0)
current_state: PinValue,
last_transition: Instant,
half_period: Duration,
high_time: Duration,
low_time: Duration,
enabled: bool,
@ -23,17 +22,14 @@ impl GenericClock {
let pin_names = vec!["CLK", "ENABLE"];
let pins = BaseComponent::create_pin_map(&pin_names, &name);
let half_period = Self::frequency_to_duration(frequency) / 2;
let mut clock = GenericClock {
base: BaseComponent::new(name, pins),
frequency,
duty_cycle: 0.5, // 50% duty cycle by default
current_state: PinValue::Low,
last_transition: Instant::now(),
half_period,
high_time: half_period, // Will be recalculated in set_duty_cycle
low_time: half_period, // Will be recalculated in set_duty_cycle
high_time: Duration::from_secs_f64(0.5 / frequency), // Will be recalculated in set_duty_cycle
low_time: Duration::from_secs_f64(0.5 / frequency), // Will be recalculated in set_duty_cycle
enabled: true,
};
@ -41,30 +37,11 @@ impl GenericClock {
clock
}
pub fn with_duty_cycle(mut self, duty_cycle: f64) -> Self {
self.set_duty_cycle(duty_cycle);
self
}
pub fn set_frequency(&mut self, frequency: f64) {
self.frequency = frequency;
self.half_period = Self::frequency_to_duration(frequency) / 2;
self.update_timing();
}
pub fn get_frequency(&self) -> f64 {
self.frequency
}
pub fn set_duty_cycle(&mut self, duty_cycle: f64) {
self.duty_cycle = duty_cycle.clamp(0.1, 0.9); // Keep within reasonable bounds
self.update_timing();
}
pub fn get_duty_cycle(&self) -> f64 {
self.duty_cycle
}
pub fn enable(&mut self) {
self.enabled = true;
// Start with known state when enabled
@ -78,16 +55,6 @@ impl GenericClock {
self.set_clock_output(PinValue::Low);
}
pub fn is_enabled(&self) -> bool {
self.enabled
}
pub fn reset(&mut self) {
self.current_state = PinValue::Low;
self.last_transition = Instant::now();
self.set_clock_output(PinValue::Low);
}
fn frequency_to_duration(frequency: f64) -> Duration {
if frequency <= 0.0 {
Duration::from_secs(0)
@ -207,48 +174,7 @@ impl Component for GenericClock {
}
// Advanced clock features
impl GenericClock {
pub fn generate_single_pulse(&mut self, width: Duration) {
self.disable(); // Stop regular clock
self.set_clock_output(PinValue::High);
// Spawn a thread to end the pulse after specified width
let clock_name = self.base.get_name().to_string();
let clock_pin = self.base.get_pin("CLK").unwrap().clone();
thread::spawn(move || {
thread::sleep(width);
if let Ok(mut pin_guard) = clock_pin.lock() {
pin_guard.set_driver(Some(clock_name), PinValue::Low);
}
});
}
pub fn generate_clock_burst(&mut self, count: usize) {
self.disable(); // Stop regular clock
let clock_name = self.base.get_name().to_string();
let clock_pin = self.base.get_pin("CLK").unwrap().clone();
let half_period = self.half_period;
thread::spawn(move || {
for i in 0..count * 2 {
// Each cycle has two transitions
let state = if i % 2 == 0 {
PinValue::High
} else {
PinValue::Low
};
if let Ok(mut pin_guard) = clock_pin.lock() {
pin_guard.set_driver(Some(clock_name.clone()), state);
}
thread::sleep(half_period);
}
});
}
}
impl GenericClock {}
#[cfg(test)]
mod tests {
@ -259,44 +185,9 @@ mod tests {
fn test_clock_creation() {
let clock = GenericClock::new("TEST_CLK".to_string(), 1_000_000.0); // 1MHz
assert_eq!(clock.name(), "TEST_CLK");
assert_eq!(clock.get_frequency(), 1_000_000.0);
assert_eq!(clock.get_duty_cycle(), 0.5);
assert!(!clock.is_running());
}
#[test]
fn test_clock_frequency_change() {
let mut clock = GenericClock::new("TEST_CLK".to_string(), 1_000_000.0);
clock.set_frequency(2_000_000.0);
assert_eq!(clock.get_frequency(), 2_000_000.0);
}
#[test]
fn test_clock_duty_cycle() {
let mut clock = GenericClock::new("TEST_CLK".to_string(), 1_000_000.0);
clock.set_duty_cycle(0.25);
assert_eq!(clock.get_duty_cycle(), 0.25);
// Test clamping
clock.set_duty_cycle(1.5);
assert_eq!(clock.get_duty_cycle(), 0.9);
clock.set_duty_cycle(-0.5);
assert_eq!(clock.get_duty_cycle(), 0.1);
}
#[test]
fn test_clock_enable_disable() {
let mut clock = GenericClock::new("TEST_CLK".to_string(), 1_000_000.0);
assert!(clock.is_enabled());
clock.disable();
assert!(!clock.is_enabled());
clock.enable();
assert!(clock.is_enabled());
}
#[test]
fn test_clock_timing_calculation() {
let _clock = GenericClock::new("TEST_CLK".to_string(), 1.0); // 1Hz
@ -306,18 +197,4 @@ mod tests {
let high_freq = GenericClock::frequency_to_duration(1_000_000.0); // 1MHz
assert_eq!(high_freq, Duration::from_micros(1));
}
#[test]
fn test_single_pulse() {
let mut clock = GenericClock::new("PULSE_CLK".to_string(), 1_000_000.0);
clock.generate_single_pulse(Duration::from_millis(10));
// Note: In a real test, you'd want to verify the pin state
}
#[test]
fn test_clock_burst() {
let mut clock = GenericClock::new("BURST_CLK".to_string(), 1_000_000.0);
clock.generate_clock_burst(5); // 5 clock cycles
// Note: In a real test, you'd want to verify the pin states
}
}

View file

@ -1,145 +0,0 @@
use crate::component::Component;
use crate::components::cpu::mos_6502::MOS6502;
use crate::components::cpu::WDC65C02;
pub trait Registers {}
pub trait CPU: Component {
fn reset(&mut self);
fn execute_instruction(&mut self);
fn get_registers(&self) -> &dyn Registers;
fn get_registers_mut(&mut self) -> &mut dyn Registers;
fn read_memory(&self, address: u16) -> u8;
fn write_memory(&mut self, address: u16, value: u8);
}
pub trait MOS6502Family: CPU {
// Common 6502 family methods
fn lda(&mut self, value: u8);
fn sta(&mut self, address: u16);
fn tax(&mut self);
// More common instructions...
}
pub trait CMOS65C02Extensions: MOS6502Family {
// 65C02-specific methods
fn bra(&mut self, address: u16);
fn phx(&mut self);
fn ply(&mut self);
fn stp(&mut self);
fn wai(&mut self);
}
impl CPU for MOS6502 {
fn reset(&mut self) {
todo!()
}
fn execute_instruction(&mut self) {
todo!()
}
fn get_registers(&self) -> &dyn Registers {
todo!()
}
fn get_registers_mut(&mut self) -> &mut dyn Registers {
todo!()
}
fn read_memory(&self, _address: u16) -> u8 {
todo!()
}
fn write_memory(&mut self, _address: u16, _value: u8) {
todo!()
}
}
// Implement for both MOS6502 and CMOS65C02
impl MOS6502Family for MOS6502 {
fn lda(&mut self, _value: u8) {
// Load accumulator with value - this is a placeholder implementation
// In a real 6502, this would load the accumulator register with the value
// For now, we'll just store it in a field if available, or do nothing
// This prevents infinite recursion while providing a valid implementation
}
fn sta(&mut self, _address: u16) {
todo!()
}
fn tax(&mut self) {
todo!()
}
// Implement other methods...
}
impl CPU for WDC65C02 {
fn reset(&mut self) {
todo!()
}
fn execute_instruction(&mut self) {
todo!()
}
fn get_registers(&self) -> &dyn Registers {
todo!()
}
fn get_registers_mut(&mut self) -> &mut dyn Registers {
todo!()
}
fn read_memory(&self, _address: u16) -> u8 {
todo!()
}
fn write_memory(&mut self, _address: u16, _value: u8) {
todo!()
}
}
impl MOS6502Family for WDC65C02 {
fn lda(&mut self, value: u8) {
self.base.lda(value)
}
fn sta(&mut self, _address: u16) {
todo!()
}
fn tax(&mut self) {
todo!()
}
// Implement other methods...
}
impl CMOS65C02Extensions for WDC65C02 {
fn bra(&mut self, _address: u16) {
// Branch always - this is a placeholder implementation
// In a real 65C02, this would branch to the specified address
// For now, we'll just do nothing to prevent infinite recursion
}
fn phx(&mut self) {
todo!()
}
fn ply(&mut self) {
todo!()
}
fn stp(&mut self) {
todo!()
}
fn wai(&mut self) {
todo!()
}
// Implement other 65C02-specific methods...
}

View file

@ -17,14 +17,6 @@ enum InstructionPhase {
Wait, // Waiting for external operations
}
/// Represents the current phase of the two-phase clock cycle
/// The 4004 uses a two-phase clock for synchronization with peripherals
#[derive(Debug, Clone, Copy, PartialEq)]
enum ClockPhase {
Phase1, // First clock phase - CPU drives bus
Phase2, // Second clock phase - Peripherals drive bus
}
/// Memory operation state machine states
/// Tracks the current phase of memory access operations
#[derive(Debug, Clone, Copy, PartialEq)]
@ -118,8 +110,6 @@ pub struct Intel4004 {
address_latch: u8, // Latched address for memory operations
data_latch: u8, // Latched data for memory operations
clock_speed: f64, // Target clock speed in Hz
last_clock_transition: Instant, // Timestamp of last clock transition
clock_phase: ClockPhase, // Current clock phase
rom_port: u8, // Currently selected ROM port (0-15)
ram_bank: u8, // Currently selected RAM bank (0-7)
@ -134,9 +124,7 @@ pub struct Intel4004 {
full_address_ready: bool, // Whether complete address is assembled
// Instruction execution state
current_op: Instruction, // Currently decoded instruction
operand_latch: u8, // Latched operand for multi-byte instructions
jump_address: Option<u16>, // Jump target address for conditional jumps
current_op: Instruction, // Currently decoded instruction
// Timing and synchronization
address_latch_time: Option<Instant>, // Timestamp when address was latched
@ -175,8 +163,6 @@ impl Intel4004 {
address_latch: 0,
data_latch: 0,
clock_speed,
last_clock_transition: Instant::now(),
clock_phase: ClockPhase::Phase1,
rom_port: 0,
ram_bank: 0,
@ -192,8 +178,6 @@ impl Intel4004 {
// Instruction execution state
current_op: Instruction::Invalid,
operand_latch: 0,
jump_address: None,
// Timing and synchronization
address_latch_time: None,
@ -392,37 +376,6 @@ impl Intel4004 {
(phi1, phi2)
}
/// Check for Φ1 rising edge
fn is_phi1_rising_edge(&self) -> bool {
let (phi1, _) = self.read_clock_pins();
phi1 == PinValue::High && self.prev_phi1 == PinValue::Low
}
/// Check for Φ1 falling edge
fn is_phi1_falling_edge(&self) -> bool {
let (phi1, _) = self.read_clock_pins();
phi1 == PinValue::Low && self.prev_phi1 == PinValue::High
}
/// Check for Φ2 rising edge
fn is_phi2_rising_edge(&self) -> bool {
let (_, phi2) = self.read_clock_pins();
phi2 == PinValue::High && self.prev_phi2 == PinValue::Low
}
/// Check for Φ2 falling edge
fn is_phi2_falling_edge(&self) -> bool {
let (_, phi2) = self.read_clock_pins();
phi2 == PinValue::Low && self.prev_phi2 == PinValue::High
}
/// Update clock state tracking for edge detection
fn update_clock_states(&mut self) {
let (phi1, phi2) = self.read_clock_pins();
self.prev_phi1 = phi1;
self.prev_phi2 = phi2;
}
/// Handle Φ1 rising edge - Address and control phase
/// Hardware: Φ1 high = CPU drives bus with address/control information
/// Memory operations start when SYNC goes high during Φ1 rising edge
@ -631,13 +584,6 @@ impl Intel4004 {
self.full_address_ready = false;
}
/// Handle clock cycle processing
/// Updates cycle count and manages clock phase transitions
fn handle_clock(&mut self) {
// Simple clock simulation for now
self.cycle_count += 1;
}
/// Decode an instruction byte into an Instruction enum
/// Parameters: opcode - 8-bit instruction opcode
/// Returns: Decoded instruction

View file

@ -1,5 +1,4 @@
// CPU components module
mod cpu_traits;
pub mod intel_4004;
pub mod mos_6502;
pub mod wdc_65c02;

View file

@ -1,405 +0,0 @@
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::{Duration, Instant};
use crate::component::{BaseComponent, Component};
use crate::pin::{Pin, PinValue};
pub struct GenericRam {
base: BaseComponent,
memory: Vec<u8>,
address_width: usize,
data_width: usize,
last_address: u32,
last_operation: RamOperation,
access_time: Duration,
last_access: Instant,
write_enable: bool,
output_enable: bool,
chip_select: bool,
}
#[derive(Debug, Clone, Copy, PartialEq)]
enum RamOperation {
Read,
Write,
Idle,
}
impl GenericRam {
pub fn new(name: String, size: usize, address_width: usize, data_width: usize) -> Self {
let mut pin_names = Vec::new();
// Address pins (A0, A1, A2, ...)
for i in 0..address_width {
pin_names.push(format!("A{}", i));
}
// Data pins (D0, D1, D2, ...) - bidirectional
for i in 0..data_width {
let name = format!("D{}", i);
pin_names.push(name.clone());
}
// Control pins
pin_names.push("CS".parse().unwrap()); // Chip Select
pin_names.push("WE".parse().unwrap()); // Write Enable
pin_names.push("OE".parse().unwrap()); // Output Enable
let pin_refs: Vec<&str> = pin_names.iter().map(|s| s.as_str()).collect();
let pins = BaseComponent::create_pin_map(&pin_refs, &name);
let memory = vec![0u8; size];
GenericRam {
base: BaseComponent::new(name, pins),
memory,
address_width,
data_width,
last_address: 0,
last_operation: RamOperation::Idle,
access_time: Duration::from_nanos(100), // 100ns access time
last_access: Instant::now(),
write_enable: false,
output_enable: false,
chip_select: false,
}
}
pub fn load_data(&mut self, data: Vec<u8>, offset: usize) -> Result<(), String> {
if offset + data.len() > self.memory.len() {
return Err(format!(
"Data exceeds RAM capacity: offset {} + data length {} > RAM size {}",
offset,
data.len(),
self.memory.len()
));
}
self.memory[offset..offset + data.len()].copy_from_slice(&data);
Ok(())
}
pub fn get_memory_size(&self) -> usize {
self.memory.len()
}
pub fn read_memory(&self, address: usize, length: usize) -> Result<Vec<u8>, String> {
if address + length > self.memory.len() {
return Err("Address range out of bounds".to_string());
}
Ok(self.memory[address..address + length].to_vec())
}
pub fn write_memory(&mut self, address: usize, data: &[u8]) -> Result<(), String> {
if address + data.len() > self.memory.len() {
return Err("Address range out of bounds".to_string());
}
self.memory[address..address + data.len()].copy_from_slice(data);
Ok(())
}
pub fn get_access_time(&self) -> Duration {
self.access_time
}
pub fn set_access_time(&mut self, access_time: Duration) {
self.access_time = access_time;
}
fn read_address(&self) -> u32 {
let mut address = 0;
for i in 0..self.address_width {
if let Ok(pin) = self.base.get_pin(&format!("A{}", i)) {
if let Ok(pin_guard) = pin.lock() {
if pin_guard.read() == PinValue::High {
address |= 1 << i;
}
}
}
}
address
}
fn read_data_bus(&self) -> u8 {
let mut data = 0;
for i in 0..self.data_width {
if let Ok(pin) = self.base.get_pin(&format!("D{}", i)) {
if let Ok(pin_guard) = pin.lock() {
if pin_guard.read() == PinValue::High {
data |= 1 << i;
}
}
}
}
data
}
fn write_data_bus(&self, data: u8) {
for i in 0..self.data_width {
if let Ok(pin) = self.base.get_pin(&format!("D{}", i)) {
if let Ok(mut pin_guard) = pin.lock() {
let bit_value = (data >> i) & 1;
let pin_value = if bit_value == 1 {
PinValue::High
} else {
PinValue::Low
};
pin_guard.set_driver(Some(self.base.get_name().parse().unwrap()), pin_value);
}
}
}
}
fn read_control_pins(&mut self) {
// Read Chip Select (active low)
self.chip_select = if let Ok(cs_pin) = self.base.get_pin("CS") {
if let Ok(cs_guard) = cs_pin.lock() {
cs_guard.read() == PinValue::Low
} else {
false
}
} else {
false
};
// Read Write Enable (active low)
self.write_enable = if let Ok(we_pin) = self.base.get_pin("WE") {
if let Ok(we_guard) = we_pin.lock() {
we_guard.read() == PinValue::Low
} else {
false
}
} else {
false
};
// Read Output Enable (active low)
self.output_enable = if let Ok(oe_pin) = self.base.get_pin("OE") {
if let Ok(oe_guard) = oe_pin.lock() {
oe_guard.read() == PinValue::Low
} else {
false
}
} else {
false
};
}
fn tri_state_data_bus(&self) {
for i in 0..self.data_width {
if let Ok(pin) = self.base.get_pin(&format!("D{}", i)) {
if let Ok(mut pin_guard) = pin.lock() {
pin_guard
.set_driver(Some(self.base.get_name().parse().unwrap()), PinValue::HighZ);
}
}
}
}
fn perform_read_operation(&mut self, address: u32) {
if (address as usize) < self.memory.len() {
let data = self.memory[address as usize];
self.write_data_bus(data);
self.last_operation = RamOperation::Read;
} else {
// Address out of bounds - tri-state
self.tri_state_data_bus();
}
}
fn perform_write_operation(&mut self, address: u32) {
if (address as usize) < self.memory.len() {
let data = self.read_data_bus();
self.memory[address as usize] = data;
self.last_operation = RamOperation::Write;
}
// During write, RAM doesn't drive the data bus
self.tri_state_data_bus();
}
}
impl Component for GenericRam {
fn name(&self) -> String {
self.base.name()
}
fn pins(&self) -> HashMap<String, Arc<Mutex<Pin>>> {
self.base.pins()
}
fn get_pin(&self, name: &str) -> Result<Arc<Mutex<Pin>>, String> {
self.base.get_pin(name)
}
fn update(&mut self) {
// Respect access timing
if self.last_access.elapsed() < self.access_time {
return;
}
self.read_control_pins();
if !self.chip_select {
// Chip not selected - tri-state all outputs
self.tri_state_data_bus();
self.last_operation = RamOperation::Idle;
return;
}
let current_address = self.read_address();
let address_changed = current_address != self.last_address;
// Determine operation based on control pins
if self.write_enable {
// Write operation (WE low)
self.perform_write_operation(current_address);
} else if self.output_enable {
// Read operation (OE low, WE high)
if address_changed || self.last_operation != RamOperation::Read {
self.perform_read_operation(current_address);
}
} else {
// Output disabled - tri-state data bus
self.tri_state_data_bus();
self.last_operation = RamOperation::Idle;
}
self.last_address = current_address;
self.last_access = Instant::now();
}
fn run(&mut self) {
self.base.set_running(true);
while self.is_running() {
self.update();
thread::sleep(Duration::from_micros(10));
}
}
fn stop(&mut self) {
self.base.set_running(false);
// Tri-state all data pins when stopped
self.tri_state_data_bus();
}
fn is_running(&self) -> bool {
self.base.is_running()
}
}
// Additional utility methods
impl GenericRam {
pub fn clear_memory(&mut self) {
for byte in &mut self.memory {
*byte = 0;
}
}
pub fn fill_memory(&mut self, value: u8) {
for byte in &mut self.memory {
*byte = value;
}
}
pub fn get_memory_snapshot(&self) -> Vec<u8> {
self.memory.clone()
}
pub fn restore_memory_snapshot(&mut self, snapshot: Vec<u8>) -> Result<(), String> {
if snapshot.len() != self.memory.len() {
return Err("Snapshot size doesn't match RAM size".to_string());
}
self.memory.copy_from_slice(&snapshot);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ram_creation() {
let ram = GenericRam::new("TEST_RAM".to_string(), 1024, 10, 8);
assert_eq!(ram.name(), "TEST_RAM");
assert_eq!(ram.memory.len(), 1024);
assert_eq!(ram.address_width, 10);
assert_eq!(ram.data_width, 8);
}
#[test]
fn test_ram_data_loading() {
let mut ram = GenericRam::new("TEST_RAM".to_string(), 256, 8, 8);
let test_data = vec![0x12, 0x34, 0x56, 0x78];
assert!(ram.load_data(test_data.clone(), 0).is_ok());
assert_eq!(ram.memory[0], 0x12);
assert_eq!(ram.memory[1], 0x34);
assert_eq!(ram.memory[2], 0x56);
assert_eq!(ram.memory[3], 0x78);
}
#[test]
fn test_ram_read_write() {
let mut ram = GenericRam::new("TEST_RAM".to_string(), 256, 8, 8);
// Test direct memory access
assert!(ram.write_memory(0x10, &[0xAA, 0xBB, 0xCC]).is_ok());
let read_data = ram.read_memory(0x10, 3).unwrap();
assert_eq!(read_data, vec![0xAA, 0xBB, 0xCC]);
}
#[test]
fn test_ram_clear_and_fill() {
let mut ram = GenericRam::new("TEST_RAM".to_string(), 256, 8, 8);
// Fill with pattern
ram.fill_memory(0x55);
assert_eq!(ram.memory[0], 0x55);
assert_eq!(ram.memory[100], 0x55);
// Clear memory
ram.clear_memory();
assert_eq!(ram.memory[0], 0);
assert_eq!(ram.memory[100], 0);
}
#[test]
fn test_ram_snapshot() {
let mut ram = GenericRam::new("TEST_RAM".to_string(), 256, 8, 8);
// Write some data
ram.write_memory(0, &[0x11, 0x22, 0x33]).unwrap();
// Take snapshot
let snapshot = ram.get_memory_snapshot();
// Modify memory
ram.write_memory(0, &[0x44, 0x55, 0x66]).unwrap();
// Restore snapshot
ram.restore_memory_snapshot(snapshot).unwrap();
// Verify restoration
let read_data = ram.read_memory(0, 3).unwrap();
assert_eq!(read_data, vec![0x11, 0x22, 0x33]);
}
#[test]
fn test_ram_is_running() {
let ram = GenericRam::new("TEST_RAM".to_string(), 256, 8, 8);
assert!(!ram.is_running());
}
}

View file

@ -1,365 +0,0 @@
use std::collections::HashMap;
use std::fs::File;
use std::io::Read;
use std::path::Path;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::{Duration, Instant};
use crate::component::{BaseComponent, Component};
use crate::pin::{Pin, PinValue};
pub struct GenericRom {
pub(crate) base: BaseComponent,
memory: Vec<u8>,
address_width: usize,
data_width: usize,
last_address: u32,
access_time: Duration,
last_access: Instant,
}
impl GenericRom {
pub fn new(name: String, size: usize, address_width: usize, data_width: usize) -> Self {
let mut pins = HashMap::new();
// Create address pins (A0, A1, A2, ...)
for i in 0..address_width {
pins.insert(
format!("A{}", i),
Arc::new(Mutex::new(Pin::new(format!("{}_A{}", name, i)))),
);
}
// Create data pins (D0, D1, D2, ...)
for i in 0..data_width {
pins.insert(
format!("D{}", i),
Arc::new(Mutex::new(Pin::new(format!("{}_D{}", name, i)))),
);
}
// Control pins
pins.insert(
"CS".to_string(),
Arc::new(Mutex::new(Pin::new(format!("{}_CS", name)))),
);
pins.insert(
"OE".to_string(),
Arc::new(Mutex::new(Pin::new(format!("{}_OE", name)))),
);
let memory = vec![0u8; size];
GenericRom {
base: BaseComponent::new(name, pins),
memory,
address_width,
data_width,
last_address: 0,
access_time: Duration::from_nanos(100), // 100ns access time
last_access: Instant::now(),
}
}
pub fn load_data(&mut self, data: Vec<u8>, offset: usize) -> Result<(), String> {
if offset + data.len() > self.memory.len() {
return Err("Data exceeds ROM capacity".to_string());
}
self.memory[offset..offset + data.len()].copy_from_slice(&data);
Ok(())
}
pub fn load_from_hex(&mut self, hex_data: &str, offset: usize) -> Result<(), String> {
let bytes: Result<Vec<u8>, _> = hex_data
.split_whitespace()
.map(|s| u8::from_str_radix(s, 16))
.collect();
match bytes {
Ok(data) => self.load_data(data, offset),
Err(_) => Err("Invalid hex data".to_string()),
}
}
pub fn load_from_binary_file<P: AsRef<Path>>(
&mut self,
path: P,
offset: usize,
) -> Result<(), String> {
let path_ref = path.as_ref();
// Check if file exists
if !path_ref.exists() {
return Err(format!("File not found: {}", path_ref.display()));
}
// Check if offset is within bounds
if offset >= self.memory.len() {
return Err(format!(
"Offset {} exceeds ROM size {}",
offset,
self.memory.len()
));
}
let mut file = File::open(path_ref).map_err(|e| format!("Failed to open file: {}", e))?;
// Get file size
let metadata = file
.metadata()
.map_err(|e| format!("Failed to get file metadata: {}", e))?;
let file_size = metadata.len() as usize;
// Check if file fits in ROM
if offset + file_size > self.memory.len() {
return Err(format!(
"File too large: offset {} + file size {} > ROM size {}",
offset,
file_size,
self.memory.len()
));
}
// Read file into buffer
let mut buffer = vec![0u8; file_size];
file.read_exact(&mut buffer)
.map_err(|e| format!("Failed to read file: {}", e))?;
// Copy into ROM memory
self.memory[offset..offset + file_size].copy_from_slice(&buffer);
Ok(())
}
pub fn load_from_binary(&mut self, data: &[u8], offset: usize) -> Result<(), String> {
if offset + data.len() > self.memory.len() {
return Err(format!(
"Data exceeds ROM capacity: offset {} + data length {} > ROM size {}",
offset,
data.len(),
self.memory.len()
));
}
self.memory[offset..offset + data.len()].copy_from_slice(data);
Ok(())
}
fn read_address(&self) -> u32 {
let mut address = 0;
for i in 0..self.address_width {
if let Ok(pin) = self.base.get_pin(&format!("A{}", i)) {
if let Ok(pin_guard) = pin.lock() {
if pin_guard.read() == PinValue::High {
address |= 1 << i;
}
}
}
}
address
}
pub(crate) fn is_selected(&self) -> bool {
// Check Chip Select (active low by convention)
if let Ok(cs_pin) = self.base.get_pin("CS") {
if let Ok(cs_guard) = cs_pin.lock() {
if cs_guard.read() == PinValue::High {
return false; // Not selected
}
}
}
true // Selected when CS is low or not connected
}
fn output_enabled(&self) -> bool {
// Check Output Enable (active low by convention)
if let Ok(oe_pin) = self.base.get_pin("OE") {
if let Ok(oe_guard) = oe_pin.lock() {
if oe_guard.read() == PinValue::High {
return false; // Output disabled
}
}
}
true // Enabled when OE is low or not connected
}
pub(crate) fn output_data(&self, data: u8) {
// Only drive data pins if selected and output enabled
if !self.is_selected() || !self.output_enabled() {
// Tri-state the outputs
for i in 0..self.data_width {
if let Ok(pin) = self.base.get_pin(&format!("D{}", i)) {
if let Ok(mut pin_guard) = pin.lock() {
pin_guard.set_driver(
Some(self.base.get_name().parse().unwrap()),
PinValue::HighZ,
);
}
}
}
return;
}
// Drive data pins according to the data value
for i in 0..self.data_width {
if let Ok(pin) = self.base.get_pin(&format!("D{}", i)) {
if let Ok(mut pin_guard) = pin.lock() {
let bit_value = (data >> i) & 1;
let pin_value = if bit_value == 1 {
PinValue::High
} else {
PinValue::Low
};
pin_guard.set_driver(Some(self.base.get_name().parse().unwrap()), pin_value);
}
}
}
}
}
impl Component for GenericRom {
fn name(&self) -> String {
self.base.name()
}
fn pins(&self) -> HashMap<String, Arc<Mutex<Pin>>> {
self.base.pins()
}
fn get_pin(&self, name: &str) -> Result<Arc<Mutex<Pin>>, String> {
self.base.get_pin(name)
}
fn update(&mut self) {
// Respect access timing
if self.last_access.elapsed() < self.access_time {
return;
}
let current_address = self.read_address();
// Only process if address changed or we need to update outputs
if current_address != self.last_address || !self.is_selected() || !self.output_enabled() {
if self.is_selected() && self.output_enabled() {
// Read from memory (handle address bounds)
if (current_address as usize) < self.memory.len() {
let data = self.memory[current_address as usize];
self.output_data(data);
} else {
// Address out of bounds - tri-state outputs
for i in 0..self.data_width {
if let Ok(pin) = self.base.get_pin(&format!("D{}", i)) {
if let Ok(mut pin_guard) = pin.lock() {
pin_guard.set_driver(
Some(self.base.get_name().parse().unwrap()),
PinValue::HighZ,
);
}
}
}
}
} else {
// Not selected or output disabled - tri-state outputs
for i in 0..self.data_width {
if let Ok(pin) = self.base.get_pin(&format!("D{}", i)) {
if let Ok(mut pin_guard) = pin.lock() {
pin_guard.set_driver(
Some(self.base.get_name().parse().unwrap()),
PinValue::HighZ,
);
}
}
}
}
self.last_address = current_address;
self.last_access = Instant::now();
}
}
fn run(&mut self) {
self.base.set_running(true);
while self.base.is_running() {
self.update();
thread::sleep(Duration::from_micros(10)); // Small delay to prevent busy waiting
}
}
fn stop(&mut self) {
self.base.set_running(false);
// Tri-state all outputs when stopped
for i in 0..self.data_width {
if let Ok(pin) = self.base.get_pin(&format!("D{}", i)) {
if let Ok(mut pin_guard) = pin.lock() {
pin_guard
.set_driver(Some(self.base.get_name().parse().unwrap()), PinValue::HighZ);
}
}
}
}
fn is_running(&self) -> bool {
self.base.is_running()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::NamedTempFile;
#[test]
fn test_rom_creation() {
let rom = GenericRom::new("TEST_ROM".to_string(), 1024, 10, 8);
assert_eq!(rom.name(), "TEST_ROM");
assert_eq!(rom.memory.len(), 1024);
}
#[test]
fn test_rom_file_loading() {
let mut rom = GenericRom::new("TEST_ROM".to_string(), 256, 8, 8);
// Create a temporary file with test data
let test_data = vec![0x12, 0x34, 0x56, 0x78];
let temp_file = NamedTempFile::new().unwrap();
fs::write(temp_file.path(), &test_data).unwrap();
assert!(rom.load_from_binary_file(temp_file.path(), 0).is_ok());
assert_eq!(rom.memory[0], 0x12);
assert_eq!(rom.memory[1], 0x34);
assert_eq!(rom.memory[2], 0x56);
assert_eq!(rom.memory[3], 0x78);
}
#[test]
fn test_rom_data_loading() {
let mut rom = GenericRom::new("TEST_ROM".to_string(), 256, 8, 8);
let test_data = vec![0x12, 0x34, 0x56, 0x78];
assert!(rom.load_data(test_data.clone(), 0).is_ok());
assert_eq!(rom.memory[0], 0x12);
assert_eq!(rom.memory[1], 0x34);
assert_eq!(rom.memory[2], 0x56);
assert_eq!(rom.memory[3], 0x78);
}
#[test]
fn test_rom_hex_loading() {
let mut rom = GenericRom::new("TEST_ROM".to_string(), 256, 8, 8);
assert!(rom.load_from_hex("12 34 56 78", 0).is_ok());
assert_eq!(rom.memory[0], 0x12);
assert_eq!(rom.memory[1], 0x34);
assert_eq!(rom.memory[2], 0x56);
assert_eq!(rom.memory[3], 0x78);
}
}

View file

@ -20,12 +20,15 @@ use crate::pin::{Pin, PinValue};
/// - SYNC logic simplified for unit testing (may need refinement for CPU integration)
pub struct Intel4001 {
base: BaseComponent,
memory: Vec<u8>, // 256-byte ROM storage
last_address: u16, // Last accessed memory address
access_time: Duration, // ROM access latency (500ns)
output_latch: u8, // 4-bit output latch for I/O operations
input_latch: u8, // 4-bit input latch for I/O operations
io_mode: IoMode, // Current I/O mode configuration
memory: Vec<u8>, // 256-byte ROM storage
last_address: u16, // Last accessed memory address
access_time: Duration, // ROM access latency (500ns)
output_latch: u8, // 4-bit output latch for I/O operations
input_latch: u8, // 4-bit input latch for I/O operations
io_mode: IoMode, // Current I/O mode configuration
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)
// Clock edge detection
prev_phi1: PinValue, // Previous Φ1 clock state for edge detection
prev_phi2: PinValue, // Previous Φ2 clock state for edge detection
@ -42,12 +45,19 @@ pub struct Intel4001 {
#[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
enum IoMode {
pub enum IoMode {
Input, // I/O pins configured as inputs (RDM instruction)
Output, // I/O pins configured as outputs (WRM instruction)
Bidirectional, // I/O pins bidirectional (not used in 4001)
}
/// I/O direction for each I/O port
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum IoDirection {
Input, // Port configured as input
Output, // Port configured as output
}
impl Intel400xClockHandling for Intel4001 {
fn get_base(&self) -> &BaseComponent {
&self.base
@ -85,6 +95,13 @@ impl Intel400xResetHandling for Intel4001 {
self.address_low_nibble = None;
self.address_high_nibble = None;
self.full_address_ready = false;
// 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();
}
}
@ -182,6 +199,9 @@ impl Intel4001 {
output_latch: 0,
input_latch: 0,
io_mode: IoMode::Input,
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
prev_phi1: PinValue::Low,
prev_phi2: PinValue::Low,
address_latch_time: None,
@ -254,22 +274,6 @@ impl Intel4001 {
data
}
fn write_io_pins(&self, data: u8) {
for i in 0..4 {
if let Ok(pin) = self.base.get_pin(&format!("IO{}", i)) {
if let Ok(mut pin_guard) = pin.lock() {
let bit_value = (data >> i) & 1;
let pin_value = if bit_value == 1 {
PinValue::High
} else {
PinValue::Low
};
pin_guard.set_driver(Some(self.base.name()), pin_value);
}
}
}
}
/// Handle Φ1 rising edge - Address and control phase
/// Hardware: Φ1 high = CPU drives bus with address/control information
/// Memory operations start when SYNC goes high during Φ1 rising edge
@ -285,16 +289,19 @@ impl Intel4001 {
// I/O access: CM=1 (chip_select), CI=1 (io_select)
let sync = self.read_sync_pin();
let chip_select = self.read_cm_rom_pin();
let io_select = self.read_reset_pin(); // Using reset pin as CI for now
let io_select = self.read_ci_pin();
if sync && chip_select && !io_select {
// Start memory address phase on Φ1 rising edge
// Start memory address phase on Φ1 rising edge (ROM access)
self.start_memory_address_phase();
}
// Handle I/O operations (higher priority than memory operations)
// I/O operations: CM=1 (chip_select), CI=1 (io_select)
// Note: SYNC is NOT required for I/O - only marks instruction fetch (ROM access)
self.handle_io_operation();
if chip_select && io_select {
self.handle_io_operation();
}
// Handle memory address phase operations during Φ1
self.handle_memory_address_operations();
@ -334,33 +341,139 @@ impl Intel4001 {
}
}
/// Handle I/O port operations
/// Set I/O mode for WRM/RDM instruction handling
/// Parameters: mode - I/O mode (Input for RDM, Output for WRM)
pub fn set_io_mode(&mut self, mode: IoMode) {
self.io_mode = mode;
}
/// Get current I/O mode
/// Returns: Current I/O mode
pub fn get_io_mode(&self) -> IoMode {
self.io_mode
}
/// Handle I/O port operations according to Intel 4001 datasheet
/// Hardware: I/O operations occur during I/O instructions (WRM/RDM)
/// Direction determined by CPU instruction, not manual configuration
/// Note: I/O pins remain driven after WRM until another WRM or reset.
/// This matches real 4001 hardware where I/O ports are latched.
/// Hardware deviation: Model writes IO latch to D0-D3 for test convenience,
/// but real hardware reads I/O pins directly.
///
/// I/O Port Addressing (datasheet):
/// - I/O ports are addressed using the lower 2 bits of the address
/// - Port 0: Address 0x00, Port 1: Address 0x01, etc.
/// - Each port is 4 bits wide (matches the data bus width)
///
/// I/O Latch Persistence:
/// - Output latch persists after WRM until next WRM or reset
/// - Input latch overwrites every RDM (I/O input pins are live reads)
fn handle_io_operation(&mut self) {
let _sync = self.read_sync_pin();
let chip_select = self.read_cm_rom_pin();
let io_select = self.read_reset_pin(); // Using reset pin as CI for now
let io_select = self.read_ci_pin(); // Proper CI pin for I/O vs ROM selection
// I/O operations occur when: CM=1 (chip_select), CI=1 (io_select)
// Note: SYNC is NOT required for I/O - only marks instruction fetch (ROM access)
if chip_select && io_select {
let _address_low = self.read_data_bus(); // I/O port address (lower 4 bits)
let port_address = self.read_data_bus() & 0x03; // Lower 2 bits for port selection (0-3)
self.selected_io_port = Some(port_address as usize);
// Real 4001 I/O behavior based on CPU instructions:
// RDM instruction: Read from I/O port
self.input_latch = self.read_io_pins();
// Hardware deviation: Write to D0-D3 for test convenience
self.write_data_bus(self.input_latch);
self.io_mode = IoMode::Input;
// Determine operation type based on current instruction phase
// In real hardware, this would be determined by the CPU instruction (WRM vs RDM)
match self.io_mode {
IoMode::Input => {
// RDM instruction: Read from I/O port
if let Some(port) = self.selected_io_port {
self.input_latch = self.read_io_port(port);
// Write input data to data bus for CPU to read
self.write_data_bus(self.input_latch);
}
}
IoMode::Output => {
// WRM instruction: Write to I/O port
if let Some(port) = self.selected_io_port {
let data = self.read_data_bus();
self.write_io_port(port, data);
}
}
IoMode::Bidirectional => {
// Not used in 4001, treat as input
if let Some(port) = self.selected_io_port {
self.input_latch = self.read_io_port(port);
self.write_data_bus(self.input_latch);
}
}
}
}
}
/// Read the 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") {
if let Ok(pin_guard) = pin.lock() {
pin_guard.read() == PinValue::High
} else {
false
}
} else {
false
}
}
/// Read from a specific I/O port
/// Parameters: port - I/O port number (0-3)
/// Returns: 4-bit value from the I/O port
fn read_io_port(&self, port: usize) -> u8 {
if port < 4 {
match self.io_direction[port] {
IoDirection::Input => {
// Read from actual I/O pins
self.read_io_pins()
}
IoDirection::Output => {
// Read from output latch (for read-back capability)
self.io_ports[port]
}
}
} else {
0
}
}
/// Write to a specific I/O port
/// Parameters: port - I/O port number (0-3), data - 4-bit data to write
fn write_io_port(&mut self, port: usize, data: u8) {
if port < 4 {
self.io_ports[port] = data & 0x0F;
self.io_direction[port] = IoDirection::Output;
self.update_io_pins();
}
}
/// Update I/O pins based on current port values and directions
/// Hardware: Only output ports drive the I/O pins
fn update_io_pins(&self) {
for i in 0..4 {
if let Ok(pin) = self.base.get_pin(&format!("IO{}", i)) {
if let Ok(mut pin_guard) = pin.lock() {
match self.io_direction[i] {
IoDirection::Output => {
// Drive pin with output port value
let bit_value = (self.io_ports[i]) & 1;
let pin_value = if bit_value == 1 {
PinValue::High
} else {
PinValue::Low
};
pin_guard.set_driver(Some(self.base.name()), pin_value);
}
IoDirection::Input => {
// Input mode - tri-state the pin
pin_guard.set_driver(Some(self.base.name()), PinValue::HighZ);
}
}
}
}
}
}
@ -553,7 +666,7 @@ impl Intel4001 {
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_reset_pin(); // Using reset pin as CI for now
let io_select = self.read_ci_pin();
println!(
"DEBUG: {} - handle_data_driving: SYNC={}, CM={}, CI={}, Address_Ready={}",
@ -729,6 +842,61 @@ impl Intel4001 {
self.input_latch
}
/// Get I/O port value
/// Parameters: port - I/O port number (0-3)
/// Returns: Some(4-bit value) if port valid, None if invalid
pub fn get_io_port(&self, port: usize) -> Option<u8> {
if port < 4 {
Some(self.io_ports[port])
} else {
None
}
}
/// Set I/O port value (for testing/debugging)
/// Parameters: port - I/O port number (0-3), data - 4-bit data to set
/// Returns: Ok(()) on success, Err(String) on failure
pub fn set_io_port(&mut self, port: usize, data: u8) -> Result<(), String> {
if port < 4 {
self.io_ports[port] = data & 0x0F;
self.io_direction[port] = IoDirection::Output;
self.update_io_pins();
Ok(())
} else {
Err("I/O port number out of range (0-3)".to_string())
}
}
/// Get I/O port direction
/// Parameters: port - I/O port number (0-3)
/// Returns: Some(direction) if port valid, None if invalid
pub fn get_io_direction(&self, port: usize) -> Option<IoDirection> {
if port < 4 {
Some(self.io_direction[port])
} else {
None
}
}
/// Set I/O port direction (for testing/debugging)
/// Parameters: port - I/O port number (0-3), direction - I/O direction
/// Returns: Ok(()) on success, Err(String) on failure
pub fn set_io_direction(&mut self, port: usize, direction: IoDirection) -> Result<(), String> {
if port < 4 {
self.io_direction[port] = direction;
self.update_io_pins();
Ok(())
} else {
Err("I/O port number out of range (0-3)".to_string())
}
}
/// Get currently selected I/O port
/// Returns: Some(port_number) if a port is selected, None otherwise
pub fn get_selected_io_port(&self) -> Option<usize> {
self.selected_io_port
}
/// Debug function to log state transitions for troubleshooting
/// Parameters: test_name - Name of the test for context
pub fn debug_state_transitions(&self, test_name: &str) {
@ -756,6 +924,15 @@ impl std::fmt::Display for IoMode {
}
}
impl std::fmt::Display for IoDirection {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
IoDirection::Input => write!(f, "Input"),
IoDirection::Output => write!(f, "Output"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
@ -841,7 +1018,6 @@ mod tests {
assert_eq!(rom.read_rom(0x01).unwrap(), 0x34);
}
#[test]
fn test_memory_operation_start() {
let mut rom = Intel4001::new_with_access_time("ROM_4001".to_string(), 1);
@ -879,9 +1055,6 @@ mod tests {
println!("Memory state after Φ1 rising: {:?}", rom.memory_state);
}
#[test]
fn test_clock_driven_memory_fetch() {
let mut rom = Intel4001::new_with_access_time("ROM_4001".to_string(), 1); // 1ns for fast testing
@ -1020,7 +1193,6 @@ mod tests {
assert_eq!(d3_pin.lock().unwrap().read(), PinValue::HighZ);
}
#[test]
fn test_intel4001_state_transitions() {
let mut rom = Intel4001::new_with_access_time("StateTestROM".to_string(), 1);
@ -1078,4 +1250,94 @@ mod tests {
assert!(rom.load_rom_data(vec![0x12], 256).is_err()); // Invalid - out of bounds
}
#[test]
fn test_intel4001_io_port_operations() {
let mut rom = Intel4001::new("IOTestROM".to_string());
// Test I/O port initialization
assert_eq!(rom.get_io_port(0).unwrap(), 0);
assert_eq!(rom.get_io_port(1).unwrap(), 0);
assert_eq!(rom.get_io_port(2).unwrap(), 0);
assert_eq!(rom.get_io_port(3).unwrap(), 0);
// Test I/O direction initialization
assert_eq!(rom.get_io_direction(0).unwrap(), IoDirection::Input);
assert_eq!(rom.get_io_direction(1).unwrap(), IoDirection::Input);
// Test setting I/O port values
assert!(rom.set_io_port(0, 0x05).is_ok());
assert_eq!(rom.get_io_port(0).unwrap(), 0x05);
assert_eq!(rom.get_io_direction(0).unwrap(), IoDirection::Output);
// Test invalid port access
assert!(rom.set_io_port(4, 0x0F).is_err());
assert!(rom.get_io_port(4).is_none());
assert!(rom.set_io_direction(4, IoDirection::Output).is_err());
assert!(rom.get_io_direction(4).is_none());
}
#[test]
fn test_intel4001_io_mode_control() {
let mut rom = Intel4001::new("ModeTestROM".to_string());
// Test initial I/O mode
assert_eq!(rom.get_io_mode(), IoMode::Input);
// Test setting I/O modes
rom.set_io_mode(IoMode::Output);
assert_eq!(rom.get_io_mode(), IoMode::Output);
rom.set_io_mode(IoMode::Input);
assert_eq!(rom.get_io_mode(), IoMode::Input);
rom.set_io_mode(IoMode::Bidirectional);
assert_eq!(rom.get_io_mode(), IoMode::Bidirectional);
}
#[test]
fn test_intel4001_io_port_selection() {
let rom = Intel4001::new("SelectionTestROM".to_string());
// Initially no port selected
assert_eq!(rom.get_selected_io_port(), None);
// 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
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());
// Set up some I/O state
rom.set_io_port(0, 0x05).unwrap();
rom.set_io_port(1, 0x0A).unwrap();
rom.set_io_direction(0, IoDirection::Output).unwrap();
rom.set_io_direction(1, IoDirection::Output).unwrap();
rom.set_io_mode(IoMode::Output);
// Verify state is set
assert_eq!(rom.get_io_port(0).unwrap(), 0x05);
assert_eq!(rom.get_io_port(1).unwrap(), 0x0A);
assert_eq!(rom.get_io_direction(0).unwrap(), IoDirection::Output);
assert_eq!(rom.get_io_mode(), IoMode::Output);
// Perform reset
rom.perform_reset();
// Verify I/O state is cleared
assert_eq!(rom.get_io_port(0).unwrap(), 0);
assert_eq!(rom.get_io_port(1).unwrap(), 0);
assert_eq!(rom.get_io_direction(0).unwrap(), IoDirection::Input);
assert_eq!(rom.get_io_mode(), IoMode::Input);
assert_eq!(rom.get_selected_io_port(), None);
}
}

View file

@ -61,17 +61,6 @@ pub enum RamVariant {
Type2, // 4002-2
}
/// Intel 4002 timing constants (based on datasheet specifications)
/// These represent the actual hardware timing requirements
struct TimingConstants;
impl TimingConstants {
const ADDRESS_SETUP: Duration = Duration::from_nanos(100); // T_ADDRESS_SETUP
const DATA_VALID: Duration = Duration::from_nanos(200); // T_DATA_VALID
const OUTPUT_DISABLE: Duration = Duration::from_nanos(150); // T_OUTPUT_DISABLE
const RAM_ACCESS: Duration = Duration::from_nanos(500); // RAM access time
}
impl Intel400xClockHandling for Intel4002 {
fn get_base(&self) -> &BaseComponent {
&self.base
@ -364,27 +353,6 @@ impl Intel4002 {
(phi1, phi2)
}
fn is_phi1_falling_edge(&self) -> bool {
let (phi1, _) = self.read_clock_pins();
phi1 == PinValue::Low && self.prev_phi1 == PinValue::High
}
fn is_phi2_rising_edge(&self) -> bool {
let (_, phi2) = self.read_clock_pins();
phi2 == PinValue::High && self.prev_phi2 == PinValue::Low
}
fn is_phi2_falling_edge(&self) -> bool {
let (_, phi2) = self.read_clock_pins();
phi2 == PinValue::Low && self.prev_phi2 == PinValue::High
}
fn update_clock_states(&mut self) {
let (phi1, phi2) = self.read_clock_pins();
self.prev_phi1 = phi1;
self.prev_phi2 = phi2;
}
/// Read all control pins from CPU
/// Returns: (sync, cm, p0, reset)
/// Hardware: Control pins determine operation type and chip state
@ -475,69 +443,6 @@ impl Intel4002 {
}
}
/// Decode RAM address based on Intel 4002 addressing scheme
/// Hardware: Complex addressing with 4 register banks, status characters, and output ports
/// Parameters: address_low - Low nibble from CPU, instruction - Instruction/opcode
/// Returns: (bank_number, effective_address)
fn decode_address(&self, address_low: u8, instruction: u8) -> (u8, u8) {
// The 4002 uses the high nibble of the address for bank selection
// and the low nibble for within-bank addressing
// Each bank has 20 nibbles (0-19), not 16
let bank = (address_low >> 4) & 0x03; // 2-bit bank from high nibble
let ram_address = (bank * 20) + (address_low & 0x0F); // 20 nibbles per bank
// Status characters are separate latches, not part of RAM
if self.is_status_character_instruction(instruction) {
// Status characters are separate from RAM - return special address
return (bank, 0xFF); // Special marker for status character access
}
// Output ports are also separate from RAM (addresses 0x14-0x17)
if address_low >= 0x14 && address_low <= 0x17 {
return (bank, 0xFF); // Special marker for output port access
}
(bank, ram_address)
}
/// Check if instruction is related to status character operations
fn is_status_character_instruction(&self, instruction: u8) -> bool {
matches!(instruction, 0x10..=0x13)
}
/// Check if instruction is a RAM-related instruction
fn is_ram_instruction(&self, instruction: u8) -> bool {
matches!(instruction, 0x00..=0x0F | 0x10..=0x17)
}
/// Handle RAM bank selection instructions
/// Hardware: DCL (Designate Command Line) instructions select RAM banks
fn handle_bank_selection(&mut self, instruction: u8) {
match instruction {
// Bank select instructions (DCL)
0xE0..=0xE3 => {
self.bank_select = instruction & 0x03;
}
_ => {}
}
}
/// Handle status character operations
/// Hardware: Status characters are separate 4-bit latches, not part of RAM
fn handle_status_character(&mut self, instruction: u8) {
match instruction {
// Status character load instructions
0xF0..=0xF3 => {
let sc_index = (instruction & 0x03) as usize;
if sc_index < 4 {
// Load status character from input latch into separate latch
self.status_characters[sc_index] = self.input_latch;
}
}
_ => {}
}
}
/// Handle output port operations
/// Hardware: Output ports are separate from RAM, continuously driven
fn handle_output_port_operation(&mut self, port: usize, data: u8) {
@ -689,27 +594,6 @@ impl Intel4002 {
}
}
/// Update timing state with more precise hardware timing
fn update_timing_state(&mut self) {
match self.ram_state {
RamState::AddressPhase => {
if let Some(latch_time) = self.address_latch_time {
if latch_time.elapsed() >= TimingConstants::ADDRESS_SETUP {
self.start_data_operation();
}
}
}
RamState::ReadData => {
if let Some(latch_time) = self.address_latch_time {
if latch_time.elapsed() >= TimingConstants::DATA_VALID {
// Data should be valid now
}
}
}
_ => {}
}
}
/// Transition to data operation state
fn start_data_operation(&mut self) {
// Determine operation type based on control signals and address
@ -803,57 +687,6 @@ impl Intel4002 {
self.current_instruction = 0;
}
/// Check if RAM should drive the bus
fn should_drive_bus(&self) -> bool {
let (sync, _cm, p0, _) = self.read_control_pins();
// Only drive bus during data phase when selected
sync && p0 && self.ram_state == RamState::ReadData
}
/// Update data bus drivers with proper contention prevention
fn update_data_bus_drivers(&self) {
if self.should_drive_bus() {
// Drive bus with RAM data
let data = self.read_ram_data();
self.write_data_bus(data);
} else {
// High impedance when not selected
self.tri_state_data_bus();
}
}
/// Read RAM data at current address
fn read_ram_data(&self) -> u8 {
if self.full_address_ready && (self.last_address as usize) < self.memory.len() {
self.memory[self.last_address as usize]
} else {
0
}
}
/// Handle instruction cycle synchronization
fn handle_instruction_cycle(&mut self) {
let (sync, _cm, _p0, _) = self.read_control_pins();
if sync && self.is_phi1_rising_edge(self.prev_phi1) {
// Start of new instruction cycle
self.instruction_phase = true;
self.current_instruction = self.read_data_bus();
// Decode instruction to determine if it's for RAM
if self.is_ram_instruction(self.current_instruction) {
self.prepare_ram_operation();
}
}
}
/// Prepare for RAM operation based on instruction
fn prepare_ram_operation(&mut self) {
// This would set up the RAM for the upcoming operation
// based on the decoded instruction
}
/// Handle special RAM instructions (bank select, status characters, etc.)
/// Hardware: These are handled during instruction cycles, not memory cycles
fn handle_special_instructions(&mut self, instruction: u8) {
@ -1358,40 +1191,6 @@ mod tests {
assert_eq!(d0_pin.lock().unwrap().read(), PinValue::HighZ);
}
#[test]
fn test_bank_selection() {
let mut ram = Intel4002::new("RAM_4002".to_string());
// Test bank selection instructions
ram.handle_bank_selection(0xE0);
assert_eq!(ram.get_bank_select(), 0);
ram.handle_bank_selection(0xE1);
assert_eq!(ram.get_bank_select(), 1);
ram.handle_bank_selection(0xE2);
assert_eq!(ram.get_bank_select(), 2);
ram.handle_bank_selection(0xE3);
assert_eq!(ram.get_bank_select(), 3);
}
#[test]
fn test_status_character_operations() {
let mut ram = Intel4002::new("RAM_4002".to_string());
// Set input latch
ram.set_input_latch(0x0A);
// Test status character load
ram.handle_status_character(0xF0);
assert_eq!(ram.get_status_character(0).unwrap(), 0x0A); // Status character 0
ram.set_input_latch(0x0B);
ram.handle_status_character(0xF1);
assert_eq!(ram.get_status_character(1).unwrap(), 0x0B); // Status character 1
}
#[test]
fn test_output_port_operations() {
let mut ram = Intel4002::new("RAM_4002".to_string());
@ -1421,31 +1220,6 @@ mod tests {
assert_eq!(ram.get_input_latch(), 0x07);
}
#[test]
fn test_ram_addressing_scheme() {
let ram = Intel4002::new("RAM_4002".to_string());
// Test address decoding for different instruction types
// For instruction 0x05 with address_low 0x00: should be bank 0, address 0
let (bank, address) = ram.decode_address(0x00, 0x05);
assert_eq!(bank, 0);
assert_eq!(address, 0); // Bank 0, address 0 (instruction 0x05 addresses location 0)
// Test with different address_low value
let (bank, address) = ram.decode_address(0x05, 0x00);
assert_eq!(bank, 0);
assert_eq!(address, 5); // Bank 0, address 5
// Test status character addressing (returns 0xFF marker)
let (_bank, address) = ram.decode_address(0x00, 0x10);
assert_eq!(address, 0xFF); // Status character marker
// Test output port addressing (returns 0xFF marker)
// For output port 0x14, we need to pass 0x14 as address_low
let (_bank, address) = ram.decode_address(0x14, 0x00);
assert_eq!(address, 0xFF); // Output port marker
}
#[test]
fn test_ram_initialization() {
let mut ram = Intel4002::new("RAM_4002".to_string());
@ -1517,33 +1291,4 @@ mod tests {
// Verify output port addressing is set up correctly
// This would require more complex setup in a real implementation
}
#[test]
fn test_bus_contention_prevention() {
let mut ram = Intel4002::new_with_access_time("RAM_4002".to_string(), 1);
// Initially should not drive bus
assert!(!ram.should_drive_bus());
// Set up RAM operation
let sync_pin = ram.get_pin("SYNC").unwrap();
let p0_pin = ram.get_pin("P0").unwrap();
{
let mut sync_guard = sync_pin.lock().unwrap();
let mut p0_guard = p0_pin.lock().unwrap();
sync_guard.set_driver(Some("TEST".to_string()), PinValue::High);
p0_guard.set_driver(Some("TEST".to_string()), PinValue::High);
}
// Start memory operation
ram.update();
// Should still not drive bus until data phase
assert!(!ram.should_drive_bus());
// Test that bus drivers are updated correctly
ram.update_data_bus_drivers();
// In a real test, we would verify the pin states
}
}

View file

@ -1,5 +1,3 @@
mod generic_ram;
pub mod generic_rom;
pub mod intel_4001;
pub mod intel_4002;
pub mod intel_4003;

View file

@ -11,7 +11,7 @@
//! - Graceful interrupt handling
use crossterm::{
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
event::{self, Event, KeyCode},
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
@ -20,12 +20,11 @@ use ratatui::{
layout::{Constraint, Direction, Layout, Rect},
style::{Color, Modifier, Style},
text::{Line, Span},
widgets::{Block, Borders, Clear, Gauge, List, ListItem, Paragraph, Wrap},
widgets::{Block, Borders, Paragraph, Wrap},
Frame, Terminal,
};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::io::{self, Stdout};
use std::io;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::{Duration, Instant};
@ -63,7 +62,6 @@ pub struct ConsoleApp {
system: Arc<Mutex<ConfigurableSystem>>,
config: ConsoleConfig,
running: bool,
last_update: Instant,
command_buffer: String,
show_help: bool,
selected_pane: usize,
@ -75,7 +73,6 @@ impl ConsoleApp {
system,
config,
running: false,
last_update: Instant::now(),
command_buffer: String::new(),
show_help: false,
selected_pane: 0,
@ -86,7 +83,8 @@ impl ConsoleApp {
// 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))?;
execute!(stdout, EnterAlternateScreen)
.map_err(|e| format!("Failed to enter alternate screen: {}", e))?;
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
@ -132,11 +130,11 @@ impl ConsoleApp {
// 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))?;
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(())
}
@ -238,17 +236,20 @@ impl ConsoleApp {
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(4), // Title bar
Constraint::Min(8), // Main content
Constraint::Length(3), // Command bar
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::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)),
@ -298,19 +299,54 @@ impl ConsoleApp {
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(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(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(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...")]),
];
@ -326,37 +362,53 @@ impl ConsoleApp {
let info_chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(8), // System info
Constraint::Min(5), // Registers
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()))]),
]
}
}
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")])]
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"))
.block(
Block::default()
.borders(Borders::ALL)
.title("System Information"),
)
.wrap(Wrap { trim: true });
f.render_widget(system_widget, info_chunks[0]);
// Registers (placeholder for now - would need CPU trait access)
// Registers (placeholder for now - would need direct CPU component access)
let register_info = vec![
Line::from(vec![Span::raw("CPU Registers:")]),
Line::from(vec![Span::raw("PC: 0x0000")]),
@ -366,7 +418,11 @@ impl ConsoleApp {
];
let register_widget = Paragraph::new(register_info)
.block(Block::default().borders(Borders::ALL).title("CPU Registers"))
.block(
Block::default()
.borders(Borders::ALL)
.title("CPU Registers"),
)
.wrap(Wrap { trim: true });
f.render_widget(register_widget, info_chunks[1]);
}
@ -414,4 +470,4 @@ pub fn run_console(
) -> Result<(), Box<dyn std::error::Error>> {
let mut app = ConsoleApp::new(system, config);
app.run()
}
}

View file

@ -9,7 +9,6 @@
//! - Extensible component system with trait-based architecture
//! - Professional project organization with clean separation of concerns
mod bus;
pub mod component;
pub mod components;
pub mod connection;

View file

@ -83,7 +83,10 @@ fn main() {
println!("===================");
println!("System: {}", system_type);
println!("Program: {}", program_file);
println!("Console: {}", if use_console { "Enabled" } else { "Disabled" });
println!(
"Console: {}",
if use_console { "Enabled" } else { "Disabled" }
);
// Load program data
let program_data = match load_program_data(&program_file) {