From 5bc1916b2fc06dc8801898b8fd113fee85f517b6 Mon Sep 17 00:00:00 2001 From: Jan-Peter von Hunnius Date: Sat, 27 Sep 2025 21:37:54 +0200 Subject: [PATCH] Introduced comments to some files Improved accuracy and test of the Intel 4001 ROM. Tests currently fail on the 4001. --- src/component.rs | 52 +- src/components/cpu/intel_4004.rs | 111 ++- src/components/memory/intel_4001.rs | 1429 ++++++++++++++++++++++++--- 3 files changed, 1420 insertions(+), 172 deletions(-) diff --git a/src/component.rs b/src/component.rs index 722309b..f721e42 100644 --- a/src/component.rs +++ b/src/component.rs @@ -8,17 +8,45 @@ use std::time::Duration; use crate::pin::Pin; -// Component is now inherently thread-safe +/// Core trait for all hardware components in the emulator +/// Provides the fundamental interface that all components must implement +/// Components are inherently thread-safe and can be used across thread boundaries pub trait Component: Send + Sync { + /// Get the name/identifier of this component + /// Returns: Component name as a String fn name(&self) -> String; + + /// Get all pins associated with this component + /// Returns: HashMap mapping pin names to pin objects fn pins(&self) -> HashMap>>; + + /// Get a specific pin by name + /// Parameters: name - The name of the pin to retrieve + /// Returns: Ok(pin) if found, Err(String) if pin not found fn get_pin(&self, name: &str) -> Result>, String>; + + /// Update the component state for one simulation cycle + /// This method is called repeatedly during component execution fn update(&mut self); + + /// Run the component in a continuous loop until stopped + /// This method blocks until the component is stopped fn run(&mut self); + + /// Stop the component and clean up resources + /// This method should tri-state all outputs and prepare for shutdown fn stop(&mut self); + + /// Check if the component is currently running + /// Returns: true if component is running, false otherwise fn is_running(&self) -> bool; } +/// Extended trait for components that can be run in their own threads +/// Provides automatic thread spawning functionality for components pub trait RunnableComponent: Component + Send + 'static { + /// Spawn the component in its own thread + /// Parameters: self - The component to spawn (consumed by the thread) + /// Returns: JoinHandle for the spawned thread fn spawn_in_thread(mut self) -> thread::JoinHandle<()> where Self: Sized, @@ -28,7 +56,9 @@ pub trait RunnableComponent: Component + Send + 'static { }) } } -// BaseComponent uses AtomicBool for thread-safe state +/// Base implementation of the Component trait providing common functionality +/// Handles thread-safe state management and basic pin operations +/// Most hardware components should embed this struct to inherit common behavior pub struct BaseComponent { name: String, pins: HashMap>>, @@ -36,6 +66,9 @@ pub struct BaseComponent { } impl BaseComponent { + /// Create a new BaseComponent with the specified name and pins + /// Parameters: name - Component identifier, pins - Pin mapping for the component + /// Returns: New BaseComponent instance pub fn new(name: String, pins: HashMap>>) -> Self { BaseComponent { name, @@ -44,18 +77,27 @@ impl BaseComponent { } } + /// Get the name of this component + /// Returns: Component name as string slice pub fn get_name(&self) -> &str { &self.name } + /// Check if the component is currently running + /// Returns: true if running, false otherwise pub fn is_running(&self) -> bool { self.running.load(Ordering::SeqCst) } + /// Set the running state of the component + /// Parameters: running - New running state pub fn set_running(&self, running: bool) { self.running.store(running, Ordering::SeqCst); } + /// Create a pin mapping from a list of pin names + /// Parameters: pin_names - List of pin name strings, component_name - Name of the component + /// Returns: HashMap mapping pin names to Pin objects with proper naming pub fn create_pin_map( pin_names: &[&str], component_name: &str, @@ -90,10 +132,14 @@ impl Component for BaseComponent { .ok_or_else(|| format!("Pin {} not found", name)) } + /// Update the component state for one simulation cycle + /// Base implementation does nothing - should be overridden by specific components fn update(&mut self) { // Base implementation does nothing } + /// Run the component in a continuous loop until stopped + /// Provides a default time-sliced execution model with 10 microsecond delays fn run(&mut self) { self.set_running(true); while self.is_running() { @@ -102,6 +148,8 @@ impl Component for BaseComponent { } } + /// Stop the component and clean up resources + /// Sets running state to false to exit the run loop fn stop(&mut self) { self.set_running(false); } diff --git a/src/components/cpu/intel_4004.rs b/src/components/cpu/intel_4004.rs index 1c7bf94..44157dc 100644 --- a/src/components/cpu/intel_4004.rs +++ b/src/components/cpu/intel_4004.rs @@ -7,41 +7,51 @@ use crate::component::{BaseComponent, Component, RunnableComponent}; use crate::pin::{Pin, PinValue}; use crate::types::U12; +/// Represents the current phase of instruction execution +/// The 4004 CPU processes instructions in distinct phases synchronized with the clock #[derive(Debug, Clone, Copy, PartialEq)] enum InstructionPhase { - Fetch, - Address, - Execute, - Wait, + Fetch, // Fetching instruction from memory + Address, // Calculating or fetching address + Execute, // Executing the instruction + 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, - Phase2, + Phase1, // First clock phase - CPU drives bus + Phase2, // Second clock phase - Peripherals drive bus } +/// Intel 4004 4-bit microprocessor implementation +/// The world's first microprocessor, featuring 4-bit data bus, 12-bit addressing, +/// 46 instructions, and 16 index registers. Part of the MCS-4 family. pub struct Intel4004 { base: BaseComponent, - accumulator: u8, - carry: bool, - index_registers: [u8; 16], - pub(crate) program_counter: U12, - stack: [U12; 3], - stack_pointer: u8, - cycle_count: u64, - instruction_phase: InstructionPhase, - current_instruction: u8, - address_latch: u8, - data_latch: u8, - clock_speed: f64, - last_clock_transition: Instant, - clock_phase: ClockPhase, - rom_port: u8, - ram_bank: u8, + accumulator: u8, // Main accumulator register (4-bit) + carry: bool, // Carry flag for arithmetic operations + index_registers: [u8; 16], // 16 4-bit index registers (R0-R15) + pub(crate) program_counter: U12, // 12-bit program counter + stack: [U12; 3], // 3-level 12-bit address stack + stack_pointer: u8, // Stack pointer (0-2) + cycle_count: u64, // Total number of clock cycles executed + instruction_phase: InstructionPhase, // Current instruction execution phase + current_instruction: u8, // Currently executing instruction + 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) } impl Intel4004 { + /// Create a new Intel 4004 CPU instance + /// Parameters: name - Component identifier, clock_speed - Target clock frequency in Hz + /// Returns: New Intel4004 instance with initialized state pub fn new(name: String, clock_speed: f64) -> Self { let pin_names = vec![ "D0", "D1", "D2", "D3", "SYNC", "CM_ROM", "CM_RAM", "TEST", "RESET", "PHI1", "PHI2", @@ -72,11 +82,16 @@ impl Intel4004 { } } + /// Set the initial program counter value for the CPU + /// Parameters: self - CPU instance, pc - Initial 12-bit program counter value + /// Returns: Modified CPU instance with new program counter pub fn with_initial_pc(mut self, pc: u16) -> Self { self.program_counter = U12::new(pc); self } + /// Reset the CPU to its initial state + /// Clears all registers, resets program counter, and tri-states all outputs pub fn reset(&mut self) { self.accumulator = 0; self.carry = false; @@ -94,6 +109,8 @@ impl Intel4004 { self.tri_state_data_bus(); } + /// 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; @@ -110,6 +127,8 @@ impl Intel4004 { data & 0x0F } + /// 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; @@ -128,6 +147,8 @@ impl Intel4004 { } } + /// Set data bus to high-impedance state to avoid bus contention + /// CRITICAL: Must be called whenever CPU 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)) { @@ -139,6 +160,8 @@ impl Intel4004 { } } + /// Set the SYNC pin state + /// Parameters: high - true for high voltage, false for low voltage fn set_sync(&self, high: bool) { if let Ok(pin) = self.base.get_pin("SYNC") { if let Ok(mut pin_guard) = pin.lock() { @@ -148,6 +171,8 @@ impl Intel4004 { } } + /// Set the CM-ROM (Chip Select ROM) pin state + /// Parameters: high - true for high voltage, false for low voltage fn set_cm_rom(&self, high: bool) { if let Ok(pin) = self.base.get_pin("CM_ROM") { if let Ok(mut pin_guard) = pin.lock() { @@ -157,6 +182,8 @@ impl Intel4004 { } } + /// Set the CM-RAM (Chip Select RAM) pin state + /// Parameters: high - true for high voltage, false for low voltage fn set_cm_ram(&self, high: bool) { if let Ok(pin) = self.base.get_pin("CM_RAM") { if let Ok(mut pin_guard) = pin.lock() { @@ -166,6 +193,8 @@ impl Intel4004 { } } + /// Read the state of all control pins + /// Returns: (sync, cm_rom, cm_ram, test) - State of control signals 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() { @@ -181,12 +210,15 @@ impl Intel4004 { (sync, false, false, true) } + /// 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; } - // Simplified instruction execution for Fibonacci demo + /// Execute a single instruction (simplified for demo purposes) + /// For Fibonacci demo, simulate simple execution with periodic accumulator updates fn execute_instruction(&mut self) { // For Fibonacci demo, simulate simple execution // Just increment PC and occasionally update accumulator to simulate Fibonacci sequence @@ -200,6 +232,9 @@ impl Intel4004 { } } + /// Simulate Fibonacci number calculation for demo purposes + /// Parameters: n - Fibonacci sequence index + /// Returns: nth Fibonacci number modulo 16 (4-bit value) fn simulate_fibonacci(&self, n: u8) -> u8 { match n { 0 => 0, @@ -217,39 +252,57 @@ impl Intel4004 { } } - // Public methods used by MCS-4 system + /// Get the current program counter value + /// Returns: 12-bit program counter as 16-bit value pub fn get_program_counter(&self) -> u16 { self.program_counter.value() } + /// Set the program counter to a specific address + /// Parameters: address - New 12-bit program counter value pub fn set_program_counter(&mut self, address: u16) { self.program_counter.set(address); } + /// Get the current accumulator value + /// Returns: 4-bit accumulator value pub fn get_accumulator(&self) -> u8 { self.accumulator } + /// Set the accumulator to a specific value + /// Parameters: value - New 4-bit accumulator value (will be masked to 4 bits) pub fn set_accumulator(&mut self, value: u8) { self.accumulator = value & 0x0F; } + /// Get the current carry flag state + /// Returns: true if carry is set, false otherwise pub fn get_carry(&self) -> bool { self.carry } + /// Get the current stack pointer value + /// Returns: Stack pointer (0-2 for the 3-level stack) pub fn get_stack_pointer(&self) -> u8 { self.stack_pointer } + /// Get the total number of clock cycles executed + /// Returns: Total cycle count since reset pub fn get_cycle_count(&self) -> u64 { self.cycle_count } + /// Get the configured clock speed + /// Returns: Clock speed in Hz pub fn get_clock_speed(&self) -> f64 { self.clock_speed } + /// Set an index register to a specific value + /// Parameters: index - Register index (0-15), value - New 4-bit register value + /// Returns: Ok(()) if successful, Err(String) if index out of range pub fn set_register(&mut self, index: u8, value: u8) -> Result<(), String> { if index < 16 { self.index_registers[index as usize] = value & 0x0F; @@ -259,6 +312,9 @@ impl Intel4004 { } } + /// Get the value of an index register + /// Parameters: index - Register index (0-15) + /// Returns: Some(register_value) if index valid, None if out of range pub fn get_register(&self, index: u8) -> Option { if index < 16 { Some(self.index_registers[index as usize]) @@ -281,6 +337,8 @@ impl Component for Intel4004 { self.base.get_pin(name) } + /// Update the CPU state for one simulation cycle + /// Processes clock cycles and executes instructions when running fn update(&mut self) { if !self.is_running() { return; @@ -290,6 +348,8 @@ impl Component for Intel4004 { self.execute_instruction(); } + /// Run the CPU in a continuous loop until stopped + /// Provides a time-sliced execution model with 10 microsecond delays between cycles fn run(&mut self) { // Time-slice model: run in a loop calling update() each cycle self.base.set_running(true); @@ -301,6 +361,8 @@ impl Component for Intel4004 { } } + /// Stop the CPU and clean up resources + /// Tri-states all outputs and prepares for shutdown fn stop(&mut self) { self.base.set_running(false); self.tri_state_data_bus(); @@ -316,4 +378,5 @@ impl Component for Intel4004 { impl RunnableComponent for Intel4004 { // No custom run_loop needed - uses default Component::run() method + // The default implementation spawns the component in its own thread } diff --git a/src/components/memory/intel_4001.rs b/src/components/memory/intel_4001.rs index 33683fd..69594e3 100644 --- a/src/components/memory/intel_4001.rs +++ b/src/components/memory/intel_4001.rs @@ -1,5 +1,6 @@ use std::collections::HashMap; use std::sync::{Arc, Mutex}; +use std::thread; use std::time::{Duration, Instant}; use crate::component::{BaseComponent, Component, RunnableComponent}; @@ -7,33 +8,64 @@ use crate::pin::{Pin, PinValue}; /// 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 { base: BaseComponent, - memory: Vec, - last_address: u16, - access_time: Duration, - last_access: Instant, - output_latch: u8, - input_latch: u8, - io_mode: IoMode, + 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 // Clock edge detection - prev_phi1: PinValue, - prev_phi2: PinValue, + prev_phi1: PinValue, // Previous Φ1 clock state for edge detection + prev_phi2: PinValue, // Previous Φ2 clock state for edge detection // Access latency modeling - address_latched: bool, - address_latch_time: Option, - data_available: bool, + address_latch_time: Option, // Timestamp when address was latched + // Two-phase addressing for 8-bit address + 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 + // Memory operation state machine + memory_state: MemoryState, // Current state of memory operation } #[derive(Debug, Clone, Copy, PartialEq)] +/// I/O mode configuration for the 4001 ROM +/// Determines how the I/O pins are configured during read/write operations enum IoMode { - Input, // I/O pins as inputs - Output, // I/O pins as outputs - Bidirectional, // I/O pins bidirectional + 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) +} + +/// Memory operation state machine states +/// Tracks the current phase of memory access operations +#[derive(Debug, Clone, Copy, PartialEq)] +enum MemoryState { + Idle, // No memory operation in progress + AddressPhase, // Currently latching address nibbles + WaitLatency, // Address latched, waiting for access time + DriveData, // Latency elapsed, driving data on bus } 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; @@ -46,17 +78,17 @@ impl Intel4001 { // Control pin behavior: // - SYNC: Marks start of instruction cycle // - RESET: Clears internal state - // - CM: Chip select for ROM vs RAM - // - CI: Distinguishes I/O (LOW) vs ROM access (HIGH) when CM-ROM is HIGH + // - 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 + "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); @@ -66,19 +98,35 @@ impl Intel4001 { base: BaseComponent::new(name, pins), memory, last_address: 0, - access_time: Duration::from_nanos(500), // 500ns access time - last_access: Instant::now(), + access_time: Duration::from_nanos(access_time_ns), output_latch: 0, input_latch: 0, io_mode: IoMode::Input, prev_phi1: PinValue::Low, prev_phi2: PinValue::Low, - address_latched: false, address_latch_time: None, - data_available: false, + address_high_nibble: None, + address_low_nibble: None, + full_address_ready: false, + memory_state: MemoryState::Idle, } } + /// 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 + } + + /// 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!( @@ -93,6 +141,9 @@ impl Intel4001 { 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() @@ -105,6 +156,8 @@ impl Intel4001 { } } + /// 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; @@ -121,6 +174,8 @@ impl Intel4001 { data } + /// 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) { for i in 0..4 { if let Ok(pin) = self.base.get_pin(&format!("D{}", i)) { @@ -169,9 +224,9 @@ impl Intel4001 { } } + /// Set data bus to high-impedance state to avoid bus contention + /// CRITICAL: Must be called whenever ROM is not actively driving valid data fn tri_state_data_bus(&self) { - // CRITICAL: Tri-state data bus to avoid contention with other chips - // The 4001 must be high-Z whenever not actively driving valid data for i in 0..4 { if let Ok(pin) = self.base.get_pin(&format!("D{}", i)) { if let Ok(mut pin_guard) = pin.lock() { @@ -182,10 +237,72 @@ impl Intel4001 { } } - fn should_drive_data_bus(&self) -> bool { - let (sync, cm_rom, cm_ram, _) = self.read_control_pins(); - // ROM drives data bus ONLY when all conditions are met: - sync && cm_rom && cm_ram && self.data_available + /// 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 as u16) << 4) | (low as u16); + self.full_address_ready = true; + self.address_latch_time = Some(Instant::now()); + + // Clear nibble storage for next address + self.address_high_nibble = None; + self.address_low_nibble = None; + } + } + + /// 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 + /// Focus: Address latching, reset, I/O operations + fn handle_phi1_rising(&mut self) { + // Handle system reset first (highest priority) + self.handle_reset(); + + // Check for memory operation start on Φ1 rising edge with SYNC high + // 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, chip_select, io_select, _) = self.read_control_pins(); + if sync && chip_select && !io_select { + // Start memory address phase on Φ1 rising edge + 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(); + + // Handle memory address phase operations during Φ1 + self.handle_memory_address_operations(); + } + + /// Handle Φ1 falling edge - End of address phase + /// Hardware: Φ1 low = End of CPU address driving phase + fn handle_phi1_falling(&mut self) { + // Currently no specific operations needed on Φ1 falling + // Address latching happens on Φ1 rising, data driving on Φ2 rising + } + + /// 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 memory data phase operations during Φ2 + self.handle_memory_data_operations(); + } + + /// 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) { + // Handle memory cleanup operations when Φ2 falls + self.handle_memory_cleanup_operations(); } fn tri_state_io_pins(&self) { @@ -199,6 +316,9 @@ impl Intel4001 { } } + /// 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() { @@ -249,6 +369,9 @@ impl Intel4001 { self.prev_phi2 = phi2; } + /// Read all control pins from CPU + /// Returns: (sync, chip_select, io_select, reset) + /// Hardware: Control pins determine operation type and chip state fn read_control_pins(&self) -> (bool, bool, bool, bool) { // SYNC: Marks the start of an instruction cycle let sync = if let Ok(pin) = self.base.get_pin("SYNC") { @@ -261,8 +384,8 @@ impl Intel4001 { false }; - // CM-ROM: Chip select for ROM vs RAM (HIGH = ROM access) - let cm_rom = if let Ok(pin) = self.base.get_pin("CM") { + // CM: Chip select (must be HIGH for ROM access) + let chip_select = if let Ok(pin) = self.base.get_pin("CM") { if let Ok(pin_guard) = pin.lock() { pin_guard.read() == PinValue::High } else { @@ -272,9 +395,8 @@ impl Intel4001 { false }; - // CM-RAM: Distinguishes I/O vs ROM access when CM-ROM is HIGH - // HIGH = ROM access, LOW = I/O access - let cm_ram = if let Ok(pin) = self.base.get_pin("CI") { + // CI: I/O select (distinguishes I/O vs memory when chip selected) + let io_select = if let Ok(pin) = self.base.get_pin("CI") { if let Ok(pin_guard) = pin.lock() { pin_guard.read() == PinValue::High } else { @@ -294,86 +416,226 @@ impl Intel4001 { false }; - (sync, cm_rom, cm_ram, reset) + (sync, chip_select, io_select, reset) } + /// Handle system reset signal + /// Hardware: RESET pin clears all internal state and tri-states outputs fn handle_reset(&mut self) { let (_, _, _, reset) = self.read_control_pins(); if reset { - // RESET is high - clear internal state + // RESET is high - clear all internal state self.output_latch = 0; self.input_latch = 0; self.io_mode = IoMode::Input; self.tri_state_data_bus(); self.tri_state_io_pins(); - // Reset access latency state - self.address_latched = false; + + // Reset memory operation state self.address_latch_time = None; - self.data_available = false; + self.memory_state = MemoryState::Idle; + + // Reset two-phase addressing state + self.address_high_nibble = None; + self.address_low_nibble = None; + self.full_address_ready = false; } } + /// Handle I/O port operations + /// 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 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, cm_rom, cm_ram, _) = self.read_control_pins(); + let (_sync, chip_select, io_select, _) = self.read_control_pins(); - // I/O operations occur when SYNC is high and CM-ROM is low - if sync && !cm_rom { - let _address_low = self.read_data_bus(); // Lower 4 bits of address from data bus + // 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) - // For I/O operations, the upper address bits determine the operation - if cm_ram { - // I/O read operation - self.input_latch = self.read_io_pins(); - self.write_data_bus(self.input_latch); - } else { - // I/O write operation - let data = self.read_data_bus(); - self.output_latch = data; - self.write_io_pins(data); - self.io_mode = IoMode::Output; + // 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; + } + } + + /// 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 + fn handle_memory_address_operations(&mut self) { + match self.memory_state { + MemoryState::Idle => { + // In idle state, ensure bus is tri-stated + // Memory operation start is checked in handle_phi1_rising() + self.tri_state_data_bus(); + } + + MemoryState::AddressPhase => { + // Currently latching address nibbles during Φ1 + self.handle_address_latching(); + } + + MemoryState::WaitLatency => { + // Address latched, waiting for access latency + // Check if latency has elapsed and we can transition to data phase + self.handle_latency_wait(); + } + + MemoryState::DriveData => { + // Data phase should be handled by Φ2, not Φ1 + // Just ensure bus is tri-stated during wrong phase + self.tri_state_data_bus(); } } } - fn handle_memory_operation(&mut self) -> bool { - let (sync, cm_rom, cm_ram, _) = self.read_control_pins(); + /// Handle memory data-related operations during Φ2 + /// Hardware-accurate: Φ2 is when peripherals drive data, so we handle data driving + /// Focus: Data driving operations + fn handle_memory_data_operations(&mut self) { + match self.memory_state { + MemoryState::Idle => { + // During data phase, idle state means tri-state the bus + self.tri_state_data_bus(); + } - // TRI-STATE RULE: ROM only drives data bus when ALL of these are true: - // - SYNC=1 (instruction cycle active) - // - CM-ROM=1 (ROM chip selected) - // - CM-RAM=1 (data phase, not address phase) - // - data_available=true (access latency has elapsed) - // - // ALL other cases MUST be high-Z to avoid bus contention with CPU/other chips + MemoryState::AddressPhase => { + // Address phase should be handled by Φ1, not Φ2 + // Tri-state bus during wrong phase + self.tri_state_data_bus(); + } - if self.should_drive_data_bus() { - // EXACT CONDITIONS: SYNC=1, CM-ROM=1, CM-RAM=1, and data available - // This is the ONLY case where ROM drives the data bus + MemoryState::WaitLatency => { + // Still waiting for latency, tri-state bus + self.tri_state_data_bus(); + } + + MemoryState::DriveData => { + // Latency elapsed, drive data on bus during Φ2 + // Data will remain on bus until Φ2 falling edge + self.handle_data_driving(); + } + } + } + + /// Handle memory cleanup operations on Φ2 falling edge + /// Hardware-accurate: Clean up when Φ2 falls (end of data phase) + /// Focus: Tri-state bus and return to idle + fn handle_memory_cleanup_operations(&mut self) { + match self.memory_state { + MemoryState::DriveData => { + // End of data phase - tri-state bus and return to idle + self.tri_state_data_bus(); + self.return_to_idle(); + } + + MemoryState::Idle | MemoryState::AddressPhase | MemoryState::WaitLatency => { + // For other states, just ensure bus is tri-stated + self.tri_state_data_bus(); + } + } + } + + /// Transition to address phase state + /// Hardware: Start of memory read cycle, CPU begins providing address + fn start_memory_address_phase(&mut self) { + self.memory_state = MemoryState::AddressPhase; + self.address_high_nibble = None; + self.address_low_nibble = None; + self.full_address_ready = false; + } + + /// Handle address nibble latching during address phase + /// Hardware: CPU provides 8-bit address as two 4-bit nibbles + fn handle_address_latching(&mut self) { + let nibble = self.read_data_bus(); + + if self.address_high_nibble.is_none() { + // First cycle: latch high nibble (bits 7-4) + self.address_high_nibble = Some(nibble); + } else if self.address_low_nibble.is_none() { + // Second cycle: latch low nibble (bits 3-0) and transition to latency wait + self.address_low_nibble = Some(nibble); + self.assemble_full_address(); + self.start_latency_wait(); + } + } + + /// Transition to latency wait state + /// Hardware: Address captured, start 500ns access time before data available + fn start_latency_wait(&mut self) { + self.memory_state = MemoryState::WaitLatency; + self.address_latch_time = Some(Instant::now()); + } + + /// Handle latency timing during wait state + /// Hardware: ROM needs 500ns to access data after address is latched + fn handle_latency_wait(&mut self) { + if let Some(latch_time) = self.address_latch_time { + if latch_time.elapsed() >= self.access_time { + // Latency elapsed, transition to data driving + self.start_data_driving(); + } + } + } + + /// Transition to data driving state + /// Hardware: Access latency complete, ready to drive data on next Φ2 cycle + fn start_data_driving(&mut self) { + self.memory_state = MemoryState::DriveData; + } + + /// Handle data driving during DriveData state + /// Hardware: ROM drives data bus when all conditions are met + /// Data stays on bus during Φ2 high period, tri-stated on Φ2 falling edge + fn handle_data_driving(&mut self) { + let (sync, chip_select, io_select, _) = self.read_control_pins(); + + // Memory read: CM=1 (chip_select), CI=0 (!io_select), valid address + if sync && chip_select && !io_select && self.full_address_ready { + // All conditions met: drive data on bus + // Data will remain on bus until Φ2 falling edge let address = self.last_address; if (address as usize) < self.memory.len() { let data = self.memory[address as usize]; self.write_data_bus(data); - return true; // Successfully drove the bus + // Note: Don't call return_to_idle() here - wait for Φ2 falling edge + } else { + // Invalid address, tri-state + self.tri_state_data_bus(); } + } else { + // Bus contention guard: ROM should not drive when conditions not met + // In real hardware, this would cause a short if CPU is still driving + if self.full_address_ready { + eprintln!("WARNING: {} - Bus contention detected! ROM attempting to drive data bus when conditions not met (SYNC={}, CM={}, CI={}, Address_Ready={})", + self.base.name(), sync, chip_select, io_select, self.full_address_ready); + } + // Conditions not met, tri-state + self.tri_state_data_bus(); } + } - // ALL other cases: tri-state the data bus - self.tri_state_data_bus(); - - // Handle address latching when in address phase (but still tri-state) - if sync && cm_rom && !cm_ram { - // First cycle: CPU outputs address, ROM latches it - let address_low = self.read_data_bus(); - self.last_address = address_low as u16; - self.address_latched = true; - self.address_latch_time = Some(Instant::now()); - self.data_available = false; - } else if !self.address_latched { - // Reset data available flag when not in active operation - self.data_available = false; - } - - false + /// Reset memory state machine to idle + /// Hardware: Called when memory operation completes or is interrupted + fn return_to_idle(&mut self) { + self.memory_state = MemoryState::Idle; + self.address_latch_time = None; + self.address_high_nibble = None; + self.address_low_nibble = None; + self.full_address_ready = false; } } @@ -390,51 +652,47 @@ impl Component for Intel4001 { 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) { - // Only update on PHI1 rising edge (synchronous with CPU clock) - if !self.is_phi1_rising_edge() { - return; - } + // 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 - let (phi1, phi2) = self.read_clock_pins(); self.prev_phi1 = phi1; self.prev_phi2 = phi2; - // Check if we have a latched address and need to make data available - if self.address_latched { - if let Some(latch_time) = self.address_latch_time { - if latch_time.elapsed() >= self.access_time { - // Access latency has elapsed, data is now available - self.data_available = true; - self.address_latched = false; - self.address_latch_time = None; - } - } + if phi1_rising { + // Φ1 Rising Edge: Address phase (CPU drives bus) - handle address latching + self.handle_phi1_rising(); } - self.handle_reset(); - - // Handle I/O operations (higher priority than memory operations) - self.handle_io_operation(); - - // Handle memory operations - let memory_accessed = self.handle_memory_operation(); - - // Additional safety: ensure data bus is tri-stated when ROM is not actively driving - // This handles any edge cases not covered in handle_memory_operation - if !memory_accessed && !self.should_drive_data_bus() { - self.tri_state_data_bus(); + if phi1_falling { + // Φ1 Falling Edge: End of address phase + self.handle_phi1_falling(); } - self.last_access = Instant::now(); + if phi2_rising { + // Φ2 Rising Edge: Data phase (ROM drives bus if ready) - handle data driving + 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(); + } } + /// Run component in time-slice mode (manual control) + /// Hardware: Simulates continuous operation with clock edge detection fn run(&mut self) { - // Time-slice model: run in a loop calling update() each cycle self.base.set_running(true); - // Initialize clock states + // Initialize clock states for edge detection let (phi1, phi2) = self.read_clock_pins(); self.prev_phi1 = phi1; self.prev_phi2 = phi2; @@ -442,10 +700,12 @@ impl Component for Intel4001 { while self.is_running() { self.update(); // Small delay to prevent busy waiting when no clock is present - std::thread::sleep(std::time::Duration::from_micros(1)); + 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); @@ -453,10 +713,8 @@ impl Component for Intel4001 { self.tri_state_data_bus(); self.tri_state_io_pins(); - // Reset access latency state - self.address_latched = false; + // Reset memory operation state self.address_latch_time = None; - self.data_available = false; } fn is_running(&self) -> bool { @@ -468,10 +726,15 @@ impl RunnableComponent for Intel4001 {} // Intel 4001 specific methods impl Intel4001 { + /// Get the 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]) @@ -480,28 +743,17 @@ impl Intel4001 { } } + /// Get the current output latch value + /// Returns: 4-bit value last written to I/O ports pub fn get_output_latch(&self) -> u8 { self.output_latch } + /// Get the current input latch value + /// Returns: 4-bit value last read from I/O ports pub fn get_input_latch(&self) -> u8 { self.input_latch } - - fn get_io_mode(&self) -> IoMode { - self.io_mode - } - - fn set_io_mode(&mut self, mode: IoMode) { - self.io_mode = mode; - match mode { - IoMode::Input => self.tri_state_io_pins(), - IoMode::Output => self.write_io_pins(self.output_latch), - IoMode::Bidirectional => { - // In bidirectional mode, I/O pins follow the data bus during writes - } - } - } } // Custom formatter for debugging @@ -524,6 +776,7 @@ mod tests { 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()); } @@ -542,15 +795,12 @@ mod tests { #[test] fn test_intel4001_io_modes() { - let mut rom = Intel4001::new("ROM_4001".to_string()); + let rom = Intel4001::new("ROM_4001".to_string()); - assert_eq!(rom.get_io_mode(), IoMode::Input); - - rom.set_io_mode(IoMode::Output); - assert_eq!(rom.get_io_mode(), IoMode::Output); - - rom.set_io_mode(IoMode::Bidirectional); - assert_eq!(rom.get_io_mode(), IoMode::Bidirectional); + // 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] @@ -571,4 +821,891 @@ mod tests { 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_address_latching() { + // Note: This test sets Φ1 and Φ2 directly for unit validation. + // For system integration testing, consider using a proper clock generator + // that drives Φ1/Φ2 continuously through realistic multiple cycles. + let mut rom = Intel4001::new_with_access_time("ROM_4001".to_string(), 1); + + // Load test data + let test_data = vec![0x12]; + rom.load_rom_data(test_data, 0).unwrap(); + + // 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(); + let phi2_pin = rom.get_pin("PHI2").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(); + + // Set up memory read operation: SYNC=1, CM=1, CI=0 (memory access) + { + let mut sync_guard = sync_pin.lock().unwrap(); + let mut cm_guard = cm_pin.lock().unwrap(); + let mut ci_guard = ci_pin.lock().unwrap(); + 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 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); // Bit 0 = 0 + d1_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Bit 1 = 0 + d2_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Bit 2 = 0 + d3_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Bit 3 = 0 + } + + // --- High nibble --- + phi1_pin + .lock() + .unwrap() + .set_driver(Some("TEST".into()), PinValue::High); + rom.update(); // rising edge -> latch high nibble + phi1_pin + .lock() + .unwrap() + .set_driver(Some("TEST".into()), PinValue::Low); + rom.update(); // falling edge + + // Should have transitioned to AddressPhase + assert_eq!(rom.memory_state, MemoryState::AddressPhase); + + // Check that high nibble was latched + assert_eq!(rom.address_high_nibble, Some(0x0)); + + // Set address low 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".into()), PinValue::Low); // Bit 0 = 0 + d1_guard.set_driver(Some("TEST".into()), PinValue::Low); // Bit 1 = 0 + d2_guard.set_driver(Some("TEST".into()), PinValue::Low); // Bit 2 = 0 + d3_guard.set_driver(Some("TEST".into()), PinValue::Low); // Bit 3 = 0 + } + + // --- Low nibble --- + phi1_pin + .lock() + .unwrap() + .set_driver(Some("TEST".into()), PinValue::High); + rom.update(); // rising edge -> latch low nibble + phi1_pin + .lock() + .unwrap() + .set_driver(Some("TEST".into()), PinValue::Low); + rom.update(); // falling edge + + // Should have assembled full address and transitioned to WaitLatency + // Note: nibbles are cleared after assembly, so check last_address instead + assert_eq!(rom.last_address, 0x00); + assert_eq!(rom.full_address_ready, true); + assert_eq!(rom.memory_state, MemoryState::WaitLatency); + + // Advance simulated time to exceed access_time (1ns) + std::thread::sleep(Duration::from_nanos(2)); + } + + #[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.memory_state, MemoryState::Idle); + + // 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(); + + // Set up memory read operation: SYNC=1, CM=1, CI=0 (memory access) + { + let mut sync_guard = sync_pin.lock().unwrap(); + let mut cm_guard = cm_pin.lock().unwrap(); + let mut ci_guard = ci_pin.lock().unwrap(); + 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 + { + let mut phi1_guard = phi1_pin.lock().unwrap(); + phi1_guard.set_driver(Some("TEST".to_string()), PinValue::High); + } + + // Update to process Φ1 rising edge + rom.update(); + + // Should have started memory operation and transitioned to AddressPhase + assert_eq!(rom.memory_state, MemoryState::AddressPhase); + println!("Memory state after Φ1 rising: {:?}", rom.memory_state); + } + + #[test] + fn test_simple_data_driving() { + // Note: This test manually sequences through clock phases for unit validation. + // For system integration, use continuous clock generator for realistic timing. + let mut rom = Intel4001::new_with_access_time("ROM_4001".to_string(), 1); + + // Load test data + let test_data = vec![0x12]; + rom.load_rom_data(test_data, 0).unwrap(); + + // 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(); + let phi2_pin = rom.get_pin("PHI2").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(); + + // Set up memory read operation: SYNC=1, CM=1, CI=0 (memory access) + { + let mut sync_guard = sync_pin.lock().unwrap(); + let mut cm_guard = cm_pin.lock().unwrap(); + let mut ci_guard = ci_pin.lock().unwrap(); + 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 + } + + // Phase 1: Address phase - latch high nibble (0x0) + { + 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); // Bit 0 = 0 + d1_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Bit 1 = 0 + d2_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Bit 2 = 0 + d3_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Bit 3 = 0 + } + + // --- High nibble --- + phi1_pin + .lock() + .unwrap() + .set_driver(Some("TEST".into()), PinValue::High); + rom.update(); // rising edge -> latch high nibble + phi1_pin + .lock() + .unwrap() + .set_driver(Some("TEST".into()), PinValue::Low); + rom.update(); // falling edge + println!( + "After Φ1 high - State: {:?}, High nibble: {:?}, Low nibble: {:?}", + rom.memory_state, rom.address_high_nibble, rom.address_low_nibble + ); + + // Phase 2: Address phase - latch low nibble (0x0) + { + 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::Low); // Bit 0 = 0 + d1_guard.set_driver(Some("TEST".into()), PinValue::Low); // Bit 1 = 0 + d2_guard.set_driver(Some("TEST".into()), PinValue::Low); // Bit 2 = 0 + d3_guard.set_driver(Some("TEST".into()), PinValue::Low); // Bit 3 = 0 + } + + // --- Low nibble --- + phi1_pin + .lock() + .unwrap() + .set_driver(Some("TEST".into()), PinValue::High); + rom.update(); // rising edge -> latch low nibble + phi1_pin + .lock() + .unwrap() + .set_driver(Some("TEST".into()), PinValue::Low); + rom.update(); // falling edge + println!("After second Φ1 high - State: {:?}, High nibble: {:?}, Low nibble: {:?}, Address: 0x{:x}", rom.memory_state, rom.address_high_nibble, rom.address_low_nibble, rom.last_address); + + // Advance simulated time to exceed access_time (1ns) + std::thread::sleep(Duration::from_nanos(2)); + + // --- Data phase --- + phi2_pin + .lock() + .unwrap() + .set_driver(Some("TEST".into()), PinValue::High); + rom.update(); // rising edge -> drive data + + // Assert DriveData state while Φ2 is high + assert_eq!(rom.memory_state, MemoryState::DriveData); + println!("After Φ2 high - State: {:?}", rom.memory_state); + + // Verify data is driven on bus while Φ2 is high: 0x12 = 0001 0010 + assert_eq!(d0_pin.lock().unwrap().read(), PinValue::Low); // Bit 0 = 0 + assert_eq!(d1_pin.lock().unwrap().read(), PinValue::High); // Bit 1 = 1 + assert_eq!(d2_pin.lock().unwrap().read(), PinValue::Low); // Bit 2 = 0 + assert_eq!(d3_pin.lock().unwrap().read(), PinValue::High); // Bit 3 = 1 + + phi2_pin + .lock() + .unwrap() + .set_driver(Some("TEST".into()), PinValue::Low); + rom.update(); // falling edge -> tri-state + } + + #[test] + fn test_memory_operation_start_detection() { + let mut rom = Intel4001::new_with_access_time("ROM_4001".to_string(), 1); + + // Load test data + let test_data = vec![0x12]; + rom.load_rom_data(test_data, 0).unwrap(); + + // 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(); + let phi2_pin = rom.get_pin("PHI2").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, memory should be in idle state + assert_eq!(rom.memory_state, MemoryState::Idle); + + // Set up memory read operation: SYNC=1, CM=1, CI=0 (memory access) + { + let mut sync_guard = sync_pin.lock().unwrap(); + let mut cm_guard = cm_pin.lock().unwrap(); + let mut ci_guard = ci_pin.lock().unwrap(); + 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 address 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); + } + + // Set Φ1 high, Φ2 low - should start memory operation + phi1_pin + .lock() + .unwrap() + .set_driver(Some("TEST".into()), PinValue::High); + phi2_pin + .lock() + .unwrap() + .set_driver(Some("TEST".into()), PinValue::Low); + + // Update to process Φ1 rising edge + rom.update(); + + // Should have transitioned to AddressPhase + assert_eq!(rom.memory_state, MemoryState::AddressPhase); + + // Set address on data bus for latching + { + 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::Low); + 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); + } + + // --- High nibble --- + phi1_pin + .lock() + .unwrap() + .set_driver(Some("TEST".into()), PinValue::Low); + rom.update(); // falling edge + + // --- Low nibble --- + phi1_pin + .lock() + .unwrap() + .set_driver(Some("TEST".into()), PinValue::High); + rom.update(); // rising edge -> latch low nibble + phi1_pin + .lock() + .unwrap() + .set_driver(Some("TEST".into()), PinValue::Low); + rom.update(); // falling edge + + // Advance simulated time to exceed access_time (1ns) + std::thread::sleep(Duration::from_nanos(2)); + + // --- Data phase --- + phi2_pin + .lock() + .unwrap() + .set_driver(Some("TEST".into()), PinValue::High); + rom.update(); // rising edge -> drive data + + // Assert DriveData state while Φ2 is high + assert_eq!(rom.memory_state, MemoryState::DriveData); + + // Verify data is driven on bus while Φ2 is high: 0x12 = 0001 0010 + assert_eq!(d0_pin.lock().unwrap().read(), PinValue::Low); // Bit 0 = 0 + assert_eq!(d1_pin.lock().unwrap().read(), PinValue::High); // Bit 1 = 1 + assert_eq!(d2_pin.lock().unwrap().read(), PinValue::Low); // Bit 2 = 0 + assert_eq!(d3_pin.lock().unwrap().read(), PinValue::High); // Bit 3 = 1 + + phi2_pin + .lock() + .unwrap() + .set_driver(Some("TEST".into()), PinValue::Low); + rom.update(); // falling edge -> tri-state + } + + #[test] + fn test_full_address_fetch_cycle_integration() { + // Note: This integration test manually steps through clock phases. + // For full system testing, implement continuous clock generator + // that maintains proper Φ1/Φ2 timing relationships. + let mut rom = Intel4001::new_with_access_time("ROM_4001".to_string(), 1); + + // Load test data: address 0x00 = 0x12, address 0x01 = 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(); + let phi1_pin = rom.get_pin("PHI1").unwrap(); + let phi2_pin = rom.get_pin("PHI2").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(); + + // Set up memory read operation: SYNC=1, CM=1, CI=0 (memory access, not I/O) + { + let mut sync_guard = sync_pin.lock().unwrap(); + let mut cm_guard = cm_pin.lock().unwrap(); + let mut ci_guard = ci_pin.lock().unwrap(); + 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 complete cycle for address 0x00 + // Step 1: 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); // Bit 0 = 0 + d1_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Bit 1 = 0 + d2_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Bit 2 = 0 + d3_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Bit 3 = 0 + } + + // Step 2: High nibble + phi1_pin + .lock() + .unwrap() + .set_driver(Some("TEST".into()), PinValue::Low); + rom.update(); // falling edge + phi1_pin + .lock() + .unwrap() + .set_driver(Some("TEST".into()), PinValue::High); + rom.update(); // rising edge for high nibble + + // Step 3: Set address low 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".into()), PinValue::Low); // Bit 0 = 0 + d1_guard.set_driver(Some("TEST".into()), PinValue::Low); // Bit 1 = 0 + d2_guard.set_driver(Some("TEST".into()), PinValue::Low); // Bit 2 = 0 + d3_guard.set_driver(Some("TEST".into()), PinValue::Low); // Bit 3 = 0 + } + + // Step 4: Low nibble + phi1_pin + .lock() + .unwrap() + .set_driver(Some("TEST".into()), PinValue::Low); + rom.update(); // falling edge + phi1_pin + .lock() + .unwrap() + .set_driver(Some("TEST".into()), PinValue::High); + rom.update(); // rising edge for low nibble + + // Advance simulated time to exceed access_time (1ns) + std::thread::sleep(Duration::from_nanos(2)); + + // Data phase + phi2_pin + .lock() + .unwrap() + .set_driver(Some("TEST".into()), PinValue::High); + rom.update(); // rising edge -> drive data + + // Step 5: Verify data is driven on bus while Φ2 is high: 0x12 = 0001 0010 + assert_eq!(d0_pin.lock().unwrap().read(), PinValue::Low); // Bit 0 = 0 + assert_eq!(d1_pin.lock().unwrap().read(), PinValue::High); // Bit 1 = 1 + assert_eq!(d2_pin.lock().unwrap().read(), PinValue::Low); // Bit 2 = 0 + assert_eq!(d3_pin.lock().unwrap().read(), PinValue::High); // Bit 3 = 1 (0x12 = 0001 0010) + + phi2_pin + .lock() + .unwrap() + .set_driver(Some("TEST".into()), PinValue::Low); + rom.update(); // falling edge -> tri-state + + // Test complete cycle for address 0x01 + // Reset memory state first + rom.return_to_idle(); + + // Step 1: 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); // Bit 0 = 0 + d1_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Bit 1 = 0 + d2_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Bit 2 = 0 + d3_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Bit 3 = 0 + } + + // Step 2: High nibble + phi1_pin + .lock() + .unwrap() + .set_driver(Some("TEST".into()), PinValue::Low); + rom.update(); // falling edge + phi1_pin + .lock() + .unwrap() + .set_driver(Some("TEST".into()), PinValue::High); + rom.update(); // rising edge for high nibble + + // Step 3: Set address low nibble (0x1) 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".into()), PinValue::High); // Bit 0 = 1 + d1_guard.set_driver(Some("TEST".into()), PinValue::Low); // Bit 1 = 0 + d2_guard.set_driver(Some("TEST".into()), PinValue::Low); // Bit 2 = 0 + d3_guard.set_driver(Some("TEST".into()), PinValue::Low); // Bit 3 = 0 + } + + // Step 4: Low nibble + phi1_pin + .lock() + .unwrap() + .set_driver(Some("TEST".into()), PinValue::Low); + rom.update(); // falling edge + phi1_pin + .lock() + .unwrap() + .set_driver(Some("TEST".into()), PinValue::High); + rom.update(); // rising edge for low nibble + + // Advance simulated time to exceed access_time (1ns) + std::thread::sleep(Duration::from_nanos(2)); + + // Data phase + phi2_pin + .lock() + .unwrap() + .set_driver(Some("TEST".into()), PinValue::High); + rom.update(); // rising edge -> drive data + + // Step 5: Verify data is driven on bus while Φ2 is high: 0x34 = 0011 0100 + assert_eq!(d0_pin.lock().unwrap().read(), PinValue::Low); // Bit 0 = 0 + assert_eq!(d1_pin.lock().unwrap().read(), PinValue::Low); // Bit 1 = 0 + assert_eq!(d2_pin.lock().unwrap().read(), PinValue::High); // Bit 2 = 1 + assert_eq!(d3_pin.lock().unwrap().read(), PinValue::High); // Bit 3 = 1 (0x34 = 0011 0100) + + phi2_pin + .lock() + .unwrap() + .set_driver(Some("TEST".into()), PinValue::Low); + rom.update(); // falling edge -> tri-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(); + 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(); + + // Set up memory read operation: SYNC=1, chip_select=1, io_select=0 (memory access) + { + let mut sync_guard = sync_pin.lock().unwrap(); + let mut cm_guard = cm_pin.lock().unwrap(); + let mut ci_guard = ci_pin.lock().unwrap(); + 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 the two-phase addressing properly + rom.update(); + + // Verify that the ROM correctly read the 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().unwrap(); + let mut cm_guard = cm_pin.lock().unwrap(); + let mut ci_guard = ci_pin.lock().unwrap(); + 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().unwrap(); + 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().unwrap().read(), PinValue::HighZ); + assert_eq!(d1_pin.lock().unwrap().read(), PinValue::HighZ); + assert_eq!(d2_pin.lock().unwrap().read(), PinValue::HighZ); + assert_eq!(d3_pin.lock().unwrap().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().unwrap().read(), PinValue::HighZ); + assert_eq!(d1_pin.lock().unwrap().read(), PinValue::HighZ); + assert_eq!(d2_pin.lock().unwrap().read(), PinValue::HighZ); + assert_eq!(d3_pin.lock().unwrap().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().unwrap(); + let mut cm_guard = cm_pin.lock().unwrap(); + let mut ci_guard = ci_pin.lock().unwrap(); + 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().unwrap().read(), PinValue::HighZ); + assert_eq!(d1_pin.lock().unwrap().read(), PinValue::HighZ); + assert_eq!(d2_pin.lock().unwrap().read(), PinValue::HighZ); + assert_eq!(d3_pin.lock().unwrap().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().unwrap().read(), PinValue::HighZ); + assert_eq!(d1_pin.lock().unwrap().read(), PinValue::HighZ); + assert_eq!(d2_pin.lock().unwrap().read(), PinValue::HighZ); + assert_eq!(d3_pin.lock().unwrap().read(), PinValue::HighZ); + } + + #[test] + fn test_clock_generator_integration() { + // Integration test with simulated clock generator stepping through multiple cycles + // This tests more realistic timing scenarios compared to direct Φ1/Φ2 manipulation + let mut rom = Intel4001::new_with_access_time("ROM_4001".to_string(), 1); + + // Load test data: address 0x00 = 0x12, address 0x01 = 0x34 + let test_data = vec![0x12, 0x34]; + rom.load_rom_data(test_data, 0).unwrap(); + + // 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(); + let phi2_pin = rom.get_pin("PHI2").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(); + + // Set up memory read operation: SYNC=1, CM=1, CI=0 (memory access) + { + let mut sync_guard = sync_pin.lock().unwrap(); + let mut cm_guard = cm_pin.lock().unwrap(); + let mut ci_guard = ci_pin.lock().unwrap(); + 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 + } + + // Simulate clock generator: step through multiple Φ1/Φ2 cycles + // Cycle 1: Address phase for high nibble (0x0) + { + 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); // Bit 0 = 0 + d1_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Bit 1 = 0 + d2_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Bit 2 = 0 + d3_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Bit 3 = 0 + } + + // High nibble + phi1_pin + .lock() + .unwrap() + .set_driver(Some("TEST".into()), PinValue::Low); + rom.update(); // falling edge + phi1_pin + .lock() + .unwrap() + .set_driver(Some("TEST".into()), PinValue::High); + rom.update(); // rising edge for high nibble + + // Cycle 2: Address phase for low nibble (0x0) + { + 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::Low); // Bit 0 = 0 + d1_guard.set_driver(Some("TEST".into()), PinValue::Low); // Bit 1 = 0 + d2_guard.set_driver(Some("TEST".into()), PinValue::Low); // Bit 2 = 0 + d3_guard.set_driver(Some("TEST".into()), PinValue::Low); // Bit 3 = 0 + } + + // Low nibble + phi1_pin + .lock() + .unwrap() + .set_driver(Some("TEST".into()), PinValue::Low); + rom.update(); // falling edge + phi1_pin + .lock() + .unwrap() + .set_driver(Some("TEST".into()), PinValue::High); + rom.update(); // rising edge for low nibble + + // Advance simulated time to exceed access_time (1ns) + std::thread::sleep(Duration::from_nanos(2)); + + // Data phase + phi2_pin + .lock() + .unwrap() + .set_driver(Some("TEST".into()), PinValue::High); + rom.update(); // rising edge -> drive data + + // Should have data 0x12 on bus while Φ2 is high: 0001 0010 + assert_eq!(d0_pin.lock().unwrap().read(), PinValue::Low); // Bit 0 = 0 + assert_eq!(d1_pin.lock().unwrap().read(), PinValue::High); // Bit 1 = 1 + assert_eq!(d2_pin.lock().unwrap().read(), PinValue::Low); // Bit 2 = 0 + assert_eq!(d3_pin.lock().unwrap().read(), PinValue::High); // Bit 3 = 1 + + phi2_pin + .lock() + .unwrap() + .set_driver(Some("TEST".into()), PinValue::Low); + rom.update(); // falling edge -> tri-state + + // Cycle 3: Next memory operation for address 0x01 + rom.return_to_idle(); + + // Set address high nibble (0x0) for address 0x01 + { + 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); // Bit 0 = 0 + d1_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Bit 1 = 0 + d2_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Bit 2 = 0 + d3_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Bit 3 = 0 + } + + // High nibble + phi1_pin + .lock() + .unwrap() + .set_driver(Some("TEST".into()), PinValue::Low); + rom.update(); // falling edge + phi1_pin + .lock() + .unwrap() + .set_driver(Some("TEST".into()), PinValue::High); + rom.update(); // rising edge for high nibble + + // Set address low nibble (0x1) for address 0x01 + { + 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); // Bit 1 = 0 + d2_guard.set_driver(Some("TEST".into()), PinValue::Low); // Bit 2 = 0 + d3_guard.set_driver(Some("TEST".into()), PinValue::Low); // Bit 3 = 0 + } + + // Low nibble + phi1_pin + .lock() + .unwrap() + .set_driver(Some("TEST".into()), PinValue::Low); + rom.update(); // falling edge + phi1_pin + .lock() + .unwrap() + .set_driver(Some("TEST".into()), PinValue::High); + rom.update(); // rising edge for low nibble + + // Advance simulated time to exceed access_time (1ns) + std::thread::sleep(Duration::from_nanos(2)); + + // Data phase + phi2_pin + .lock() + .unwrap() + .set_driver(Some("TEST".into()), PinValue::High); + rom.update(); // rising edge -> drive data + + // Should have data 0x34 on bus while Φ2 is high: 0011 0100 + assert_eq!(d0_pin.lock().unwrap().read(), PinValue::Low); // Bit 0 = 0 + assert_eq!(d1_pin.lock().unwrap().read(), PinValue::Low); // Bit 1 = 0 + assert_eq!(d2_pin.lock().unwrap().read(), PinValue::High); // Bit 2 = 1 + assert_eq!(d3_pin.lock().unwrap().read(), PinValue::High); // Bit 3 = 1 + + phi2_pin + .lock() + .unwrap() + .set_driver(Some("TEST".into()), PinValue::Low); + rom.update(); // falling edge -> tri-state + } }