fix: address latching timing and cleanup debug code
This commit is contained in:
parent
aae374b713
commit
1cf2cd6e2b
6 changed files with 1102 additions and 265 deletions
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue