feat(cpu): add A1/A2/A3 address cycle timing and fix pin propagation

This commit is contained in:
Jan-Peter von Hunnius 2026-02-24 18:58:14 +01:00
parent 3cdf99630c
commit 20884b57c3
17 changed files with 4165 additions and 270 deletions

View file

@ -391,11 +391,12 @@ impl Intel4001 {
/// 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
///
/// 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
fn handle_phi1_rising(&mut self) {
println!(
"DEBUG: [ROM] Φ1 rising edge detected, current state: {:?}",
self.state_machine.get_state()
);
// Handle system reset first (highest priority)
self.handle_reset("RESET");
@ -407,17 +408,23 @@ impl Intel4001 {
let sync = self.read_sync_pin();
let chip_select = self.read_cm_rom_pin();
let io_select = self.read_ci_pin();
println!(
"DEBUG: [ROM] Φ1 rising - SYNC: {}, CM: {}, CI: {}",
sync, chip_select, io_select
);
if sync && chip_select && !io_select {
// Start memory address phase on Φ1 rising edge (ROM access)
// Only start a new memory operation if we're currently idle
if self.state_machine.get_state() == Intel400xUnifiedState::Idle {
self.process_chip_select();
println!("DEBUG: [ROM] Starting memory operation");
}
// 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();
}
}
}
@ -440,10 +447,6 @@ impl Intel4001 {
/// Hardware: Φ2 high = Peripherals drive bus with data
/// Focus: Data driving operations
fn handle_phi2_rising(&mut self) {
println!(
"DEBUG: [ROM] Φ2 rising edge detected, current state: {:?}",
self.state_machine.get_state()
);
// Execute current state logic
self.execute_state_machine();
}
@ -453,10 +456,6 @@ impl Intel4001 {
/// 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: [ROM] Φ2 falling edge detected, current state: {:?}",
self.state_machine.get_state()
);
match self.state_machine.get_state() {
Intel400xUnifiedState::DriveData => {
// End of data phase - tri-state bus and return to idle
@ -629,10 +628,6 @@ impl Intel4001 {
/// Focus: Address latching and setup operations
#[allow(dead_code)] // This method is kept for compatibility but not currently used
fn handle_memory_address_operations(&mut self) {
println!(
"DEBUG: handle_memory_address_operations called, current state: {:?}",
self.state_machine.get_state()
);
match self.state_machine.get_state() {
Intel400xUnifiedState::Idle => {
// In idle state, ensure bus is tri-stated
@ -706,10 +701,6 @@ impl Intel4001 {
/// Handle address nibble latching during address phase
/// Hardware: CPU provides 8-bit address as two 4-bit nibbles
fn handle_address_latching(&mut self) {
println!(
"DEBUG: handle_address_latching called, current state: {:?}",
self.state_machine.current_state
);
// Use common address latching functionality
let nibble = self.read_data_bus() & 0x0F; // Only 4 bits are valid for address nibbles
@ -723,15 +714,10 @@ impl Intel4001 {
/// Hardware: ROM needs 500ns to access data after address is latched
#[allow(dead_code)] // This method is kept for compatibility with legacy code
fn handle_latency_wait(&mut self) {
println!(
"DEBUG: handle_latency_wait called, state: {:?}",
self.state_machine.current_state
);
// This is now handled by state machine
if self.state_machine.is_latency_elapsed() {
// Latency elapsed, transition to data driving
// Data will be driven on next Φ2 rising edge
println!("DEBUG: Latency elapsed, transitioning to DriveData state");
self.process_latency_elapsed();
}
}
@ -748,23 +734,14 @@ impl Intel4001 {
let address = self.state_machine.get_full_address().unwrap_or(0) as u16;
if (address as usize) < self.memory.len() {
let data = self.memory[address as usize];
println!(
"DEBUG: [ROM] Driving data 0x{:02X} from address 0x{:02X} onto bus",
data, address
);
self.write_data_bus(data);
// Note: Don't call return_to_idle() here - wait for Φ2 falling edge
} else {
// Invalid address, tri-state
println!(
"DEBUG: [ROM] Invalid address 0x{:02X}, tri-stating bus",
address
);
self.tri_state_data_bus();
}
} else {
// Address not ready, tri-state
println!("DEBUG: [ROM] Address not ready, tri-stating bus");
self.tri_state_data_bus();
}
}
@ -849,9 +826,7 @@ impl Component for Intel4001 {
/// 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;
}
// Handle both rising and falling edges for proper two-phase operation
@ -869,7 +844,6 @@ impl Component for Intel4001 {
let mut events = Vec::new();
if phi1_rising {
println!("DEBUG: phi1_rising detected");
events.push(Intel400xEvent::ClockPhi1Rising);
}