fix: RESET pin active-LOW and clock edge state reset

This commit is contained in:
Jan-Peter von Hunnius 2026-02-26 11:04:25 +01:00
parent 1cf2cd6e2b
commit c9f39aa600
12 changed files with 520 additions and 327 deletions

View file

@ -294,10 +294,11 @@ pub trait Intel400xControlPins {
}
/// Read RESET pin state
/// Hardware: RESET is active LOW (pin LOW = reset active)
fn read_reset_pin(&self) -> bool {
if let Ok(pin) = self.get_base().get_pin("RESET") {
if let Ok(pin_guard) = pin.lock() {
pin_guard.read() == PinValue::High
pin_guard.read() == PinValue::Low // Active LOW: LOW means reset is active
} else {
false
}
@ -313,10 +314,11 @@ pub trait Intel400xResetHandling {
/// Handle system reset signal
/// Hardware: RESET pin clears all internal state and tri-states outputs
/// Note: RESET is active LOW (pin LOW = reset active)
fn handle_reset(&mut self, reset_pin_name: &str) -> bool {
let reset = if let Ok(pin) = self.get_base().get_pin(reset_pin_name) {
if let Ok(pin_guard) = pin.lock() {
pin_guard.read() == PinValue::High
pin_guard.read() == PinValue::Low // Active LOW: LOW means reset is active
} else {
false
}

View file

@ -126,7 +126,7 @@ pub struct Intel4004 {
current_instruction: u8, // Currently executing instruction
address_latch: u8, // Latched address for memory operations
#[allow(dead_code)] // Reserved for future memory operations implementation
data_latch: u8, // Latched data for memory operations
data_latch: u8, // Latched data for memory operations
clock_speed: f64, // Target clock speed in Hz
rom_port: u8, // Currently selected ROM port (0-15)
ram_bank: u8, // Currently selected RAM bank (0-7)

View file

@ -95,6 +95,11 @@ impl Intel400xResetHandling for Intel4001 {
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;
}
}
@ -1212,7 +1217,10 @@ mod tests {
// 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);
assert_eq!(
rom.state_machine.current_state,
Intel400xUnifiedState::AddressPhase
);
println!(
"Memory state after Φ1 rising: {:?}",
rom.state_machine.current_state

View file

@ -20,6 +20,15 @@ use crate::pin::{Pin, PinValue};
/// - I/O port writes are immediate (real hardware may have different timing)
/// - Status character generation timing simplified for testing
/// - RAM access timing may not exactly match real hardware
/// Pending operation type for the next memory cycle
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum PendingOperation {
None, // No operation pending
Read, // Read from RAM
Write, // Write to RAM
OutputPort, // Write to output port
}
pub struct Intel4002 {
pub base: BaseComponent,
memory: [u8; 320], // 320-byte RAM storage (4 registers * 80 characters)
@ -34,6 +43,8 @@ pub struct Intel4002 {
// Clock edge detection
prev_phi1: PinValue, // Previous Φ1 clock state for edge detection
prev_phi2: PinValue, // Previous Φ2 clock state for edge detection
// Operation type for next cycle (determines ReadData vs WriteData vs OutputPort)
pending_operation: PendingOperation,
}
/// RAM register operations (for WRM/WRR instructions)
@ -94,6 +105,11 @@ impl Intel400xResetHandling for Intel4002 {
self.status_character = 0;
self.selected_register = None;
self.selected_character = None;
// 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;
}
}
@ -179,7 +195,10 @@ impl Intel400xComponentBehavior for Intel4002 {
}
Intel400xUnifiedState::AddressPhase => {
// Entry actions for AddressPhase state
// Address latching will happen in execute_state_logic
// Clear address nibbles to prepare for new address
self.state_machine.set_address_high_nibble(None);
self.state_machine.set_address_low_nibble(None);
self.state_machine.set_address_latch_time(None);
}
Intel400xUnifiedState::WaitLatency => {
// Entry actions for WaitLatency state
@ -343,6 +362,7 @@ impl Intel4002 {
),
prev_phi1: PinValue::Low,
prev_phi2: PinValue::Low,
pending_operation: PendingOperation::Read, // Default to read
}
}
@ -414,7 +434,8 @@ impl Intel4002 {
for i in 0..4 {
if let Ok(pin) = self.base.get_pin(&format!("D{}", i)) {
if let Ok(mut pin_guard) = pin.lock() {
pin_guard.set_driver(Some(self.base.name()), PinValue::HighZ);
// Use the same driver name as write_data_bus() to properly tri-state
pin_guard.set_driver(Some(format!("{}_DATA", self.base.name())), PinValue::HighZ);
}
}
}
@ -475,19 +496,51 @@ impl Intel4002 {
}
}
/// Handle Φ1 falling edge - End of address phase
/// Hardware: Φ1 low = End of CPU address driving phase
/// Handle Φ1 falling edge - Latch address nibbles
/// Hardware: Φ1 low = CPU has finished driving address, data is stable
/// RAM latches address nibbles on PHI1 falling edge to ensure data is stable
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
// Only latch if we're in AddressPhase
// Note: We do NOT check chip_select here because:
// 1. Chip select was already verified when we entered AddressPhase
// 2. CM pin may be lowered between A1 and A2 cycles (this is valid)
// 3. Once address phase starts, we must complete both nibbles
// 4. The io_select (CI) pin distinguishes I/O vs memory operations
if self.state_machine.get_state() == Intel400xUnifiedState::AddressPhase {
// Latch address nibble on PHI1 falling edge (data is now stable)
let nibble = self.read_data_bus() & 0x0F;
if self.state_machine.handle_address_nibble(nibble) {
// Address is complete (both nibbles latched), transition to latency wait
self.process_address_complete();
}
}
}
/// Handle Φ2 rising edge - Data phase
/// Hardware: Φ2 high = Peripherals drive bus with data
/// Focus: Data driving operations
fn handle_phi2_rising(&mut self) {
// Execute current state logic
self.execute_state_machine();
// Check for latency elapsed and transition to ReadData/WriteData/OutputPort if needed
if self.state_machine.get_state() == Intel400xUnifiedState::WaitLatency {
if self.state_machine.is_latency_elapsed() {
self.process_latency_elapsed();
}
}
// Drive data if in ReadData/WriteData/OutputPort state
match self.state_machine.get_state() {
Intel400xUnifiedState::ReadData => {
self.handle_ram_read();
}
Intel400xUnifiedState::WriteData => {
self.handle_ram_write();
}
Intel400xUnifiedState::OutputPort => {
self.handle_status_output();
}
_ => {}
}
}
/// Handle Φ2 falling edge - End of data phase
@ -561,23 +614,46 @@ impl Intel4002 {
}
}
/// Handle status character output operation
/// Hardware: RAM drives status character pins with current status
/// Handle output port operation
/// Hardware: RAM writes data from bus to I/O port based on address
/// Addresses 0x00-0x4F are RAM data, 0x14-0x17 are output ports
fn handle_status_output(&mut self) {
self.drive_status_character();
if self.state_machine.is_full_address_ready() {
let address = self.state_machine.get_full_address().unwrap_or(0) as u8;
let data = self.read_data_bus();
// Determine which output port based on address
// Output ports are at addresses 0x14-0x17 (port 0-3)
let port = match address {
0x14 => Some(0),
0x15 => Some(1),
0x16 => Some(2),
0x17 => Some(3),
_ => None,
};
if let Some(port_num) = port {
// Write to I/O port
let _ = self.write_io_port(port_num, data);
}
}
}
/// Helper function to process latency elapsed event
fn process_latency_elapsed(&mut self) {
let event = Intel400xEvent::LatencyElapsed;
// Use the process_event_direct function to avoid borrowing conflicts
let new_state = process_event_direct(&mut self.state_machine, event);
// Determine the target state based on pending operation type
let target_state = match self.pending_operation {
PendingOperation::Read => Intel400xUnifiedState::ReadData,
PendingOperation::Write => Intel400xUnifiedState::WriteData,
PendingOperation::OutputPort => Intel400xUnifiedState::OutputPort,
PendingOperation::None => Intel400xUnifiedState::ReadData, // Default to read
};
// Handle state entry/exit manually
if new_state != self.state_machine.get_state() {
// Transition directly to the target state
if target_state != self.state_machine.get_state() {
self.on_state_exit(self.state_machine.get_state());
self.state_machine.current_state = new_state;
self.on_state_entry(new_state);
self.state_machine.current_state = target_state;
self.on_state_entry(target_state);
}
}
@ -661,40 +737,36 @@ impl Component for Intel4002 {
self.prev_phi1 = phi1;
self.prev_phi2 = phi2;
// Collect all events to process
let mut events = Vec::new();
// 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
// These handlers contain the actual logic for chip select, address latching, etc.
if phi1_rising {
events.push(Intel400xEvent::ClockPhi1Rising);
self.handle_phi1_rising();
}
if phi1_falling {
events.push(Intel400xEvent::ClockPhi1Falling);
self.handle_phi1_falling();
}
if phi2_rising {
events.push(Intel400xEvent::ClockPhi2Rising);
self.handle_phi2_rising();
}
if phi2_falling {
events.push(Intel400xEvent::ClockPhi2Falling);
self.handle_phi2_falling();
}
// Process all events
for event in events {
// Use the process_event_direct method to avoid borrowing conflicts
let new_state = process_event_direct(&mut self.state_machine, event);
// Handle state entry/exit manually
if new_state != self.state_machine.get_state() {
self.on_state_exit(self.state_machine.get_state());
self.state_machine.current_state = new_state;
self.on_state_entry(new_state);
}
}
// Execute current state logic
self.execute_state_machine();
// 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 in time-slice mode (manual control)
@ -804,6 +876,30 @@ impl Intel4002 {
self.last_address
}
/// Set the state machine state directly (for testing purposes)
/// Parameters: state - The unified state to set
/// Note: This triggers on_state_entry to properly initialize state-specific variables
pub fn set_state_for_test(&mut self, state: Intel400xUnifiedState) {
let old_state = self.state_machine.get_state();
if old_state != state {
self.on_state_exit(old_state);
self.state_machine.current_state = state;
self.on_state_entry(state);
}
}
/// Set the pending operation type (for testing purposes)
/// Parameters: operation - The operation type for the next memory cycle
pub fn set_pending_operation(&mut self, operation: PendingOperation) {
self.pending_operation = operation;
}
/// Get the pending operation type (for testing purposes)
/// Returns: The current pending operation type
pub fn get_pending_operation(&self) -> PendingOperation {
self.pending_operation
}
/// 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) {

View file

@ -81,6 +81,11 @@ impl Intel400xResetHandling for Intel4003 {
self.input_buffer = 0;
self.output_buffer = 0;
self.serial_mode = SerialMode::Hold;
// 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;
}
}
@ -367,18 +372,14 @@ impl Intel4003 {
/// 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
// Intel 4003 ignores address transmission (responds_to_address() = false)
// No address latching needed for shift register operations
}
/// Handle Φ2 rising edge - Data phase
/// Hardware: Φ2 high = Peripherals drive bus with data
/// Focus: Data driving operations
fn handle_phi2_rising(&mut self) {
println!(
"DEBUG: [SHIFT] Φ2 rising edge detected, current state: {:?}",
self.state_machine.get_state()
);
// Execute current state logic
self.execute_state_machine();
}
@ -388,10 +389,6 @@ impl Intel4003 {
/// 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) {
println!(
"DEBUG: [SHIFT] Φ2 falling edge detected, current state: {:?}",
self.state_machine.get_state()
);
match self.state_machine.get_state() {
Intel400xUnifiedState::ShiftData | Intel400xUnifiedState::LoadData => {
// End of data phase - tri-state bus and return to idle
@ -417,10 +414,6 @@ impl Intel4003 {
/// Hardware: CPU provides 8-bit address as two 4-bit nibbles
/// Note: Intel 4003 mostly ignores address transmission for shift operations
fn handle_address_latching(&mut self) {
println!(
"DEBUG: handle_address_latching called, current state: {:?}",
self.state_machine.current_state
);
// Intel 4003 mostly ignores address transmission for shift operations
// But we still need to handle the address nibble for compatibility
let nibble = self.read_data_bus() & 0x0F; // Only 4 bits are valid for address nibbles
@ -436,18 +429,12 @@ impl Intel4003 {
fn handle_shift_operation(&mut self) {
// Read data from bus
let data = self.read_data_bus() & 0x0F; // Only 4 bits are valid
println!("DEBUG: [SHIFT] Shifting data 0x{:X} into register", data);
// Shift data into register
self.shift_register = ((self.shift_register << 4) | (data as u16)) & 0x03FF; // Keep only 10 bits
// Update output buffer with shifted-out bit
self.output_buffer = ((self.shift_register >> 9) & 1) as u8;
println!(
"DEBUG: [SHIFT] Register after shift: 0x{:03X}, Output bit: {}",
self.shift_register, self.output_buffer
);
}
/// Handle shift register load operation
@ -455,18 +442,12 @@ impl Intel4003 {
fn handle_load_operation(&mut self) {
// Read data from bus
let data = self.read_data_bus() & 0x0F; // Only 4 bits are valid
println!("DEBUG: [SHIFT] Loading data 0x{:X} into register", data);
// Load data into register
self.shift_register = (self.shift_register & 0xFF00) | (data as u16); // Keep lower 8 bits
// Update output buffer with loaded data
self.output_buffer = data;
println!(
"DEBUG: [SHIFT] Register after load: 0x{:03X}, Output: 0x{:X}",
self.shift_register, self.output_buffer
);
}
/// Helper function to process latency elapsed event
@ -549,11 +530,19 @@ impl Component for Intel4003 {
/// Main update cycle - handles clock edge detection and operation dispatch
/// Hardware: Responds to Φ1 and Φ2 clock edges from CPU
fn update(&mut self) {
println!("DEBUG: Update called");
if !self.is_running() {
println!("DEBUG: Component not running, returning");
return;
}
// Check for reset first (highest priority, independent of clock edges)
// This matches Intel 4001 behavior where reset is processed immediately
let reset_active = self.read_reset_pin();
if reset_active {
self.perform_reset();
// Skip further processing if reset is active
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);
@ -569,7 +558,6 @@ impl Component for Intel4003 {
let mut events = Vec::new();
if phi1_rising {
println!("DEBUG: phi1_rising detected");
events.push(Intel400xEvent::ClockPhi1Rising);
}