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

@ -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) {