use parking_lot::Mutex; use std::any::Any; use std::collections::HashMap; use std::sync::Arc; use std::thread; use std::time::Duration; use crate::component::{BaseComponent, Component, RunnableComponent}; use crate::component_timing::ComponentTiming; use crate::components::common::intel_400x::{ process_event_direct, Intel400xAddressHandling, Intel400xClockHandling, Intel400xComponentBehavior, Intel400xComponentType, Intel400xControlPins, Intel400xDataBus, Intel400xEvent, Intel400xResetHandling, Intel400xStateMachine, Intel400xTimingState, Intel400xUnifiedState, TimingState, }; use crate::pin::{Pin, PinValue}; use crate::virtual_clock::VirtualClock; /// Intel 4001 - 256-byte ROM with integrated I/O /// Part of the MCS-4 family, designed to work with Intel 4004 CPU /// Features 256 bytes of mask-programmable ROM and 4 I/O pins /// /// Hardware Deviations: /// - I/O read results are written to D0-D3 for test convenience /// (real hardware reads directly from I/O pins) /// - SYNC logic simplified for unit testing (may need refinement for CPU integration) pub struct Intel4001 { pub base: BaseComponent, memory: Vec, // 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, // Currently selected I/O port (0-3) // Unified state machine implementation state_machine: Intel400xStateMachine, // Unified state machine for Intel 400x components // Clock edge detection prev_phi1: PinValue, // Previous Φ1 clock state for edge detection prev_phi2: PinValue, // Previous Φ2 clock state for edge detection // M1/M2 data phase tracking // M1: ROM drives instruction high nibble (OPR, bits 7-4) // M2: ROM drives instruction low nibble (OPA, bits 3-0) data_nibble_phase: bool, // false = OPR (high nibble), true = OPA (low nibble) // A3 wait counter: after latching both address nibbles (A1+A2), the ROM // must wait one more full clock cycle for the CPU's A3 cycle before driving data. // This counter tracks how many PHI2 rising edges to wait in WaitLatency. a3_wait_cycles: u8, // Track whether data was actually driven during this PHI2 high period data_driven_this_cycle: bool, // Virtual time support (Phase 6) virtual_clock: Option>, // Injected by ConfigurableSystem (Phase 7) /// Virtual time snapshot for the current update cycle. /// Set at the start of `update_at_time()` so all internal methods use a /// consistent time value instead of reading `VirtualClock::now()` which /// may have advanced since the cycle started. current_update_time: Duration, } #[derive(Debug, Clone, Copy, PartialEq)] /// I/O mode configuration for 4001 ROM /// Determines how I/O pins are configured during read/write operations pub enum IoMode { Input, // I/O pins configured as inputs (RDM instruction) Output, // I/O pins configured as outputs (WRM instruction) 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 } } impl Intel400xDataBus for Intel4001 { fn get_base(&self) -> &BaseComponent { &self.base } } impl Intel400xAddressHandling for Intel4001 { fn get_base(&self) -> &BaseComponent { &self.base } } impl Intel400xControlPins for Intel4001 { fn get_base(&self) -> &BaseComponent { &self.base } } impl Intel400xResetHandling for Intel4001 { fn get_base(&self) -> &BaseComponent { &self.base } fn perform_reset(&mut self) { // Reset operations specific to Intel4001 // Note: This is called from handle_reset, so we don't need to check reset pin again self.state_machine.reset(); self.tri_state_data_bus(); self.tri_state_io_pins(); // Reset I/O state self.io_ports = [0u8; 4]; self.io_direction = [IoDirection::Input; 4]; self.selected_io_port = None; self.io_mode = IoMode::Input; // Reset I/O mode to Input // Reset clock edge detection state to ensure proper edge detection after reset // This is critical for tests that reset the chip between operations self.prev_phi1 = PinValue::Low; self.prev_phi2 = PinValue::Low; // Reset A3 wait counter and data nibble phase self.a3_wait_cycles = 0; self.data_nibble_phase = false; } } impl Intel400xTimingState for Intel4001 { fn get_timing_state(&self) -> TimingState { // Convert from Intel400xUnifiedState to TimingState match self.state_machine.get_state() { Intel400xUnifiedState::Idle => TimingState::Idle, Intel400xUnifiedState::AddressPhase => TimingState::AddressPhase, Intel400xUnifiedState::WaitLatency => TimingState::WaitLatency, Intel400xUnifiedState::DriveData => TimingState::DriveData, // Intel 4001 doesn't use component-specific states _ => TimingState::Idle, } } fn set_timing_state(&mut self, state: TimingState) { // Convert from TimingState to Intel400xUnifiedState let unified_state = match state { TimingState::Idle => Intel400xUnifiedState::Idle, TimingState::AddressPhase => Intel400xUnifiedState::AddressPhase, TimingState::WaitLatency => Intel400xUnifiedState::WaitLatency, TimingState::DriveData => Intel400xUnifiedState::DriveData, }; // Use state machine's reset method to transition to new state // This is a compatibility method for legacy state handling self.state_machine.reset(); self.state_machine.current_state = unified_state; } fn get_address_latch_time(&self) -> Option { self.state_machine.get_address_latch_time() } fn set_address_latch_time(&mut self, time: Option) { self.state_machine.set_address_latch_time(time); } fn get_full_address_ready(&self) -> bool { self.state_machine.is_full_address_ready() } fn set_full_address_ready(&mut self, _ready: bool) { // This is handled by state machine, but keep for compatibility } fn get_address_high_nibble(&self) -> Option { self.state_machine.get_address_high_nibble() } fn set_address_high_nibble(&mut self, nibble: Option) { self.state_machine.set_address_high_nibble(nibble); } fn get_address_low_nibble(&self) -> Option { self.state_machine.get_address_low_nibble() } fn set_address_low_nibble(&mut self, nibble: Option) { self.state_machine.set_address_low_nibble(nibble); } fn get_access_time(&self) -> Duration { self.state_machine.get_access_time() } fn is_latency_elapsed_at(&self, virtual_time: Duration) -> bool { self.state_machine.is_latency_elapsed_at(virtual_time) } } impl Intel400xComponentBehavior for Intel4001 { fn get_component_type(&self) -> Intel400xComponentType { Intel400xComponentType::Intel4001 } fn on_state_entry(&mut self, state: Intel400xUnifiedState) { match state { Intel400xUnifiedState::Idle => { // Entry actions for Idle state self.tri_state_data_bus(); // Reset address state for next fetch self.state_machine.set_address_high_nibble(None); self.state_machine.set_address_low_nibble(None); // Reset data driving state for next fetch self.data_driven_this_cycle = false; self.data_nibble_phase = false; // Reset to OPR phase } Intel400xUnifiedState::AddressPhase => { // Entry actions for AddressPhase state // Address latching will happen in execute_state_logic // Reset address state to ensure clean latch self.state_machine.set_address_high_nibble(None); self.state_machine.set_address_low_nibble(None); } Intel400xUnifiedState::WaitLatency => { // Entry actions for WaitLatency state // Address is already latched, start access timer } Intel400xUnifiedState::DriveData => { // Entry actions for DriveData state // Data driving will happen in execute_state_logic } // Intel 4001 doesn't use component-specific states _ => {} // Handle all other states } } fn on_state_exit(&mut self, state: Intel400xUnifiedState) { match state { Intel400xUnifiedState::DriveData => { // Exit actions for DriveData state self.tri_state_data_bus(); } _ => {} // Handle all other states } } fn handle_component_event(&mut self, event: Intel400xEvent) -> bool { match event { Intel400xEvent::ClockPhi1Rising => { // Handle Φ1 rising edge self.handle_phi1_rising(); true // Event was handled } Intel400xEvent::ClockPhi1Falling => { // Handle Φ1 falling edge self.handle_phi1_falling(); true // Event was handled } Intel400xEvent::ClockPhi2Rising => { // Handle Φ2 rising edge self.handle_phi2_rising(); true // Event was handled } Intel400xEvent::ClockPhi2Falling => { // Handle Φ2 falling edge self.handle_phi2_falling(); true // Event was handled } Intel400xEvent::ResetActive => { // Handle reset self.perform_reset(); true // Event was handled } _ => false, // Let the generic state machine handle other events } } fn execute_state_logic(&mut self, state: Intel400xUnifiedState) { match state { Intel400xUnifiedState::Idle => { // In idle state, ensure bus is tri-stated self.tri_state_data_bus(); } Intel400xUnifiedState::AddressPhase => { // Handle address latching during address phase self.handle_address_latching(); } Intel400xUnifiedState::WaitLatency => { // Check if latency has elapsed using virtual time. // // **With VirtualClock (production):** `address_latch_time` was recorded // by `handle_address_nibble_at()` in virtual time. We check if enough // virtual time has elapsed since then (≥ 500ns access time). // // **Without VirtualClock (standalone tests):** `current_virtual_time()` // returns `Duration::ZERO`, so `is_latency_elapsed_at()` would always // return `false` (since virtual time never advances). The `!has_clock` // guard short-circuits to skip the wait immediately. // // NOTE: The old `Instant::now()`-based code would naturally wait ~500ns // because wall-clock time always advances. This new code skips the wait // in legacy mode, making standalone tests less hardware-accurate but // preventing them from hanging forever in WaitLatency. let has_clock = self.virtual_clock.is_some(); let vtime = self.current_update_time; if !has_clock || self.state_machine.is_latency_elapsed_at(vtime) { self.process_latency_elapsed(); } } Intel400xUnifiedState::DriveData => { // Drive data on bus self.handle_data_driving(); } // Intel 4001 doesn't use component-specific states _ => {} } } fn responds_to_address(&self) -> bool { true // Intel 4001 responds to address transmission } } impl Intel4001 { /// Create a new Intel 4001 ROM with specified access time /// Parameters: name - Component identifier, access_time_ns - Memory access time in nanoseconds /// Returns: New Intel4001 instance with configurable access timing pub fn new(name: String) -> Self { Self::new_with_access_time(name, 500) // Default 500ns access time } /// Create a new Intel 4001 ROM with custom access time (for testing) /// Parameters: name - Component identifier, access_time_ns - Memory access time in nanoseconds /// Returns: New Intel4001 instance with configurable access timing pub fn new_with_access_time(name: String, access_time_ns: u64) -> Self { // Intel 4001 has 256 bytes of ROM let rom_size = 256; // Intel 4001 pinout (per datasheet): // - 4 data pins (D0-D3) for multiplexed address/data // - 4 I/O pins (IO0-IO3) // - Control pins: SYNC, RESET, CM, CI // - Clock pins: Φ1, Φ2 (two-phase clock from 4004 CPU) // // Control pin behavior: // - SYNC: Marks start of instruction cycle // - RESET: Clears internal state // - CM: Chip select (must be HIGH for ROM access) // - CI: I/O select (distinguishes I/O vs memory when chip selected) let pin_names = vec![ "D0", "D1", "D2", "D3", // Data/Address pins "IO0", "IO1", "IO2", "IO3", // I/O pins "SYNC", // Sync signal "CM", // CM-ROM: ROM/RAM Chip Select "CI", // CM-RAM: RAM Chip Select (I/O vs ROM access) "RESET", // Reset "PHI1", // Clock phase 1 "PHI2", // Clock phase 2 ]; let pins = BaseComponent::create_pin_map(&pin_names, &name); let memory = vec![0u8; rom_size]; Intel4001 { base: BaseComponent::new(name, pins), memory, last_address: 0, access_time: Duration::from_nanos(access_time_ns), 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 state_machine: Intel400xStateMachine::new( Intel400xComponentType::Intel4001, Duration::from_nanos(access_time_ns), ), prev_phi1: PinValue::Low, prev_phi2: PinValue::Low, data_nibble_phase: false, // Start with OPR (high nibble) a3_wait_cycles: 0, // No A3 wait initially data_driven_this_cycle: false, // No data driven yet virtual_clock: None, current_update_time: Duration::ZERO, } } /// Inject a VirtualClock for virtual time-based operation. /// When set, `update()` uses virtual time and `run()` uses `wait_until()`. /// This method is called by ConfigurableSystem in Phase 7. pub fn set_virtual_clock(&mut self, clock: Arc) { self.virtual_clock = Some(clock); } /// Get current virtual time, or Duration::ZERO if no VirtualClock is injected. /// /// # Zero-Time Invariant /// /// When no VirtualClock is injected, this returns `Duration::ZERO`. The `WaitLatency` /// handler checks `vtime == Duration::ZERO` to skip latency enforcement in legacy mode. /// This is intentional: legacy tests don't use VirtualClock and expect latency to be /// skipped for backward compatibility. If this ever returns a non-zero value without /// a proper VirtualClock, the latency check would use zero as the latch time and /// produce incorrect results. fn current_virtual_time(&self) -> Duration { self.virtual_clock.as_ref().map_or(Duration::ZERO, |vc| vc.now()) } /// Set memory access time for simulation /// Parameters: access_time_ns - Access time in nanoseconds pub fn set_access_time(&mut self, access_time_ns: u64) { self.access_time = Duration::from_nanos(access_time_ns); self.state_machine .set_access_time(Duration::from_nanos(access_time_ns)); } /// Get current memory access time /// Returns: Access time in nanoseconds pub fn get_access_time(&self) -> u64 { self.access_time.as_nanos() as u64 } /// Load binary data into ROM at specified offset /// Parameters: data - Binary data to load, offset - Starting address /// Returns: Ok(()) on success, Err(String) on failure pub fn load_rom_data(&mut self, data: Vec, 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(()) } /// Load hexadecimal data into ROM at specified offset /// Parameters: hex_data - Space-separated hex bytes, offset - Starting address /// Returns: Ok(()) on success, Err(String) on failure pub fn load_from_hex(&mut self, hex_data: &str, offset: usize) -> Result<(), String> { let bytes: Result, _> = hex_data .split_whitespace() .map(|s| u8::from_str_radix(s.trim(), 16)) .collect(); match bytes { Ok(data) => self.load_rom_data(data, offset), Err(e) => Err(format!("Invalid hex data: {}", e)), } } /// Data bus methods now use common functionality fn read_io_pins(&self) -> u8 { let mut data = 0; for i in 0..4 { if let Ok(pin) = self.base.get_pin(&format!("IO{}", i)) { let pin_guard = pin.lock(); if pin_guard.read() == PinValue::High { data |= 1 << i; } } } data } /// Handle Φ1 rising edge - Start of address phase /// Hardware: Φ1 high = CPU starts driving bus with address/control information /// Memory operations start when SYNC goes high during Φ1 rising edge /// Focus: Start memory operations, I/O operations /// /// Intel 4004 Address Timing (CPU drives address during PHI1): /// - A1 cycle: CPU drives PC high nibble on PHI1 rising /// - A2 cycle: CPU drives PC low nibble on PHI1 rising /// - ROM latches each nibble on PHI1 RISING edge (CPU has just driven the address) fn handle_phi1_rising(&mut self) { // Handle system reset first (highest priority) self.handle_reset("RESET"); // Check for memory operation start on Φ1 rising edge with SYNC high // Hardware: Memory operations start when SYNC goes high during Φ1 // Note: SYNC marks instruction fetch start, but exact logic involves CPU cycle state. // ROM access: CM=1 (chip_select), CI=0 (!io_select) // 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_ci_pin(); if sync && chip_select && !io_select { // Start memory address phase on Φ1 rising edge (ROM access) // Only start a new memory operation if we're currently idle if self.state_machine.get_state() == Intel400xUnifiedState::Idle { self.process_chip_select(); } } // Latch address nibble on PHI1 rising edge // The CPU drives the address on PHI1 rising, and since CPU update runs // before ROM update, the address is already on the bus at this point. if self.state_machine.get_state() == Intel400xUnifiedState::AddressPhase { let nibble = self.read_data_bus() & 0x0F; if self.state_machine.handle_address_nibble_at(nibble, self.current_update_time) { // Address is complete — virtual time was recorded atomically by handle_address_nibble_at() // Transition to latency wait let _addr = self.state_machine.get_full_address().unwrap_or(0xFFFF); self.process_address_complete(); // Set A3 wait counter self.a3_wait_cycles = 1; } } // 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) if chip_select && io_select { self.handle_io_operation(); } } /// Handle Φ1 falling edge - No specific action needed /// Address latching is now done on PHI1 rising edge fn handle_phi1_falling(&mut self) { // Address latching moved to PHI1 rising edge } /// Handle Φ2 rising edge - Data phase /// Hardware: Φ2 high = Peripherals drive bus with data /// Focus: Data driving operations fn handle_phi2_rising(&mut self) { // Handle A3 wait cycle: after address complete, wait one full clock cycle // for the CPU's A3 cycle before transitioning to DriveData. if self.state_machine.get_state() == Intel400xUnifiedState::WaitLatency { if self.a3_wait_cycles > 0 { // Still waiting for A3 cycle to complete self.a3_wait_cycles -= 1; } else { // A3 wait complete, transition to DriveData. // IMPORTANT: Do NOT drive data on this same edge. // The first data drive happens on the NEXT PHI2 rising edge. // This ensures the ROM's OPR/OPA phases align with the CPU's M1/M2 cycles. self.process_latency_elapsed(); return; // Skip data driving on this edge } } // Drive data if in DriveData state (only on subsequent PHI2 rising edges) if self.state_machine.get_state() == Intel400xUnifiedState::DriveData { self.handle_data_driving(); } } /// Handle Φ2 falling edge - End of data phase /// Hardware: Φ2 low = End of data driving phase, time to tri-state bus /// Data is held on bus during Φ2 high, tri-stated when Φ2 falls /// Focus: Clean up and return to idle state fn handle_phi2_falling(&mut self) { match self.state_machine.get_state() { Intel400xUnifiedState::DriveData => { if self.data_driven_this_cycle { // Data was actually driven this cycle - toggle nibble phase if !self.data_nibble_phase { // Just finished OPR (M1), switch to OPA (M2) self.data_nibble_phase = true; self.tri_state_data_bus(); // Stay in DriveData for M2 cycle } else { // Just finished OPA (M2) - full 8-bit instruction sent // Return to idle so the next fetch can begin self.data_nibble_phase = false; self.tri_state_data_bus(); self.process_operation_complete(); } } else { // No data driven this cycle (e.g., just entered DriveData) // Don't toggle - just tri-state self.tri_state_data_bus(); } } Intel400xUnifiedState::Idle | Intel400xUnifiedState::AddressPhase | Intel400xUnifiedState::WaitLatency => { // For other states, just ensure bus is tri-stated self.tri_state_data_bus(); } // Intel 4001 doesn't use component-specific states _ => { self.tri_state_data_bus(); } } } fn tri_state_io_pins(&self) { for i in 0..4 { if let Ok(pin) = self.base.get_pin(&format!("IO{}", i)) { let mut pin_guard = pin.lock(); pin_guard.set_driver(Some(self.base.name()), PinValue::HighZ); } } } /// 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. /// /// I/O Port Addressing (datasheet): /// - I/O ports are addressed using lower 2 bits of address /// - Port 0: Address 0x00, Port 1: Address 0x01, etc. /// - Each port is 4 bits wide (matches 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 chip_select = self.read_cm_rom_pin(); 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 port_address = self.read_data_bus() & 0x03; // Lower 2 bits for port selection (0-3) self.selected_io_port = Some(port_address as usize); // Determine operation type based on current instruction phase // In real hardware, this would be determined by 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 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") { let pin_guard = pin.lock(); pin_guard.read() == PinValue::High } else { false } } /// Read from a specific I/O port /// Parameters: port - I/O port number (0-3) /// Returns: 4-bit value from 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 I/O pins fn update_io_pins(&self) { for i in 0..4 { if let Ok(pin) = self.base.get_pin(&format!("IO{}", i)) { let 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 pin pin_guard.set_driver(Some(self.base.name()), PinValue::HighZ); } } } } } /// Handle memory address-related operations during Φ1 /// Hardware-accurate: Φ1 is when CPU drives address, so we handle address latching /// Memory operation start is checked in handle_phi1_rising() on SYNC high + Φ1 rising /// Focus: Address latching and setup operations #[allow(dead_code)] // This method is kept for compatibility but not currently used fn handle_memory_address_operations(&mut self) { match self.state_machine.get_state() { Intel400xUnifiedState::Idle => { // In idle state, ensure bus is tri-stated // Memory operation start is checked in handle_phi1_rising() self.tri_state_data_bus(); } Intel400xUnifiedState::AddressPhase => { // Currently latching address nibbles during Φ1 self.handle_address_latching(); } Intel400xUnifiedState::WaitLatency => { // Address latched, waiting for access latency // Check if latency has elapsed, and we can transition to data phase self.handle_latency_wait(); } Intel400xUnifiedState::DriveData => { // Data phase should be handled by Φ2, not Φ1 // Just ensure bus is tri-stated during wrong phase self.tri_state_data_bus(); } // Intel 4001 doesn't use component-specific states _ => { self.tri_state_data_bus(); } } } /// Handle memory data-related operations during Φ2 /// Hardware-accurate: Φ2 is when peripherals drive data, so we handle data driving /// Focus: Data driving operations #[allow(dead_code)] // This method is kept for compatibility but not currently used fn handle_memory_data_operations(&mut self) { // Reduced debug output - only show when actually driving data match self.state_machine.get_state() { Intel400xUnifiedState::Idle => { // During data phase, idle state means tri-state bus self.tri_state_data_bus(); } Intel400xUnifiedState::AddressPhase => { // Address phase should be handled by Φ1, not Φ2 // Tri-state bus during wrong phase self.tri_state_data_bus(); } Intel400xUnifiedState::WaitLatency => { // Address latched, waiting for access latency // Check if latency has elapsed, and we can transition to data phase self.handle_latency_wait(); // If we transitioned to DriveData, handle data driving if self.state_machine.get_state() == Intel400xUnifiedState::DriveData { self.handle_data_driving(); } } Intel400xUnifiedState::DriveData => { // Latency elapsed, drive data on bus during Φ2 // Data will remain on bus until Φ2 falling edge self.handle_data_driving(); } // Intel 4001 doesn't use component-specific states _ => { self.tri_state_data_bus(); } } } /// Handle address nibble latching during address phase /// Hardware: CPU provides 8-bit address as two 4-bit nibbles fn handle_address_latching(&mut self) { // Use common address latching functionality let nibble = self.read_data_bus() & 0x0F; // Only 4 bits are valid for address nibbles if self.state_machine.handle_address_nibble_at(nibble, self.current_update_time) { // Address is complete — virtual time was recorded atomically by handle_address_nibble_at() // Transition to latency wait self.process_address_complete(); } } /// Handle latency timing during wait state /// Hardware: ROM needs 500ns to access data after address is latched #[allow(dead_code)] // This method is kept for compatibility with legacy code fn handle_latency_wait(&mut self) { // Note: Legacy method — latency check now requires virtual time // Always transition for backward compatibility self.process_latency_elapsed(); } /// Handle data driving during DriveData state /// Hardware: ROM drives data bus when all conditions are met /// Data stays on bus during Φ2 high period, tri-stated on Φ2 falling edge /// IMPORTANT: Do NOT return to idle here after OPA - the data must stay on the bus /// for the CPU to read. Return to idle happens in handle_phi2_falling(). fn handle_data_driving(&mut self) { // Reset the data-driven flag before each drive attempt self.data_driven_this_cycle = false; // Once we're in DriveData state, we should drive data regardless of current control signals // The control signals were already checked when we started memory operation if self.state_machine.is_full_address_ready() { // Drive data on bus // Data will remain on bus until Φ2 falling edge let address = self.state_machine.get_full_address().unwrap_or(0) as u16; if (address as usize) < self.memory.len() { let data = self.memory[address as usize]; // Intel 4004 data bus is 4 bits wide. Instructions are 8 bits. // M1 (data_nibble_phase=false): Drive high nibble (OPR, bits 7-4) // M2 (data_nibble_phase=true): Drive low nibble (OPA, bits 3-0) let nibble = if self.data_nibble_phase { data & 0x0F // OPA: low nibble } else { (data >> 4) & 0x0F // OPR: high nibble }; self.write_data_bus(nibble); self.data_driven_this_cycle = true; // Do NOT return to idle here! The data must persist on the bus // for the CPU to read. Return to idle happens in handle_phi2_falling(). } else { // Invalid address, tri-state self.tri_state_data_bus(); } } else { // Address not ready, tri-state self.tri_state_data_bus(); } } /// Helper function to process latency elapsed event fn process_latency_elapsed(&mut self) { let old_state = self.state_machine.get_state(); let event = Intel400xEvent::LatencyElapsed; let new_state = process_event_direct(&mut self.state_machine, event); // Handle state entry/exit manually if new_state != old_state { self.on_state_exit(old_state); self.state_machine.current_state = new_state; self.on_state_entry(new_state); } } /// Helper function to process chip select event fn process_chip_select(&mut self) { let old_state = self.state_machine.get_state(); let event = Intel400xEvent::ChipSelect; let new_state = process_event_direct(&mut self.state_machine, event); if new_state != old_state { self.on_state_exit(old_state); self.on_state_entry(new_state); } } /// Helper function to process operation complete event fn process_operation_complete(&mut self) { let old_state = self.state_machine.get_state(); let event = Intel400xEvent::OperationComplete; let new_state = process_event_direct(&mut self.state_machine, event); if new_state != old_state { self.on_state_exit(old_state); self.on_state_entry(new_state); } } /// Helper function to process address complete event fn process_address_complete(&mut self) { let old_state = self.state_machine.get_state(); let event = Intel400xEvent::AddressComplete; let new_state = process_event_direct(&mut self.state_machine, event); if new_state != old_state { self.on_state_exit(old_state); self.on_state_entry(new_state); } } /// Helper function to execute unified state machine logic /// Note: This method is reserved for future use when state machine execution /// needs to be triggered explicitly rather than through clock edge handlers #[allow(dead_code)] fn execute_state_machine(&mut self) { // Execute the current state logic using the unified state machine let current_state = self.state_machine.get_state(); self.execute_state_logic(current_state); } } impl Component for Intel4001 { 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 - delegates to update_at_time with current virtual time /// Hardware: Responds to Φ1 and Φ2 clock edges from CPU fn update(&mut self) { let vtime = self.current_virtual_time(); self.update_at_time(vtime); } /// Virtual-time-aware update cycle - handles clock edge detection and operation dispatch /// Hardware: Responds to Φ1 and Φ2 clock edges from CPU fn update_at_time(&mut self, virtual_time: Duration) { // Snapshot virtual_time for the entire update cycle so internal methods // (handle_phi1_rising, execute_state_logic, etc.) use a consistent time // instead of reading VirtualClock::now() which may have advanced. self.current_update_time = virtual_time; if !self.is_running() { return; } // Handle both rising and falling edges for proper two-phase operation let phi1_rising = self.is_phi1_rising_edge(self.prev_phi1); let phi1_falling = self.is_phi1_falling_edge(self.prev_phi1); let phi2_rising = self.is_phi2_rising_edge(self.prev_phi2); let phi2_falling = self.is_phi2_falling_edge(self.prev_phi2); // Update clock states for next edge detection let (phi1, phi2) = self.read_clock_pins(); self.prev_phi1 = phi1; self.prev_phi2 = phi2; // Check for reset first (highest priority, independent of clock edges) let reset_active = self.read_reset_pin(); if reset_active { self.perform_reset(); // Skip further processing if reset is active return; } // Process clock events through component-specific handlers // IMPORTANT ORDER: Handle falling edges BEFORE rising edges. // When PHI1 rises and PHI2 falls simultaneously (same half-cycle), // we must complete the previous operation (PHI2 falling) before // starting a new one (PHI1 rising). This ensures the ROM returns // to idle from OPA completion before starting a new address phase. if phi2_falling { self.handle_phi2_falling(); } if phi1_falling { self.handle_phi1_falling(); } if phi1_rising { self.handle_phi1_rising(); } if phi2_rising { self.handle_phi2_rising(); } // Note: execute_state_machine() is NOT called here because: // 1. Address latching is handled directly in handle_phi1_rising() // 2. Data driving is handled directly in handle_phi2_rising() // 3. Calling it would cause double-latching of address nibbles } /// Run component with VirtualClock support or legacy fallback /// 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; // If no VirtualClock is injected, fall back to wall-clock run loop let vclock = match self.virtual_clock.as_ref() { Some(vc) => Arc::clone(vc), None => { // Legacy mode: use wall-clock timing while self.is_running() { self.update(); thread::sleep(Duration::from_micros(1)); } return; } }; // Virtual time mode: wait for next tick to synchronize with clock while self.is_running() { let next_tick = vclock.now() + vclock.tick_size(); if let Some(current_vtime) = vclock.wait_until(next_tick) { self.update_at_time(current_vtime); } else { // Clock was interrupted, exit break; } } } /// Get timing constraints for Intel 4001 ROM /// Returns: ComponentTiming with 500ns access time, 200ns propagation delay fn timing(&self) -> ComponentTiming { ComponentTiming::intel_4001() } /// 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_io_pins(); } fn is_running(&self) -> bool { self.base.is_running() } fn set_running(&mut self, running: bool) { self.base.set_running(running); } fn as_any(&self) -> &dyn Any { self } fn as_any_mut(&mut self) -> &mut dyn Any { self } fn set_virtual_clock_optional(&mut self, clock: Option>) { self.virtual_clock = clock; } fn has_virtual_clock(&self) -> bool { self.virtual_clock.is_some() } } impl RunnableComponent for Intel4001 {} // Intel 4001 specific methods impl Intel4001 { /// Debug: get current state machine state name pub fn debug_state(&self) -> &'static str { match self.state_machine.get_state() { Intel400xUnifiedState::Idle => "Idle", Intel400xUnifiedState::AddressPhase => "AddrPhase", Intel400xUnifiedState::WaitLatency => "WaitLatency", Intel400xUnifiedState::DriveData => "DriveData", _ => "Unknown", } } /// Debug: get current data nibble phase pub fn debug_nibble_phase(&self) -> bool { self.data_nibble_phase } /// Get ROM size in bytes /// Returns: Total number of bytes in ROM (256 for 4001) pub fn get_rom_size(&self) -> usize { self.memory.len() } /// Read a byte from ROM at specified address /// Parameters: address - 8-bit address (0-255) /// Returns: Some(data) if address valid, None if out of bounds pub fn read_rom(&self, address: u8) -> Option { if (address as usize) < self.memory.len() { Some(self.memory[address as usize]) } else { None } } /// Get current output latch value /// Returns: 4-bit value last written to I/O ports pub fn get_output_latch(&self) -> u8 { self.output_latch } /// Get current input latch value /// Returns: 4-bit value last read from I/O ports pub fn get_input_latch(&self) -> u8 { 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 { 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 { 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 { 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) { println!( "{} - State: {:?}, High: {:?}, Low: {:?}, Address: 0x{:x}, Ready: {}, Latch time: {:?}", test_name, self.state_machine.get_state(), self.state_machine.get_address_high_nibble(), self.state_machine.get_address_low_nibble(), self.last_address, self.state_machine.is_full_address_ready(), self.state_machine.get_address_latch_time() ); } } // Custom formatter for debugging impl std::fmt::Display for IoMode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { IoMode::Input => write!(f, "Input"), IoMode::Output => write!(f, "Output"), IoMode::Bidirectional => write!(f, "Bidirectional"), } } } 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::*; #[test] fn test_intel4001_creation() { let rom = Intel4001::new("ROM_4001".to_string()); assert_eq!(rom.name(), "ROM_4001"); assert_eq!(rom.get_rom_size(), 256); assert_eq!(rom.get_access_time(), 500); // Default 500ns assert!(!rom.is_running()); } #[test] fn test_intel4001_rom_loading() { let mut rom = Intel4001::new("ROM_4001".to_string()); let test_data = vec![0x12, 0x34, 0x56, 0x78]; assert!(rom.load_rom_data(test_data.clone(), 0).is_ok()); assert_eq!(rom.read_rom(0).unwrap(), 0x12); assert_eq!(rom.read_rom(1).unwrap(), 0x34); assert_eq!(rom.read_rom(2).unwrap(), 0x56); assert_eq!(rom.read_rom(3).unwrap(), 0x78); } #[test] fn test_intel4001_io_modes() { let rom = Intel4001::new("ROM_4001".to_string()); // Test that I/O mode starts as Input (after reset) // Note: IoMode is now internal and not exposed via public API // I/O direction is determined by CPU instructions (WRM/RDM) assert_eq!(rom.name(), "ROM_4001"); } #[test] fn test_intel4001_latches() { let rom = Intel4001::new("ROM_4001".to_string()); // Initial state assert_eq!(rom.get_output_latch(), 0); assert_eq!(rom.get_input_latch(), 0); // These would be set during actual I/O operations // The test verifies latch structures exist } #[test] fn test_io_mode_display() { assert_eq!(IoMode::Input.to_string(), "Input"); assert_eq!(IoMode::Output.to_string(), "Output"); assert_eq!(IoMode::Bidirectional.to_string(), "Bidirectional"); } #[test] fn test_configurable_access_time() { let mut rom = Intel4001::new("ROM_4001".to_string()); // Test default access time assert_eq!(rom.get_access_time(), 500); // Test setting custom access time rom.set_access_time(100); assert_eq!(rom.get_access_time(), 100); // Test constructor with custom access time let fast_rom = Intel4001::new_with_access_time("FAST_ROM".to_string(), 1); assert_eq!(fast_rom.get_access_time(), 1); assert_eq!(fast_rom.name(), "FAST_ROM"); } #[test] fn test_basic_memory_read() { let mut rom = Intel4001::new_with_access_time("ROM_4001".to_string(), 1); // Load test data let test_data = vec![0x12, 0x34]; rom.load_rom_data(test_data, 0).unwrap(); // Test direct memory read (bypassing pin simulation) assert_eq!(rom.read_rom(0x00).unwrap(), 0x12); 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); // Initially should be in idle state assert_eq!(rom.state_machine.current_state, Intel400xUnifiedState::Idle); // Start component so it can process updates rom.base.set_running(true); assert!(rom.is_running()); // Get pin references let sync_pin = rom.get_pin("SYNC").unwrap(); let cm_pin = rom.get_pin("CM").unwrap(); let ci_pin = rom.get_pin("CI").unwrap(); let phi1_pin = rom.get_pin("PHI1").unwrap(); // Initialize PHI1 to Low first { let mut phi1_guard = phi1_pin.lock(); phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low); } // Set up memory read operation: SYNC=1, CM=1, CI=0 (memory access) { let mut sync_guard = sync_pin.lock(); let mut cm_guard = cm_pin.lock(); let mut ci_guard = ci_pin.lock(); sync_guard.set_driver(Some("TEST".to_string()), PinValue::High); cm_guard.set_driver(Some("TEST".to_string()), PinValue::High); ci_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Memory access } // Set Φ1 high (creating rising edge) { let mut phi1_guard = phi1_pin.lock(); phi1_guard.set_driver(Some("TEST".to_string()), PinValue::High); } // Debug: Check conditions that would trigger memory operation let sync = rom.read_sync_pin(); let chip_select = rom.read_cm_rom_pin(); let io_select = rom.read_ci_pin(); println!( "DEBUG: Conditions - sync: {}, chip_select: {}, io_select: {}, combined: {}", sync, chip_select, io_select, sync && chip_select && !io_select ); // Update to process Φ1 rising edge rom.update(); // Debug: Check edge detection results println!("DEBUG: After update - prev_phi1: {:?}", rom.prev_phi1); // Should have started memory operation and transitioned to AddressPhase // because SYNC=1, CM=1, CI=0 triggers chip select on PHI1 rising edge assert_eq!( rom.state_machine.current_state, Intel400xUnifiedState::AddressPhase ); println!( "Memory state after Φ1 rising: {:?}", rom.state_machine.current_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 // Load test data: address 0x01 should contain 0x34 let test_data = vec![0x12, 0x34, 0x56, 0x78]; rom.load_rom_data(test_data, 0).unwrap(); // Get pin references for simulation let sync_pin = rom.get_pin("SYNC").unwrap(); let cm_pin = rom.get_pin("CM").unwrap(); let ci_pin = rom.get_pin("CI").unwrap(); // Set up memory read operation: SYNC=1, chip_select=1, io_select=0 (memory access) { let mut sync_guard = sync_pin.lock(); let mut cm_guard = cm_pin.lock(); let mut ci_guard = ci_pin.lock(); sync_guard.set_driver(Some("TEST".to_string()), PinValue::High); cm_guard.set_driver(Some("TEST".to_string()), PinValue::High); ci_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Memory access, not I/O } // Test that memory state machine works correctly // The state machine should handle two-phase addressing properly rom.update(); // Verify that ROM correctly read test data assert_eq!(rom.read_rom(0).unwrap(), 0x12); assert_eq!(rom.read_rom(1).unwrap(), 0x34); assert_eq!(rom.read_rom(2).unwrap(), 0x56); assert_eq!(rom.read_rom(3).unwrap(), 0x78); } #[test] fn test_reset_behavior() { let mut rom = Intel4001::new_with_access_time("ROM_4001".to_string(), 1); // 1ns for fast testing // Load test data let test_data = vec![0x12, 0x34, 0x56, 0x78]; rom.load_rom_data(test_data, 0).unwrap(); // Set up some state first let sync_pin = rom.get_pin("SYNC").unwrap(); let cm_pin = rom.get_pin("CM").unwrap(); let ci_pin = rom.get_pin("CI").unwrap(); let reset_pin = rom.get_pin("RESET").unwrap(); // Set memory operation active { let mut sync_guard = sync_pin.lock(); let mut cm_guard = cm_pin.lock(); let mut ci_guard = ci_pin.lock(); sync_guard.set_driver(Some("TEST".to_string()), PinValue::High); cm_guard.set_driver(Some("TEST".to_string()), PinValue::High); ci_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Memory access, not I/O } // Trigger some state changes rom.update(); // Now assert RESET { let mut reset_guard = reset_pin.lock(); reset_guard.set_driver(Some("TEST".to_string()), PinValue::High); } // Update with RESET high - should clear all state rom.update(); // Verify all state is cleared assert_eq!(rom.get_output_latch(), 0); assert_eq!(rom.get_input_latch(), 0); // Verify data bus is tri-stated let d0_pin = rom.get_pin("D0").unwrap(); let d1_pin = rom.get_pin("D1").unwrap(); let d2_pin = rom.get_pin("D2").unwrap(); let d3_pin = rom.get_pin("D3").unwrap(); // After reset, all pins should be HighZ (tri-stated) assert_eq!(d0_pin.lock().read(), PinValue::HighZ); assert_eq!(d1_pin.lock().read(), PinValue::HighZ); assert_eq!(d2_pin.lock().read(), PinValue::HighZ); assert_eq!(d3_pin.lock().read(), PinValue::HighZ); } #[test] fn test_tri_state_behavior() { let mut rom = Intel4001::new_with_access_time("ROM_4001".to_string(), 1); // 1ns for fast testing // Load test data let test_data = vec![0x12, 0x34, 0x56, 0x78]; rom.load_rom_data(test_data, 0).unwrap(); let d0_pin = rom.get_pin("D0").unwrap(); let d1_pin = rom.get_pin("D1").unwrap(); let d2_pin = rom.get_pin("D2").unwrap(); let d3_pin = rom.get_pin("D3").unwrap(); // Initially, no operation - should be tri-stated assert_eq!(d0_pin.lock().read(), PinValue::HighZ); assert_eq!(d1_pin.lock().read(), PinValue::HighZ); assert_eq!(d2_pin.lock().read(), PinValue::HighZ); assert_eq!(d3_pin.lock().read(), PinValue::HighZ); // Set up memory operation but don't complete address phase let sync_pin = rom.get_pin("SYNC").unwrap(); let cm_pin = rom.get_pin("CM").unwrap(); let ci_pin = rom.get_pin("CI").unwrap(); let mut sync_guard = sync_pin.lock(); let mut cm_guard = cm_pin.lock(); let mut ci_guard = ci_pin.lock(); sync_guard.set_driver(Some("TEST".to_string()), PinValue::High); cm_guard.set_driver(Some("TEST".to_string()), PinValue::High); ci_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Memory access, not I/O // Trigger update - should start address phase but still tri-state rom.update(); // Should still be tri-stated during address phase assert_eq!(d0_pin.lock().read(), PinValue::HighZ); assert_eq!(d1_pin.lock().read(), PinValue::HighZ); assert_eq!(d2_pin.lock().read(), PinValue::HighZ); assert_eq!(d3_pin.lock().read(), PinValue::HighZ); // Complete address phase but don't wait for latency // Should still be tri-stated rom.update(); // Should still be tri-stated (waiting for latency) assert_eq!(d0_pin.lock().read(), PinValue::HighZ); assert_eq!(d1_pin.lock().read(), PinValue::HighZ); assert_eq!(d2_pin.lock().read(), PinValue::HighZ); assert_eq!(d3_pin.lock().read(), PinValue::HighZ); } #[test] fn test_intel4001_state_transitions() { let mut rom = Intel4001::new_with_access_time("StateTestROM".to_string(), 1); // Test all state transitions let states = vec![ TimingState::Idle, TimingState::AddressPhase, TimingState::WaitLatency, TimingState::DriveData, ]; for &state in &states { rom.set_timing_state(state); assert_eq!(rom.get_timing_state(), state); } // Test return to idle rom.set_timing_state(TimingState::Idle); assert_eq!(rom.get_timing_state(), TimingState::Idle); } #[test] fn test_intel4001_address_handling() { let mut rom = Intel4001::new_with_access_time("AddressTestROM".to_string(), 1); // Test address nibble handling rom.set_address_high_nibble(Some(0x12)); rom.set_address_low_nibble(Some(0x34)); rom.set_full_address_ready(true); assert_eq!(rom.get_address_high_nibble(), Some(0x12)); assert_eq!(rom.get_address_low_nibble(), Some(0x34)); assert_eq!(rom.state_machine.is_full_address_ready(), true); // Test clearing rom.set_address_high_nibble(None); rom.set_address_low_nibble(None); rom.set_full_address_ready(false); assert_eq!(rom.get_address_high_nibble(), None); assert_eq!(rom.get_address_low_nibble(), None); assert_eq!(rom.state_machine.is_full_address_ready(), false); } #[test] fn test_intel4001_error_conditions() { let mut rom = Intel4001::new("ErrorTestROM".to_string()); // Test invalid memory access assert_eq!(rom.read_rom(0xFF), Some(0x00)); // Default value for unmapped memory // Test invalid data loading assert!(rom.load_rom_data(vec![0x12], 255).is_ok()); // Valid 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 field exists and can be modified assert_eq!(rom.selected_io_port, None); } #[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); } }