fix: RESET pin active-LOW and clock edge state reset
This commit is contained in:
parent
1cf2cd6e2b
commit
c9f39aa600
12 changed files with 520 additions and 327 deletions
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue