fix: address latching timing and cleanup debug code

This commit is contained in:
Jan-Peter von Hunnius 2026-02-25 11:55:16 +01:00
parent aae374b713
commit 1cf2cd6e2b
6 changed files with 1102 additions and 265 deletions

View file

@ -387,15 +387,15 @@ impl Intel4001 {
data
}
/// Handle Φ1 rising edge - Address and control phase
/// Hardware: Φ1 high = CPU drives bus with address/control information
/// 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: Address latching, reset, I/O operations
/// 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 when it sees them on the bus during AddressPhase
/// - ROM latches each nibble on PHI1 FALLING edge (after data is stable)
fn handle_phi1_rising(&mut self) {
// Handle system reset first (highest priority)
self.handle_reset("RESET");
@ -415,17 +415,8 @@ impl Intel4001 {
if self.state_machine.get_state() == Intel400xUnifiedState::Idle {
self.process_chip_select();
}
// CRITICAL: Latch address nibbles during PHI1 when CPU is driving them
// The CPU drives address nibbles on PHI1 rising, so we must latch here
if self.state_machine.get_state() == Intel400xUnifiedState::AddressPhase {
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();
}
}
// NOTE: Address nibble latching moved to PHI1 falling edge
// This gives the data bus time to stabilize after CPU drives it on PHI1 rising
}
// Handle I/O operations (higher priority than memory operations)
@ -436,19 +427,42 @@ impl Intel4001 {
}
}
/// 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
/// ROM 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 DriveData 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 DriveData state
if self.state_machine.get_state() == Intel400xUnifiedState::DriveData {
self.handle_data_driving();
}
}
/// Handle Φ2 falling edge - End of data phase
@ -803,6 +817,9 @@ impl Intel4001 {
}
/// 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();
@ -840,40 +857,36 @@ impl Component for Intel4001 {
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 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)
@ -1198,7 +1211,8 @@ mod tests {
println!("DEBUG: After update - prev_phi1: {:?}", rom.prev_phi1);
// Should have started memory operation and transitioned to AddressPhase
assert_eq!(rom.state_machine.current_state, Intel400xUnifiedState::Idle);
// 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