use std::collections::HashMap; use std::sync::{Arc, Mutex}; use std::thread; use std::time::{Duration, Instant}; use crate::component::{BaseComponent, Component, RunnableComponent}; use crate::components::common::intel_400x::{ Intel400xAddressHandling, Intel400xClockHandling, Intel400xControlPins, Intel400xDataBus, Intel400xResetHandling, Intel400xTimingState, RamState, TimingState, }; use crate::pin::{Pin, PinValue}; /// Intel 4002 - 320-bit RAM (80 nibbles × 4 bits) with integrated output ports /// Part of the MCS-4 family, designed to work with Intel 4004 CPU /// Features 80 nibbles of RAM organized in 4 banks of 20 nibbles each, /// plus 4 status characters and 4 output ports /// /// Hardware Architecture: /// - 4 banks × 20 registers × 4 bits = 80 nibbles total /// - Status characters are separate 4-bit latches (not part of RAM) /// - 4 output ports, each 4 bits wide /// - Complex addressing via SRC/WRM/RDM instructions from CPU /// /// Hardware Deviations: /// - Simplified timing model for unit testing (may need refinement for CPU integration) /// - Output port behavior matches 4001 I/O latch persistence /// - Bank selection and status character handling follows Intel MCS-4 architecture pub struct Intel4002 { base: BaseComponent, variant: RamVariant, // RAM variant (4002-1 or 4002-2) memory: [u8; 80], // 80 nibbles of RAM (320 bits total) - 4 banks × 20 nibbles last_address: u8, // Last accessed memory address access_time: Duration, // RAM access latency (500ns typical) address_latch_time: Option, // Timestamp when address was latched output_ports: [u8; 4], // 4 output ports (4 bits each) - TODO: Make [[u8; 4]; 4] for 4-bit ports input_latch: u8, // Input data latch for I/O operations status_characters: [u8; 4], // 4 separate status character latches (4 bits each) bank_select: u8, // RAM bank selection (2 bits) // Clock edge detection (same as 4001) prev_phi1: PinValue, // Previous Φ1 clock state for edge detection prev_phi2: PinValue, // Previous Φ2 clock state for edge detection // Two-phase addressing for 8-bit address (same as 4001) address_high_nibble: Option, // High nibble of 8-bit address address_low_nibble: Option, // Low nibble of 8-bit address full_address_ready: bool, // Whether complete address is assembled // RAM operation state machine ram_state: RamState, // Current state of RAM operation // Data latching for RAM operations data_latch: Option, // Latched data for write operations // Instruction cycle tracking instruction_phase: bool, // Whether we're in instruction phase current_instruction: u8, // Current instruction being processed // Cycle counting for debug output cycle_count: u64, // Total number of clock cycles executed } /// Intel 4002 RAM variants /// - 4002-1: Standard RAM variant /// - 4002-2: RAM variant with different configuration #[derive(Debug, Clone, Copy, PartialEq)] pub enum RamVariant { Type1, // 4002-1 Type2, // 4002-2 } impl Intel400xClockHandling for Intel4002 { fn get_base(&self) -> &BaseComponent { &self.base } } impl Intel400xDataBus for Intel4002 { fn get_base(&self) -> &BaseComponent { &self.base } } impl Intel400xAddressHandling for Intel4002 { fn get_base(&self) -> &BaseComponent { &self.base } } impl Intel400xControlPins for Intel4002 { fn get_base(&self) -> &BaseComponent { &self.base } } impl Intel400xResetHandling for Intel4002 { fn get_base(&self) -> &BaseComponent { &self.base } fn perform_reset(&mut self) { // Reset operations specific to Intel4002 // Note: This is called from handle_reset, so we don't need to check reset pin again self.set_timing_state(TimingState::Idle) } } impl Intel400xTimingState for Intel4002 { fn get_timing_state(&self) -> TimingState { self.ram_state.into() } fn set_timing_state(&mut self, state: TimingState) { self.ram_state = state.into(); } fn get_address_latch_time(&self) -> Option { self.address_latch_time } fn set_address_latch_time(&mut self, time: Option) { self.address_latch_time = time; } fn get_full_address_ready(&self) -> bool { self.full_address_ready } fn set_full_address_ready(&mut self, ready: bool) { self.full_address_ready = ready; } fn get_address_high_nibble(&self) -> Option { self.address_high_nibble } fn set_address_high_nibble(&mut self, nibble: Option) { self.address_high_nibble = nibble; } fn get_address_low_nibble(&self) -> Option { self.address_low_nibble } fn set_address_low_nibble(&mut self, nibble: Option) { self.address_low_nibble = nibble; } fn get_access_time(&self) -> Duration { self.access_time } } impl Intel4002 { /// Create a new Intel 4002 RAM with specified access time /// Parameters: name - Component identifier, access_time_ns - Memory access time in nanoseconds /// Returns: New Intel4002 instance with configurable access timing pub fn new(name: String) -> Self { Self::new_with_variant_and_access_time(name, RamVariant::Type1, 500) // Default 500ns access time } /// Create a new Intel 4002 RAM with custom access time (for testing) /// Parameters: name - Component identifier, access_time_ns - Memory access time in nanoseconds /// Returns: New Intel4002 instance with configurable access timing pub fn new_with_access_time(name: String, access_time_ns: u64) -> Self { Self::new_with_variant_and_access_time(name, RamVariant::Type1, access_time_ns) } /// Create a new Intel 4002 RAM with specified variant and access time /// Parameters: name - Component identifier, variant - RAM variant (Type1 or Type2), access_time_ns - Memory access time in nanoseconds /// Returns: New Intel4002 instance with configurable variant and access timing pub fn new_with_variant_and_access_time( name: String, variant: RamVariant, access_time_ns: u64, ) -> Self { // Intel 4002 pinout (based on MCS-4 architecture): // - 4 data pins (D0-D3) for multiplexed address/data // - 4 output port pins (O0-O3) // - Control pins: SYNC, CM, P0, RESET // - Clock pins: Φ1, Φ2 (two-phase clock from 4004 CPU) // // Control pin behavior: // - SYNC: Marks start of instruction cycle // - CM: ROM chip select (must be HIGH for ROM access) // - P0: RAM chip select (must be HIGH for RAM access) // - RESET: Clears internal state let pin_names = vec![ "D0", "D1", "D2", "D3", // Data/Address pins "O0", "O1", "O2", "O3", // Output port pins "SYNC", // Sync signal "CM", // ROM Chip Select "P0", // RAM Chip Select "RESET", // Reset "PHI1", // Clock phase 1 "PHI2", // Clock phase 2 ]; let pins = BaseComponent::create_pin_map(&pin_names, &name); Intel4002 { base: BaseComponent::new(name, pins), variant, memory: [0u8; 80], // 80 nibbles = 4 banks × 20 nibbles each last_address: 0, access_time: Duration::from_nanos(access_time_ns), address_latch_time: None, output_ports: [0u8; 4], input_latch: 0, status_characters: [0u8; 4], // 4 separate status character latches bank_select: 0, prev_phi1: PinValue::Low, prev_phi2: PinValue::Low, address_high_nibble: None, address_low_nibble: None, full_address_ready: false, ram_state: RamState::Idle, data_latch: None, instruction_phase: false, current_instruction: 0, cycle_count: 0, } } /// Set the 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); } /// Get the current memory access time /// Returns: Access time in nanoseconds pub fn get_access_time(&self) -> u64 { self.access_time.as_nanos() as u64 } /// Initialize RAM with data /// Parameters: data - Binary data to load (max 80 nibbles) /// Returns: Ok(()) on success, Err(String) on failure pub fn initialize_ram(&mut self, data: &[u8]) -> Result<(), String> { if data.len() > 80 { return Err("Data exceeds RAM capacity (80 nibbles)".to_string()); } for (i, &byte) in data.iter().enumerate() { self.memory[i] = byte & 0x0F; // Store only lower 4 bits } Ok(()) } /// Read the 4-bit data bus from D0-D3 pins /// Returns: 4-bit value from data bus pins fn read_data_bus(&self) -> u8 { let mut data = 0; for i in 0..4 { 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 & 0x0F // Return only lower 4 bits } /// Drive the 4-bit data bus with the specified value /// Parameters: data - 4-bit value to drive on D0-D3 pins fn write_data_bus(&self, data: u8) { let nibble = data & 0x0F; // Only lower 4 bits for i in 0..4 { if let Ok(pin) = self.base.get_pin(&format!("D{}", i)) { if let Ok(mut pin_guard) = pin.lock() { let bit_value = (nibble >> i) & 1; let pin_value = if bit_value == 1 { PinValue::High } else { PinValue::Low }; // Use unique driver name to avoid conflicts with other components pin_guard.set_driver(Some(format!("{}_DATA", self.base.name())), pin_value); } } } } /// Update output port pins based on current output port values /// Hardware: Output ports are driven continuously until changed or reset fn update_output_ports(&self) { for port in 0..4 { if let Ok(pin) = self.base.get_pin(&format!("O{}", port)) { if let Ok(mut pin_guard) = pin.lock() { // Each output port drives its corresponding pin let bit_value = (self.output_ports[port] >> 0) & 1; let pin_value = if bit_value == 1 { PinValue::High } else { PinValue::Low }; // Use unique driver name for output ports pin_guard.set_driver(Some(format!("{}_OUTPUT", self.base.name())), pin_value); } } } } /// Set data bus to high-impedance state to avoid bus contention /// CRITICAL: Must be called whenever RAM is not actively driving valid data fn tri_state_data_bus(&self) { for i in 0..4 { if let Ok(pin) = self.base.get_pin(&format!("D{}", i)) { if let Ok(mut pin_guard) = pin.lock() { pin_guard .set_driver(Some(format!("{}_DATA", self.base.name())), PinValue::HighZ); } } } } /// Tri-state output port pins /// Hardware: Output ports remain driven until explicitly changed fn tri_state_output_ports(&self) { for port in 0..4 { if let Ok(pin) = self.base.get_pin(&format!("O{}", port)) { if let Ok(mut pin_guard) = pin.lock() { pin_guard.set_driver( Some(format!("{}_OUTPUT", self.base.name())), PinValue::HighZ, ); } } } } /// Read the two-phase clock pins from CPU /// Returns: (PHI1_value, PHI2_value) /// Hardware: Intel 4004 provides two-phase clock for synchronization fn read_clock_pins(&self) -> (PinValue, PinValue) { let phi1 = if let Ok(pin) = self.base.get_pin("PHI1") { if let Ok(pin_guard) = pin.lock() { pin_guard.read() } else { PinValue::Low } } else { PinValue::Low }; let phi2 = if let Ok(pin) = self.base.get_pin("PHI2") { if let Ok(pin_guard) = pin.lock() { pin_guard.read() } else { PinValue::Low } } else { PinValue::Low }; (phi1, phi2) } /// Read all control pins from CPU /// Returns: (sync, cm, p0, reset) /// Hardware: Control pins determine operation type and chip state fn read_control_pins(&self) -> (bool, bool, bool, bool) { let sync = if let Ok(pin) = self.base.get_pin("SYNC") { if let Ok(pin_guard) = pin.lock() { pin_guard.read() == PinValue::High } else { false } } else { false }; let cm = if let Ok(pin) = self.base.get_pin("CM") { if let Ok(pin_guard) = pin.lock() { pin_guard.read() == PinValue::High } else { false } } else { false }; let p0 = if let Ok(pin) = self.base.get_pin("P0") { if let Ok(pin_guard) = pin.lock() { pin_guard.read() == PinValue::High } else { false } } else { false }; let reset = if let Ok(pin) = self.base.get_pin("RESET") { if let Ok(pin_guard) = pin.lock() { pin_guard.read() == PinValue::High } else { false } } else { false }; (sync, cm, p0, reset) } /// Handle system reset signal /// Hardware: RESET pin clears all internal state and tri-states outputs fn handle_reset(&mut self) { if Intel400xResetHandling::handle_reset(self, "RESET") { // Hardware reset - clear all registers specific to Intel4002 self.memory = [0u8; 80]; // Clear 80 nibbles self.output_ports = [0u8; 4]; self.input_latch = 0; self.status_characters = [0u8; 4]; // Clear 4 status character latches self.bank_select = 0; // Reset all state machines using common functionality self.set_timing_state(TimingState::Idle); self.set_address_latch_time(None); self.set_address_high_nibble(None); self.set_address_low_nibble(None); self.set_full_address_ready(false); // Reset address ready on reset self.data_latch = None; self.instruction_phase = false; self.current_instruction = 0; self.cycle_count = 0; // Tri-state all outputs self.tri_state_data_bus(); self.tri_state_output_ports(); } } /// Assemble complete 8-bit address from high and low nibbles /// Hardware: Intel 4004 provides address in two 4-bit phases /// Format: (high_nibble << 4) | low_nibble fn assemble_full_address(&mut self) { if let (Some(high), Some(low)) = (self.address_high_nibble, self.address_low_nibble) { // Assemble 8-bit address: (high << 4) | low self.last_address = (high << 4) | low; self.full_address_ready = true; self.address_latch_time = Some(Instant::now()); println!("DEBUG: [{}] Addr Assembled | High: 0x{:X} | Low: 0x{:X} | Full: 0x{:02X} | Ready: {} | Cycles: {}", self.base.name(), high, low, self.last_address, self.full_address_ready, self.get_cycle_count()); // Clear nibble storage for next address self.address_high_nibble = None; self.address_low_nibble = None; } } /// Handle output port operations /// Hardware: Output ports are separate from RAM, continuously driven fn handle_output_port_operation(&mut self, port: usize, data: u8) { if port < 4 { self.output_ports[port] = data & 0x0F; self.update_output_ports(); } } /// Handle input latch operations /// Hardware: RDM (Read Data Memory) instructions read from input pins fn handle_input_latch_operation(&mut self) { // In real hardware, this would read from external input pins // For emulation, we use the input latch self.write_data_bus(self.input_latch); } /// Handle Φ1 rising edge - Address and control phase /// Hardware: Φ1 high = CPU drives bus with address/control information /// RAM 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 RAM operation start on Φ1 rising edge with SYNC high let (sync, _cm, p0, _) = self.read_control_pins(); if sync && p0 && self.ram_state == RamState::Idle { // Start RAM address phase on Φ1 rising edge (only when idle) self.start_ram_address_phase(); } // Handle RAM address phase operations during Φ1 self.handle_ram_address_operations(); } /// Handle Φ1 falling edge - End of address phase fn handle_phi1_falling(&mut self) { // Currently no specific operations needed on Φ1 falling } /// Handle Φ2 rising edge - Data phase fn handle_phi2_rising(&mut self) { self.handle_ram_data_operations(); } /// Handle Φ2 falling edge - End of data phase fn handle_phi2_falling(&mut self) { self.handle_ram_cleanup_operations(); } /// Handle RAM address-related operations during Φ1 fn handle_ram_address_operations(&mut self) { match self.ram_state { RamState::Idle => { self.tri_state_data_bus(); } RamState::AddressPhase => { self.handle_address_latching(); } RamState::WaitLatency => { self.handle_latency_wait(); } RamState::ReadData | RamState::WriteData | RamState::OutputPort => { self.tri_state_data_bus(); } } } /// Handle RAM data-related operations during Φ2 fn handle_ram_data_operations(&mut self) { match self.ram_state { RamState::Idle => { self.tri_state_data_bus(); } RamState::AddressPhase => { self.tri_state_data_bus(); } RamState::WaitLatency => { self.handle_latency_wait(); if self.ram_state == RamState::ReadData || self.ram_state == RamState::WriteData { self.handle_data_operations(); } } RamState::ReadData => { self.handle_data_operations(); } RamState::WriteData => { self.handle_data_operations(); } RamState::OutputPort => { self.handle_output_port_state(); } } } /// Handle RAM cleanup operations on Φ2 falling edge fn handle_ram_cleanup_operations(&mut self) { match self.ram_state { RamState::ReadData | RamState::WriteData | RamState::OutputPort => { self.tri_state_data_bus(); self.return_to_idle(); } RamState::Idle | RamState::AddressPhase | RamState::WaitLatency => { self.tri_state_data_bus(); } } } /// Transition to address phase state fn start_ram_address_phase(&mut self) { self.ram_state = RamState::AddressPhase; // Clear address state for new operation self.address_high_nibble = None; self.address_low_nibble = None; self.full_address_ready = false; self.last_address = 0; } /// Handle address nibble latching during address phase fn handle_address_latching(&mut self) { let nibble = self.read_data_bus(); if self.address_high_nibble.is_none() { self.address_high_nibble = Some(nibble); println!("DEBUG: [{}] Addr Latch High | Nibble: 0x{:X} | State: {:?} | Cycles: {}", self.base.name(), nibble, self.ram_state, self.get_cycle_count()); } else if self.address_low_nibble.is_none() { self.address_low_nibble = Some(nibble); println!("DEBUG: [{}] Addr Latch Low | High: 0x{:X} | Low: 0x{:X} | State: {:?} | Cycles: {}", self.base.name(), self.address_high_nibble.unwrap(), nibble, self.ram_state, self.get_cycle_count()); self.assemble_full_address(); self.start_latency_wait(); } } /// Transition to latency wait state fn start_latency_wait(&mut self) { self.ram_state = RamState::WaitLatency; self.address_latch_time = Some(Instant::now()); } /// Handle latency timing during wait state fn handle_latency_wait(&mut self) { if let Some(latch_time) = self.address_latch_time { if latch_time.elapsed() >= self.access_time { self.start_data_operation(); } } } /// Transition to data operation state fn start_data_operation(&mut self) { // Determine operation type based on control signals and address let (sync, _cm, p0, _) = self.read_control_pins(); if sync && p0 && self.full_address_ready { let address = self.last_address; if address >= 0x14 && address <= 0x17 { // Output port operation self.ram_state = RamState::OutputPort; } else { // RAM read/write operation // For now, assume read - write detection happens in data phase self.ram_state = RamState::ReadData; } } } /// Handle data operations during ReadData or WriteData state fn handle_data_operations(&mut self) { let (sync, cm, p0, _) = self.read_control_pins(); if sync && p0 && self.full_address_ready { let address = self.last_address; if address == 0xFF { // Status character operation let data = self.read_data_bus(); if !cm { // First cycle - read from status character // For now, return status character 0 - this should be determined by instruction self.write_data_bus(self.status_characters[0]); println!("DEBUG: [{}] Status Char Read | Index: 0 | Data: 0x{:X} | State: {:?} | Cycles: {}", self.base.name(), self.status_characters[0], self.ram_state, self.get_cycle_count()); } else { // Second cycle - write to status character // For now, write to status character 0 - this should be determined by instruction let old_status = self.status_characters[0]; self.status_characters[0] = data & 0x0F; self.ram_state = RamState::WriteData; println!("DEBUG: [{}] Status Char Write | Index: 0 | Old: 0x{:X} | New: 0x{:X} | State: {:?} | Cycles: {}", self.base.name(), old_status, data & 0x0F, self.ram_state, self.get_cycle_count()); } } else if address < 80 { // RAM operation let data = self.read_data_bus(); if !cm { // First cycle - read from RAM let ram_data = self.memory[address as usize]; self.write_data_bus(ram_data); println!("DEBUG: [{}] RAM Read | Addr: 0x{:02X} | Data: 0x{:X} | Bank: {} | State: {:?} | Cycles: {}", self.base.name(), address, ram_data, self.bank_select, self.ram_state, self.get_cycle_count()); } else { // Second cycle - write to RAM let old_data = self.memory[address as usize]; self.memory[address as usize] = data & 0x0F; self.ram_state = RamState::WriteData; println!("DEBUG: [{}] RAM Write | Addr: 0x{:02X} | Old: 0x{:X} | New: 0x{:X} | Bank: {} | State: {:?} | Cycles: {}", self.base.name(), address, old_data, data & 0x0F, self.bank_select, self.ram_state, self.get_cycle_count()); } } } else { self.tri_state_data_bus(); } } /// Handle output port state operations fn handle_output_port_state(&mut self) { let (sync, cm, p0, _) = self.read_control_pins(); if sync && p0 && self.full_address_ready { let address = self.last_address; if address >= 0x14 && address <= 0x17 { let port = (address - 0x14) as usize; if !cm { // First cycle - read from output port (not typical for 4002) let port_data = self.output_ports[port]; self.write_data_bus(port_data); println!("DEBUG: [{}] Output Port Read | Port: {} | Data: 0x{:X} | State: {:?} | Cycles: {}", self.base.name(), port, port_data, self.ram_state, self.get_cycle_count()); } else { // Second cycle - write to output port let data = self.read_data_bus(); let old_port_data = self.output_ports[port]; self.handle_output_port_operation(port, data); println!("DEBUG: [{}] Output Port Write | Port: {} | Old: 0x{:X} | New: 0x{:X} | State: {:?} | Cycles: {}", self.base.name(), port, old_port_data, data & 0x0F, self.ram_state, self.get_cycle_count()); } } } else { self.tri_state_data_bus(); } } /// Reset RAM state machine to idle fn return_to_idle(&mut self) { self.ram_state = RamState::Idle; self.address_latch_time = None; // Don't reset address nibbles and ready flag immediately - keep them for potential reuse // They will be cleared when a new address operation starts self.data_latch = None; self.instruction_phase = false; self.current_instruction = 0; } /// 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) { match instruction { // DCL (Designate Command Line) - selects RAM bank 0xF0..=0xF3 => { self.bank_select = instruction & 0x03; } // SRC (Send Register Control) - sets output port address 0x20..=0x2F => { // SRC instruction sets up output port addressing // For now, this is handled by the output port operation logic } // Input latch operations (RDM - Read Data Memory) 0x60..=0x63 => { self.handle_input_latch_operation(); } _ => { // Other instructions may be handled elsewhere } } } } impl Component for Intel4002 { fn name(&self) -> String { self.base.name() } fn pins(&self) -> HashMap>> { self.base.pins() } fn get_pin(&self, name: &str) -> Result>, String> { self.base.get_pin(name) } /// Main update cycle - handles clock edge detection and operation dispatch /// Hardware: Responds to Φ1 and Φ2 clock edges from CPU fn update(&mut self) { if !self.is_running() { return; } // Debug: Show RAM state periodically to monitor for changes static mut CYCLE_COUNTER: u64 = 0; unsafe { CYCLE_COUNTER += 1; if CYCLE_COUNTER % 5000 == 0 { // Less frequent than CPU to avoid spam println!("DEBUG: [{}] RAM State | Bank: {} | Status: {:?} | Ready: {} | LastAddr: 0x{:02X} | Cycles: {} | HighNib: {:?} | LowNib: {:?}", self.base.name(), self.bank_select, self.ram_state, self.full_address_ready, self.last_address, self.get_cycle_count(), self.address_high_nibble, self.address_low_nibble); } } // Handle both rising and falling edges for proper two-phase operation let (phi1, phi2) = self.read_clock_pins(); let phi1_rising = phi1 == PinValue::High && self.prev_phi1 == PinValue::Low; let phi1_falling = phi1 == PinValue::Low && self.prev_phi1 == PinValue::High; let phi2_rising = phi2 == PinValue::High && self.prev_phi2 == PinValue::Low; let phi2_falling = phi2 == PinValue::Low && self.prev_phi2 == PinValue::High; // Update clock states for next edge detection self.prev_phi1 = phi1; self.prev_phi2 = phi2; if phi1_rising { // Φ1 Rising Edge: Address phase (CPU drives bus) - handle address latching self.handle_phi1_rising(); } if phi1_falling { // Φ1 Falling Edge: End of address phase self.handle_phi1_falling(); } if phi2_rising { // Φ2 Rising Edge: Data phase (RAM drives bus if ready) - handle data operations self.handle_phi2_rising(); } if phi2_falling { // Φ2 Falling Edge: End of data phase - tri-state bus and return to idle self.handle_phi2_falling(); } // Handle special instructions when not in memory operation let (sync, cm, p0, _) = self.read_control_pins(); if sync && !p0 && !cm { // Instruction phase - handle special RAM instructions let instruction = self.read_data_bus(); self.handle_special_instructions(instruction); } // Increment cycle count for debug output self.cycle_count += 1; } /// Run component in time-slice mode (manual control) /// Hardware: Simulates continuous operation with clock edge detection fn run(&mut self) { self.base.set_running(true); // Initialize clock states for edge detection let (phi1, phi2) = self.read_clock_pins(); self.prev_phi1 = phi1; self.prev_phi2 = phi2; while self.is_running() { self.update(); // Small delay to prevent busy waiting when no clock is present thread::sleep(Duration::from_micros(1)); } } /// Stop component and tri-state all outputs /// Hardware: Component enters low-power state, all pins high-impedance fn stop(&mut self) { self.base.set_running(false); // Tri-state all outputs when stopped self.tri_state_data_bus(); self.tri_state_output_ports(); // Reset RAM operation state self.address_latch_time = None; } fn is_running(&self) -> bool { self.base.is_running() } } impl RunnableComponent for Intel4002 {} // Intel 4002 specific methods impl Intel4002 { /// Get the RAM size in nibbles /// Returns: Total number of nibbles in RAM (80 for 4002) pub fn get_ram_size(&self) -> usize { self.memory.len() } /// Read a nibble from RAM at specified address /// Parameters: address - RAM address (0-79) /// Returns: Some(data) if address valid, None if out of bounds pub fn read_ram(&self, address: u8) -> Option { if (address as usize) < self.memory.len() { Some(self.memory[address as usize]) } else { None } } /// Write a nibble to RAM at specified address /// Parameters: address - RAM address (0-79), data - 4-bit data to write /// Returns: Ok(()) on success, Err(String) on failure pub fn write_ram(&mut self, address: u8, data: u8) -> Result<(), String> { if (address as usize) < self.memory.len() { self.memory[address as usize] = data & 0x0F; Ok(()) } else { Err("Address out of range (0-79)".to_string()) } } /// Get the current output port value /// Parameters: port - Port number (0-3) /// Returns: Some(data) if port valid, None if invalid pub fn get_output_port(&self, port: usize) -> Option { if port < 4 { Some(self.output_ports[port]) } else { None } } /// Set an output port value /// Parameters: port - Port number (0-3), data - 4-bit data to set /// Returns: Ok(()) on success, Err(String) on failure pub fn set_output_port(&mut self, port: usize, data: u8) -> Result<(), String> { if port < 4 { self.output_ports[port] = data & 0x0F; self.update_output_ports(); Ok(()) } else { Err("Port number out of range (0-3)".to_string()) } } /// Set the input latch value /// Parameters: data - 4-bit input data pub fn set_input_latch(&mut self, data: u8) { self.input_latch = data & 0x0F; } /// Get the current input latch value /// Returns: 4-bit input latch value pub fn get_input_latch(&self) -> u8 { self.input_latch } /// Get the current status character value /// Parameters: index - Status character index (0-3) /// Returns: 4-bit status character value pub fn get_status_character(&self, index: usize) -> Option { if index < 4 { Some(self.status_characters[index]) } else { None } } /// Get all status characters /// Returns: Array of 4 status character values pub fn get_all_status_characters(&self) -> [u8; 4] { self.status_characters } /// Get the current bank select value /// Returns: 2-bit bank select value (0-3) pub fn get_bank_select(&self) -> u8 { self.bank_select } /// Get the RAM variant /// Returns: The RAM variant (Type1 or Type2) pub fn get_variant(&self) -> RamVariant { self.variant } /// Get the current cycle count for the RAM component /// Returns: Current cycle count since component start pub fn get_cycle_count(&self) -> u64 { self.cycle_count } /// Clear all RAM to zero pub fn clear_ram(&mut self) { self.memory = [0u8; 80]; // Clear 80 nibbles } /// Get all RAM data for a specific bank /// Parameters: bank - Bank number (0-3) /// Returns: Vector of 20 nibbles for the bank (4 banks × 20 nibbles = 80 total) pub fn get_ram_bank(&self, bank: u8) -> Vec { let bank = (bank & 0x03) as usize; let start = bank * 20; // 20 nibbles per bank let end = start + 20; if end <= self.memory.len() { self.memory[start..end].to_vec() } else if start >= self.memory.len() { Vec::new() } else { self.memory[start..].to_vec() } } /// 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) { println!( "{} - State: {:?}, Bank: {}, High: {:?}, Low: {:?}, Address: 0x{:x}, Ready: {}", test_name, self.ram_state, self.bank_select, self.address_high_nibble, self.address_low_nibble, self.last_address, self.full_address_ready ); } /// Setup helper for instruction testing pub fn setup_instruction_test(&mut self, instruction: u8) { self.current_instruction = instruction; self.instruction_phase = true; } } // Custom formatter for debugging impl std::fmt::Display for RamState { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { RamState::Idle => write!(f, "Idle"), RamState::AddressPhase => write!(f, "AddressPhase"), RamState::WaitLatency => write!(f, "WaitLatency"), RamState::ReadData => write!(f, "ReadData"), RamState::WriteData => write!(f, "WriteData"), RamState::OutputPort => write!(f, "OutputPort"), } } } #[cfg(test)] mod tests { use super::*; #[test] fn test_intel4002_creation() { let ram = Intel4002::new("RAM_4002".to_string()); assert_eq!(ram.name(), "RAM_4002"); assert_eq!(ram.get_ram_size(), 80); assert_eq!(ram.get_access_time(), 500); // Default 500ns assert!(!ram.is_running()); } #[test] fn test_intel4002_ram_operations() { let mut ram = Intel4002::new("RAM_4002".to_string()); // Test RAM write/read assert!(ram.write_ram(0, 0x0A).is_ok()); assert_eq!(ram.read_ram(0).unwrap(), 0x0A); // Test out of bounds assert!(ram.write_ram(80, 0x0F).is_err()); assert!(ram.read_ram(80).is_none()); } #[test] fn test_intel4002_output_ports() { let mut ram = Intel4002::new("RAM_4002".to_string()); // Test output port operations assert!(ram.set_output_port(0, 0x05).is_ok()); assert_eq!(ram.get_output_port(0).unwrap(), 0x05); // Test invalid port assert!(ram.set_output_port(4, 0x0F).is_err()); assert!(ram.get_output_port(4).is_none()); } #[test] fn test_intel4002_input_latch() { let mut ram = Intel4002::new("RAM_4002".to_string()); ram.set_input_latch(0x07); assert_eq!(ram.get_input_latch(), 0x07); // Test that only lower 4 bits are stored ram.set_input_latch(0x1F); assert_eq!(ram.get_input_latch(), 0x0F); } #[test] fn test_intel4002_ram_banks() { let mut ram = Intel4002::new("RAM_4002".to_string()); // Write test data to different banks ram.write_ram(0, 0x01).unwrap(); // Bank 0, address 0 ram.write_ram(20, 0x02).unwrap(); // Bank 1, address 0 ram.write_ram(40, 0x03).unwrap(); // Bank 2, address 0 let bank0 = ram.get_ram_bank(0); assert_eq!(bank0.len(), 20); // 20 nibbles per bank assert_eq!(bank0[0], 0x01); let bank1 = ram.get_ram_bank(1); assert_eq!(bank1.len(), 20); // 20 nibbles per bank assert_eq!(bank1[0], 0x02); let bank2 = ram.get_ram_bank(2); assert_eq!(bank2.len(), 20); // 20 nibbles per bank assert_eq!(bank2[0], 0x03); } #[test] fn test_intel4002_clear_ram() { let mut ram = Intel4002::new("RAM_4002".to_string()); ram.write_ram(0, 0x0A).unwrap(); ram.write_ram(10, 0x0B).unwrap(); assert_eq!(ram.read_ram(0).unwrap(), 0x0A); assert_eq!(ram.read_ram(10).unwrap(), 0x0B); ram.clear_ram(); assert_eq!(ram.read_ram(0).unwrap(), 0); assert_eq!(ram.read_ram(10).unwrap(), 0); } #[test] fn test_configurable_access_time() { let mut ram = Intel4002::new("RAM_4002".to_string()); // Test default access time assert_eq!(ram.get_access_time(), 500); // Test setting custom access time ram.set_access_time(100); assert_eq!(ram.get_access_time(), 100); // Test constructor with custom access time let fast_ram = Intel4002::new_with_access_time("FAST_RAM".to_string(), 1); assert_eq!(fast_ram.get_access_time(), 1); assert_eq!(fast_ram.name(), "FAST_RAM"); } #[test] fn test_address_latching() { let mut ram = Intel4002::new_with_access_time("RAM_4002".to_string(), 1); // Start the component so it can process updates ram.base.set_running(true); assert!(ram.is_running()); // Get pin references let sync_pin = ram.get_pin("SYNC").unwrap(); let p0_pin = ram.get_pin("P0").unwrap(); let phi1_pin = ram.get_pin("PHI1").unwrap(); let d0_pin = ram.get_pin("D0").unwrap(); let d1_pin = ram.get_pin("D1").unwrap(); let d2_pin = ram.get_pin("D2").unwrap(); let d3_pin = ram.get_pin("D3").unwrap(); // Initialize clock pins to Low { let mut phi1_guard = phi1_pin.lock().unwrap(); phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low); } // Set up RAM operation: SYNC=1, CM-RAM=1 { 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); } // Set address high nibble (0x0) on data bus { let mut d0_guard = d0_pin.lock().unwrap(); let mut d1_guard = d1_pin.lock().unwrap(); let mut d2_guard = d2_pin.lock().unwrap(); let mut d3_guard = d3_pin.lock().unwrap(); d0_guard.set_driver(Some("TEST".to_string()), PinValue::Low); d1_guard.set_driver(Some("TEST".to_string()), PinValue::Low); d2_guard.set_driver(Some("TEST".to_string()), PinValue::Low); d3_guard.set_driver(Some("TEST".to_string()), PinValue::Low); } // Debug: Check pin states before first update println!("DEBUG: Before first update - SYNC: {:?}, P0: {:?}, PHI1: {:?}", sync_pin.lock().unwrap().read(), p0_pin.lock().unwrap().read(), phi1_pin.lock().unwrap().read()); // Debug: Check conditions that would trigger RAM operation let (sync, _cm, p0, _) = ram.read_control_pins(); println!("DEBUG: Control pins - sync: {}, p0: {}, combined: {}", sync, p0, sync && p0); // Debug: Check initial clock state println!("DEBUG: Before first update - prev_phi1: {:?}", ram.prev_phi1); // High nibble phi1_pin .lock() .unwrap() .set_driver(Some("TEST".into()), PinValue::High); ram.update(); // rising edge -> latch high nibble // Debug: Check clock state after first update println!("DEBUG: After first update - prev_phi1: {:?}", ram.prev_phi1); phi1_pin .lock() .unwrap() .set_driver(Some("TEST".into()), PinValue::Low); ram.update(); // falling edge // Should have transitioned to AddressPhase and latched high nibble assert_eq!(ram.ram_state, RamState::AddressPhase); assert_eq!(ram.address_high_nibble, Some(0x0)); // Note: full_address_ready should be false until both nibbles are assembled // Set address low nibble (0x1) on data bus - different from high nibble { let mut d0_guard = d0_pin.lock().unwrap(); let mut d1_guard = d1_pin.lock().unwrap(); let mut d2_guard = d2_pin.lock().unwrap(); let mut d3_guard = d3_pin.lock().unwrap(); d0_guard.set_driver(Some("TEST".into()), PinValue::High); // Bit 0 = 1 d1_guard.set_driver(Some("TEST".into()), PinValue::Low); d2_guard.set_driver(Some("TEST".into()), PinValue::Low); d3_guard.set_driver(Some("TEST".into()), PinValue::Low); } // Debug: Check state before low nibble println!("DEBUG: Before low nibble - High: {:?}, Low: {:?}, Ready: {}", ram.address_high_nibble, ram.address_low_nibble, ram.full_address_ready); // Low nibble phi1_pin .lock() .unwrap() .set_driver(Some("TEST".into()), PinValue::High); ram.update(); // rising edge -> latch low nibble // Debug: Check state after low nibble println!("DEBUG: After low nibble - High: {:?}, Low: {:?}, Ready: {}", ram.address_high_nibble, ram.address_low_nibble, ram.full_address_ready); phi1_pin .lock() .unwrap() .set_driver(Some("TEST".into()), PinValue::Low); ram.update(); // falling edge // Should have assembled full address and transitioned to WaitLatency assert_eq!(ram.last_address, 0x01); // High nibble 0x0, low nibble 0x1 = 0x01 assert_eq!(ram.full_address_ready, true); // Now both nibbles are assembled assert_eq!(ram.ram_state, RamState::WaitLatency); } #[test] fn test_reset_behavior() { let mut ram = Intel4002::new_with_access_time("RAM_4002".to_string(), 1); // Set up some state first let sync_pin = ram.get_pin("SYNC").unwrap(); let p0_pin = ram.get_pin("P0").unwrap(); let reset_pin = ram.get_pin("RESET").unwrap(); // Set RAM operation active { 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); } // Trigger some state changes ram.update(); // Now assert RESET { let mut reset_guard = reset_pin.lock().unwrap(); reset_guard.set_driver(Some("TEST".to_string()), PinValue::High); } // Update with RESET high - should clear all state ram.update(); // Verify all state is cleared assert_eq!(ram.get_output_port(0).unwrap(), 0); assert_eq!(ram.get_input_latch(), 0); assert_eq!(ram.get_bank_select(), 0); // Verify data bus is tri-stated let d0_pin = ram.get_pin("D0").unwrap(); assert_eq!(d0_pin.lock().unwrap().read(), PinValue::HighZ); } #[test] fn test_output_port_operations() { let mut ram = Intel4002::new("RAM_4002".to_string()); // Test output port operations ram.handle_output_port_operation(0, 0x05); assert_eq!(ram.get_output_port(0).unwrap(), 0x05); ram.handle_output_port_operation(1, 0x0A); assert_eq!(ram.get_output_port(1).unwrap(), 0x0A); // Test invalid port (should not crash) ram.handle_output_port_operation(4, 0x0F); } #[test] fn test_input_latch_operations() { let mut ram = Intel4002::new("RAM_4002".to_string()); // Set input latch ram.set_input_latch(0x07); // Mock data bus read to return input latch value // In real test, this would involve setting up pin states // For now, just verify the latch is set correctly assert_eq!(ram.get_input_latch(), 0x07); } #[test] fn test_ram_initialization() { let mut ram = Intel4002::new("RAM_4002".to_string()); let test_data = vec![0x01, 0x02, 0x03, 0x04, 0x05]; assert!(ram.initialize_ram(&test_data).is_ok()); assert_eq!(ram.read_ram(0).unwrap(), 0x01); assert_eq!(ram.read_ram(1).unwrap(), 0x02); assert_eq!(ram.read_ram(2).unwrap(), 0x03); assert_eq!(ram.read_ram(3).unwrap(), 0x04); assert_eq!(ram.read_ram(4).unwrap(), 0x05); // Test overflow let large_data = vec![0u8; 81]; // More than 80 nibbles assert!(ram.initialize_ram(&large_data).is_err()); } #[test] fn test_ram_state_transitions() { let ram = Intel4002::new_with_access_time("RAM_4002".to_string(), 1); // Initially should be in idle state assert_eq!(ram.ram_state, RamState::Idle); // Test state transitions through debug function ram.debug_state_transitions("INITIAL"); assert_eq!(ram.ram_state, RamState::Idle); } #[test] fn test_pin_configuration() { let ram = Intel4002::new("RAM_4002".to_string()); // Test that all expected pins are present assert!(ram.get_pin("D0").is_ok()); assert!(ram.get_pin("D1").is_ok()); assert!(ram.get_pin("D2").is_ok()); assert!(ram.get_pin("D3").is_ok()); assert!(ram.get_pin("O0").is_ok()); assert!(ram.get_pin("O1").is_ok()); assert!(ram.get_pin("O2").is_ok()); assert!(ram.get_pin("O3").is_ok()); assert!(ram.get_pin("SYNC").is_ok()); assert!(ram.get_pin("CM").is_ok()); assert!(ram.get_pin("P0").is_ok()); assert!(ram.get_pin("RESET").is_ok()); assert!(ram.get_pin("PHI1").is_ok()); assert!(ram.get_pin("PHI2").is_ok()); } #[test] fn test_dcl_instruction() { let mut ram = Intel4002::new("TEST".to_string()); // Simulate DCL instruction on data bus during instruction phase ram.setup_instruction_test(0xF1); // DCL to select bank 1 ram.handle_special_instructions(0xF1); assert_eq!(ram.get_bank_select(), 1); } #[test] fn test_src_instruction() { let mut ram = Intel4002::new("TEST".to_string()); // SRC instruction sets up output port addressing ram.setup_instruction_test(0x25); // SRC with specific address // Verify output port addressing is set up correctly // This would require more complex setup in a real implementation } }